Tengo un escenario en el que un usuario quiere aplicar varios filtros a un objeto Pandas DataFrame o Series. Esencialmente, quiero encadenar eficientemente un conjunto 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 un marco de datos.
Estoy pensando que el uso de apply()
, map()
o algo similar podría ser mejor. Sin embargo, soy bastante nuevo en Pandas, así que todavía estoy tratando de entender todo.
TL; DR
Quiero tomar un diccionario de la siguiente forma y aplicar cada operación a un objeto Serie dado y devolver un objeto Serie 'filtrado'.
relops = {'>=': [1], '<=': [1]}
Ejemplo largo
Comenzaré con un ejemplo de lo que tengo actualmente y simplemente filtrando un solo objeto de la Serie. A continuación se muestra 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 desea 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 muchas copias posiblemente innecesarias de los datos para los pasos intermedios.
Además, me gustaría expandir esto para que el diccionario aprobado pueda incluir las columnas para operar y filtrar un DataFrame completo basado en el diccionario de entrada. Sin embargo, supongo que todo lo que funcione para la Serie se puede expandir fácilmente a un Marco de datos.
df.query
ypd.eval
parece que encaja bien con su caso de uso. Para obtener información sobre lapd.eval()
familia de funciones, sus características y casos de uso, visite Evaluación de expresión dinámica en pandas usando pd.eval () .Respuestas:
Los pandas (y numpy) permiten la indexación booleana , que será mucho más eficiente:
Si desea escribir funciones de ayuda para esto, considere algo en este sentido:
Actualización: pandas 0.13 tiene un método de consulta para este tipo de casos de uso, suponiendo que los nombres de columna son identificadores válidos de los siguientes trabajos (y pueden ser más eficientes para marcos grandes, ya que usa numexpr detrás de escena):
fuente
df[(ge(df['col1'], 1) & le(df['col1'], 1)]
. El problema para mí es que el diccionario con los filtros podría contener muchos operadores y encadenarlos es engorroso. ¿Tal vez podría agregar cada matriz booleana intermedia a una gran matriz y luego usarlamap
para aplicarles eland
operador?f()
necesita tomar en*b
lugar de solob
? ¿Es esto así que el usuariof()
todavía podría usar elout
parámetro opcionallogical_and()
? Esto lleva a otra pequeña pregunta secundaria. ¿Cuál es el beneficio de rendimiento / compensación de pasar en matriz a través deout()
usar el devueltological_and()
? ¡Gracias de nuevo!*b
es necesario porque está pasando las dos matricesb1
yb2
necesita desempaquetarlas al llamarlogical_and
. Sin embargo, la otra pregunta sigue en pie. ¿Existe un beneficio de rendimiento al pasar una matriz a través deout
parámetros alogical_and()
vs simplemente usando su 'valor de retorno?Las condiciones de encadenamiento crean largas colas, que no son recomendadas por pep8. El uso del método .query obliga a usar cadenas, que son potentes pero poco pitónicas y no muy dinámicas.
Una vez que cada uno de los filtros está en su lugar, un enfoque es
np.logical funciona y es rápido, pero no toma 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 a nivel global b) Cada una de las condiciones individuales se ejecuta en los datos iniciales completos. Aún así, espero que esto sea lo suficientemente eficiente para muchas aplicaciones y que sea muy legible.
También puede hacer una disyunción (en la que solo una de las condiciones debe ser verdadera) usando en su
np.logical_or
lugar:fuente
c_1
,c_2
,c_3
, ...c_n
en una lista, y luego pasardata[conjunction(conditions_list)]
, pero obtendrá un errorValueError: Item wrong length 5 instead of 37.
también intentódata[conjunction(*conditions_list)]
pero conseguir un resultado diferente quedata[conjunction(c_1, c_2, c_3, ... c_n )]
, sin saber lo que está pasando.data[conjunction(*conditions_list)]
funciona después de empaquetar los marcos de datos en una lista y desempaquetar la lista en su lugardf[f_2 & f_3 & f_4 & f_5 ]
conf_2 = df["a"] >= 0
etc. No hay necesidad de esa función ... (buen uso de la función de orden superior ...)La más simple de todas las soluciones:
Utilizar:
Otro ejemplo , para filtrar el marco de datos para los valores que pertenecen a febrero de 2018, use el siguiente código
fuente
Desde la actualización de pandas 0.22 , las opciones de comparación están disponibles como:
y muchos más. Estas funciones devuelven una matriz booleana. Veamos cómo podemos usarlos:
fuente
¿Por qué no haces esto?
Manifestación:
Resultado:
Puede ver que la columna 'a' se ha filtrado donde a> = 2.
Esto es un poco más rápido (tiempo de escritura, no rendimiento) que el encadenamiento del operador. Por supuesto, puede colocar la importación en la parte superior del archivo.
fuente
También podemos seleccionar filas en función de los valores de una columna que no están en una lista ni en ninguna iterable. Crearemos una variable booleana como antes, pero ahora negaremos la variable booleana colocando ~ en el frente.
Por ejemplo
fuente