TLDR; Logika Operatorzy w pandy &
, |
i ~
, i nawiasy (...)
jest ważne!
Pythona and
, or
a not
operatory 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 ( exp1
i exp2
są 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 df
wyż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 and
i ~
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 < 3
as 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 query
i eval
w 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_and
jest ufunc (funkcje uniwersalne) , a większość ufunc ma reduce
metodę. Oznacza to, że łatwiej jest uogólniać, logical_and
jeśli masz wiele masek AND. Na przykład, aby i masek m1
i m2
i m3
z &
, 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 df
powyż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_and
mogą być podstawione np.bitwise_and
, logical_or
z bitwise_or
i logical_not
z invert
.