2012-08-08 11 views
176

większość operacji w pandas można wykonać za pomocą kaskadowego operatora (groupby, aggregate, apply itd), lecz tylko w ten sposób, że już stwierdzono, że filtr rzędów za pomocą zwykłego zamka indeksowaniapandy: rzędy filtracyjne DataFrame z operatorem łańcuchowym

df_filtered = df[df['column'] == value] 

To jest nieprzyjemne, ponieważ wymaga przypisania df do zmiennej, zanim będzie możliwe filtrowanie jej wartości. Czy jest coś takiego jak poniżej?

df_filtered = df.mask(lambda x: x['column'] == value) 

Odpowiedz

235

Nie jestem do końca pewien, co chcesz, a ostatnia linia kodu nie pomaga, ale i tak:

„przykuty” Filtrowanie odbywa się poprzez „łańcuchowym” kryteriów w logiczną indeks.

In [96]: df 
Out[96]: 
    A B C D 
a 1 4 9 1 
b 4 5 0 2 
c 5 5 1 0 
d 1 3 9 6 

In [99]: df[(df.A == 1) & (df.D == 6)] 
Out[99]: 
    A B C D 
d 1 3 9 6 

Jeśli chcesz połączyć łańcuchy, możesz dodać własną metodę maski i użyć tej.

In [90]: def mask(df, key, value): 
    ....:  return df[df[key] == value] 
    ....: 

In [92]: pandas.DataFrame.mask = mask 

In [93]: df = pandas.DataFrame(np.random.randint(0, 10, (4,4)), index=list('abcd'), columns=list('ABCD')) 

In [95]: df.ix['d','A'] = df.ix['a', 'A'] 

In [96]: df 
Out[96]: 
    A B C D 
a 1 4 9 1 
b 4 5 0 2 
c 5 5 1 0 
d 1 3 9 6 

In [97]: df.mask('A', 1) 
Out[97]: 
    A B C D 
a 1 4 9 1 
d 1 3 9 6 

In [98]: df.mask('A', 1).mask('D', 6) 
Out[98]: 
    A B C D 
d 1 3 9 6 
+1

Świetna odpowiedź! Więc w '' '(df.A == 1) & (df.D == 6)' '' jest "&" przeciążonym operatorem w Pandach? – Shawn

+2

Rzeczywiście, zobacz także http://pandas.pydata.org/pandas-docs/stable/indexing.html?#boolean-operators –

+0

To jest naprawdę fajne rozwiązanie - nie wiedziałem nawet, że możesz zastosować metody jury-rig tak jak w pytonie. Taka funkcja byłaby naprawdę przyjemna w Pandas. – naught101

54

Odpowiedź od @lodagro jest świetna. Chciałbym przedłużyć go uogólniając funkcję maski jako:

def mask(df, f): 
    return df[f(df)] 

wtedy można zrobić rzeczy jak:

df.mask(lambda x: x[0] < 0).mask(lambda x: x[1] > 0) 
+5

Przydatne uogólnienie! Szkoda, że ​​nie zostały zintegrowane bezpośrednio z 'DataFrame's już! – duckworthd

+0

Zobacz żądanie funkcji na https://github.com/pydata/pandas/issues/5900 – naught101

4

Jeśli chcesz zastosować wszystkie wspólne maski logicznych, jak również ogólnego przeznaczenia maskować można rzucić następujących w pliku, a następnie po prostu przypisać je wszystkie, co następuje:

pd.DataFrame = apply_masks() 

Zastosowanie:

