AttributeError: у объекта 'PandasExprVisitor' нет атрибута 'visit_Ellipsis', используя pandas eval

У меня есть серия вида:

s

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

Обратите внимание, что его элементы являются строками:

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

Я пытаюсь использовать pd.eval разобрать эту строку в столбец списков. Это работает для этого примера данных.

pd.eval(s)

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

Тем не менее, на гораздо больших данных (порядка 10 КБ) это с треском проваливается!

len(s)
300000

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

Что мне здесь не хватает? Что-то не так с функцией или моими данными?

2 ответа

Решение

TL;DR
По состоянию на v0.21, это ошибка и открытый вопрос на GitHub. Смотри GH16289.


Почему я получаю эту ошибку?
Это (по всей вероятности) pd.eval Ошибка, которая не может разобрать серию с более чем 100 строками. Вот пример.

len(s)
300000

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

В то время как,

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

Эта проблема сохраняется, независимо от парсера или движка.


Что означает эта ошибка?
Когда серия с более чем 100 строк пройдена, pd.eval работает на __repr__ серии, а не объекты, содержащиеся в нем (что является причиной этой ошибки). __repr__ усеченные строки, заменяя их ... (Многоточие). Этот многоточие неверно истолковывается двигателем как Ellipsis объект -

...
Ellipsis

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

Именно это и является причиной этой ошибки.


Что я могу сделать, чтобы это работало?
На данный момент решения не существует (проблема по-прежнему остается открытой на 28.12.2017), однако есть несколько обходных путей.

Опция 1
ast.literal_eval
Эта опция должна работать "из коробки", если вы можете гарантировать, что у вас нет искаженных строк.

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 

Если есть вероятность искаженных данных, вам нужно написать небольшой код обработки ошибок. Вы можете сделать это с помощью функции -

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

Передайте эту функцию apply -

s.apply(safe_parse)

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

ast работает для любого числа строк, и медленно, но надежно. Вы также можете использовать pd.json.loads для данных JSON, применяя те же идеи, что и с literal_eval,

Вариант 2
yaml.load
Еще один отличный вариант для разбора простых данных, я взял это у @ayhan некоторое время назад.

import yaml
s.apply(yaml.load)

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

Я не проверял это на более сложных структурах, но это должно работать практически для любого базового строкового представления данных.

Вы можете найти документацию по PyYAML здесь. Прокрутите немного вниз, и вы найдете более подробную информацию о load функция.


Заметка

  • Если вы работаете с данными JSON, возможно, будет удобно прочитать ваш файл, используя pd.read_json или же pd.io.json.json_normalize начать с.
  • Вы также можете выполнять синтаксический анализ, как вы читаете в ваших данных, используя read_csv -

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

    Где converters Аргумент будет применять эту функцию, передаваемую к столбцу при его чтении, поэтому вам не придется разбирать его позже.

  • Продолжая пункт выше, если вы работаете с фреймом данных, передайте dict -

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

    куда col это столбец, который нужно проанализировать Вы также можете передать pd.json.loads (для данных JSON), или pd.eval (если у вас есть 100 строк или меньше).


Кредиты MaxU и Moondra для раскрытия этой проблемы.

Ваши данные в порядке, и pandas.eval глючит, но не так, как вы думаете. На соответствующей странице выпуска github есть подсказка, которая побуждает меня поближе взглянуть на документацию.

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

Как вы можете видеть, документированное поведение заключается в передаче строк pd.evalв соответствии с общим (и ожидаемым) поведением eval/exec класс функций. Вы передаете строку и в итоге получаете произвольный объект.

Как я вижу это, pandas.eval глючит, потому что не отвергает Series вход expr впереди, заставляя его угадывать перед лицом неоднозначности. Тот факт, что по умолчанию сокращение Series' __repr__ Разработанный для красивой печати может кардинально повлиять на ваш результат, является лучшим доказательством этой ситуации.

Решение состоит в том, чтобы отойти от проблемы XY и использовать правильный инструмент для преобразования ваших данных, и предпочтительно прекратить использование pandas.eval для этого целиком. Даже в рабочих случаях, когда Series маленький, вы не можете быть уверены, что будущие версии панд не нарушат эту "функцию" полностью.

Другие вопросы по тегам