AttributeError: 'PandasExprVisitor' object has no attribute 'visit_Ellipsis', using pandas eval

AttributeError: 'PandasExprVisitor' object has no attribute 'visit_Ellipsis', using pandas eval

我有一系列的表格:

s

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

注意它的元素是 strings:

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)

然而,对于更大的数据(10K 量级),这会失败得很惨!

len(s)
300000

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

我在这里错过了什么?功能或我的数据有问题吗?

TL;DR
这无疑是 eval 中的一个错误。查看开放 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'

无论使用何种解析器或引擎,此问题仍然存在。


这个错误是什么意思?
pd.eval 对系列的 __repr__ 进行操作,而不是对其中包含的对象进行操作(这是此错误的原因)。 __repr__ 截断行,用 ...(省略号)替换它们。该省略号被引擎误解为 Ellipsis 对象 -

...
Ellipsis

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

pd.eval 技术上不应该解析一系列字符串(文档中提到它是为了接收字符串)并且(如接受的答案所述)将尝试对结果进行合理的猜测完全拒绝输入。

这是预期的行为还是不完整的行为(许多 pandas 方法根据输入以不同的方式运行 - eval 可以通过将自身映射到每一行来处理一系列,这就是我最初假设的方式无论如何这是有效的)有待讨论,因为有一个未解决的问题跟踪这个。


我该怎么做才能让它发挥作用?
目前,没有解决方案(该问题截至 2017 年 12 月 28 日仍未解决),但是,有一些解决方法。

选项 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 的文档 here。向下滚动一点,您会发现有关 load 函数的更多详细信息。


备注

  • 如果您正在处理 JSON 数据,那么开始使用 pd.read_json or 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 有问题,但不是你想的那样。有提示in the relevant github issue page that urged me to take a closer look at the 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.
        [...]

如您所见,记录的行为是将 strings 传递给 pd.eval,与 [=13= 的一般(和预期)行为一致]/exec class 个函数。你传递一个字符串,最后得到一个任意对象。

在我看来,pandas.eval 是有问题的,因为它不会预先拒绝 Series 输入 expr,导致它在面对歧义时进行猜测。 Series' __repr__ 设计用于漂亮打印的默认缩短会极大地影响您的结果,这一事实就是这种情况的最好证明。

解决方案是从 XY 问题退后一步,使用 ,最好完全停止使用 pandas.eval。即使在 Series 很小的工作情况下,你也不能真正确定未来的 pandas 版本不会完全破坏这个 "feature"。