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

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

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

existe con error?

Ejemplo:

 a=pd.DataFrame({'x':[1,1],'y':[10,20]}) In: a[(a['x']==1)&(a['y']==10)] Out: xy 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() 

Cuando tu dices

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

Usted está implícitamente pidiendo 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 utiliza 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 muchas 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ícito, llamando al método empty() , all() o any() para indicar qué comportamiento desea.

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

 (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']==10 se evaluaría como a['x'] == (1 & a['y']) == 10 que sería a su vez, será 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 and con dos series volvería a activar el mismo ValueError que el anterior. Es por eso que los paréntesis son obligatorios.

TLDR; Operadores lógicos en Pandas son & , | y ~ , y los paréntesis (...) es importante!

Los operadores de Python and , or not , están diseñados para trabajar con escalares. Así que Pandas tuvo que hacer una mejor y anular los operadores bitwise para lograr una versión vectorizada (de forma elemental) de esta funcionalidad.

Así que 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 los 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 lógico AND, | para OR lógico, y ~ para NO lógico.

Considere la siguiente configuración:

 np.random.seed(0) df = pd.DataFrame(np.random.choice(10, (5, 3)), columns=list('ABC')) df ABC 0 5 0 3 1 3 7 9 2 3 5 2 3 4 7 6 4 8 8 1 

Y lógico

Para el df anterior, diga que le gustaría devolver todas las filas donde A <5 y B> 5. Esto se hace calculando las máscaras para cada condición por separado, y ANDANDOlas.

Sobrecargado Bitwise & Operador
Antes de continuar, tome nota de este extracto en particular de los documentos, que indican

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 manera predeterminada, Python evaluará una expresión como 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 bitwise & :

 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)] ABC 1 3 7 9 3 4 7 6 

Los paréntesis se utilizan para anular el orden de precedencia predeterminado de los operadores bitwise, que tienen mayor prioridad sobre los operadores condicionales < y > . Vea la sección de precedencia del operador en los documentos de python.

Si no utiliza paréntesis, la expresión se evalúa incorrectamente. Por ejemplo, si intentas 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 en (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 lanza

 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 bastante simple. La mayoría de los operadores tienen un método de enlace 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 

Vea 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 los paréntesis es usar DataFrame.query (o eval ):

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

He documentado ampliamente la query y la evaluación en Evaluación de Expresión Dinámica en pandas usando pd.eval () .

operator.and_
Le permite realizar esta operación de una manera funcional. Llama internamente a Series.__and__ que corresponde al operador bitwise.

 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)] ABC 1 3 7 9 3 4 7 6 

Normalmente no necesitarás 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)] ABC 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 método de reduce . Esto significa que es más fácil generalizar con logical_and si tiene varias máscaras para AND. Por ejemplo, para AND máscaras m1 y m2 y m3 con & , tendrías 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 máscaras dinámicamente en una lista de comprensión 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] ABC 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 bien.


O lógico

Para el df anterior, diga que le gustaría devolver todas las filas donde A == 3 o B == 7.

Sobrecargado 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)] ABC 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ógica 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)] ABC 1 3 7 9 2 3 5 2 3 4 7 6 

operator.or_
Series.__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)] ABC 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)] ABC 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])] ABC 1 3 7 9 2 3 5 2 3 4 7 6 

Lógica NO

Dada una máscara, como

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

Si necesita invertir cada valor booleano (de modo que el resultado final sea [False, False, True] ), puede utilizar cualquiera de los métodos a continuación.

A nivel de bit ~

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

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

Pero no lo uses directamente.

operator.inv
Llama internamente a __invert__ en 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 

Tenga en cuenta que np.logical_and puede sustituirse por np.bitwise_and , logical_or con bitwise_or , y logical_not con invert .

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

Es importante darse cuenta de que no puede usar ninguno de los operadores lógicos de Python ( and , or not ) en pandas.Series o pandas.DataFrame s (del mismo modo, no puede usarlos en numpy.array s con más de un elemento). La razón por la que no puede usarlos es porque implícitamente llaman a bool en sus operandos, lo que lanza una excepción porque estas estructuras de datos decidieron que el 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 ampliamente 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.

NumPys funciones lógicas

Sin embargo, NumPy proporciona equivalentes operativos de elementos para estos operadores como funciones que se pueden usar en numpy.array , pandas.Series , pandas.DataFrame o cualquier otra subclase numpy.array (conforme a las numpy.array ):

  • and tiene np.logical_and
  • or tiene np.logical_or
  • not tiene np.logical_not
  • numpy.logical_xor que no tiene un equivalente de Python pero es una operación “exclusiva o” lógica

Entonces, esencialmente, uno debería usar (asumiendo que df1 y df2 son pandas DataFrames):

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

Funciones bitwise y operadores bitwise para booleanos.

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

  • bitwise y: np.bitwise_and o el operador &
  • bitwise o: np.bitwise_or o la | operador
  • bitwise no: np.invert (o el alias np.bitwise_not ) o el operador ~
  • bitwise xor: np.bitwise_xor o el operador ^

Normalmente se utilizan los operadores. Sin embargo, cuando se combina con operadores de comparación, hay que recordar que se debe ajustar la comparación entre paréntesis porque los operadores a nivel de bits tienen una prioridad 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 precendencia más baja que los operadores de comparación, por lo que normalmente escribe a < 10 and b > 10 (donde a y b son, por ejemplo, enteros simples) y no necesita el paréntesis.

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

Es realmente importante enfatizar que las operaciones de bits y lógicas solo son equivalentes para las matrices NumPy booleanas (y Series & DataFrames booleanas). Si estos no contienen valores booleanos, las operaciones darán resultados diferentes. Incluiré ejemplos utilizando 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, los pandas) hace cosas diferentes para los índices booleanos ( arrays de índices booleanos o de “máscara” ) e índices de enteros ( arrays 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 pandas DataFrames. Los otros trabajan en estas estructuras de datos (y objetos de Python sin formato) y trabajan en forma de elementos. Sin embargo, tenga cuidado con la inversión a nivel de bit en bools de Python planos porque el bool se interpretará como enteros en este contexto (por ejemplo, ~False devuelve -1 y ~True devuelve -2 ).