python - AttributeError: el objeto ''PandasExprVisitor'' no tiene el atributo ''visit_Ellipsis'', utilizando pandas eval
apply (2)
Sus datos están bien, y
pandas.eval
tiene errores, pero no en la forma en que piensa.
Hay una pista
en la página relevante de problemas de github
que me instó a echar un vistazo más de cerca
a la documentación
.
pandas.eval(expr, parser=''pandas'', engine=None, truediv=True, local_dict=None,
global_dict=None, resolvers=(), level=0, target=None, inplace=False)
Evaluate a Python expression as a string using various backends.
Parameters:
expr: str or unicode
The expression to evaluate. This string cannot contain any Python
statements, only Python expressions.
[...]
Como puede ver, el comportamiento documentado es pasar
cadenas
a
pd.eval
, en línea con el comportamiento general (y esperado) de la clase de funciones
eval
/
exec
.
Pasas una cadena y terminas con un objeto arbitrario.
Tal como lo veo,
pandas.eval
tiene errores porque no rechaza la entrada de la
Series
expr
por adelantado, lo que hace que adivine ante la ambigüedad.
El hecho de que el acortamiento predeterminado de la
Series
__repr__
diseñada para una impresión bonita pueda afectar drásticamente su resultado es la mejor prueba de esta situación.
La solución es entonces retroceder del problema XY y usar
la herramienta adecuada para convertir sus datos
, y preferiblemente dejar de usar
pandas.eval
para este propósito por completo.
Incluso en los casos de trabajo donde la
Series
es pequeña, no puedes estar seguro de que las futuras versiones de pandas no rompan por completo esta "característica".
Tengo una serie de la forma:
s
0 [133, 115, 3, 1]
1 [114, 115, 2, 3]
2 [51, 59, 1, 1]
dtype: object
Tenga en cuenta que sus elementos son cadenas :
s[0]
''[133, 115, 3, 1]''
Estoy tratando de usar
pd.eval
para analizar esta cadena en una columna de listas.
Esto funciona para estos datos de muestra.
pd.eval(s)
array([[133, 115, 3, 1],
[114, 115, 2, 3],
[51, 59, 1, 1]], dtype=object)
Sin embargo, en datos mucho más grandes (orden de 10K), ¡esto falla miserablemente!
len(s)
300000
pd.eval(s)
AttributeError: ''PandasExprVisitor'' object has no attribute ''visit_Ellipsis''
¿Que me estoy perdiendo aqui? ¿Hay algún problema con la función o mis datos?
TL; DR
A partir de
v0.21
, este es un error y un problema abierto en GitHub.
Ver
GH16289
.
¿Por qué recibo este error?
Esto (con toda probabilidad) es
pd.eval
de
pd.eval
, que no puede analizar series con más de 100 filas.
Aquí hay un ejemplo.
len(s)
300000
pd.eval(s.head(100)) # returns a parsed result
Mientras,
pd.eval(s.head(101))
AttributeError: ''PandasExprVisitor'' object has no attribute ''visit_Ellipsis''
Este problema persiste, independientemente del analizador o del motor.
¿Qué significa este error?
Cuando se pasa una serie con más de 100 filas,
pd.eval
opera en el
__repr__
de la Serie, en lugar de los objetos que contiene (que es la causa de este error).
Las filas truncadas
__repr__
, reemplazándolas con
...
(puntos suspensivos).
El motor malinterpreta esta elipsis como un objeto de
Ellipsis
:
...
Ellipsis
pd.eval(''...'')
AttributeError: ''PandasExprVisitor'' object has no attribute ''visit_Ellipsis''
Cuál es exactamente la causa de este error.
¿Qué puedo hacer para que esto funcione?
En este momento, no hay una solución (el problema aún está abierto a partir del 28/12/2017),
sin embargo
, hay un par de soluciones.
Opción 1
ast.literal_eval
Esta opción debería funcionar de inmediato si puede garantizar que no tiene cadenas con formato incorrecto.
from ast import literal_eval
s.apply(literal_eval)
0 [133, 115, 3, 1]
1 [114, 115, 2, 3]
2 [51, 59, 1, 1]
dtype: object
Si existe la posibilidad de datos mal formados, deberá escribir un pequeño código de manejo de errores. Puedes hacerlo con una función:
def safe_parse(x):
try:
return literal_eval(x)
except (SyntaxError, ValueError):
return np.nan # replace with any suitable placeholder value
Pase esta función para
apply
-
s.apply(safe_parse)
0 [133, 115, 3, 1]
1 [114, 115, 2, 3]
2 [51, 59, 1, 1]
dtype: object
ast
funciona para cualquier número de filas, y es lento, pero confiable.
También puede usar
pd.json.loads
para datos JSON, aplicando las mismas ideas que con
literal_eval
.
opcion 2
yaml.load
Otra gran opción para analizar datos simples, lo tomé de @ayhan hace un tiempo.
import yaml
s.apply(yaml.load)
0 [133, 115, 3, 1]
1 [114, 115, 2, 3]
2 [51, 59, 1, 1]
dtype: object
No he probado esto en estructuras más complejas, pero debería funcionar para casi cualquier representación básica de datos en cadena.
Puede encontrar la documentación para PyYAML
here
.
Desplácese un poco hacia abajo y encontrará más detalles sobre la función de
load
.
Nota
-
Si está trabajando con datos JSON, puede ser adecuado leer su archivo usando
pd.read_json
opd.io.json.json_normalize
para empezar. -
También puede realizar el análisis mientras lee sus datos, usando
read_csv
-s = pd.read_csv(converters=literal_eval, squeeze=True)
Donde el argumento de los
converters
aplicará esa función pasada en la columna a medida que se lee, para que no tenga que lidiar con el análisis posterior. -
Continuando con el punto anterior, si está trabajando con un marco de datos, pase un
dict
-df = pd.read_csv(converters={''col'' : literal_eval})
Donde
col
es la columna que debe analizarse También puede pasarpd.json.loads
(para datos json) opd.eval
(si tiene 100 filas o menos).
Créditos a MaxU y Moondra por descubrir este problema.