Operatory logiczne do indeksowania wartości logicznych w Pandach

152

Pracuję z indeksem boolowskim w Pandach. Pytanie brzmi, dlaczego stwierdzenie:

a[(a['some_column']==some_number) & (a['some_other_column']==some_other_number)]

działa dobrze, podczas gdy

a[(a['some_column']==some_number) and (a['some_other_column']==some_other_number)]

kończy pracę z błędem?

Przykład:

a=pd.DataFrame({'x':[1,1],'y':[10,20]})

In: a[(a['x']==1)&(a['y']==10)]
Out:    x   y
     0  1  10

In: a[(a['x']==1) and (a['y']==10)]
Out: ValueError: The truth value of an array with more than one element is ambiguous.     Use a.any() or a.all()
user2988577
źródło
6
Dzieje się tak, ponieważ tablice numpy i serie pandy używają operatorów bitowych, a nie logicznych, ponieważ porównujesz każdy element w tablicy / serii z innym. Dlatego w tej sytuacji nie ma sensu używać operatora logicznego. zobacz powiązane: stackoverflow.com/questions/8632033/…
EdChum
9
W Pythonie and != &. andOperator Pythonie nie może być pominięte, natomiast &operator ( __and__) puszki. Stąd wybór zastosowania &u numpy i pand.
Steven Rumbalski

Odpowiedzi:

208

Kiedy powiesz

(a['x']==1) and (a['y']==10)

Niejawnie prosisz Pythona o konwersję (a['x']==1)i (a['y']==10)na wartości logiczne.

Tablice NumPy (o długości większej niż 1) i obiekty Pandy, takie jak Series, nie mają wartości logicznej - innymi słowy, podnoszą

ValueError: The truth value of an array is ambiguous. Use a.empty, a.any() or a.all().

kiedy jest używany jako wartość logiczna. Dzieje się tak, ponieważ nie jest jasne, kiedy powinno być Prawdą czy fałszem . Niektórzy użytkownicy mogą założyć, że mają wartość True, jeśli mają niezerową długość, jak lista w Pythonie. Inni mogą chcieć, aby była prawdziwa tylko wtedy, gdy wszystkie jej elementy są prawdziwe. Inni mogą chcieć, aby była prawdziwa, jeśli którykolwiek z jej elementów jest prawdziwy.

Ponieważ istnieje tak wiele sprzecznych oczekiwań, projektanci NumPy i Pandas odmawiają zgadywania i zamiast tego zgłaszają błąd ValueError.

Zamiast tego, trzeba być wyraźny, poprzez wywołanie empty(), all()lub any()metodę, aby wskazać zachowanie pragnienie.

W tym przypadku jednak wygląda na to, że nie chcesz wartościowania boolowskiego, chcesz logicznego i logicznego punktu widzenia . Oto, co &wykonuje operator binarny:

(a['x']==1) & (a['y']==10)

zwraca tablicę logiczną.


Nawiasem mówiąc, jak zauważa alexpmil , nawiasy są obowiązkowe, ponieważ &mają wyższy priorytet operatorów niż ==. Bez nawiasów a['x']==1 & a['y']==10zostanie oszacowane, a['x'] == (1 & a['y']) == 10co z kolei byłoby równoważne z połączonym porównaniem (a['x'] == (1 & a['y'])) and ((1 & a['y']) == 10). To jest wyraz formy Series and Series. Użycie andz dwiema seriami ponownie uruchomiłoby to samo, ValueErrorco powyżej. Dlatego nawiasy są obowiązkowe.

unutbu
źródło
3
Tablice numpy mają tę właściwość, jeśli mają długość jeden. Tylko twórcy pand (uparcie) odmawiają zgadywania: p
Andy Hayden
4
Czy „&” nie ma takiej samej niejednoznacznej krzywej jak „i”? Jak to się dzieje, że jeśli chodzi o „&”, nagle wszyscy użytkownicy zgadzają się, że powinno to być elementarne, podczas gdy gdy widzą „i”, ich oczekiwania są różne?
Indominus
16
@Indominus: sam język Python wymaga , aby wyrażenie wyzwalało x and yocenę bool(x)i bool(y). Python „najpierw ocenia x; jeśli xma wartość false, zwracana jest jego wartość; w przeciwnym razie yjest oceniana i zwracana jest wynikowa wartość”. Tak więc składnia x and ynie może być używana do logicznych elementów, a ponieważ tylko xlub ymoże być zwracana. Natomiast x & ywyzwalacze x.__and__(y)i __and__metodę można zdefiniować tak, aby zwracały wszystko, co nam się podoba.
unutbu
2
Ważna uwaga: nawiasy wokół ==klauzuli są obowiązkowe . a['x']==1 & a['y']==10zwraca ten sam błąd co w pytaniu.
Alex P. Miller
1
Po co jest "|"?
Euler_Salter
62

