Logische operators voor Booleaanse indexering in Panda’s

Ik werk met Booleaanse index in Panda’s.
De vraag is waarom de verklaring:

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

Werkt prima, terwijl

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

Verlaat met fout?

Voorbeeld:

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

Antwoord 1, Autoriteit 100%

Wanneer u

zegt

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

U vraagt ​​impliciet Python om (a['x']==1)en (a['y']==10)naar Boolean-waarden te converteren .

Numpy-arrays (van lengte groter dan 1) en panda-objecten zoals serie hebben geen Booleaanse waarde – met andere woorden, ze verhogen

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

Bij gebruik als een Booleaanse waarde. Dat komt omdat het onduidelijk wanneer het zou moeten wees waar of onwaar . Sommige gebruikers kunnen ervan uitgaan dat ze waar zijn als ze niet-nullengte hebben, zoals een Python-lijst. Anderen zouden willen dat het alleen waar is als alle zijn elementen waar zijn. Anderen willen misschien dat het waar is als elke van zijn elementen waar zijn.

Omdat er zoveel tegenstrijdige verwachtingen zijn, weigeren de ontwerpers van Numpy en Panda’s te raden en brengen in plaats daarvan een waardeerror op.

In plaats daarvan moet u expliciet zijn door de empty(), all()of any()methode te bellen om aan te geven welk gedrag je wenst.

In dit geval lijkt het echter erop dat u geen Booleaanse evaluatie wilt, u wilt element-wise logisch-en. Dat is wat de &binaire operator uitgevoerd:

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

Retourneert een Boolean-array.


trouwens, zoals AlexPmil Notes ,
De haakjes zijn verplicht sinds &heeft een hogere exploitant voorrang dan ==.
Zonder de haakjes, a['x']==1 & a['y']==10zou worden geëvalueerd als a['x'] == (1 & a['y']) == 10die op zijn beurt zou zijn Wees gelijk aan de geketende vergelijking (a['x'] == (1 & a['y'])) and ((1 & a['y']) == 10). Dat is een uitdrukking van het formulier Series and Series.
Het gebruik van andmet twee Series zou opnieuw dezelfde ValueErroractiveren als hierboven. Daarom zijn de haakjes verplicht.


Antwoord 2, autoriteit 47%

TLDR; Logische operators in Panda’s zijn &, |en ~, en haakjes (...)is belangrijk!

Python’s and, oren notlogische operators zijn ontworpen om te werken met scalaire waarden. Panda’s moesten het dus beter doen en de bitsgewijze operatoren overschrijven om een gevectoriseerde(elementsgewijs) versie van deze functionaliteit te krijgen.

Dus het volgende in python (exp1en exp2zijn uitdrukkingen die een booleaans resultaat opleveren)…

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

…wordt vertaald naar…

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

voor panda’s.

Als u tijdens het uitvoeren van een logische bewerking een ValueErrorkrijgt, dan moet u haakjes gebruiken om te groeperen:

(exp1) op (exp2)

Bijvoorbeeld

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

Enzovoort.


Boolean indexeren: Een veelvoorkomende bewerking is het berekenen van booleaanse maskers door middel van logische voorwaarden om de gegevens te filteren. Pandas biedt drieoperators: &voor logische AND, |voor logische OR en ~voor logische NOT.

Overweeg de volgende opstelling:

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

Logisch EN

Voor dfhierboven, stel dat u alle rijen wilt retourneren waarin A < 5 en B > 5. Dit wordt gedaan door maskers voor elke aandoening afzonderlijk te berekenen en deze te ANDen.

Overbelaste Bitwise &-operator
Voordat u verder gaat, dient u kennis te nemen van dit specifieke uittreksel van de documenten, waarin staat

Een andere veel voorkomende bewerking is het gebruik van booleaanse vectoren om de
gegevens. De operators zijn: |voor or, &voor anden ~voor not. Deze
moet worden gegroepeerd door haakjes te gebruiken
, omdat Python standaard
evalueer een uitdrukking zoals df.A > 2 & df.B < 3als df.A > (2 &
df.B) < 3
, terwijl de gewenste evaluatievolgorde (df.A > 2) & (df.B <
3)
.

