pandy: wiele warunków podczas indeksowania ramki danych - nieoczekiwane zachowanie

135

Filtruję wiersze w ramce danych według wartości w dwóch kolumnach.

Z jakiegoś powodu operator OR zachowuje się tak, jak powinienem oczekiwać operatora AND i odwrotnie.

Mój kod testowy:

import pandas as pd

df = pd.DataFrame({'a': range(5), 'b': range(5) })

# let's insert some -1 values
df['a'][1] = -1
df['b'][1] = -1
df['a'][3] = -1
df['b'][4] = -1

df1 = df[(df.a != -1) & (df.b != -1)]
df2 = df[(df.a != -1) | (df.b != -1)]

print pd.concat([df, df1, df2], axis=1,
                keys = [ 'original df', 'using AND (&)', 'using OR (|)',])

A wynik:

      original df      using AND (&)      using OR (|)    
             a  b              a   b             a   b
0            0  0              0   0             0   0
1           -1 -1            NaN NaN           NaN NaN
2            2  2              2   2             2   2
3           -1  3            NaN NaN            -1   3
4            4 -1            NaN NaN             4  -1

[5 rows x 6 columns]

Jak widać, ANDoperator porzuca każdy wiersz, w którym przynajmniej jedna wartość jest równa -1. Z drugiej strony ORoperator wymaga, aby obie wartości były równe, aby -1je usunąć. Spodziewałbym się dokładnie odwrotnego wyniku. Czy ktoś mógłby wyjaśnić to zachowanie?

Używam pand 0.13.1.

Wojciech Walczak
źródło
1
df.queryi pd.evalwydaje się, że dobrze pasuje do tego przypadku użycia. Aby uzyskać informacje na temat pd.eval()rodziny funkcji, ich funkcji i przypadków użycia, odwiedź stronę Dynamic Expression Evaluation w pandach przy użyciu pd.eval () .
cs95

Odpowiedzi:

211

Jak widać, operator AND upuszcza każdy wiersz, w którym przynajmniej jedna wartość jest równa -1. Z drugiej strony operator OR wymaga, aby obie wartości były równe -1, aby je usunąć.

Zgadza się. Pamiętaj, że piszesz warunek w kategoriach tego, co chcesz zachować , a nie tego, co chcesz upuścić. Dla df1:

df1 = df[(df.a != -1) & (df.b != -1)]

Mówisz „zachowaj wiersze, w których df.anie jest -1 i df.bnie jest -1”, co jest tym samym, co usuwanie każdego wiersza, w którym co najmniej jedna wartość to -1.

Dla df2:

df2 = df[(df.a != -1) | (df.b != -1)]

Mówisz „keep wiersze, w których albo df.aczy df.bnie jest -1”, która jest taka sama jak upuszczenie wiersze gdzie obie wartości -1.

PS: dostęp łańcuchowy df['a'][1] = -1może wpędzić cię w kłopoty. Lepiej nabrać nawyku używania .loci .iloc.

DSM
źródło
24
DataFrame.query()tu też dobrze działa. df.query('a != -1 or b != -1').
Phillip Cloud,
6
Zdarzyć wiedzieć, dlaczego chce pandy &i |nad andi or?
piece kominkowe
3
@stoves: w normalnym kodzie Pythona andi orma podstawową semantykę Pythona, której nie można modyfikować. &i |, z drugiej strony, mają odpowiadające specjalnych metod, które kontrolują swoje zachowania. (Oczywiście w ciągach zapytań możemy zastosować dowolne analizowanie, które nam się podoba.)
DSM
co ciekawe, wygląda na to, że się df[True & False]nie df[(True) & (False)]udaje (nie testowano na tym przykładzie)
3pitt
1
Czy byłoby możliwe rozbicie tego rodzaju składni w wielu wierszach? Co byłoby najbardziej PEP8?
tommy.carstensen
42

Możesz użyć query () , czyli:

df_filtered = df.query('a == 4 & b != 2')
CONvid19
źródło
Mam sytuację, w której myślę, że ta składnia ma więcej sensu, np .: df.query ('' (a == 4 & b! = 2) | c == 3 ")
Aus_10
9

Tutaj trochę teorii logiki matematycznej :

„NOT a AND NOT b” to to samo, co „NOT (a OR b)” , więc:

„a NOT -1 AND b NOT -1” jest odpowiednikiem „NOT (a is -1 OR b is -1)” , które jest przeciwieństwem (Complement) z „(a jest -1 LUB b jest -1)” .

Więc jeśli chcesz uzyskać dokładnie odwrotny wynik, df1 i df2 powinny wyglądać jak poniżej:

df1 = df[(df.a != -1) & (df.b != -1)]
df2 = df[(df.a == -1) | (df.b == -1)]
Jake
źródło