pandas: filtrar filas de DataFrame con el encadenamiento del operador

La mayoría de las operaciones en pandas se pueden realizar con el encadenamiento de operadores ( groupby , aggregate , apply , etc.), pero la única forma que he encontrado para filtrar filas es a través de la indexación de corchetes normal

 df_filtered = df[df['column'] == value] 

Esto no es atractivo ya que requiere que asigne df a una variable antes de poder filtrar por sus valores. ¿Hay algo más parecido a lo siguiente?

 df_filtered = df.mask(lambda x: x['column'] == value) 

No estoy completamente seguro de lo que quieres, y tu última línea de código tampoco ayuda, pero de todos modos:

El filtrado “encadenado” se realiza “encadenando” los criterios en el índice booleano.

 In [96]: df Out[96]: ABCD a 1 4 9 1 b 4 5 0 2 c 5 5 1 0 d 1 3 9 6 In [99]: df[(df.A == 1) & (df.D == 6)] Out[99]: ABCD d 1 3 9 6 

Si desea encadenar métodos, puede agregar su propio método de máscara y usar ese.

 In [90]: def mask(df, key, value): ....: return df[df[key] == value] ....: In [92]: pandas.DataFrame.mask = mask In [93]: df = pandas.DataFrame(np.random.randint(0, 10, (4,4)), index=list('abcd'), columns=list('ABCD')) In [95]: df.ix['d','A'] = df.ix['a', 'A'] In [96]: df Out[96]: ABCD a 1 4 9 1 b 4 5 0 2 c 5 5 1 0 d 1 3 9 6 In [97]: df.mask('A', 1) Out[97]: ABCD a 1 4 9 1 d 1 3 9 6 In [98]: df.mask('A', 1).mask('D', 6) Out[98]: ABCD d 1 3 9 6 

