Logische Operatoren für die boolesche Indizierung in Pandas

152

Ich arbeite mit dem booleschen Index in Pandas. Die Frage ist, warum die Aussage:

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

funktioniert gut während

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

Exits mit Fehler?

Beispiel:

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
quelle
6
Dies liegt daran, dass Numpy-Arrays und Pandas-Reihen die bitweisen Operatoren verwenden und nicht logisch, da Sie jedes Element in dem Array / der Reihe mit einem anderen vergleichen. Es ist daher in dieser Situation nicht sinnvoll, den logischen Operator zu verwenden. siehe verwandte: stackoverflow.com/questions/8632033/…
EdChum
9
In Python and != &. Der andOperator in Python kann nicht überschrieben werden, wohingegen der &Operator ( __and__) dies kann. Daher die Wahl der Verwendung &bei Numpy und Pandas.
Steven Rumbalski

Antworten:

208

Wenn du sagst

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

Sie fordern Python implizit auf, (a['x']==1)und zu konvertieren(a['y']==10) zu Boolesche Werte.

NumPy-Arrays (mit einer Länge größer als 1) und Pandas-Objekte wie Series haben keinen booleschen Wert - mit anderen Worten, sie werden erhöht

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

bei Verwendung als boolescher Wert. Das liegt daran, dass unklar ist, wann es wahr oder falsch sein sollte . Einige Benutzer nehmen möglicherweise an, dass sie True sind, wenn sie eine Länge ungleich Null haben, wie z. B. eine Python-Liste. Andere möchten vielleicht, dass es nur dann wahr ist, wenn alle seine Elemente wahr sind. Andere möchten vielleicht, dass es wahr ist, wenn überhaupt seiner Elemente wahr sind.

Da es so viele widersprüchliche Erwartungen gibt, weigern sich die Designer von NumPy und Pandas zu raten und lösen stattdessen einen ValueError aus.

Stattdessen müssen Sie explizit sein, indem Sie das empty(), all()oder aufrufenany() Methode um anzugeben, welches Verhalten Sie wünschen.

In diesem Fall sieht es jedoch so aus, als ob Sie keine boolesche Auswertung wünschen, sondern ein elementweises logisches und. Das führt der &Binäroperator aus:

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

Gibt ein boolesches Array zurück.


Übrigens, wie alexpmil bemerkt , sind die Klammern obligatorisch, da &sie eine höhere Operatorpriorität haben als ==. Ohne die Klammern a['x']==1 & a['y']==10würde bewertet, a['x'] == (1 & a['y']) == 10was wiederum dem verketteten Vergleich entspricht (a['x'] == (1 & a['y'])) and ((1 & a['y']) == 10). Das ist ein Ausdruck der Form Series and Series. Die Verwendung andmit zwei Serien würde wieder das gleiche ValueErrorwie oben auslösen . Deshalb sind die Klammern obligatorisch.

unutbu
quelle
3
Numpy-Arrays haben diese Eigenschaft, wenn sie die Länge eins haben. Nur Pandas-Entwickler (hartnäckig) weigern sich zu raten: p
Andy Hayden
4
Trägt '&' nicht dieselbe mehrdeutige Kurve wie 'und'? Wie kommt es, dass bei "&" plötzlich alle Benutzer der Meinung sind, dass es elementweise sein sollte, während ihre Erwartungen variieren, wenn sie "und" sehen?
Indominus
16
@Indominus: Die Python-Sprache selbst erfordert, dass der Ausdruck x and ydie Auswertung von bool(x)und auslöst bool(y). Python "wertet zuerst aus x; wenn xes falsch ist, wird sein Wert zurückgegeben; andernfalls wird yes ausgewertet und der resultierende Wert wird zurückgegeben." Die Syntax x and ykann also nicht für elementweise logisch verwendet werden - und da nur xoder ykann zurückgegeben werden. Im Gegensatz dazu können x & yTrigger x.__and__(y)und die __and__Methode so definiert werden, dass sie alles zurückgeben, was uns gefällt.
Unutbu
2
Wichtig zu beachten: Die Klammern um die ==Klausel sind obligatorisch . a['x']==1 & a['y']==10gibt den gleichen Fehler wie in der Frage zurück.
Alex P. Miller
1
Wofür ist "|"?
Euler_Salter
62

