TLDR; Gli operatori logici in Panda sono &
, |
e ~
, e le parentesi (...)
sono importanti!
Python and
, or
e not
operatori 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 ( exp1
e exp2
sono 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 df
sopra, 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 and
e ~
per not
. Questi devono essere raggruppati usando le parentesi , poiché per impostazione predefinita Python valuterà un'espressione df.A > 2 & df.B < 3
come 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 query
e eval
nella 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 reduce
metodo. Ciò significa che è più facile generalizzare logical_and
se si dispone di più maschere su AND. Ad esempio, per le maschere AND m1
e m2
e m3
con &
, 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 df
sopra, 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_and
può essere sostituito np.bitwise_and
, logical_or
con bitwise_or
, e logical_not
con invert
.