Dus, met dit in gedachten, kan elementgewijs logische AND worden geïmplementeerd met de bitsgewijze operator &:

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

En de volgende filterstap is eenvoudig,

df[(df['A'] < 5) & (df['B'] > 5)]
   A  B  C
1  3  7  9
3  4  7  6

De haakjes worden gebruikt om de standaardvoorrangsvolgorde van bitsgewijze operatoren te overschrijven, die een hogere prioriteit hebben dan de voorwaardelijke operatoren <en >. Zie het gedeelte over Operatorvoorrangin de Python-documenten.

Als u geen haakjes gebruikt, wordt de uitdrukking onjuist geëvalueerd. Als u bijvoorbeeld per ongeluk iets probeert zoals

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

Het wordt geparseerd als

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

Wat wordt,

df['A'] < something_you_dont_want > 5

Wat wordt (zie de python-documenten op geketende operatorvergelijking) ,

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

Wat wordt,

# Both operands are Series...
something_else_you_dont_want1 and something_else_you_dont_want2

Welke worpen

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

Maak die fout dus niet!1

Groepering tussen haakjes vermijden
De oplossing is eigenlijk vrij eenvoudig. De meeste operators hebben een overeenkomstige gebonden methode voor DataFrames. Als de individuele maskers zijn opgebouwd met behulp van functies in plaats van voorwaardelijke operators, hoeft u niet langer te groeperen op haakjes om de evaluatievolgorde te specificeren:

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

Zie het gedeelte over Flexibele vergelijkingen.. Samenvattend hebben we

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

Een andere optie om haakjes te vermijden is het gebruik van DataFrame.query(of eval):

df.query('A < 5 and B > 5')
   A  B  C
1  3  7  9
3  4  7  6

Ik heb uitgebreidegedocumenteerde queryen evalin Evaluatie van dynamische expressies in panda’s met pd.eval().

operator.and_
Hiermee kunt u deze bewerking op een functionele manier uitvoeren. Roept intern Series.__and__aan wat overeenkomt met de bitsgewijze operator.

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

Dit heb je normaal gesproken niet nodig, maar het is wel handig om te weten.

Algemeen: np.logical_and(en logical_and.reduce)
Een ander alternatief is het gebruik van np.logical_and, waarvoor ook geen groepering tussen haakjes nodig is:

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_andis een ufunc (universele functies), en de meeste ufuncs hebben een reducemethode. Dit betekent dat het makkelijker is om te generaliseren met logical_andals je meerdere maskers hebt naar AND. Als u bijvoorbeeld m1en m2en m3wilt maskeren met &, moet u

m1 & m2 & m3

Een eenvoudigere optie is echter

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

Dit is krachtig, omdat je hier bovenop kunt bouwen met complexere logica (bijvoorbeeld dynamisch genereren van maskers in een lijstbegrip en ze allemaal toevoegen):

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 – Ik weet dat ik op dit punt blijf hameren, maar heb alsjeblieft geduld. Dit is een zeer, zeerveelvoorkomende beginnersfout en moet zeer grondig worden uitgelegd.


Logisch OF

Voor de dfhierboven, stel dat je alle rijen wilt retourneren waarbij A == 3 of B == 7.

Overbelast Bitwise |

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

Als je dat nog niet hebt gedaan, lees dan ook het gedeelte over Logisch ENhierboven, alle voorbehouden zijn hier van toepassing.

Als alternatief kan deze bewerking worden gespecificeerd met

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_
Roept Series.__or__onder de motorkap op.

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
Gebruik voor twee voorwaarden 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

Gebruik voor meerdere maskers 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 NIET

Gegeven een masker, zoals

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

Als u elke Booleaanse waarde moet omkeren (zodat het eindresultaat [False, False, True]is), kunt u een van de onderstaande methoden gebruiken.

Bitwise ~

~mask
0    False
1    False
2     True
dtype: bool

