Operadores lógicos para la indexación booleana en pandas

153

Estoy trabajando con el índice booleano en Pandas. La pregunta es por qué la declaración:

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

funciona bien mientras

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

sale con error?

Ejemplo:

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()
usuario2988577
fuente
66
Esto se debe a que las matrices numpy y las series pandas utilizan los operadores bit a bit en lugar de lógicos, ya que está comparando cada elemento en la matriz / serie con otro. Por lo tanto, no tiene sentido utilizar el operador lógico en esta situación. ver relacionado: stackoverflow.com/questions/8632033/…
EdChum
9
En Python and != &. El andoperador en Python no se puede anular, mientras que el &operador ( __and__) sí. De ahí la elección del uso &en numpy y pandas.
Steven Rumbalski

Respuestas:

209

Cuando tu dices

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

Está pidiendo implícitamente a Python que convierta (a['x']==1)y (a['y']==10)a valores booleanos.

Las matrices NumPy (de longitud mayor que 1) y los objetos Pandas como Series no tienen un valor booleano; en otras palabras, aumentan

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

cuando se usa como un valor booleano. Eso es porque no está claro cuándo debería ser Verdadero o Falso . Algunos usuarios pueden asumir que son Verdaderos si tienen una longitud distinta de cero, como una lista de Python. Otros pueden desear que sea Verdadero solo si todos sus elementos son Verdaderos. Otros pueden querer que sea Verdadero si alguno de sus elementos es Verdadero.

Debido a que hay tantas expectativas en conflicto, los diseñadores de NumPy y Pandas se niegan a adivinar, y en su lugar plantean un ValueError.

En su lugar, debe ser explícita, llamando al empty(), all()o any()método para indicar el comportamiento deseado.

En este caso, sin embargo, parece que no desea una evaluación booleana, desea elementos lógicos y. Eso es lo &que realiza el operador binario:

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

devuelve una matriz booleana.


Por cierto, como señala alexpmil , los paréntesis son obligatorios ya que &tiene una precedencia de operador más alta que ==. Sin los paréntesis, a['x']==1 & a['y']==10se evaluaría como a['x'] == (1 & a['y']) == 10lo que a su vez sería equivalente a la comparación encadenada (a['x'] == (1 & a['y'])) and ((1 & a['y']) == 10). Esa es una expresión de la forma Series and Series. El uso de anddos series volvería a desencadenar lo mismo ValueErrorque anteriormente. Por eso los paréntesis son obligatorios.

unutbu
fuente
3
Las matrices numpy tienen esta propiedad si son de longitud uno. Solo los desarrolladores de pandas (tercamente) se niegan a adivinar: p
Andy Hayden
44
¿'&' No tiene la misma curva ambigua que 'y'? ¿Cómo es que cuando se trata de '&', de repente todos los usuarios están de acuerdo en que debe ser inteligente, mientras que cuando ven 'y', sus expectativas varían?
Indominus
16
@Indominus: el lenguaje Python en sí mismo requiere que la expresión x and yactive la evaluación de bool(x)y bool(y). Python "primero evalúa x; si xes falso, se devuelve su valor; de lo contrario, yse evalúa y se devuelve el valor resultante". Por lo que la sintaxis x and yno se puede utilizar para el elemento lógico-wised, y puesto que solamente xo ypueden ser devueltos. Por el contrario, los x & ydesencadenantes x.__and__(y)y el __and__método pueden definirse para devolver lo que queramos.
unutbu
2
Importante tener en cuenta: los paréntesis alrededor de la ==cláusula son obligatorios . a['x']==1 & a['y']==10devuelve el mismo error que en la pregunta.
Alex P. Miller
1
¿Para qué sirve "|"?
Euler_Salter
62

TLDR; Los operadores lógicos en Pandas son &, |y ~, ¡y los paréntesis (...)son importantes!