TLDR; Logika Operatorzy w pandy &, |i ~, i nawiasy (...)jest ważne!

Pythona and, ora notoperatory logiczne są przeznaczone do pracy z skalarów. Dlatego Pandy musiały zrobić coś lepszego i przesłonić operatory bitowe, aby uzyskać wektoryzowaną (elementarną) wersję tej funkcji.

A więc następujące w Pythonie ( exp1i exp2są to wyrażenia, które dają wynik boolowski) ...

exp1 and exp2              # Logical AND
exp1 or exp2               # Logical OR
not exp1                   # Logical NOT

... przełoży się na ...

exp1 & exp2                # Element-wise logical AND
exp1 | exp2                # Element-wise logical OR
~exp1                      # Element-wise logical NOT

dla pand.

Jeśli w trakcie wykonywania operacji logicznej otrzymasz a ValueError, musisz użyć nawiasów do grupowania:

(exp1) op (exp2)

Na przykład,

(df['col1'] == x) & (df['col2'] == y) 

I tak dalej.


Indeksowanie boolowskie : Typową operacją jest obliczanie masek boolowskich na podstawie warunków logicznych w celu filtrowania danych. Pandas udostępnia trzy operatory:&logiczne AND,|logiczne OR i~logiczne NIE.

Rozważ następującą konfigurację:

np.random.seed(0)
df = pd.DataFrame(np.random.choice(10, (5, 3)), columns=list('ABC'))
df

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

Logiczne AND

Dla dfwyżej, że chcesz zwrócić wszystkie wiersze, gdzie A <5 i B> 5. Dokonuje się tego poprzez wyliczenie maski dla każdego warunku oddzielnie, a ich Anding.

Przeciążony &operator bitowy
Przed kontynuowaniem zwróć uwagę na ten konkretny fragment dokumentacji, który stwierdza

Inną powszechną operacją jest użycie wektorów boolowskich do filtrowania danych. Operatory to: |for or, &for andi ~for not. Muszą być one pogrupowane przy użyciu nawiasów , ponieważ domyślnie Python oceni wyrażenie, takie jak df.A > 2 & df.B < 3as df.A > (2 & df.B) < 3, podczas gdy żądana kolejność oceny to (df.A > 2) & (df.B < 3).

Mając to na uwadze, element mądry logiczne AND można zaimplementować za pomocą operatora bitowego &:

df['A'] < 5

0    False
1     True
2     True
3     True
4    False
Name: A, dtype: bool

df['B'] > 5

0    False
1     True
2    False
3     True
4     True
Name: B, dtype: bool

(df['A'] < 5) & (df['B'] > 5)

0    False
1     True
2    False
3     True
4    False
dtype: bool

Kolejny krok filtrowania to po prostu

df[(df['A'] < 5) & (df['B'] > 5)]

   A  B  C
1  3  7  9
3  4  7  6

Nawiasy są używane do przesłonięcia domyślnej kolejności pierwszeństwa operatorów bitowych, które mają wyższy priorytet w stosunku do operatorów warunkowych <i >. Zobacz sekcję Pierwszeństwo operatorów w dokumentacji Pythona.

Jeśli nie użyjesz nawiasów, wyrażenie zostanie ocenione niepoprawnie. Na przykład, jeśli przypadkowo spróbujesz czegoś takiego jak

df['A'] < 5 & df['B'] > 5

Jest analizowany jako

df['A'] < (5 & df['B']) > 5

Który staje się,

df['A'] < something_you_dont_want > 5

Który staje się (zobacz dokumentację Pythona na temat porównania operatorów łańcuchowych ),

(df['A'] < something_you_dont_want) and (something_you_dont_want > 5)

Który staje się,

# Both operands are Series...
something_else_you_dont_want1 and something_else_you_dont_want2

Który rzuca

ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

Więc nie popełniaj tego błędu! 1

