Operatori logici per l'indicizzazione booleana in Panda


153

Sto lavorando con l'indice booleano in Panda. La domanda è perché l'affermazione:

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

funziona benissimo mentre

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

esce con errore?

Esempio:

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

6
Questo perché gli array e le serie di panda intorpiditi usano gli operatori bit per bit anziché logici poiché si stanno confrontando ogni elemento dell'array / serie con un altro. Pertanto non ha senso utilizzare l'operatore logico in questa situazione. vedi correlate: stackoverflow.com/questions/8632033/...
EdChum

9
In Python and != &. L' andoperatore in Python non può essere sovrascritto, mentre l' &operatore ( __and__) può. Da qui la scelta l'uso &in numpy e panda.
Steven Rumbalski,

Risposte:


209

Quando dici

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

Stai implicitamente chiedendo a Python di convertire (a['x']==1)e (a['y']==10)di valori booleani.

Le matrici NumPy (di lunghezza maggiore di 1) e gli oggetti Panda come Serie non hanno un valore booleano - in altre parole, aumentano

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

se usato come valore booleano. Questo perché non è chiaro quando dovrebbe essere Vero o Falso . Alcuni utenti potrebbero supporre che siano Veri se hanno una lunghezza diversa da zero, come un elenco Python. Altri potrebbero desiderare che sia vero solo se tutti i suoi elementi sono veri. Altri potrebbero desiderare che sia vero se uno qualsiasi dei suoi elementi è vero.

Poiché ci sono così tante aspettative contrastanti, i progettisti di NumPy e Pandas si rifiutano di indovinare, e invece aumentano un ValueError.

Invece, devi essere esplicito, chiamando il metodo empty(), all()o any()per indicare quale comportamento desideri.

In questo caso, tuttavia, sembra che tu non voglia una valutazione booleana, vuoi un elemento logico e logico. Questo è ciò &che esegue l'operatore binario:

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

restituisce un array booleano.


A proposito, come osserva alexpmil , le parentesi sono obbligatorie poiché &hanno una precedenza dell'operatore maggiore di ==. Senza le parentesi, a['x']==1 & a['y']==10verrebbe valutato come a['x'] == (1 & a['y']) == 10equivalente a sua volta al confronto incatenato (a['x'] == (1 & a['y'])) and ((1 & a['y']) == 10). Questa è un'espressione della forma Series and Series. L'uso di anddue serie si innescherebbe nuovamente ValueErrorcome sopra. Ecco perché le parentesi sono obbligatorie.


3
gli array intorpiditi hanno questa proprietà se sono di lunghezza uno. Solo gli sviluppatori panda (ostinatamente) si rifiutano di indovinare: p
Andy Hayden,

4
'&' Non porta la stessa curva ambigua di 'e'? Come mai quando si tratta di "&", improvvisamente tutti gli utenti concordano sul fatto che dovrebbe essere saggio elemento, mentre quando vedono "e", le loro aspettative variano?
Indominus,

16
@Indominus: il linguaggio Python stesso richiede che l'espressione x and yinneschi la valutazione di bool(x)e bool(y). Python "prima valuta x; se xè falso, viene restituito il suo valore; in caso contrario, yviene valutato e viene restituito il valore risultante." Quindi la sintassi x and ynon può essere utilizzata per elementi logici e poiché solo xo ypuò essere restituita. Al contrario, i x & ytrigger x.__and__(y)e il __and__metodo possono essere definiti per restituire tutto ciò che ci piace.
unutbu,

2
Importante da notare: le parentesi attorno alla ==clausola sono obbligatorie . a['x']==1 & a['y']==10restituisce lo stesso errore della domanda.
Alex P. Miller,

1
A cosa serve "|"?
Euler_Salter,

62

TLDR; Gli operatori logici in Panda sono &, |e ~, e le parentesi (...)sono importanti!

Python and, ore notoperatori logici sono progettati per lavorare con scalari. Quindi Pandas ha dovuto fare di meglio e sovrascrivere gli operatori bit a bit per ottenere una versione vettoriale (dal punto di vista degli elementi) di questa funzionalità.

Quindi quanto segue in Python ( exp1e exp2sono espressioni che valutano un risultato booleano) ...

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

... si tradurrà in ...

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

per i panda.

Se nel processo di esecuzione dell'operazione logica ottieni un ValueError, allora devi usare le parentesi per raggruppare:

(exp1) op (exp2)

Per esempio,

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

E così via.


Indicizzazione booleana : un'operazione comune è calcolare le maschere booleane attraverso condizioni logiche per filtrare i dati. Panda fornisce tre operatori:&per AND logico,|per OR logico e~per NOT logico.