TLDR; Logische Operatoren in Pandas sind &, |und ~, und Klammern (...)sind wichtig!

Python ist and, orund notlogische Operatoren sind mit Skalare Arbeit entwickelt. Pandas musste also eine bessere Leistung erbringen und die bitweisen Operatoren überschreiben, um eine vektorisierte (elementweise) Version dieser Funktionalität zu erhalten.

Also das Folgende in Python ( exp1und exp2sind Ausdrücke, die zu einem booleschen Ergebnis führen) ...

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

... wird übersetzt in ...

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

für Pandas.

Wenn Sie bei der Ausführung einer logischen Operation eine erhalten ValueError, müssen Sie Klammern für die Gruppierung verwenden:

(exp1) op (exp2)

Beispielsweise,

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

Und so weiter.


Boolesche Indizierung : Eine übliche Operation besteht darin, boolesche Masken unter logischen Bedingungen zu berechnen, um die Daten zu filtern. Pandas bietet drei Operatoren:&für logisches UND,|für logisches ODER und~ für logisches NICHT.

Betrachten Sie das folgende Setup:

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

Logisches UND

Zum df oben, sagen Sie alle Zeilen zurückgeben möchten , in der A <5 und B> 5. Diese durch Berechnung Masken für jede Bedingung separat durchgeführt wird, und Anding sie.

Überladener bitweiser &Operator
Bevor Sie fortfahren, beachten Sie bitte diesen speziellen Auszug der Dokumente, die angeben

Eine weitere häufige Operation ist die Verwendung von Booleschen Vektoren zum Filtern der Daten. Die Operatoren sind: |für or, &für andund ~für not. Diese müssen durch die Verwendung von Klammern gruppiert werden , da standardmäßig Python wertet den Ausdruck wie df.A > 2 & df.B < 3wie df.A > (2 & df.B) < 3, während die gewünschte Auswertungsreihenfolge ist (df.A > 2) & (df.B < 3).

In diesem Sinne kann das elementweise logische UND mit dem bitweisen Operator implementiert werden &:

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

Und der nachfolgende Filterungsschritt ist einfach:

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

   A  B  C
1  3  7  9
3  4  7  6

Die Klammern werden verwendet, um die Standardrangfolge von bitweisen Operatoren zu überschreiben, die eine höhere Priorität als die bedingten Operatoren <und haben >. Siehe den Abschnitt Operator-Vorrang im in den Python-Dokumenten.

Wenn Sie keine Klammern verwenden, wird der Ausdruck falsch ausgewertet. Zum Beispiel, wenn Sie versehentlich etwas versuchen wie

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

Es wird analysiert als

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

Welches wird,

df['A'] < something_you_dont_want > 5

Welches wird (siehe die Python-Dokumente zum verketteten Operatorvergleich ),

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

Welches wird,

# Both operands are Series...
something_else_you_dont_want1 and something_else_you_dont_want2

Welches wirft

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

Machen Sie diesen Fehler also nicht! 1

Vermeiden der Gruppierung von Klammern
Die Korrektur ist eigentlich recht einfach. Die meisten Operatoren haben eine entsprechende gebundene Methode für DataFrames. Wenn die einzelnen Masken mithilfe von Funktionen anstelle von bedingten Operatoren erstellt werden, müssen Sie nicht mehr nach Parens gruppieren, um die Bewertungsreihenfolge anzugeben:

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

Siehe den Abschnitt über flexible Vergleiche. . Zusammenfassend haben wir

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

Eine weitere Option zum Vermeiden von Klammern ist die Verwendung DataFrame.query(oder eval):

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

   A  B  C
1  3  7  9
3  4  7  6

Ich habe ausführlich dokumentiert queryund evalin dynamischer Expression Evaluation in Pandas mit pd.eval () .