Unikanie grupowania w nawiasy
Poprawka jest właściwie dość prosta. Większość operatorów ma odpowiednią metodę powiązaną dla DataFrames. Jeśli poszczególne maski są tworzone przy użyciu funkcji zamiast operatorów warunkowych, nie będzie już konieczne grupowanie według parenów, aby określić kolejność oceny:

df['A'].lt(5)

0     True
1     True
2     True
3     True
4    False
Name: A, dtype: bool

df['B'].gt(5)

0    False
1     True
2    False
3     True
4     True
Name: B, dtype: bool

df['A'].lt(5) & df['B'].gt(5)

0    False
1     True
2    False
3     True
4    False
dtype: bool

Zobacz sekcję dotyczącą elastycznych porównań. . Podsumowując, mamy

╒════╤════════════╤════════════╕
     Operator    Function   
╞════╪════════════╪════════════╡
  0  >           gt         
├────┼────────────┼────────────┤
  1  >=          ge         
├────┼────────────┼────────────┤
  2  <           lt         
├────┼────────────┼────────────┤
  3  <=          le         
├────┼────────────┼────────────┤
  4  ==          eq         
├────┼────────────┼────────────┤
  5  !=          ne         
╘════╧════════════╧════════════╛

Inną opcją unikania nawiasów jest użycie DataFrame.query(lub eval):

df.query('A < 5 and B > 5')

   A  B  C
1  3  7  9
3  4  7  6

I obszernie udokumentowane queryi evalw dynamicznej oceny ekspresji w pand pomocą pd.eval () .

operator.and_
Umożliwia wykonanie tej operacji w funkcjonalny sposób. Połączenia wewnętrzne, Series.__and__które odpowiadają operatorowi bitowemu.

import operator 

operator.and_(df['A'] < 5, df['B'] > 5)
# Same as,
# (df['A'] < 5).__and__(df['B'] > 5) 

0    False
1     True
2    False
3     True
4    False
dtype: bool

df[operator.and_(df['A'] < 5, df['B'] > 5)]

   A  B  C
1  3  7  9
3  4  7  6

Zwykle nie będziesz tego potrzebować, ale warto wiedzieć.

Uogólnianie: np.logical_and(i logical_and.reduce)
Inną alternatywą jest użycie np.logical_and, które również nie wymaga grupowania w nawiasach:

np.logical_and(df['A'] < 5, df['B'] > 5)

0    False
1     True
2    False
3     True
4    False
Name: A, dtype: bool

df[np.logical_and(df['A'] < 5, df['B'] > 5)]

   A  B  C
1  3  7  9
3  4  7  6

np.logical_andjest ufunc (funkcje uniwersalne) , a większość ufunc ma reducemetodę. Oznacza to, że łatwiej jest uogólniać, logical_andjeśli masz wiele masek AND. Na przykład, aby i masek m1i m2i m3z &, trzeba by zrobić

m1 & m2 & m3

Jednak łatwiejszą opcją jest

np.logical_and.reduce([m1, m2, m3])

Jest to potężne narzędzie, ponieważ pozwala na tworzenie dodatkowych elementów z bardziej złożoną logiką (na przykład dynamiczne generowanie masek w postaci listy i dodawanie ich wszystkich):

import operator

cols = ['A', 'B']
ops = [np.less, np.greater]
values = [5, 5]

m = np.logical_and.reduce([op(df[c], v) for op, c, v in zip(ops, cols, values)])
m 
# array([False,  True, False,  True, False])

df[m]
   A  B  C
1  3  7  9
3  4  7  6

1 - Wiem, że dręczy mnie ten punkt, ale proszę o wyrozumiałość. Jest to bardzo , bardzo powszechny błąd początkującego i należy go bardzo dokładnie wyjaśnić.


Logiczne LUB

W dfpowyższym przypadku powiedz, że chcesz zwrócić wszystkie wiersze, w których A == 3 lub B == 7.

Przeciążony bitowo |

df['A'] == 3

0    False
1     True
2     True
3    False
4    False
Name: A, dtype: bool

df['B'] == 7

0    False
1     True
2    False
3     True
4    False
Name: B, dtype: bool

(df['A'] == 3) | (df['B'] == 7)

0    False
1     True
2     True
3     True
4    False
dtype: bool

df[(df['A'] == 3) | (df['B'] == 7)]

   A  B  C
1  3  7  9
2  3  5  2
3  4  7  6