Python es and, ory notoperadores 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 ( exp1y exp2son 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 dfanterior, 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 andy ~para not. Estos deben agruparse utilizando paréntesis , ya que, de forma predeterminada, Python evaluará una expresión df.A > 2 & df.B < 3como 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 evalen 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_andes un ufunc (funciones universales) , y la mayoría de los ufuncs tienen un reducemétodo. Esto significa que es más fácil generalizar logical_andsi tiene varias máscaras para AND. Por ejemplo, para y máscaras m1y m2y m3con &, 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 dfanterior, 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_andpuede ser sustituido por np.bitwise_and, logical_orcon bitwise_ory logical_notcon invert.

cs95
fuente
@ cs95 en el TLDR, para OR booleano por elementos, usted recomienda usar |, que es equivalente a numpy.bitwise_or, en lugar de numpy.logical_or. ¿Puedo preguntar por qué? ¿No está numpy.logical_ordiseñado específicamente para esta tarea? ¿Por qué agregar la carga de hacerlo a nivel de bits para cada par de elementos?
flow2k
@ flow2k, ¿puede citar el texto relevante, por favor? No puedo encontrar a qué te refieres. FWIW Mantengo que logical_ * es el equivalente funcional correcto de los operadores.
cs95
@ cs95 Me refiero a la primera línea de la respuesta: "TLDR; los operadores lógicos en pandas son &, | y ~".
flow2k
@ flow2k Está literalmente en la documentación : "Otra operación común es el uso de vectores booleanos para filtrar los datos. Los operadores son: | for or, & for and, y ~ for not".
cs95
@ cs95, ok, acabo de leer esta sección, y se usa |para operaciones booleanas basadas en elementos. Pero para mí, esa documentación es más un "tutorial", y en contraste, siento que estas referencias de API están más cerca de la fuente de la verdad: numpy.bitwise_or y numpy.logical_or , así que estoy tratando de entender qué es descrito aquí.
flow2k
4

Operadores lógicos para la indexación booleana en pandas

Es importante darse cuenta de que no se puede utilizar cualquiera de los Python operadores lógicos ( and, oro not) en pandas.Serieso pandas.DataFrames (de manera similar no se puede utilizar en numpy.arrays con más de un elemento). La razón por la que no puede usarlos es porque invocan implícitamente boolsus operandos que arrojan una Excepción porque estas estructuras de datos decidieron que el valor booleano de una matriz es 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().

Cubrí esto más extensamente en mi respuesta al "El valor de verdad de una Serie es ambiguo. Use a.empty, a.bool (), a.item (), a.any () o a.all ()" Q + A .

Funciones lógicas de NumPys

Sin embargo NumPy proporciona elemento a elemento equivalentes operativo para estos operadores como las funciones que se pueden utilizar en numpy.array, pandas.Series, pandas.DataFrame, o cualquier otra (conforme) numpy.arraysubclase:

Entonces, esencialmente, uno debe usar (suponiendo df1y df2son pandas DataFrames):

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

Funciones bit a bit y operadores bit a bit para booleanos

Sin embargo, en caso de que tenga una matriz booleana NumPy, series pandas o marcos de datos pandas, también podría usar las funciones bit a nivel de elemento (para los booleanos son, o al menos deberían ser) indistinguibles de las funciones lógicas:

Por lo general, se utilizan los operadores. Sin embargo, cuando se combina con operadores de comparación, uno debe recordar ajustar la comparación entre paréntesis porque los operadores bit a bit tienen una precedencia más alta que los operadores de comparación :

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

Esto puede ser irritante porque los operadores lógicos de Python tienen una precedencia más baja que los operadores de comparación, por lo que normalmente escribe a < 10 and b > 10(dónde ay bson, por ejemplo, enteros simples) y no necesita el paréntesis.

Diferencias entre operaciones lógicas y bit a bit (en no booleanos)

Es realmente importante enfatizar que las operaciones bit y lógicas solo son equivalentes para matrices booleanas NumPy (y series booleanas y marcos de datos). Si estos no contienen booleanos, las operaciones darán resultados diferentes. Incluiré ejemplos usando matrices NumPy pero los resultados serán similares para las estructuras de datos de pandas:

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

Y dado que NumPy (y de manera similar pandas) hace cosas diferentes para los índices booleanos ( matrices de índices booleanos o "enmascarados" ) y enteros ( matrices de índices), los resultados de la indexación también serán diferentes:

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

Tabla de resumen

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

Donde el operador lógico no funciona para matrices NumPy , series pandas y marcos de datos pandas. Los otros trabajan en estas estructuras de datos (y objetos Python simples) y trabajan en función de los elementos. Sin embargo, tenga cuidado con la inversión bit a bit en Python simple boolporque el bool se interpretará como enteros en este contexto (por ejemplo, ~Falseretornos -1y ~Trueretornos -2).

MSeifert
fuente