TLDR; Los operadores lógicos en Pandas son &
, |
y ~
, ¡y los paréntesis (...)
son importantes!
Python es and
, or
y not
operadores lógicos están diseñados para trabajar con escalares. Así que Pandas tuvo que hacerlo mejor y anular los operadores bit a bit para lograr la versión vectorizada (por elementos) de esta funcionalidad.
Entonces, lo siguiente en python ( exp1
y exp2
son expresiones que evalúan un resultado booleano) ...
exp1 and exp2 # Logical AND
exp1 or exp2 # Logical OR
not exp1 # Logical NOT
... se traducirá a ...
exp1 & exp2 # Element-wise logical AND
exp1 | exp2 # Element-wise logical OR
~exp1 # Element-wise logical NOT
para pandas
Si en el proceso de realizar una operación lógica obtiene un ValueError
, entonces necesita usar paréntesis para agrupar:
(exp1) op (exp2)
Por ejemplo,
(df['col1'] == x) & (df['col2'] == y)
Y así.
Indexación booleana : Una operación común es calcular máscaras booleanas a través de condiciones lógicas para filtrar los datos. Pandas proporciona tres operadores:&
para AND lógico,|
para OR lógico y~
para NOT lógico.
Considere la siguiente configuración:
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
Lógico Y
Para lo df
anterior, digamos que desea devolver todas las filas donde A <5 y B> 5. Esto se hace calculando máscaras para cada condición por separado, y ANDing.
&
Operador Bitwise sobrecargado
Antes de continuar, tome nota de este extracto particular de los documentos, que establece
Otra operación común es el uso de vectores booleanos para filtrar los datos. Los operadores son: |
para or
, &
para and
y ~
para not
. Estos deben agruparse utilizando paréntesis , ya que, de forma predeterminada, Python evaluará una expresión df.A > 2 & df.B < 3
como df.A > (2 &
df.B) < 3
, mientras que el orden de evaluación deseado es (df.A > 2) & (df.B <
3)
.
Entonces, con esto en mente, el elemento lógico AND se puede implementar con el operador 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
Y el siguiente paso de filtrado es simplemente,
df[(df['A'] < 5) & (df['B'] > 5)]
A B C
1 3 7 9
3 4 7 6
Los paréntesis se utilizan para anular el orden de precedencia predeterminado de los operadores bit a bit, que tienen mayor precedencia sobre los operadores condicionales <
y >
. Consulte la sección de Precedencia del operador en los documentos de Python.
Si no usa paréntesis, la expresión se evalúa incorrectamente. Por ejemplo, si intenta accidentalmente algo como
df['A'] < 5 & df['B'] > 5
Se analiza como
df['A'] < (5 & df['B']) > 5
Que se convierte
df['A'] < something_you_dont_want > 5
Que se convierte (ver los documentos de Python en la comparación de operadores encadenados ),
(df['A'] < something_you_dont_want) and (something_you_dont_want > 5)
Que se convierte
# Both operands are Series...
something_else_you_dont_want1 and something_else_you_dont_want2
Que tira
ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
Entonces, ¡no cometas ese error! 1
Evitar la agrupación de paréntesis
La solución es en realidad bastante simple. La mayoría de los operadores tienen un método enlazado correspondiente para DataFrames. Si las máscaras individuales se crean utilizando funciones en lugar de operadores condicionales, ya no necesitará agrupar por parens para especificar el orden de evaluación:
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
Consulte la sección sobre Comparaciones flexibles. . Para resumir, tenemos
╒════╤════════════╤════════════╕
│ │ Operator │ Function │
╞════╪════════════╪════════════╡
│ 0 │ > │ gt │
├────┼────────────┼────────────┤
│ 1 │ >= │ ge │
├────┼────────────┼────────────┤
│ 2 │ < │ lt │
├────┼────────────┼────────────┤
│ 3 │ <= │ le │
├────┼────────────┼────────────┤
│ 4 │ == │ eq │
├────┼────────────┼────────────┤
│ 5 │ != │ ne │
╘════╧════════════╧════════════╛
Otra opción para evitar paréntesis es usar DataFrame.query
(o eval
):
df.query('A < 5 and B > 5')
A B C
1 3 7 9
3 4 7 6
He documentado extensamentequery
y eval
en evaluación de expresión dinámica en pandas usando pd.eval () .
operator.and_
Le permite realizar esta operación de manera funcional. Llama internamente lo Series.__and__
que corresponde al operador 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
Por lo general, no necesitará esto, pero es útil saberlo.
Generalizando: np.logical_and
(y logical_and.reduce
)
Otra alternativa es usar np.logical_and
, que tampoco necesita agrupación de paréntesis:
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
es un ufunc (funciones universales) , y la mayoría de los ufuncs tienen un reduce
método. Esto significa que es más fácil generalizar logical_and
si tiene varias máscaras para AND. Por ejemplo, para y máscaras m1
y m2
y m3
con &
, usted tendría que hacer
m1 & m2 & m3
Sin embargo, una opción más fácil es
np.logical_and.reduce([m1, m2, m3])
Esto es poderoso, porque le permite construir sobre esto con una lógica más compleja (por ejemplo, generar dinámicamente máscaras en una comprensión de lista y agregarlas todas):
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 - Sé que estoy insistiendo en este punto, pero por favor tengan paciencia conmigo. Este es un error de principiante muy , muy común, y debe explicarse muy a fondo.
O lógico
Para lo df
anterior, digamos que desea devolver todas las filas donde A == 3 o B == 7.
Bitwise sobrecargado |
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
Si aún no lo ha hecho, lea también la sección sobre Lógico Y arriba, todas las advertencias se aplican aquí.
Alternativamente, esta operación se puede especificar 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_
Llamadas Series.__or__
bajo el capó.
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
Para dos condiciones, use 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
Para máscaras múltiples, use 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
NO lógico
Dada una máscara, como
mask = pd.Series([True, True, False])
Si necesita invertir cada valor booleano (para que el resultado final sea [False, False, True]
), puede usar cualquiera de los métodos a continuación.
Bitwise ~
~mask
0 False
1 False
2 True
dtype: bool
Una vez más, las expresiones deben estar entre paréntesis.
~(df['A'] == 3)
0 True
1 False
2 False
3 True
4 True
Name: A, dtype: bool
Esto internamente llama
mask.__invert__()
0 False
1 False
2 True
dtype: bool
Pero no lo use directamente.
operator.inv
Internamente llama __invert__
a la serie.
operator.inv(mask)
0 False
1 False
2 True
dtype: bool
np.logical_not
Esta es la variante numpy.
np.logical_not(mask)
0 False
1 False
2 True
dtype: bool
Nota, np.logical_and
puede ser sustituido por np.bitwise_and
, logical_or
con bitwise_or
y logical_not
con invert
.