Pandas 错误:__setitem__() 无法将字典值识别为列名列表

Posted

技术标签:

【中文标题】Pandas 错误:__setitem__() 无法将字典值识别为列名列表【英文标题】:Pandas bug: __setitem__() doesnt recognize dictionary values as a list of column names 【发布时间】:2021-07-01 19:43:59 【问题描述】:

编辑:看起来这是 Pandas 中的一个潜在错误。查看这个 GitHub issue @NicMoetsch 提出的有用的信息,注意到分配字典值的意外行为与框架的 __setitem__()__getitem__() 之间的差异有关。


之前在我的代码中,我用字典重命名了一些列:

cols_dict = 
     'Long_column_Name': 'first_column',
     'Other_Long_Column_Name': 'second_column',
     'AnotherLongColName': 'third_column'

for key, val in cols_dict.items():
    df.rename(columns=key: val, inplace=True)

(我知道这里不需要循环——在我的实际代码中,我必须在数据帧列表中搜索数据帧的列,并获得与字典键匹配的子字符串。)

稍后我用applymap() 做一些清理,用字典值索引,它工作正常

pibs[cols_dict.values()].applymap(
    lambda x: np.nan if ':' in str(x) else x
)

但是当我尝试将切片分配回自身时,我收到一个关键错误(完整的错误消息here)。

pibs[cols_dict.values()] = pibs[cols_dict.values()].applymap(
    lambda x: np.nan if ':' in str(x) else x
)
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
~/.local/lib/python3.7/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance)
   3079             try:
-> 3080                 return self._engine.get_loc(casted_key)
   3081             except KeyError as err:

pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()
pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()
pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()
pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()
KeyError: dict_values(['first_column', 'second_column', 'third_column'])

如果我将字典值转换为列表,代码运行良好

pibs[list(cols_dict.values())] = ...

所以我想我只是想知道为什么我能够使用字典值进行切片并在其上运行 applymap(),但是当我转身尝试分配结果时我无法使用字典值进行切片返回数据框。

简单地说:为什么pandas 将cols_dict.values() 用于索引时将其识别为列名列表,但在用于分配索引时却不识别?

【问题讨论】:

【参考方案1】:

这个问题似乎与applymap() 无关,因为在没有applymap() 的情况下使用了 aneroid 的示例:

import copy

cols_dict = 
     'Long_column_Name': 'first_column',
     'Other_Long_Column_Name': 'second_column',
     'AnotherLongColName': 'third_column'


df = pd.DataFrame('Long_column_Name': range(3),
                   'Other_Long_Column_Name': range(3, 6),
                   'AnotherLongColName': range(15, 10, -2),
)
df.rename(columns=cols_dict, inplace=True)

df[cols_dict.values()] = df[cols_dict.values()]

产生同样的错误。

显然不是操作部分不起作用,而是赋值部分,as

df = df[cols_dict.values()]

工作正常。 使用不同的 DataFrame 组合显示错误消息中的3

ValueError: Wrong number of items passed 3, placement implies 1

不是由分配部分引起的,因为尝试分配四列 DataFrame 会引发不同的错误:

df2 = pd.DataFrame('Long_column_Name': range(3),
                   'Other_Long_Column_Name': range(3, 6),
                   'AnotherLongColName': range(15, 10, -2),
                    'ShtClNm': range(10, 13))

产量

ValueError: Wrong number of items passed 4, placement implies 1

因此,我尝试只分配一列,这样理论上它只传递一项工作正常而不会引发错误的项目。

df[cols_dict.values()] = df2['Long_column_Name']

然而结果不是预期的:

df
   first_column  second_column  third_column (first_column, second_column,third_column)  
0            0              3            15                                          0
1            1              4            13                                          1
2            1              5            11                                          2  

所以对我来说,似乎正在发生的事情是,pandas 没有将传递给 df[...] =cols_dict.values() 识别为列名列表,而是将其识别为一个新列的名称 (first_column, second_column,third_column).

这就是为什么它会尝试用传递给赋值的值填充新列。由于您传递给许多 (3) 列以分配给它破坏的一个新列。

当您在df[list(cols_dict.values())] = 中使用list() 时,它可以正常工作,因为它会识别出传递了一个列列表。

深入了解pandas DataFrames,我想我找到了问题所在。

据我了解,pandas 使用__setitem__() 进行分配,使用__getitem__() 进行查找。这两个函数都使用了convert_to_index_sliceable() 定义的here。 convert_to_index_sliceable(),如果您传递的任何内容都是可切片的,则返回一个切片,如果不是,则返回 None