operator.and_
Ermöglicht es Ihnen, diesen Vorgang auf funktionale Weise auszuführen. Interne Aufrufe, Series.__and__die dem bitweisen Operator entsprechen.

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

Normalerweise brauchen Sie das nicht, aber es ist nützlich zu wissen.

Verallgemeinern: np.logical_and(und logical_and.reduce)
Eine andere Alternative ist die Verwendung np.logical_and, für die auch keine Gruppierung in Klammern erforderlich ist:

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_andist ein Ufunc (Universal Functions) , und die meisten Ufuncs haben eine reduceMethode. Dies bedeutet, dass es einfacher ist, zu verallgemeinern, logical_andwenn Sie mehrere Masken für AND haben. Zum Beispiel zu UND-Masken m1und m2und m3mit &müssten Sie tun

m1 & m2 & m3

Eine einfachere Option ist jedoch

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

Dies ist leistungsstark, da Sie mit einer komplexeren Logik darauf aufbauen können (z. B. indem Sie Masken in einem Listenverständnis dynamisch generieren und alle hinzufügen):

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 - Ich weiß, dass ich in diesem Punkt Harfe spiele, aber bitte ertrage es mit mir. Dies ist ein sehr , sehr häufiger Anfängerfehler und muss sehr gründlich erklärt werden.


Logisches ODER

Für die dfoben, sagen Sie alle Zeilen zurückgeben möchten , in der A == 3 oder B == 7.

Bitweise überladen |

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

Wenn Sie dies noch nicht getan haben, lesen Sie bitte auch den Abschnitt über logisches UND oben. Alle Vorbehalte gelten hier.

Alternativ kann diese Operation mit angegeben werden

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_
Ruft Series.__or__unter der Haube.

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
Verwenden Sie für zwei Bedingungen 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

Verwenden Sie für mehrere Masken 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

Logisch NICHT

Gegeben eine Maske, wie z

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

Wenn Sie jeden booleschen Wert invertieren müssen (damit das Endergebnis ist [False, False, True] ), können Sie eine der folgenden Methoden verwenden.

Bitweise ~

~mask

0    False
1    False
2     True
dtype: bool

Auch hier müssen Ausdrücke in Klammern gesetzt werden.

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

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

Dies ruft intern auf

mask.__invert__()

0    False
1    False
2     True
dtype: bool

Aber benutze es nicht direkt.

operator.inv
Intern ruft __invert__die Serie auf.

operator.inv(mask)

0    False
1    False
2     True
dtype: bool

np.logical_not
Dies ist die numpy Variante.

np.logical_not(mask)

0    False
1    False
2     True
dtype: bool

Hinweis, np.logical_andkann durch np.bitwise_and, logical_ormit bitwise_orund logical_notmit ersetzt werden invert.

cs95
quelle
@ cs95 in der TLDR befürworten Sie für elementweises boolesches ODER die Verwendung von |, was äquivalent zu ist numpy.bitwise_or, anstelle von numpy.logical_or. Darf ich fragen warum? Ist nicht numpy.logical_orspeziell für diese Aufgabe konzipiert? Warum die Last hinzufügen, dies für jedes Elementpaar bitweise zu tun?
flow2k
@ flow2k kannst du bitte den relevanten text zitieren? Ich kann nicht finden, worauf Sie sich beziehen. FWIW Ich behaupte, dass logisch_ * das korrekte funktionale Äquivalent der Operatoren ist.
CS95
@ cs95 Ich beziehe mich auf die erste Zeile der Antwort: "TLDR; Logische Operatoren in Pandas sind &, | und ~".
flow2k
@ flow2k In der Dokumentation steht wörtlich : "Eine weitere häufige Operation ist die Verwendung von Booleschen Vektoren zum Filtern der Daten. Die Operatoren sind: | für oder, & für und und ~ für nicht."
CS95
@ cs95, ok, ich habe gerade diesen Abschnitt gelesen und er wird |für elementweise boolesche Operationen verwendet. Für mich ist diese Dokumentation jedoch eher ein "Tutorial", und im Gegensatz dazu sind diese API-Referenzen meiner Meinung nach näher an der Quelle der Wahrheit: numpy.bitwise_or und numpy.logical_or - also versuche ich zu verstehen, was ist hier beschrieben.
flow2k
4

