python - examples - query pandas data frame
Evaluación de expresión dinámica en pandas usando pd.eval() (2)
Teniendo en cuenta dos DataFrames
np.random.seed(0)
df1 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list(''ABCD''))
df2 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list(''ABCD''))
df1
A B C D
0 5 0 3 3
1 7 9 3 5
2 2 4 7 6
3 8 8 1 6
4 7 7 8 1
df2
A B C D
0 5 9 8 9
1 4 3 0 3
2 5 0 2 3
3 8 1 3 3
4 3 7 0 1
Me gustaría realizar aritmética en una o más columnas usando
pd.eval
.
Específicamente, me gustaría portar el siguiente código:
x = 5
df2[''D''] = df1[''A''] + (df1[''B''] * x)
... para codificar utilizando
eval
.
La razón para usar
eval
es que me gustaría automatizar muchos flujos de trabajo, por lo que crearlos de forma dinámica me será útil.
Estoy tratando de entender mejor los argumentos del
engine
y del
parser
para determinar la mejor manera de resolver mi problema.
He revisado la
documentation
pero no se me aclaró la diferencia.
- ¿Qué argumentos se deben usar para garantizar que mi código funcione al máximo rendimiento?
-
¿Hay una manera de asignar el resultado de la expresión de nuevo a
df2
? -
Además, para hacer las cosas más complicadas, ¿cómo paso
x
como un argumento dentro de la expresión de cadena?
Esta respuesta se adentra en las diversas características y funcionalidades ofrecidas por
documentation
,
df.query
y
df.eval
.
Preparar
Los ejemplos incluirán estos DataFrames (a menos que se especifique lo contrario).
np.random.seed(0)
df1 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list(''ABCD''))
df2 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list(''ABCD''))
df3 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list(''ABCD''))
df4 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list(''ABCD''))
documentation - El "Manual que falta"
Nota
De las tres funciones que se discuten,pd.eval
es la más importante.df.eval
ydf.query
llamanpd.eval
debajo del capó. El comportamiento y el uso son más o menos consistentes en las tres funciones, con algunas variaciones semánticas menores que se destacarán más adelante. Esta sección presentará la funcionalidad que es común en las tres funciones, esto incluye, pero no se limita a , sintaxis permitida, reglas de precedencia y argumentos de palabras clave.
pd.eval
puede evaluar expresiones aritméticas que pueden consistir en variables y / o literales.
Estas expresiones deben pasarse como cadenas.
Por lo tanto,
para responder a la pregunta
como se indica, puede hacer
x = 5
pd.eval("df1.A + (df1.B * x)")
Algunas cosas a tener en cuenta aquí:
- La expresión entera es una cadena.
-
df1
,df2
yx
refieren a variables en el espacio de nombres global, estas son recogidas poreval
cuando se analiza la expresión -
Se accede a columnas específicas utilizando el atributo accessor index.
También puede usar
"df1[''A''] + (df1[''B''] * x)"
para el mismo efecto.
Abordaré el problema específico de la reasignación en la sección que explica el atributo
target=...
continuación.
Pero por ahora, aquí hay ejemplos más simples de operaciones válidas con
pd.eval
:
pd.eval("df1.A + df2.A") # Valid, returns a pd.Series object
pd.eval("abs(df1) ** .5") # Valid, returns a pd.DataFrame object
...y así. Las expresiones condicionales también son compatibles de la misma manera. Las siguientes declaraciones son todas expresiones válidas y serán evaluadas por el motor.
pd.eval("df1 > df2")
pd.eval("df1 > 5")
pd.eval("df1 < df2 and df3 < df4")
pd.eval("df1 in [1, 2, 3]")
pd.eval("1 < 2 < 3")
En la documentation se puede encontrar una lista que detalla todas las características y la sintaxis compatibles. En resumen,
- Operaciones aritméticas, excepto para los operadores de desplazamiento a la izquierda (
<<
) y a la derecha (>>
), por ejemplo,df + 2 * pi / s ** 4 % 42
- the_golden_ratio- Operaciones de comparación, incluidas comparaciones encadenadas, por ejemplo,
2 < df < df2
- Operaciones booleanas, por ejemplo,
df < df2 and df3 < df4
onot df_bool
list
y literales detuple
, por ejemplo,[1, 2]
o(1, 2)
- Acceso a atributos, por ejemplo,
df.a
- Expresiones de subíndices, por ejemplo,
df[0]
- Evaluación de variable simple, por ejemplo,
pd.eval(''df'')
(esto no es muy útil)- Funciones matemáticas: sin, cos, exp, log, expm1, log1p, sqrt, sinh, cosh, tanh, arcsin, arccos, arctan, arccosh, arcsinh, arctanh, abs y arctan2.
Esta sección de la documentación también especifica reglas de sintaxis que no son compatibles, incluidos los literales
set
/
dict
, sentencias if-else, bucles y comprensiones, y expresiones generadoras.
De la lista, es obvio que también puede pasar expresiones que incluyan el índice, como
pd.eval(''df1.A * (df1.index > 1)'')
Selección de analizador: El
parser=...
argumento
pd.eval
admite dos opciones de analizador diferentes al analizar la cadena de expresión para generar el árbol de sintaxis:
pandas
y
python
.
La principal diferencia entre los dos se resalta mediante reglas de precedencia ligeramente diferentes.
Usando los
pandas
analizadores por defecto, los operadores bitwise sobrecargados
&
y
|
los cuales implementan operaciones AND y OR vectorizadas con objetos pandas tendrán la misma prioridad de operador que y ''o.
Asi que,
pd.eval("(df1 > df2) & (df3 < df4)")
Sera lo mismo que
pd.eval("df1 > df2 & df3 < df4")
# pd.eval("df1 > df2 & df3 < df4", parser=''pandas'')
Y también lo mismo que
pd.eval("df1 > df2 and df3 < df4")
Aquí, los paréntesis son necesarios. Para hacer esto de manera convencional, se requeriría que los parenteses anulen la precedencia más alta de los operadores a nivel de bits:
(df1 > df2) & (df3 < df4)
Sin eso, terminamos con
df1 > df2 & df3 < df4
ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
Utilice
parser=''python''
si desea mantener la coherencia con las reglas de precedencia del operador real de python mientras evalúa la cadena.
pd.eval("(df1 > df2) & (df3 < df4)", parser=''python'')
La otra diferencia entre los dos tipos de analizadores es la semántica de los operadores
==
y
!=
Con nodos de tupla y lista, que tienen la semántica similar a la
in
y
not in
respectivamente, cuando se usa el analizador
''pandas''
.
Por ejemplo,
pd.eval("df1 == [1, 2, 3]")
Es válido y se ejecutará con la misma semántica que
pd.eval("df1 in [1, 2, 3]")
OTOH,
pd.eval("df1 == [1, 2, 3]", parser=''python'')
lanzará un error
NotImplementedError
.
Selección de fondo: El argumento
engine=...
Hay dos opciones:
numexpr
(predeterminado) y
python
.
La opción
numexpr
usa el backend
numexpr
que está optimizado para el rendimiento.
Con
''python''
backend
''python''
, su expresión se evalúa de manera similar a simplemente pasar la expresión a la función
eval
de python.
Tiene la flexibilidad de hacer más expresiones internas, como operaciones de cadena, por ejemplo.
df = pd.DataFrame({''A'': [''abc'', ''def'', ''abacus'']})
pd.eval(''df.A.str.contains("ab")'', engine=''python'')
0 True
1 False
2 True
Name: A, dtype: bool
Desafortunadamente, este método
no
ofrece beneficios de rendimiento sobre el motor
numexpr
, y existen muy pocas medidas de seguridad para garantizar que las expresiones peligrosas no se evalúen, ¡así que
UTILICE A SU PROPIO RIESGO
!
Generalmente no se recomienda cambiar esta opción a
''python''
menos que sepa lo que está haciendo.
local_dict
y
global_dict
A veces, es útil proporcionar valores para las variables utilizadas dentro de las expresiones, pero que actualmente no están definidas en su espacio de nombres.
Puedes pasar un diccionario a
local_dict
Por ejemplo,
pd.eval("df1 > thresh")
UndefinedVariableError: name ''thresh'' is not defined
Esto falla porque la
thresh
no está definida.
Sin embargo, esto funciona:
pd.eval("df1 > x", local_dict={''thresh'': 10})
Esto es útil cuando tiene variables para suministrar desde un diccionario.
Alternativamente, con el motor
''python''
, simplemente puedes hacer esto:
mydict = {''thresh'': 5}
# Dictionary values with *string* keys cannot be accessed without
# using the ''python'' engine.
pd.eval(''df1 > mydict["thresh"]'', engine=''python'')
Pero esto posiblemente será
mucho
más lento que usar el motor
''numexpr''
y pasar un diccionario a
local_dict
o
global_dict
.
Con suerte, esto debería hacer un argumento convincente para el uso de estos parámetros.
El argumento de
target
(+
inplace
) y las expresiones de asignación
Esto no suele ser un requisito porque usualmente hay formas más simples de hacerlo, pero puede asignar el resultado de
pd.eval
a un objeto que implementa
__getitem__
como
dict
s, y (lo adivinó) DataFrames.
Considera el ejemplo de la pregunta.
x = 5 df2[''D''] = df1[''A''] + (df1[''B''] * x)
Para asignar una columna "D" a
df2
, hacemos
pd.eval(''D = df1.A + (df1.B * x)'', target=df2)
A B C D
0 5 9 8 5
1 4 3 0 52
2 5 0 2 22
3 8 1 3 48
4 3 7 0 42
Esta no es una modificación in situ de
df2
(pero puede ser ... sigue leyendo).
Considere otro ejemplo:
pd.eval(''df1.A + df2.A'')
0 10
1 11
2 7
3 16
4 10
dtype: int32
Si quisiera (por ejemplo) volver a asignar esto a un DataFrame, podría usar el argumento de
target
siguiente manera:
df = pd.DataFrame(columns=list(''FBGH''), index=df1.index)
df
F B G H
0 NaN NaN NaN NaN
1 NaN NaN NaN NaN
2 NaN NaN NaN NaN
3 NaN NaN NaN NaN
4 NaN NaN NaN NaN
df = pd.eval(''B = df1.A + df2.A'', target=df)
# Similar to
# df = df.assign(B=pd.eval(''df1.A + df2.A''))
df
F B G H
0 NaN 10 NaN NaN
1 NaN 11 NaN NaN
2 NaN 7 NaN NaN
3 NaN 16 NaN NaN
4 NaN 10 NaN NaN
Si desea realizar una mutación in situ en
df
, configure
inplace=True
.
pd.eval(''B = df1.A + df2.A'', target=df, inplace=True)
# Similar to
# df[''B''] = pd.eval(''df1.A + df2.A'')
df
F B G H
0 NaN 10 NaN NaN
1 NaN 11 NaN NaN
2 NaN 7 NaN NaN
3 NaN 16 NaN NaN
4 NaN 10 NaN NaN
Si se establece
inplace
sin un objetivo, se
ValueError
un
ValueError
.
Si bien es divertido jugar con el argumento
target
, rara vez tendrá que usarlo.
Si quisiera hacer esto con
df.eval
, usaría una expresión que involucra una asignación:
df = df.eval("B = @df1.A + @df2.A")
# df.eval("B = @df1.A + @df2.A", inplace=True)
df
F B G H
0 NaN 10 NaN NaN
1 NaN 11 NaN NaN
2 NaN 7 NaN NaN
3 NaN 16 NaN NaN
4 NaN 10 NaN NaN
Nota
Uno de los usos no deseados de
pd.eval
es analizar cadenas literales de una manera muy similar a
ast.literal_eval
:
pd.eval("[1, 2, 3]")
array([1, 2, 3], dtype=object)
También puede analizar listas anidadas con el motor
''python''
:
pd.eval("[[1, 2, 3], [4, 5], [10]]", engine=''python'')
[[1, 2, 3], [4, 5], [10]]
Y listas de cuerdas:
pd.eval(["[1, 2, 3]", "[4, 5]", "[10]"], engine=''python'')
[[1, 2, 3], [4, 5], [10]]
El problema, sin embargo, es para listas con una longitud mayor que 10:
pd.eval(["[1]"] * 100, engine=''python'') # Works
pd.eval(["[1]"] * 101, engine=''python'')
AttributeError: ''PandasExprVisitor'' object has no attribute ''visit_Ellipsis''
Puede encontrar más información sobre este error, causas, soluciones y soluciones alternativas here .
DataFrame.eval
- Una yuxtaposición con
pandas.eval
Como se mencionó anteriormente,
df.eval
llama a
pd.eval
bajo el capó.
El
código fuente v0.23
muestra esto:
def eval(self, expr, inplace=False, **kwargs):
from pandas.core.computation.eval import eval as _eval
inplace = validate_bool_kwarg(inplace, ''inplace'')
resolvers = kwargs.pop(''resolvers'', None)
kwargs[''level''] = kwargs.pop(''level'', 0) + 1
if resolvers is None:
index_resolvers = self._get_index_resolvers()
resolvers = dict(self.iteritems()), index_resolvers
if ''target'' not in kwargs:
kwargs[''target''] = self
kwargs[''resolvers''] = kwargs.get(''resolvers'', ()) + tuple(resolvers)
return _eval(expr, inplace=inplace, **kwargs)
eval
crea argumentos, hace un poco de validación y pasa los argumentos a
pd.eval
.
Para más información, puede leer en: cuándo usar DataFrame.eval () versus pandas.eval () o python eval ()
Diferencias de uso
Expresiones con DataFrames v / s Series Expressions
Para consultas dinámicas asociadas con DataFrames completos, debería preferir
pd.eval
.
Por ejemplo, no hay una forma sencilla de especificar el equivalente de
pd.eval("df1 + df2")
cuando llama a
df1.eval
o
df2.eval
.
Especificar nombres de columna
Otra diferencia importante es cómo se accede a las columnas.
Por ejemplo, para agregar dos columnas "A" y "B" en
df1
, debería llamar a
pd.eval
con la siguiente expresión:
pd.eval("df1.A + df1.B")
Con df.eval, solo necesita proporcionar los nombres de columna:
df1.eval("A + B")
Dado que, dentro del contexto de
df1
, está claro que "A" y "B" se refieren a nombres de columna.
También puede consultar el índice y las columnas utilizando el
index
(a menos que se nombre el índice, en cuyo caso usted usaría el nombre).
df1.eval("A + index")
O, más generalmente, para cualquier DataFrame con un índice que tenga 1 o más niveles, puede referirse al nivel k
th
del índice en una expresión usando la variable
"ilevel_k"
que significa "
i
ndex a
nivel k
".
IOW, la expresión anterior se puede escribir como
df1.eval("A + ilevel_0")
.
Estas reglas también se aplican a la
query
.
Acceso a las variables en el espacio de nombres local / global
Las variables suministradas dentro de las expresiones deben ir precedidas por el símbolo "@", para evitar confusiones con los nombres de las columnas.
A = 5
df1.eval("A > @A")
Lo mismo ocurre con la
query
/
No hace falta decir que los nombres de sus columnas deben seguir las reglas para que los nombres de identificadores válidos en Python sean accesibles dentro de
eval
.
Vea
here
una lista de reglas para nombrar identificadores.
Consultas multilínea y asignación
Un hecho poco conocido es que
eval
soporta expresiones multilínea que se ocupan de la asignación.
Por ejemplo, para crear dos nuevas columnas "E" y "F" en df1 basadas en algunas operaciones aritméticas en algunas columnas, y una tercera columna "G" basada en las "E" y "F" creadas previamente, podemos hacer
df1.eval("""
E = A + B
F = @df2.A + @df2.B
G = E >= F
""")
A B C D E F G
0 5 0 3 3 5 14 False
1 7 9 3 5 16 7 True
2 2 4 7 6 6 5 True
3 8 8 1 6 16 9 True
4 7 7 8 1 14 10 True
...¡Hábil!
Sin embargo, tenga en cuenta que esto no es compatible con la
query
.
eval
v / s
query
- Word final
Es
df.query
pensar en
df.query
como una función que usa
pd.eval
como subrutina.
Normalmente, la
query
(como su nombre lo indica) se usa para evaluar expresiones condicionales (es decir, expresiones que dan como resultado los valores Verdadero / Falso) y devolver las filas correspondientes al resultado
True
.
El resultado de la expresión se pasa luego a
loc
(en la mayoría de los casos) para devolver las filas que satisfacen la expresión.
Según la documentación,
El resultado de la evaluación de esta expresión se pasa primero a
DataFrame.loc
y si eso falla debido a una clave multidimensional (por ejemplo, un DataFrame), el resultado se pasará aDataFrame.__getitem__()
.Este método utiliza la función
pandas.eval()
nivelpandas.eval()
para evaluar la consulta pasada.
En términos de similitud,
query
y
df.eval
son iguales en cuanto a cómo acceden a los nombres de columna y las variables.
Esta diferencia clave entre los dos, como se mencionó anteriormente, es cómo manejan el resultado de la expresión. Esto se vuelve obvio cuando en realidad ejecutas una expresión a través de estas dos funciones. Por ejemplo, considere
df1.A
0 5
1 7
2 2
3 8
4 7
Name: A, dtype: int32
df2.B
0 9
1 3
2 0
3 1
4 7
Name: B, dtype: int32
Para obtener todas las filas donde "A"> = "B" en
df1
,
df1
eval
esta manera:
m = df1.eval("A >= B")
m
0 True
1 False
2 False
3 True
4 True
dtype: bool
m
representa el resultado intermedio generado al evaluar la expresión "A> = B".
Luego usamos la máscara para filtrar
df1
:
df1[m]
# df1.loc[m]
A B C D
0 5 0 3 3
3 8 8 1 6
4 7 7 8 1
Sin embargo, con la
query
, el resultado intermedio "m" se pasa directamente a
loc
, por lo que con la
query
, simplemente debe hacer
df1.query("A >= B")
A B C D
0 5 0 3 3
3 8 8 1 6
4 7 7 8 1
En cuanto al rendimiento, es exactamente el mismo.
df1_big = pd.concat([df1] * 100000, ignore_index=True)
%timeit df1_big[df1_big.eval("A >= B")]
%timeit df1_big.query("A >= B")
14.7 ms ± 33.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
14.7 ms ± 24.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Pero este último es más conciso y expresa la misma operación en un solo paso.
Tenga en cuenta que también puede hacer cosas raras con una
query
como esta (para, por ejemplo, devolver todas las filas indexadas por df1.index)
df1.query("index")
# Same as df1.loc[df1.index] # Pointless,... I know
A B C D
0 5 0 3 3
1 7 9 3 5
2 2 4 7 6
3 8 8 1 6
4 7 7 8 1
Pero no lo hagas
Línea inferior: utilice la
query
al consultar o filtrar filas según una expresión condicional.
Ya tiene un gran tutorial, pero tenga en cuenta que antes de saltar al uso de
eval/query
atraído por su sintaxis más simple, tiene graves problemas de rendimiento si su conjunto de datos tiene menos de 15,000 filas.
En ese caso, simplemente use
df.loc[mask1, mask2]
.
Consulte: https://pandas.pydata.org/pandas-docs/version/0.22/enhancingperf.html#enhancingperf-eval