__getitem__()__setitem__() 首先检查convert_to_index_sliceable() 是否返回None,但如果它不返回None,则它们不同。

__getitem__()将索引器转换为np.intp,这是numpy在返回切片之前的索引日期类型,如下所示:

        # Do we have a slicer (on rows)?
        indexer = convert_to_index_sliceable(self, key)
        if indexer is not None:
            if isinstance(indexer, np.ndarray):
                indexer = lib.maybe_indices_to_slice(
                    indexer.astype(np.intp, copy=False), len(self)
                )
            # either we have a slice or we have a string that can be converted
            #  to a slice for partial-string date indexing
            return self._slice(indexer, axis=0)

另一方面__setitem__()马上返回:

        # see if we can slice the rows
        indexer = convert_to_index_sliceable(self, key)
        if indexer is not None:
            # either we have a slice or we have a string that can be converted
            #  to a slice for partial-string date indexing
            return self._setitem_slice(indexer, value)

假设没有向__getitem__() 添加不必要的代码,我认为__setitem__() 一定是缺少该代码,因为两个返回前的cmets 声明的内容与indexer 可能的内容完全相同。

我将提出一个 GitHub 问题,询问这是否是预期行为。

【讨论】:

是的,我认为这是提出问题的最佳方式。它在 python documentation 中声明,dict.values() 只返回一个视图,因此 pandas 必须在内部将其转换为列表。我不知道为什么它在分配时不这样做。据我了解,pandas 不会搜索分配是否匹配,它只是覆盖。因此,它使用与查找不同的功能进行分配确实是有道理的,我只是想不出为什么查找可以和分配不能处理视图的原因 @KristianCanler 更新了我的答案。 @Kristian Link。我不确定网络礼仪,因为我对编程比较陌生,所以我至少添加了你的问题链接,但如果你愿意,我也可以在问题中标记你。我只是想提供了解问题所需的最少信息。 他们是否总是相对较快地发现 Pandas 的 GitHub 上的错误,还是会漏掉漏洞? @Kristian 老实说我不知道​​。【参考方案2】:

不是您的问题的直接答案,为什么您能够使用 dict.values() 切片获取记录但没有设置它 - 但是,它可能与索引有关:因为如果我使用 loc,它可以工作很好。

让我们设置一下:

cols_dict = 
     'Long_column_Name': 'first_column',
     'Other_Long_Column_Name': 'second_column',
     'AnotherLongColName': 'third_column'


df = pd.DataFrame('Long_column_Name': range(3),
                   'Other_Long_Column_Name': range(3, 6),
                   'AnotherLongColName': range(15, 10, -2),
)
df.rename(columns=cols_dict, inplace=True)
df
   first_column  second_column  third_column
0             0              3            15
1             1              4            13
2             2              5            11

使用applymap

df[cols_dict.values()].applymap(lambda x: -1 if x % 2 == 0 else x ** 2)
   first_column  second_column  third_column
0            -1              9           225
1             1             -1           169
2            -1             25           121

这行抛出你得到的错误:

df[cols_dict.values()] = df[cols_dict.values()].applymap(lambda x: -1 if x % 2 == 0 else x ** 2)
# error thrown

但这有效,df.loc:

df.loc[:, cols_dict.values()] = df[cols_dict.values()].applymap(lambda x: -1 if x % 2 == 0 else x ** 2)
df
   first_column  second_column  third_column
0            -1              9           225
1             1             -1           169
2            -1             25           121

编辑,一些可能是错误的部分推断:顺便说一句,the longer error 显示了其他可能发生的情况:

KeyError: dict_values(['first_column', 'second_column', 'third_column'])

During handling of the above exception, another exception occurred:
# later:
ValueError: Wrong number of items passed 3, placement implies 1

...经历了insertmake_block 的一部分,这让我认为它试图创建 列并在那里失败。并且该部分是为 setitem 而不是为 getitem 调用的-因此发生的查找没有相同的结果。相反,我会期待“使用副本设置”错误。

【讨论】:

确认df.loc 也适用于我的上下文。 编辑我的问题以包含您发布到 pastebin 的较长错误。

以上是关于Pandas 错误:__setitem__() 无法将字典值识别为列名列表的主要内容,如果未能解决你的问题,请参考以下文章

__setitem__,__getitem,__delitem__

python学习之__getitem__,__setitem__,__delitem__

python - __setitem__/__getitem__/__delitem__类的内置方法

__getitem__\__setitem__\__delitem__

__getitem__ __setitem__ __delitem__ 使用

python 魔法方法之:__getitem__ __setitem__ __delitem__