python pandas eval apply

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 o pd.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 pasar pd.json.loads (para datos json) o pd.eval (si tiene 100 filas o menos).

Créditos a MaxU y Moondra por descubrir este problema.