属性错误: 'PandasExprVisitor' 对象没有属性 'visit_Ellipsis',使用Pandas eval函数。

8

我有一系列的表单:

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)

然而,当处理更大量级的数据(约10K)时,这种方法会彻底失败!
len(s)
300000

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

我在这里缺少什么?函数或数据有问题吗?
2个回答

9
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 操作的是 Series 的 __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 的文档。向下滚动一点,您将找到有关load函数的更多详细信息。

注意

  • If you're working with JSON data, it might be suitable to read your file using pd.read_json or pd.io.json.json_normalize to begin with.

  • You can also perform parsing as you read in your data, using read_csv -

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

    Where the converters argument will apply that function passed on the column as it is read, so you don't have to deal with parsing later.

  • Continuing the point above, if you're working with a dataframe, pass a dict -

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

    Where col is the column that needs to be parsed You can also pass pd.json.loads (for json data), or pd.eval (if you have 100 rows or less).


感谢MaxU和Moondra发现了这个问题。


1
是的,我记得那个案例... ;) - MaxU - stand with Ukraine
@MaxU 我在回答中确保给了你和Moondra正确的贡献表彰。如果有任何需要改进的地方,请告知我! - cs95
1
我也非常喜欢@ayhan的解决方案 - MaxU - stand with Ukraine
1
@MaxU 干杯!真不敢相信我居然没想起来那个。 - cs95
1
直到今天我从未遇到过这个错误。感谢指出。 - Bharath M Shetty
显示剩余3条评论

3
你的数据没有问题,但是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,在面对歧义时导致猜测。而系列默认缩短的 __repr__ 设计用于美观打印,可以极大地影响您的结果,这是最好的证明。
解决方案是远离 XY 问题,使用正确的工具转换数据,最好完全停止使用 pandas.eval 实现此目的。即使在 Series 很小的工作情况下,您也无法确保未来的 pandas 版本不会完全破坏此“功能”。

好的,你提到了一个 XY 问题。你有什么建议? - cs95
@cᴏʟᴅsᴘᴇᴇᴅ 你的回答列出了替代方案,我只想强调一点,即应该选择其中之一,而不是等待pd.eval被“修复” :) - Andras Deak -- Слава Україні
啊,好的。一开始我没有注意到这一点。感谢您的澄清!此外,我同意您的答案,因为文档没有提到应该传递什么和不应该传递什么。所以,人们常犯的错误是假设 pd.eval 会单独处理系列中的每个字符串,而不是将整个系列 __repr__ 作为一个整体来处理,这才是它的工作方式。 - cs95
2
@cᴏʟᴅsᴘᴇᴇᴅ 我不同意:文档非常明确,expr 必须是 str/unicode 实例。有一条备注说:“支持 SeriesDataFrame 对象,并且与普通的 Python 评估行为相同”,但这是指传递的字符串的_内容_。其他任何东西都是一厢情愿的:P 但相关 SO 帖子中的评论表明,这确实会偶尔使用户困惑,因此提供解决方案/替代方案具有价值。 - Andras Deak -- Слава Україні

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接