Considera la seguente configurazione:

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

AND logico

Per quanto dfsopra, supponi di voler restituire tutte le righe in cui A <5 e B> 5. Questo viene fatto calcolando le maschere per ciascuna condizione separatamente e ANDandole.

&Operatore Bitwise sovraccarico
Prima di continuare, prendi nota di questo particolare estratto dei documenti, che indica

Un'altra operazione comune è l'uso di vettori booleani per filtrare i dati. Gli operatori sono: |per or, &per ande ~per not. Questi devono essere raggruppati usando le parentesi , poiché per impostazione predefinita Python valuterà un'espressione df.A > 2 & df.B < 3come df.A > (2 & df.B) < 3, mentre l'ordine di valutazione desiderato è (df.A > 2) & (df.B < 3).

Quindi, con questo in mente, un logico elemento logico AND può essere implementato con l'operatore bit a bit &:

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

E il successivo passaggio di filtraggio è semplicemente,

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

   A  B  C
1  3  7  9
3  4  7  6

Le parentesi vengono utilizzate per sovrascrivere l'ordine di precedenza predefinito degli operatori bit a bit, che hanno una precedenza maggiore sugli operatori condizionali <e >. Vedi la sezione Precedenza dell'operatore nei documenti di Python.

Se non si utilizzano le parentesi, l'espressione viene valutata in modo errato. Ad esempio, se si tenta accidentalmente qualcosa come

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

Viene analizzato come

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

Che diventa

df['A'] < something_you_dont_want > 5

Che diventa (vedi i documenti di Python sul confronto degli operatori concatenati ),

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

Che diventa

# Both operands are Series...
something_else_you_dont_want1 and something_else_you_dont_want2

Che getta

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

Quindi, non fare questo errore! 1

Evitare il raggruppamento delle parentesi
La correzione è in realtà abbastanza semplice. La maggior parte degli operatori ha un metodo associato corrispondente per DataFrames. Se le singole maschere vengono create utilizzando le funzioni anziché gli operatori condizionali, non sarà più necessario raggruppare per parentesi per specificare l'ordine di valutazione:

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

Vedi la sezione Confronti flessibili. . Per riassumere, abbiamo

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

Un'altra opzione per evitare le parentesi è usare DataFrame.query(o eval):

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

   A  B  C
1  3  7  9
3  4  7  6

Ho ampiamente documentato querye evalnella valutazione dell'espressione dinamica nei panda usando pd.eval () .

operator.and_
Consente di eseguire questa operazione in modo funzionale. Chiamate interne Series.__and__che corrispondono all'operatore bit a bit.

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

Di solito non ti servirà, ma è utile saperlo.

Generalizzare: np.logical_and(e logical_and.reduce)
Un'altra alternativa sta usando np.logical_and, che inoltre non ha bisogno del raggruppamento di parentesi:

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è un ufunc (Funzioni universali) e la maggior parte degli ufunc ha un reducemetodo. Ciò significa che è più facile generalizzare logical_andse si dispone di più maschere su AND. Ad esempio, per le maschere AND m1e m2e m3con &, dovresti fare

m1 & m2 & m3

Tuttavia, è un'opzione più semplice

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

Questo è potente, perché ti consente di basarti su questo con una logica più complessa (ad esempio, generare dinamicamente maschere in una comprensione di elenco e aggiungendole tutte):

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 - So che mi sto cacciando su questo punto, ma per favore abbi pazienza con me. Questo è un errore del principiante molto , molto comune, e deve essere spiegato molto attentamente.


OR logico

Per quanto dfsopra, supponi di voler restituire tutte le righe in cui A == 3 o B == 7.

Sovraccarico 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

Se non l'hai ancora fatto, ti preghiamo di leggere anche la sezione su Logica E sopra, tutte le avvertenze si applicano qui.

In alternativa, questa operazione può essere specificata con

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_
Chiamate Series.__or__sotto il cofano.

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
Per due condizioni, utilizzare 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

Per più maschere, utilizzare 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

NOT logico

Dato una maschera, come ad esempio

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

Se è necessario invertire ogni valore booleano (in modo che il risultato finale sia [False, False, True]), è possibile utilizzare uno dei metodi seguenti.

bitwise ~

~mask

0    False
1    False
2     True
dtype: bool

Ancora una volta, le espressioni devono essere tra parentesi.

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

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

Questo chiama internamente

mask.__invert__()

0    False
1    False
2     True
dtype: bool

Ma non usarlo direttamente.

operator.inv
Chiamate interne __invert__alla serie.

operator.inv(mask)

0    False
1    False
2     True
dtype: bool