A = pd.DataFrame(np.random.randn(4, 4), columns=["A", "B", "C", "D"]) 
A.le_mask("A", 0.7).ge_mask("B", 0.2)... (May be repeated as necessary 

To trochę hacky, ale może sprawić, że wszystko będzie trochę czystsze, jeśli ciągle kroisz i zmieniasz zbiory danych według filtrów. Istnieje również filtr ogólnego przeznaczenia, dostosowany od Daniela Velkowa powyżej, w funkcji gen_mask, którego można użyć z funkcjami lambda lub w razie potrzeby w inny sposób.

plików przeznaczonych do zapisania (użyć masks.py)

import pandas as pd 

def eq_mask(df, key, value): 
    return df[df[key] == value] 

def ge_mask(df, key, value): 
    return df[df[key] >= value] 

def gt_mask(df, key, value): 
    return df[df[key] > value] 

def le_mask(df, key, value): 
    return df[df[key] <= value] 

def lt_mask(df, key, value): 
    return df[df[key] < value] 

def ne_mask(df, key, value): 
    return df[df[key] != value] 

def gen_mask(df, f): 
    return df[f(df)] 

def apply_masks(): 

    pd.DataFrame.eq_mask = eq_mask 
    pd.DataFrame.ge_mask = ge_mask 
    pd.DataFrame.gt_mask = gt_mask 
    pd.DataFrame.le_mask = le_mask 
    pd.DataFrame.lt_mask = lt_mask 
    pd.DataFrame.ne_mask = ne_mask 
    pd.DataFrame.gen_mask = gen_mask 

    return pd.DataFrame 

if __name__ == '__main__': 
    pass 
51

Filtr może być połączony za pomocą Pandy query:

df = pd.DataFrame(np.random.randn(30,3), columns = ['a','b','c']) 
df_filtered = df.query('a>0').query('0<b<2') 

Filtry mogą być także połączone w jeden zapytania:

df_filtered = df.query('a>0 and 0<b<2') 
+1

Jeśli potrzebujesz odwołać się do zmiennych Pythona w kwerendzie, [dokumentacja] (http://pandas.pydata.org/pandas -docs/stable/generated/pandas.DataFrame.query.html) mówi: "Możesz odnieść się do zmiennych w środowisku, poprzedzając je znakiem" @ ", takim jak @a + b". Zwróć uwagę, że poniższe są poprawne: 'df.query ('a in list ([1,2])')', 's = set ([1,2]); df.query ("a in @s") '. – user3780389

+1

Z drugiej strony wygląda na to, że ocena zapytania zakończy się niepowodzeniem, jeśli nazwa kolumny zawiera określone znaki specjalne: np. "Nazwa miejsca". – user3780389

8

Miałem to samo pytanie, z wyjątkiem tego, że chciałem połączyć kryteria w OR ndycja.Format podane przez Wouter Overmeire łączy kryteria do AND warunek taki, że oba muszą być spełnione:

In [96]: df 
Out[96]: 
    A B C D 
a 1 4 9 1 
b 4 5 0 2 
c 5 5 1 0 
d 1 3 9 6 

In [99]: df[(df.A == 1) & (df.D == 6)] 
Out[99]: 
    A B C D 
d 1 3 9 6 

Ale odkryłem, że jeśli owinąć każdą sytuację w (... == True) i dołącz do kryteriów z rurą, kryteria są połączone w warunek OR, spełnione, gdy którekolwiek z nich jest prawdziwe:

df[((df.A==1) == True) | ((df.D==6) == True)] 
+9

Nie "df [(df.A == 1) | (df.D == 6)] 'Wystarczająco dużo dla tego, co próbujesz osiągnąć? – eenblam

5

Moja odpowiedź jest podobna do innych. Jeśli nie chcesz tworzyć nowej funkcji, możesz użyć tego, co już zdefiniowałeś dla pand. Użyj metody rurowej.

df.pipe(lambda d: d[d['column'] == value]) 
+0

** To jest to, czego chcesz, jeśli chcesz łańcuchować polecenia, takie jak 'a.join (b) .pipe (lambda df: df [df.column_to_filter == 'VALUE'])' – displayname

11

pandas.DataFrame.query
query powstał dokładnie w tym celu. Rozważmy dataframe df

import pandas as pd 
import numpy as np 

np.random.seed([3,1415]) 
df = pd.DataFrame(
    np.random.randint(10, size=(10, 5)), 
    columns=list('ABCDE') 
) 

df 

    A B C D E 
0 0 2 7 3 8 
1 7 0 6 8 6 
2 0 2 0 4 9 
3 7 3 2 4 3 
4 3 6 7 7 4 
5 5 3 7 5 9 
6 8 7 6 4 7 
7 6 2 6 6 5 
8 2 8 7 5 8 
9 4 7 6 1 5 

Użyjmy query filtrować wszystkie wiersze gdzie D > B

df.query('D > B') 

    A B C D E 
0 0 2 7 3 8 
1 7 0 6 8 6 
2 0 2 0 4 9 
3 7 3 2 4 3 
4 3 6 7 7 4 
5 5 3 7 5 9 
7 6 2 6 6 5 

Które łańcucha

df.query('D > B').query('C > B') 
# equivalent to 
# df.query('D > B and C > B') 
# but defeats the purpose of demonstrating chaining 

    A B C D E 
0 0 2 7 3 8 
1 7 0 6 8 6 
4 3 6 7 7 4 
5 5 3 7 5 9 
7 6 2 6 6 5 
0

Po ustawieniu kolumn szukać jak indeksy, a następnie można użyj DataFrame.xs(), aby uzyskać przekrój. Nie jest to tak uniwersalne jak odpowiedź query, ale może być przydatne w niektórych sytuacjach.

import pandas as pd 
import numpy as np 

np.random.seed([3,1415]) 
df = pd.DataFrame(
    np.random.randint(3, size=(10, 5)), 
    columns=list('ABCDE') 
) 

df 
# Out[55]: 
# A B C D E 
# 0 0 2 2 2 2 
# 1 1 1 2 0 2 
# 2 0 2 0 0 2 
# 3 0 2 2 0 1 
# 4 0 1 1 2 0 
# 5 0 0 0 1 2 
# 6 1 0 1 1 1 
# 7 0 0 2 0 2 
# 8 2 2 2 2 2 
# 9 1 2 0 2 1 

df.set_index(['A', 'D']).xs([0, 2]).reset_index() 
# Out[57]: 
# A D B C E 
# 0 0 2 2 2 2 
# 1 0 2 1 1 0 
6

Ponieważ version 0.18.1 sposób .loc przyjmuje wywoływalnym wyboru. Wraz z funkcji lambda można tworzyć bardzo elastyczne filtry chainable:

import numpy as np 
import pandas as pd 

df = pd.DataFrame(np.random.randint(0,100,size=(100, 4)), columns=list('ABCD')) 
df.loc[lambda df: df.A == 80] # equivalent to df[df.A == 80] but chainable 

df.sort_values('A').loc[lambda df: df.A > 80].loc[lambda df: df.B > df.A] 

Jeśli wszystko robisz jest filtrowany, można również pominąć .loc.

+1

To jest poprawna odpowiedź –

1

To rozwiązanie jest bardziej hackowe pod względem implementacji, ale uważam je za znacznie czystsze pod względem użytkowania i jest z pewnością bardziej ogólne niż inne proponowane.

https://github.com/toobaz/generic_utils/blob/master/generic_utils/pandas/condition.py

Nie trzeba ściągnąć całą repo: Zapisywanie pliku i robi

from condition import Condition as C 

powinno wystarczyć.Następnie użyć go tak:

df = pd.DataFrame([[1, 2, True], 
        [3, 4, False], 
        [5, 7, True]], 
        index=range(3), columns=['a', 'b', 'c']) 
# On specific column: 
print(df.loc[C('a') > 2]) 
print(df.loc[-C('a') == C('b')]) 
print(df.loc[~C('c')]) 
# On entire DataFrame: 
print(df.loc[C().sum(axis=1) > 3]) 
print(df.loc[C(['a', 'b']).diff(axis=1)['b'] > 1]) 

nieznacznie mniej głupi przykład wykorzystania:

data = pd.read_csv('ugly_db.csv').loc[~(C() == '$null$').any(axis=1)] 

Przy okazji: nawet w przypadku, w którym jest tylko przy użyciu logicznych cols,

df.loc[C('cond1')].loc[C('cond2')] 

może być znacznie bardziej wydajny niż

df.loc[C('cond1') & C('cond2')] 

ponieważ ocenia cond2 tylko tam, gdzie cond1 jest True.

OŚWIADCZENIE: Najpierw podałem tę odpowiedź elsewhere, ponieważ nie widziałem tego.

0

Można również wykorzystać bibliotekę numpy do operacji logicznych. Jest dość szybki.

df[np.logical_and(df['A'] == 1 ,df['B'] == 6)] 
1

tylko dodać demonstrację używając loc filtrowanie nie tylko wiersze, ale także przez niektórych kolumn i zasługi dla powiązanej operacji.

Poniższy kod może filtrować wiersze według wartości.

df_filtered = df.loc[df['column'] == value] 

Modyfikując nieco, można również filtrować kolumny.

df_filtered = df.loc[df['column'] == value, ['year', 'column']] 

Dlaczego więc potrzebujemy metody łańcuchowej? Odpowiedź brzmi, że można łatwo przeczytać, jeśli masz wiele operacji. Na przykład:

res = df\ 
    .loc[df['station']=='USA', ['TEMP', 'RF']]\ 
    .groupby('year')\ 
    .agg(np.nanmean)