8 votes

AttributeError : l'objet 'PandasExprVisitor' n'a pas d'attribut 'visit_Ellipsis', utilisation de pandas eval

J'ai une série de la forme :

s

0    [133, 115, 3, 1]
1    [114, 115, 2, 3]
2      [51, 59, 1, 1]
dtype: object

Notez que ses éléments sont cordes :

s[0]
'[133, 115, 3, 1]'

J'essaie d'utiliser pd.eval pour analyser cette chaîne en une colonne de listes. Cela fonctionne pour cet exemple de données.

pd.eval(s)

array([[133, 115, 3, 1],
       [114, 115, 2, 3],
       [51, 59, 1, 1]], dtype=object)

Cependant, sur des données beaucoup plus importantes (de l'ordre de 10 000), cette méthode échoue lamentablement !

len(s)
300000

pd.eval(s)
AttributeError: 'PandasExprVisitor' object has no attribute 'visit_Ellipsis'

Qu'est-ce qui m'échappe ? Y a-t-il un problème avec la fonction ou mes données ?

9voto

coldspeed Points 111053

TL;DR
Depuis le v0.21 il s'agit d'un bogue, et d'un problème ouvert sur GitHub. Voir aussi GH16289 .


Pourquoi est-ce que je reçois cette erreur ?
Il s'agit (selon toute probabilité) pd.eval qui ne peut pas analyser les séries de plus de 100 lignes. Voici un exemple.

len(s)
300000

pd.eval(s.head(100))  # returns a parsed result

Considérant que

pd.eval(s.head(101))
AttributeError: 'PandasExprVisitor' object has no attribute 'visit_Ellipsis'

Ce problème persiste, quel que soit l'analyseur ou le moteur.


Que signifie cette erreur ?
Lorsqu'une série de plus de 100 lignes est transmise, pd.eval opère sur le __repr__ de la série, plutôt que les objets qu'elle contient (ce qui est la cause de ce bogue). La série __repr__ les lignes tronquées, en les remplaçant par un ... (ellipse). Cette ellipse est interprétée à tort par le moteur comme une Ellipsis objet -

...
Ellipsis

pd.eval('...')
AttributeError: 'PandasExprVisitor' object has no attribute 'visit_Ellipsis'

Ce qui est exactement la cause de cette erreur.


Que puis-je faire pour que cela fonctionne ?
Pour l'instant, il n'y a pas de solution (la question est toujours ouverte au 28/12/2017), cependant mais il existe quelques solutions de contournement.

Option 1
ast.literal_eval
Cette option devrait fonctionner d'emblée si vous pouvez garantir que vous n'avez pas de chaînes malformées.

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 

S'il existe une possibilité de données mal formées, vous devrez écrire un petit code de gestion des erreurs. Vous pouvez le faire avec une fonction -

def safe_parse(x):
    try:
        return literal_eval(x)
    except (SyntaxError, ValueError):
        return np.nan # replace with any suitable placeholder value

Transmettre cette fonction à apply -

s.apply(safe_parse)

0    [133, 115, 3, 1]
1    [114, 115, 2, 3]
2      [51, 59, 1, 1]
dtype: object

ast fonctionne pour n'importe quel nombre de lignes et est lent, mais fiable. Vous pouvez également utiliser pd.json.loads pour les données JSON, en appliquant les mêmes idées qu'avec literal_eval .

Option 2
yaml.load
Une autre option intéressante pour analyser des données simples, I a récupéré ceci de @ayhan il y a quelque temps.

import yaml
s.apply(yaml.load)

0    [133, 115, 3, 1]
1    [114, 115, 2, 3]
2      [51, 59, 1, 1]
dtype: object

Je n'ai pas testé cette méthode sur des structures plus complexes, mais elle devrait fonctionner pour presque toutes les représentations de données sous forme de chaînes de caractères.

Vous pouvez trouver la documentation de PyYAML aquí . Faites défiler la page un peu plus bas et vous trouverez plus de détails sur la load función.


Note

  • Si vous travaillez avec des données JSON, il peut être judicieux de lire votre fichier en utilisant la fonction pd.read_json o pd.io.json.json_normalize pour commencer.

  • Vous pouvez également effectuer l'analyse au fur et à mesure que vous lisez vos données, en utilisant la fonction read_csv -

    s = pd.read_csv(converters=literal_eval, squeeze=True)

    Où le converters appliquera la fonction passée à la colonne au fur et à mesure qu'elle est lue, de sorte que vous n'aurez pas à vous occuper de l'analyse ultérieurement.

  • Dans la continuité de ce qui précède, si vous travaillez avec un dataframe, passez une valeur de dict -

    df =  pd.read_csv(converters={'col' : literal_eval})

    col est la colonne qui doit être analysée Vous pouvez également passer pd.json.loads (pour les données json), ou pd.eval (si vous avez 100 lignes ou moins).


Nous remercions MaxU et Moondra d'avoir découvert ce problème.

3voto

Andras Deak Points 18455

Vos données sont correctes, et pandas.eval est bogué, mais pas comme vous le pensez. Il y a un indice dans la page de l'enjeu github concerné qui m'a incité à regarder de plus près à la documentation .

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.
        [...]

Comme vous pouvez le voir, le comportement documenté est de passer cordes a pd.eval conformément au comportement général (et attendu) de l'initiative de l'Union européenne. eval / exec classe de fonctions. Vous passez une chaîne de caractères et vous obtenez un objet arbitraire.

C'est ainsi que je vois les choses, pandas.eval est bogué parce qu'il ne rejette pas l'élément Series entrée expr à l'avance, ce qui l'amène à deviner en cas d'ambiguïté. Le fait que le raccourcissement par défaut du Series ' __repr__ conçu pour une belle impression peut affecter de manière drastique votre résultat est la meilleure preuve de cette situation.

La solution consiste alors à prendre du recul par rapport au problème XY et à utiliser le bon outil pour convertir vos données et, de préférence, cesser d'utiliser pandas.eval à cette fin. Même dans les cas de travail où le Series est petite, on ne peut pas vraiment être sûr que les futures versions de pandas ne casseront pas complètement cette "fonctionnalité".

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X