Jeśli jeszcze tego nie zrobiłeś, przeczytaj również sekcję o logicznym AND powyżej, wszystkie zastrzeżenia mają tutaj zastosowanie.

Alternatywnie tę operację można określić za pomocą

df[df['A'].eq(3) | df['B'].eq(7)]

   A  B  C
1  3  7  9
2  3  5  2
3  4  7  6

operator.or_
Wzywa Series.__or__pod maską.

operator.or_(df['A'] == 3, df['B'] == 7)
# Same as,
# (df['A'] == 3).__or__(df['B'] == 7)

0    False
1     True
2     True
3     True
4    False
dtype: bool

df[operator.or_(df['A'] == 3, df['B'] == 7)]

   A  B  C
1  3  7  9
2  3  5  2
3  4  7  6

np.logical_or
W przypadku dwóch warunków użyj logical_or:

np.logical_or(df['A'] == 3, df['B'] == 7)

0    False
1     True
2     True
3     True
4    False
Name: A, dtype: bool

df[np.logical_or(df['A'] == 3, df['B'] == 7)]

   A  B  C
1  3  7  9
2  3  5  2
3  4  7  6

W przypadku wielu masek użyj logical_or.reduce:

np.logical_or.reduce([df['A'] == 3, df['B'] == 7])
# array([False,  True,  True,  True, False])

df[np.logical_or.reduce([df['A'] == 3, df['B'] == 7])]

   A  B  C
1  3  7  9
2  3  5  2
3  4  7  6

Logiczne NIE

Biorąc pod uwagę maskę, taką jak

mask = pd.Series([True, True, False])

Jeśli chcesz odwrócić każdą wartość logiczną (tak, aby wynik końcowy był taki [False, False, True]), możesz użyć dowolnej z poniższych metod.

Bitowo ~

~mask

0    False
1    False
2     True
dtype: bool

Ponownie, wyrażenia należy ująć w nawiasy.

~(df['A'] == 3)

0     True
1    False
2    False
3     True
4     True
Name: A, dtype: bool

To wywołuje wewnętrznie

mask.__invert__()

0    False
1    False
2     True
dtype: bool

Ale nie używaj go bezpośrednio.

operator.inv
Wewnętrznie wzywa __invert__Serię.

operator.inv(mask)

0    False
1    False
2     True
dtype: bool

np.logical_not
To jest wariant numpy.

np.logical_not(mask)

0    False
1    False
2     True
dtype: bool

Uwaga, np.logical_andmogą być podstawione np.bitwise_and, logical_orz bitwise_ori logical_notz invert.

cs95
źródło
@ cs95 w TLDR, dla elementu logicznego OR, zaleca się użycie |, które jest równoważne numpy.bitwise_orzamiast numpy.logical_or. Czy mogę zapytać dlaczego? Czy nie jest numpy.logical_orprzeznaczony specjalnie do tego zadania? Po co dodawać ciężar robienia tego bitowo dla każdej pary elementów?
flow2k
@ flow2k Czy możesz zacytować odpowiedni tekst? Nie mogę znaleźć tego, o czym mówisz. FWIW Uważam, że logical_ * jest poprawnym funkcjonalnym odpowiednikiem operatorów.
cs95
@ cs95 Mam na myśli pierwszy wiersz odpowiedzi: „TLDR; operatory logiczne w pandach to &, | i ~”.
flow2k
@ flow2k Dosłownie w dokumentacji : „Inną powszechną operacją jest użycie wektorów boolowskich do filtrowania danych. Operatory to: | for or, & for and oraz ~ for not.”
cs95
@ cs95, ok, właśnie przeczytałem tę sekcję i używa ona |elementów logicznych. Ale dla mnie ta dokumentacja jest bardziej „samouczkiem”, a dla kontrastu czuję, że te odwołania do API są bliższe źródłu prawdy: numpy.bitwise_or i numpy.logical_or - więc staram się zrozumieć, co jest opisane tutaj.
flow2k
4

Operatory logiczne do indeksowania wartości logicznych w Pandach

Ważne jest, aby zdać sobie sprawę, że nie można użyć dowolnego z pytona operatorów logicznych ( and, orlub not) na pandas.Serieslub pandas.DataFrames (podobnie nie można z nich korzystać w numpy.arrays z więcej niż jednego elementu). Powodem, dla którego nie możesz ich użyć, jest to, że niejawnie wywołują one boolswoje operandy, które zgłaszają wyjątek, ponieważ te struktury danych zdecydowały, że wartość logiczna tablicy jest niejednoznaczna:

