jak filtrować ramki danych pandy według wielu kolumn

103

Aby przefiltrować ramkę danych (df) według pojedynczej kolumny, jeśli weźmiemy pod uwagę dane dotyczące mężczyzn i kobiet, możemy:

males = df[df[Gender]=='Male']

Pytanie 1 - Ale co, jeśli dane obejmują wiele lat i chciałbym zobaczyć tylko mężczyzn w 2014 roku?

W innych językach mógłbym zrobić coś takiego:

if A = "Male" and if B = "2014" then 

(poza tym, że chcę to zrobić i uzyskać podzbiór oryginalnej ramki danych w nowym obiekcie dataframe)

Pytanie 2. Jak to zrobić w pętli i utworzyć obiekt Dataframe dla każdego unikalnego zestawu roku i płci (np. Df dla: 2013-Mężczyzna, 2013-Kobieta, 2014-Mężczyzna i 2014-Kobieta

for y in year:

for g in gender:

df = .....
yoshiserry
źródło
Chcesz to przefiltrować czy zgrupować ? Jeśli chcesz utworzyć osobną ramkę DataFrame dla każdego unikalnego zestawu roku i płci, spójrz na groupby.
BrenBarn
1
Ta odpowiedź daje kompleksowy przegląd indeksowania boolowskiego i operatorów logicznych w pandach.
cs95

Odpowiedzi:

182

Używając &operatora, nie zapomnij otoczyć instrukcji podrzędnych ():

males = df[(df[Gender]=='Male') & (df[Year]==2014)]

Aby przechowywać ramki danych w dictpętli for:

from collections import defaultdict
dic={}
for g in ['male', 'female']:
  dic[g]=defaultdict(dict)
  for y in [2013, 2014]:
    dic[g][y]=df[(df[Gender]==g) & (df[Year]==y)] #store the DataFrames to a dict of dict

EDYTOWAĆ:

Demo dla getDF:

def getDF(dic, gender, year):
  return dic[gender][year]

print genDF(dic, 'male', 2014)
zhangxaochen
źródło
świetna odpowiedź zhangxaochen - czy mógłbyś edytować swoją odpowiedź, aby pokazać na dole, jak możesz zrobić pętlę for, która tworzy ramki danych (z danymi roku i płci), ale dodaje je do słownika, aby można było uzyskać do nich dostęp później za pomocą mojej metody getDF? def GetDF (dict, key): return dict [key]
yoshiserry
@yoshiserry, jakie jest keytwoje getDF? pojedynczy parametr czy krotka kluczy? be specific plz;)
zhangxaochen
cześć to pojedynczy klucz, tylko słowo, które odpowiadałoby płci (mężczyzna lub kobieta) lub rokowi (13, 14). Nie wiedziałem, że możesz mieć krotkę kluczy. Czy mógłbyś podzielić się przykładem, kiedy i jak byś to zrobił?
yoshiserry
czy mógłbyś rzucić okiem na to pytanie. Czuję, że mógłbyś na to odpowiedzieć. Ponownie odnosi się do ramek danych pandy. stackoverflow.com/questions/22086619/…
yoshiserry
1
Należy zauważyć, że Genderi Yearpowinny być zarówno ciągi, czyli 'Gender'i 'Year'.
Steven C. Howell
22

Aby uzyskać bardziej ogólne funkcje logiczne, których chciałbyś użyć jako filtru i które zależą od więcej niż jednej kolumny, możesz użyć:

df = df[df[['col_1','col_2']].apply(lambda x: f(*x), axis=1)]

gdzie f jest funkcją, która jest stosowana do każdej pary elementów (x1, x2) z col_1 i col_2 i zwraca True lub False w zależności od dowolnego warunku (x1, x2).

guibor
źródło
13

Zacznij od pandy 0.13 , jest to najbardziej efektywny sposób.

df.query('Gender=="Male" & Year=="2014" ')
redreamality
źródło
1
Dlaczego miałoby to być skuteczniejsze niż zaakceptowana odpowiedź?
Bouncner
@Bouncner po prostu zweryfikuj to z odpowiedzią, która została wysoko oceniona.
redreamality
6
Tę odpowiedź można poprawić, pokazując benchmark
nardeas
10

W przypadku gdyby ktoś się zastanawiał, jaki jest szybszy sposób filtrowania (odpowiedź zaakceptowana lub ta z @redreamality):

import pandas as pd
import numpy as np

length = 100_000
df = pd.DataFrame()
df['Year'] = np.random.randint(1950, 2019, size=length)
df['Gender'] = np.random.choice(['Male', 'Female'], length)

%timeit df.query('Gender=="Male" & Year=="2014" ')
%timeit df[(df['Gender']=='Male') & (df['Year']==2014)]

Wyniki dla 100 000 wierszy:

6.67 ms ± 557 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
5.54 ms ± 536 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Wyniki dla 10 000 000 wierszy:

326 ms ± 6.52 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
472 ms ± 25.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Zatem wyniki zależą od rozmiaru i danych. Na moim laptopie query()przyspiesza po 500 tys. Rzędów. Co więcej, wyszukiwanie ciągów Year=="2014"ma niepotrzebny narzut ( Year==2014jest szybsze).

Bouncner
źródło
2
Uważam jednak, że queryskładnia jest schludniejsza i zbliżona do SQL, co sprawia, że ​​od tego czasu jest przyjemna dla danych. Smutne na torcie jest to, że jest szybszy z wieloma rzędami :)
csgroen
1

Możesz stworzyć własną funkcję filtrującą za pomocą queryin pandas. Tutaj masz filtrowanie dfwyników według wszystkich kwargsparametrów. Nie zapomniałem dodać niektórych walidatorów ( kwargsfiltrowania), aby uzyskać własną funkcję filtrującą df.

def filter(df, **kwargs):
    query_list = []
    for key in kwargs.keys():
        query_list.append(f'{key}=="{kwargs[key]}"')
    query = ' & '.join(query_list)
    return df.query(query)
Alex
źródło
Dzięki za eleganckie rozwiązanie! Myślę, że to najlepsze z całej reszty. Łączy w sobie efektywność używania zapytania z wszechstronnością posiadania go jako funkcji.
Merii
0

Możesz filtrować według wielu kolumn (więcej niż dwóch), używając np.logical_andoperatora do zamiany &(lub np.logical_orzamiany |)

Oto przykładowa funkcja, która wykonuje swoje zadanie, jeśli podasz wartości docelowe dla wielu pól. Możesz go dostosować do różnych typów filtrowania i nie tylko:

def filter_df(df, filter_values):
    """Filter df by matching targets for multiple columns.

    Args:
        df (pd.DataFrame): dataframe
        filter_values (None or dict): Dictionary of the form:
                `{<field>: <target_values_list>}`
            used to filter columns data.
    """
    import numpy as np
    if filter_values is None or not filter_values:
        return df
    return df[
        np.logical_and.reduce([
            df[column].isin(target_values) 
            for column, target_values in filter_values.items()
        ])
    ]

Stosowanie:

df = pd.DataFrame({'a': [1, 2, 3, 4], 'b': [1, 2, 3, 4]})

filter_df(df, {
    'a': [1, 2, 3],
    'b': [1, 2, 4]
})
Tom Bug
źródło