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
маленький, вы не можете быть уверены, что будущие версии панд не нарушат эту "функцию" полностью.