>>> import numpy as np
>>> import pandas as pd
>>> arr = np.array([1,2,3])
>>> s = pd.Series([1,2,3])
>>> df = pd.DataFrame([1,2,3])
>>> bool(arr)
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
>>> bool(s)
ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
>>> bool(df)
ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

Omówiłem to szerzej w mojej odpowiedzi na pytanie „Wartość prawdy serii jest niejednoznaczna. Użyj a.empty, a.bool (), a.item (), a.any () lub a.all ()” Q + A .

Funkcje logiczne NumPys

Jednak NumPy stanowi element mądry odpowiedniki operacyjnego do tych operatorów jak funkcje, które można wykorzystać na numpy.array, pandas.Series, pandas.DataFramelub jakiegokolwiek innego (zgodnego) numpy.arraypodklasy:

Zasadniczo więc należy użyć (zakładając df1i df2są pandami DataFrames):

np.logical_and(df1, df2)
np.logical_or(df1, df2)
np.logical_not(df1)
np.logical_xor(df1, df2)

Funkcje bitowe i operatory bitowe dla wartości logicznych

Jednak w przypadku, gdy masz tablicę logiczną NumPy, serię pandy lub pandy DataFrames, możesz również użyć funkcji bitowych z uwzględnieniem elementów (dla wartości logicznych są one - lub przynajmniej powinny być - nie do odróżnienia od funkcji logicznych):

Zwykle używane są operatory. Jednak w połączeniu z operatorami porównania należy pamiętać o zawinięciu porównania w nawiasach, ponieważ operatory bitowe mają wyższy priorytet niż operatory porównania :

(df1 < 10) | (df2 > 10)  # instead of the wrong df1 < 10 | df2 > 10

Może to być irytujące, ponieważ operatory logiczne Pythona mają mniejszą dokładność niż operatory porównania, więc zwykle piszesz a < 10 and b > 10(gdzie ai bsą to na przykład proste liczby całkowite) i nie potrzebujesz nawiasów.

Różnice między operacjami logicznymi i bitowymi (w przypadku operacji innych niż logiczne)

Naprawdę ważne jest, aby podkreślić, że operacje bitowe i logiczne są równoważne tylko dla tablic logicznych NumPy (oraz logicznych Series i DataFrames). Jeśli nie zawierają one wartości logicznych, operacje dadzą różne wyniki. Podam przykłady wykorzystujące tablice NumPy, ale wyniki będą podobne dla struktur danych pandy:

>>> import numpy as np
>>> a1 = np.array([0, 0, 1, 1])
>>> a2 = np.array([0, 1, 0, 1])

>>> np.logical_and(a1, a2)
array([False, False, False,  True])
>>> np.bitwise_and(a1, a2)
array([0, 0, 0, 1], dtype=int32)

A ponieważ NumPy (i podobnie pandy) robi różne rzeczy dla indeksów boolowskich ( tablice indeksowe Boolean lub „maski” ) i całkowitych ( tablice indeksowe ), wyniki indeksowania również będą inne:

>>> a3 = np.array([1, 2, 3, 4])

>>> a3[np.logical_and(a1, a2)]
array([4])
>>> a3[np.bitwise_and(a1, a2)]
array([1, 1, 1, 2])

Tabelka podsumowująca

Logical operator | NumPy logical function | NumPy bitwise function | Bitwise operator
-------------------------------------------------------------------------------------
       and       |  np.logical_and        | np.bitwise_and         |        &
-------------------------------------------------------------------------------------
       or        |  np.logical_or         | np.bitwise_or          |        |
-------------------------------------------------------------------------------------
                 |  np.logical_xor        | np.bitwise_xor         |        ^
-------------------------------------------------------------------------------------
       not       |  np.logical_not        | np.invert              |        ~

Gdzie operator logiczny nie działa dla tablic NumPy , pandas Series i pandas DataFrames. Pozostali pracują na tych strukturach danych (i zwykłych obiektach Pythona) i pracują na elementach. Należy jednak zachować ostrożność przy odwracaniu bitów w zwykłym Pythonie, boolponieważ bool zostanie zinterpretowany jako liczby całkowite w tym kontekście (na przykład ~Falsezwraca -1i ~Truezwraca -2).

MSeifert
źródło