np.logical_not
Questa è la variante intorpidita.

np.logical_not(mask)

0    False
1    False
2     True
dtype: bool

Nota, np.logical_andpuò essere sostituito np.bitwise_and, logical_orcon bitwise_or, e logical_notcon invert.


@ cs95 nel TLDR, per OR booleana dal punto di vista degli elementi, si consiglia di utilizzare |, che equivale a numpy.bitwise_or, anziché numpy.logical_or. Posso chiedere perchè? Non è numpy.logical_orprogettato specificamente per questa attività? Perché aggiungere l'onere di farlo bit per bit per ogni coppia di elementi?
flow2k,

@ flow2k puoi citare il testo pertinente per favore? Non riesco a trovare quello a cui ti riferisci. FWIW Ritengo che la logica_ * sia l'equivalente funzionale corretto degli operatori.
cs95,

@ cs95 Mi riferisco alla prima riga della risposta: "TLDR; Gli operatori logici in Panda sono &, | e ~".
flow2k,

@ flow2k È letteralmente nella documentazione : "Un'altra operazione comune è l'uso di vettori booleani per filtrare i dati. Gli operatori sono: | per o, & per e e ~ per no."
cs95,

@ cs95, ok, ho appena letto questa sezione e lo usa |per l'operazione booleana in termini di elementi. Ma per me, quella documentazione è più un "tutorial", e al contrario, penso che questi riferimenti API siano più vicini alla fonte della verità: numpy.bitwise_or e numpy.logical_or - quindi sto cercando di dare un senso a ciò che è descritto qui.
flow2k

4

Operatori logici per l'indicizzazione booleana in Panda

È importante rendersi conto che non è possibile utilizzare nessuno degli operatori logici di Python ( and, oro not) su pandas.Serieso pandas.DataFrames (allo stesso modo non è possibile utilizzarli su numpy.arrays con più di un elemento). Il motivo per cui non è possibile utilizzarli è perché chiamano implicitamente i boolloro operandi che generano un'eccezione perché queste strutture di dati hanno deciso che il valore booleano di un array è ambiguo:

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

Ho trattato questo argomento in modo più ampio nella mia risposta al "Il valore di verità di una serie è ambiguo. Usa a.empty, a.bool (), a.item (), a.any () o a.all ()" Q + A .

Funzioni logiche NumPys

Tuttavia NumPy fornisce elemento-saggio equivalenti operativi a questi operatori come funzioni che possono essere utilizzate su numpy.array, pandas.Series, pandas.DataFrame, o qualsiasi altro (conforme) numpy.arraysottoclasse:

Quindi, in sostanza, si dovrebbe usare (assumendo df1e df2sono DataFrame Panda):

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

Funzioni bit a bit e operatori bit a bit per booleani

Tuttavia, nel caso in cui tu abbia un array NumPy booleano, una serie di panda o DataFrame di panda, potresti anche usare le funzioni bit per bit degli elementi (per i booleani sono - o almeno dovrebbero essere - indistinguibili dalle funzioni logiche):

In genere vengono utilizzati gli operatori. Tuttavia, quando combinato con operatori di confronto si deve ricordare di racchiudere il confronto tra parentesi perché gli operatori bit a bit hanno una precedenza superiore rispetto agli operatori di confronto :

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

Questo può essere irritante perché gli operatori logici Python hanno una precendenza inferiore rispetto agli operatori di confronto, quindi normalmente scrivi a < 10 and b > 10(dove ae bsono, ad esempio, interi semplici) e non hai bisogno della parentesi.

Differenze tra operazioni logiche e bit per bit (su valori non booleani)

È davvero importante sottolineare che le operazioni bit e logiche sono equivalenti solo agli array booleani NumPy (e alle serie booleane e ai DataFrame). Se questi non contengono valori booleani, le operazioni daranno risultati diversi. Includerò esempi usando le matrici NumPy ma i risultati saranno simili per le strutture di dati dei panda:

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

E poiché NumPy (e similmente panda) fa cose diverse per gli indici di indice booleano ( booleano o "maschera" ) e intero ( array di indici), anche i risultati dell'indicizzazione saranno diversi:

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

Tabella riassuntiva

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

Dove l'operatore logico non funziona per matrici NumPy , panda serie e panda DataFrames. Gli altri lavorano su queste strutture di dati (e semplici oggetti Python) e funzionano in termini di elementi. Tuttavia, fai attenzione con l'inversione bit per bit sui semplici Python bools poiché il bool verrà interpretato come numero intero in questo contesto (ad esempio ~Falserestituisce -1e ~Truerestituisce -2).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.