una tablas seleccionar recorrer para leer funciones filtros filtrar filas fila datos data con columnas python algorithm pandas

python - tablas - Manera eficiente de aplicar filtros mĂșltiples a pandas DataFrame o Series



seleccionar columnas de un dataframe pandas (5)

Tengo un escenario en el que un usuario desea aplicar varios filtros a un objeto Pandas DataFrame o Serie. Básicamente, quiero encadenar eficientemente un grupo de filtros (operaciones de comparación) que el usuario especifica en tiempo de ejecución.

Los filtros deben ser aditivos (es decir, cada uno aplicado debe reducir los resultados).

Actualmente estoy usando reindex() pero esto crea un nuevo objeto cada vez y copia los datos subyacentes (si entiendo la documentación correctamente). Por lo tanto, esto podría ser realmente ineficiente al filtrar una gran Serie o DataFrame.

Estoy pensando que usar apply() , map() o algo similar podría ser mejor. Aunque soy bastante nuevo para los Pandas, aún trato de entenderlo todo.

TL; DR

Quiero tomar un diccionario del siguiente formulario y aplicar cada operación a un objeto Serie dado y devolver un objeto Serie ''filtrado''.

relops = {''>='': [1], ''<='': [1]}

Ejemplo largo

Empezaré con un ejemplo de lo que tengo actualmente y solo filtraré un único objeto de la Serie. Debajo está la función que estoy usando actualmente:

def apply_relops(series, relops): """ Pass dictionary of relational operators to perform on given series object """ for op, vals in relops.iteritems(): op_func = ops[op] for val in vals: filtered = op_func(series, val) series = series.reindex(series[filtered]) return series

El usuario proporciona un diccionario con las operaciones que desean realizar:

>>> df = pandas.DataFrame({''col1'': [0, 1, 2], ''col2'': [10, 11, 12]}) >>> print df >>> print df col1 col2 0 0 10 1 1 11 2 2 12 >>> from operator import le, ge >>> ops ={''>='': ge, ''<='': le} >>> apply_relops(df[''col1''], {''>='': [1]}) col1 1 1 2 2 Name: col1 >>> apply_relops(df[''col1''], relops = {''>='': [1], ''<='': [1]}) col1 1 1 Name: col1

Nuevamente, el ''problema'' con mi enfoque anterior es que creo que hay una gran cantidad de posible copia innecesaria de los datos para los pasos intermedios.

Además, me gustaría expandir esto para que el diccionario pasado pueda incluir las columnas para operar y filtrar un DataFrame completo basado en el diccionario de entrada. Sin embargo, supongo que cualquier cosa que funcione para la Serie se puede expandir fácilmente a un DataFrame.


¿Por qué no hacer esto?

def filt_spec(df, col, val, op): import operator ops = {''eq'': operator.eq, ''neq'': operator.ne, ''gt'': operator.gt, ''ge'': operator.ge, ''lt'': operator.lt, ''le'': operator.le} return df[ops[op](df[col], val)] pandas.DataFrame.filt_spec = filt_spec

Manifestación:

df = pd.DataFrame({''a'': [1,2,3,4,5], ''b'':[5,4,3,2,1]}) df.filt_spec(''a'', 2, ''ge'')

Resultado:

a b 1 2 4 2 3 3 3 4 2 4 5 1

Puede ver que la columna ''a'' se ha filtrado donde a> = 2.

Esto es un poco más rápido (tiempo de mecanografía, no de rendimiento) que el encadenamiento del operador. Por supuesto, puede poner la importación en la parte superior del archivo.


Desde la actualización de pandas 0.22 , las opciones de comparación están disponibles como:

  • gt (mayor que)
  • lt (menor que)
  • eq (igual a)
  • ne (no es igual a)
  • ge (mayor que o igual a)

y muchos más. Estas funciones devuelven una matriz booleana. Veamos cómo podemos usarlos:

# sample data df = pd.DataFrame({''col1'': [0, 1, 2,3,4,5], ''col2'': [10, 11, 12,13,14,15]}) # get values from col1 greater than or equals to 1 df.loc[df[''col1''].ge(1),''col1''] 1 1 2 2 3 3 4 4 5 5 # where co11 values is better 0 and 2 df.loc[df[''col1''].between(0,2)] col1 col2 0 0 10 1 1 11 2 2 12 # where col1 > 1 df.loc[df[''col1''].gt(1)] col1 col2 2 2 12 3 3 13 4 4 14 5 5 15


Las condiciones de encadenamiento crean líneas largas, que pep8 desaconseja. El uso del método .query obliga a utilizar cadenas, que son potentes pero antipáticas y poco dinámicas.

Una vez que cada uno de los filtros está en su lugar, un enfoque es

import numpy as np import functools def conjunction(*conditions): return functools.reduce(np.logical_and, conditions) c_1 = data.col1 == True c_2 = data.col2 < 64 c_3 = data.col3 != 4 data_filtered = data[conjunction(c1,c2,c3)]

np.logical funciona y es rápido, pero no requiere más de dos argumentos, que es manejado por functools.reduce.

Tenga en cuenta que esto todavía tiene algunas redundancias: a) el acceso directo no ocurre en un nivel global b) Cada una de las condiciones individuales se ejecuta en la totalidad de los datos iniciales. Aún así, espero que esto sea lo suficientemente eficiente para muchas aplicaciones y es muy legible.


Los pandas (y numpy) permiten la indexación booleana , que será mucho más eficiente:

In [11]: df.loc[df[''col1''] >= 1, ''col1''] Out[11]: 1 1 2 2 Name: col1 In [12]: df[df[''col1''] >= 1] Out[12]: col1 col2 1 1 11 2 2 12 In [13]: df[(df[''col1''] >= 1) & (df[''col1''] <=1 )] Out[13]: col1 col2 1 1 11

Si desea escribir funciones auxiliares para esto, considere algo como sigue:

In [14]: def b(x, col, op, n): return op(x[col],n) In [15]: def f(x, *b): return x[(np.logical_and(*b))] In [16]: b1 = b(df, ''col1'', ge, 1) In [17]: b2 = b(df, ''col1'', le, 1) In [18]: f(df, b1, b2) Out[18]: col1 col2 1 1 11

Actualización: pandas 0.13 tiene un método de consulta para este tipo de casos de uso, asumiendo que los nombres de las columnas son identificadores válidos los siguientes trabajos (y puede ser más eficiente para los marcos grandes ya que usa numexpr detrás de las escenas):

In [21]: df.query(''col1 <= 1 & 1 <= col1'') Out[21]: col1 col2 1 1 11


La más simple de todas las soluciones:

Utilizar:

filtered_df = df[(df[''col1''] >= 1) & (df[''col1''] <= 1)]

Otro ejemplo : para filtrar el marco de datos para los valores pertenecientes a Feb-2018, use el siguiente código

filtered_df = df[(df[''year''] == 2018) & (df[''month''] == 2)]