Nogmaals, uitdrukkingen moeten tussen haakjes staan.

~(df['A'] == 3)
0     True
1    False
2    False
3     True
4     True
Name: A, dtype: bool

Dit roept intern

. op

mask.__invert__()
0    False
1    False
2     True
dtype: bool

Maar gebruik het niet rechtstreeks.

operator.inv
Belt intern __invert__op de Series.

operator.inv(mask)
0    False
1    False
2     True
dtype: bool

np.logical_not
Dit is de numpy variant.

np.logical_not(mask)
0    False
1    False
2     True
dtype: bool

Opmerking, np.logical_andkan worden vervangen door np.bitwise_and, logical_ordoor bitwise_or, en logical_notmet invert.


Antwoord 3, autoriteit 2%

Logische operatoren voor booleaanse indexering in Panda’s

Het is belangrijk om te beseffen dat je geen van de Python logische operatoren(and, orof not) op pandas.Seriesof pandas.DataFrames (op dezelfde manier kun je ze niet gebruiken op numpy.arrays met meer dan één element). De reden waarom je die niet kunt gebruiken is omdat ze impliciet boolaanroepen op hun operanden, wat een Exception genereert omdat deze datastructuren besloten dat de boolean van een array dubbelzinnig is:

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

Ik heb dit uitgebreider bedekt in mijn antwoord op de “Truth-waarde van een serie is dubbelzinnig. Gebruik A.Egty, a. BOOL (), A.item (), A.Alle () of A.Alle () “Q + A .

Numpys logische functies

Echter numpy biedt element-wise operationele equivalenten aan deze Operators als functies die kunnen worden gebruikt op numpy.array, pandas.Series, pandas.DataFrameof een andere (conforme) numpy.arraysubclass:

Dus, in wezen, men moet gebruiken (aannemen van df1en df2zijn pandas-dataframes):

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

Bitise-functies en bitwise-exploitanten voor Booleans

Als u echter een boolean NumPy-array, pandas Series of pandas DataFrames heeft, kunt u ook de elementsgewijze bitsgewijze functies(voor booleans zijn ze – of zouden ze in ieder geval moeten zijn – niet te onderscheiden van de logische functies):

Normaal gesproken worden de operators gebruikt. In combinatie met vergelijkingsoperatoren moet men er echter aan denken om de vergelijking tussen haakjes te plaatsen, omdat de bitsgewijze operatoren een hogere prioriteit dan de vergelijkingsoperatoren:

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

Dit kan irritant zijn omdat de logische operatoren van Python een lagere prioriteit hebben dan de vergelijkingsoperatoren, dus je schrijft normaal gesproken a < 10 and b > 10(waarbij aen bbijvoorbeeld eenvoudige gehele getallen zijn) en hebben geen haakjes nodig.

Verschillen tussen logische en bitsgewijze bewerkingen (op niet-booleans)

Het is erg belangrijk om te benadrukken dat bit- en logische bewerkingen alleen equivalent zijn voor Booleaanse NumPy-arrays (en Booleaanse Series & DataFrames). Als deze geen booleans bevatten, zullen de bewerkingen andere resultaten opleveren. Ik zal voorbeelden toevoegen met behulp van NumPy-arrays, maar de resultaten zullen vergelijkbaar zijn voor de gegevensstructuren van panda’s:

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

En aangezien NumPy (en soortgelijke panda’s) verschillende dingen doet voor boolean (Boolean of “mask” index-arrays) en integer (Index arrays) indexen zullen de resultaten van indexering ook anders zijn:

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

Samenvattingstabel

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

Waar de logische operator niet werkt voor NumPy-arrays, pandas Series en pandas DataFrames. De anderen werken aan deze datastructuren (en gewone Python-objecten) en werken elementsgewijs.
Wees echter voorzichtig met de bitsgewijze invert op gewone Python bools, omdat de bool in deze context zal worden geïnterpreteerd als gehele getallen (bijvoorbeeld ~Falsegeeft -1en ~Trueretourneert -2).

Other episodes