Los filtros se pueden encadenar usando una consulta de Pandas:

 df = pd.DataFrame( np.random.randn(30,3), columns = ['a','b','c']) df_filtered = df.query('a>0').query('0 

Los filtros también se pueden combinar en una sola consulta:

 df_filtered = df.query('a>0 and 0 

La respuesta de @lodagro es genial. Lo extendería generalizando la función de máscara como:

 def mask(df, f): return df[f(df)] 

Entonces puedes hacer cosas como:

 df.mask(lambda x: x[0] < 0).mask(lambda x: x[1] > 0) 

Desde la versión 0.18.1, el método .loc acepta un llamable para la selección. Junto con las funciones lambda, puede crear filtros chainable muy flexibles:

 import numpy as np import pandas as pd df = pd.DataFrame(np.random.randint(0,100,size=(100, 4)), columns=list('ABCD')) df.loc[lambda df: df.A == 80] # equivalent to df[df.A == 80] but chainable df.sort_values('A').loc[lambda df: df.A > 80].loc[lambda df: df.B > df.A] 

Si todo lo que está haciendo es filtrar, también puede omitir el .loc .

Ofrezco esto para ejemplos adicionales. Esta es la misma respuesta que https://stackoverflow.com/a/28159296/

Añadiré otras ediciones para hacer esta publicación más útil.

pandas.DataFrame.query
query fue hecha exactamente para este propósito. Considere el df dataframe

 import pandas as pd import numpy as np np.random.seed([3,1415]) df = pd.DataFrame( np.random.randint(10, size=(10, 5)), columns=list('ABCDE') ) df ABCDE 0 0 2 7 3 8 1 7 0 6 8 6 2 0 2 0 4 9 3 7 3 2 4 3 4 3 6 7 7 4 5 5 3 7 5 9 6 8 7 6 4 7 7 6 2 6 6 5 8 2 8 7 5 8 9 4 7 6 1 5 

Usemos la query para filtrar todas las filas donde D > B

 df.query('D > B') ABCDE 0 0 2 7 3 8 1 7 0 6 8 6 2 0 2 0 4 9 3 7 3 2 4 3 4 3 6 7 7 4 5 5 3 7 5 9 7 6 2 6 6 5 

Que encadenamos

 df.query('D > B').query('C > B') # equivalent to # df.query('D > B and C > B') # but defeats the purpose of demonstrating chaining ABCDE 0 0 2 7 3 8 1 7 0 6 8 6 4 3 6 7 7 4 5 5 3 7 5 9 7 6 2 6 6 5 

Tenía la misma pregunta, excepto que quería combinar los criterios en una condición OR. El formato dado por Wouter Overmeire combina los criterios en una condición AND tal que ambos deben cumplirse:

 In [96]: df Out[96]: ABCD a 1 4 9 1 b 4 5 0 2 c 5 5 1 0 d 1 3 9 6 In [99]: df[(df.A == 1) & (df.D == 6)] Out[99]: ABCD d 1 3 9 6 

Pero descubrí que, si envuelve cada condición en (... == True) y une los criterios con una canalización, los criterios se combinan en una condición OR, satisfechos siempre que alguno de ellos sea verdadero:

 df[((df.A==1) == True) | ((df.D==6) == True)] 

Mi respuesta es similar a las otras. Si no desea crear una nueva función, puede usar lo que pandas ya ha definido para usted. Utilice el método de la tubería.

 df.pipe(lambda d: d[d['column'] == value]) 

Si desea aplicar todas las máscaras booleanas comunes, así como una máscara de propósito general, puede colocar lo siguiente en un archivo y simplemente asignarlos a todos de la siguiente manera:

 pd.DataFrame = apply_masks() 

Uso:

 A = pd.DataFrame(np.random.randn(4, 4), columns=["A", "B", "C", "D"]) A.le_mask("A", 0.7).ge_mask("B", 0.2)... (May be repeated as necessary 

Es un poco intrincado, pero puede hacer las cosas un poco más limpias si está continuamente cortando y cambiando conjuntos de datos según los filtros. También hay un filtro de propósito general adaptado de Daniel Velkov arriba en la función gen_mask que puede usar con las funciones lambda o si lo desea.

Archivo a guardar (uso masks.py):

 import pandas as pd def eq_mask(df, key, value): return df[df[key] == value] def ge_mask(df, key, value): return df[df[key] >= value] def gt_mask(df, key, value): return df[df[key] > value] def le_mask(df, key, value): return df[df[key] <= value] def lt_mask(df, key, value): return df[df[key] < value] def ne_mask(df, key, value): return df[df[key] != value] def gen_mask(df, f): return df[f(df)] def apply_masks(): pd.DataFrame.eq_mask = eq_mask pd.DataFrame.ge_mask = ge_mask pd.DataFrame.gt_mask = gt_mask pd.DataFrame.le_mask = le_mask pd.DataFrame.lt_mask = lt_mask pd.DataFrame.ne_mask = ne_mask pd.DataFrame.gen_mask = gen_mask return pd.DataFrame if __name__ == '__main__': pass 

Esta solución es más complicada en términos de implementación, pero me parece mucho más limpia en términos de uso, y ciertamente es más general que las otras propuestas.

https://github.com/toobaz/generic_utils/blob/master/generic_utils/pandas/where.py

No es necesario descargar el repository completo: guardar el archivo y hacerlo

 from where import where as W 

debería bastar Entonces lo usas así:

 df = pd.DataFrame([[1, 2, True], [3, 4, False], [5, 7, True]], index=range(3), columns=['a', 'b', 'c']) # On specific column: print(df.loc[W['a'] > 2]) print(df.loc[-W['a'] == W['b']]) print(df.loc[~W['c']]) # On entire - or subset of a - DataFrame: print(df.loc[W.sum(axis=1) > 3]) print(df.loc[W[['a', 'b']].diff(axis=1)['b'] > 1]) 

Un ejemplo de uso un poco menos estúpido:

 data = pd.read_csv('ugly_db.csv').loc[~(W == '$null$').any(axis=1)] 

Por cierto: incluso en el caso de que solo esté utilizando cols booleanos,

 df.loc[W['cond1']].loc[W['cond2']] 

puede ser mucho más eficiente que

 df.loc[W['cond1'] & W['cond2']] 

porque evalúa cond2 solo donde cond1 es True .

DESCARGO DE RESPONSABILIDAD: Primero di esta respuesta en otro lugar porque no había visto esto.

pandas ofrece dos alternativas a la respuesta de Wouter Overmeire que no requieren ninguna anulación. Uno es .loc[.] Con una llamada, como en

 df_filtered = df.loc[lambda x: x['column'] == value] 

el otro es .pipe() , como en

 df_filtered = df.pipe(lambda x: x['column'] == value) 

Solo desea agregar una demostración usando loc para filtrar no solo por filas sino también por columnas y algunos méritos a la operación encadenada.

El siguiente código puede filtrar las filas por valor.

 df_filtered = df.loc[df['column'] == value] 

Al modificarlo un poco puedes filtrar las columnas también.

 df_filtered = df.loc[df['column'] == value, ['year', 'column']] 

Entonces, ¿por qué queremos un método encadenado? La respuesta es que es fácil de leer si tiene muchas operaciones. Por ejemplo,

 res = df\ .loc[df['station']=='USA', ['TEMP', 'RF']]\ .groupby('year')\ .agg(np.nanmean) 

Esto no es atractivo ya que requiere que asigne df a una variable antes de poder filtrar por sus valores.

 df[df["column_name"] != 5].groupby("other_column_name") 

Parece funcionar: también se puede encadenar el operador [] . Tal vez lo agregaron desde que hiciste la pregunta.

Si configura sus columnas para buscar como índices, entonces puede usar DataFrame.xs() para tomar una sección transversal. Esto no es tan versátil como responde la query , pero puede ser útil en algunas situaciones.

 import pandas as pd import numpy as np np.random.seed([3,1415]) df = pd.DataFrame( np.random.randint(3, size=(10, 5)), columns=list('ABCDE') ) df # Out[55]: # ABCDE # 0 0 2 2 2 2 # 1 1 1 2 0 2 # 2 0 2 0 0 2 # 3 0 2 2 0 1 # 4 0 1 1 2 0 # 5 0 0 0 1 2 # 6 1 0 1 1 1 # 7 0 0 2 0 2 # 8 2 2 2 2 2 # 9 1 2 0 2 1 df.set_index(['A', 'D']).xs([0, 2]).reset_index() # Out[57]: # ADBCE # 0 0 2 2 2 2 # 1 0 2 1 1 0 

También puede aprovechar la biblioteca numpy para operaciones lógicas. Es bastante rápido.

 df[np.logical_and(df['A'] == 1 ,df['B'] == 6)]