Logische Operatoren für die boolesche Indizierung in Pandas

Es ist wichtig zu erkennen , dass Sie keine der Python verwenden können logische Operatoren ( and, oroder not) auf pandas.Seriesoder pandas.DataFrames (ähnlich können Sie nicht verwenden sie auf numpy.arrays mit mehr als einem Element). Der Grund, warum Sie diese nicht verwenden können, liegt darin, dass sie implizit boolihre Operanden aufrufen, was eine Ausnahme auslöst, da diese Datenstrukturen entschieden haben, dass der Boolesche Wert eines Arrays nicht eindeutig ist:

>>> 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().

Ich habe dies in meiner Antwort auf den "Wahrheitswert einer Reihe ist mehrdeutig. Verwenden Sie a.empty, a.bool (), a.item (), a.any () oder a.all ()" Q ausführlicher behandelt + A .

NumPys logische Funktionen

Jedoch NumPy bietet elementweise Betriebs Äquivalente zu diesen Operatoren als Funktionen , die auf die verwendet werden können numpy.array, pandas.Series, pandas.DataFrameoder jede andere (entsprechend) numpy.arrayUnterklasse:

Im Wesentlichen sollte man also Folgendes verwenden (vorausgesetzt df1und df2es handelt sich um Pandas DataFrames):

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

Bitweise Funktionen und bitweise Operatoren für Boolesche Werte

Wenn Sie jedoch über ein boolesches NumPy-Array, Pandas Series oder Pandas DataFrames verfügen, können Sie auch die elementweisen bitweisen Funktionen verwenden (für Boolesche Werte sind oder sollten sie zumindest nicht von den logischen Funktionen zu unterscheiden sein):

In der Regel werden die Operatoren verwendet. In Kombination mit Vergleichsoperatoren muss jedoch daran gedacht werden, den Vergleich in Klammern zu setzen, da die bitweisen Operatoren eine höhere Priorität haben als die Vergleichsoperatoren :

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

Dies kann irritierend sein, da die logischen Python-Operatoren eine geringere Priorität als die Vergleichsoperatoren haben, sodass Sie normalerweise schreiben a < 10 and b > 10(wo aund bsind beispielsweise einfache Ganzzahlen) und die Klammer nicht benötigen.

Unterschiede zwischen logischen und bitweisen Operationen (bei Nicht-Booleschen)

Es ist wirklich wichtig zu betonen, dass Bit- und logische Operationen nur für boolesche NumPy-Arrays (und boolesche Serien- und Datenrahmen) äquivalent sind. Wenn diese keine Booleschen Werte enthalten, führen die Operationen zu unterschiedlichen Ergebnissen. Ich werde Beispiele mit NumPy-Arrays einfügen, aber die Ergebnisse für die Pandas-Datenstrukturen sind ähnlich:

>>> 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)

Und da NumPy (und ähnlich Pandas) verschiedene Dinge für boolesche ( Boolesche oder "Masken" -Index-Arrays ) und ganzzahlige ( Index-Arrays ) Indizes ausführt, sind auch die Ergebnisse der Indizierung unterschiedlich:

>>> 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])

Übersichtstabelle

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              |        ~

Wo der logische Operator für NumPy-Arrays , Pandas Series und Pandas DataFrames nicht funktioniert . Die anderen arbeiten an diesen Datenstrukturen (und einfachen Python-Objekten) und arbeiten elementweise. Seien Sie jedoch vorsichtig mit der bitweisen Invertierung auf einfachen Pythons, boolda der Bool in diesem Kontext als Ganzzahlen interpretiert wird (z. B. ~FalseRückgaben -1und ~TrueRückgaben -2).

MSeifert
quelle