重命名列后出现键错误

13

我有 df :

df = pd.DataFrame({'a':[7,8,9],
                   'b':[1,3,5],
                   'c':[5,3,6]})

print (df)
   a  b  c
0  7  1  5
1  8  3  3
2  9  5  6

然后按照这个链接的方法将第一个值重新命名:

df.columns.values[0] = 'f'

一切看起来都很不错:

print (df)
   f  b  c
0  7  1  5
1  8  3  3
2  9  5  6

print (df.columns)
Index(['f', 'b', 'c'], dtype='object')

print (df.columns.values)
['f' 'b' 'c']

如果选择b,它运作得很好:

print (df['b'])
0    1
1    3
2    5
Name: b, dtype: int64

但如果选择 a,它将返回列 f

Translated:

But if select a it return column f:

->

但如果选择 a,它将返回列 f

print (df['a'])
0    7
1    8
2    9
Name: f, dtype: int64

如果选择f会出现键错误。

print (df['f'])
#KeyError: 'f'

print (df.info())
#KeyError: 'f'

问题是什么?有人能解释一下吗?或者是错误?


在这个答案的评论中提到了这种行为。由于正在修改此索引对象的内部状态,因此可能不会传播到使用它的所有实例。我认为使用df.rename(columns={'a': 'f'})是预期的方法。 - Jan Trienes
1个回答

26

您不需要改变values属性。

尝试使用df.columns.values = ['a', 'b', 'c'],您将得到:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-61-e7e440adc404> in <module>()
----> 1 df.columns.values = ['a', 'b', 'c']

AttributeError: can't set attribute
那是因为 pandas 检测到您正在尝试设置属性并停止了您。然而,它不能阻止您改变底层的 values 对象本身。当您使用 rename 时,pandas 会跟进一堆清理工作。我已经粘贴了源代码。最终,您所做的是在不启动清理的情况下改变了值。您可以通过后续调用 _data.rename_axis(示例可以在下面的源代码中看到)来启动它自己。这将强制运行清理,然后您就可以访问 ['f']
df._data = df._data.rename_axis(lambda x: x, 0, True)
df['f']

0    7
1    8
2    9
Name: f, dtype: int64

故事寓意:以这种方式重命名列可能不是个好主意。

但这个故事变得更奇怪了。

没问题

df = pd.DataFrame({'a':[7,8,9],
                   'b':[1,3,5],
                   'c':[5,3,6]})

df.columns.values[0] = 'f'

df['f']

0    7
1    8
2    9
Name: f, dtype: int64

df = pd.DataFrame({'a':[7,8,9],
                   'b':[1,3,5],
                   'c':[5,3,6]})

print(df)

df.columns.values[0] = 'f'

df['f']
KeyError:

原来,我们可以在显示 df 之前修改 values 属性,这样它将会在第一次 display 时运行所有初始化操作。如果在修改 values 属性之前显示它,它将出现错误。

更奇怪的是

df = pd.DataFrame({'a':[7,8,9],
                   'b':[1,3,5],
                   'c':[5,3,6]})

print(df)

df.columns.values[0] = 'f'

df['f'] = 1

df['f']

   f  f
0  7  1
1  8  1
2  9  1

仿佛我们之前并不知道这是个坏主意...


rename的来源

def rename(self, *args, **kwargs):

    axes, kwargs = self._construct_axes_from_arguments(args, kwargs)
    copy = kwargs.pop('copy', True)
    inplace = kwargs.pop('inplace', False)

    if kwargs:
        raise TypeError('rename() got an unexpected keyword '
                        'argument "{0}"'.format(list(kwargs.keys())[0]))

    if com._count_not_none(*axes.values()) == 0:
        raise TypeError('must pass an index to rename')

    # renamer function if passed a dict
    def _get_rename_function(mapper):
        if isinstance(mapper, (dict, ABCSeries)):

            def f(x):
                if x in mapper:
                    return mapper[x]
                else:
                    return x
        else:
            f = mapper

        return f

    self._consolidate_inplace()
    result = self if inplace else self.copy(deep=copy)

    # start in the axis order to eliminate too many copies
    for axis in lrange(self._AXIS_LEN):
        v = axes.get(self._AXIS_NAMES[axis])
        if v is None:
            continue
        f = _get_rename_function(v)

        baxis = self._get_block_manager_axis(axis)
        result._data = result._data.rename_axis(f, axis=baxis, copy=copy)
        result._clear_item_cache()

    if inplace:
        self._update_inplace(result._data)
    else:
        return result.__finalize__(self)

4
非常有趣的研究! - MaxU - stand with Ukraine
2
我正在想如何解释 print 引起的这种差异。你有什么想法吗?我以前从未见过这种情况。 - jezrael
@jezrael 我的理论是,在第一次打印时会发生初始化。 - piRSquared
@jezrael 当调用print时,它会调用__repr__方法。此时我猜测如果缓存脚本之前没有运行过,那么pandas会运行一些缓存脚本。 - piRSquared
嗯,我查看了这个并尝试在rename之前和之后打印print(df.__dict__)。在print(df)之后,它被更改了,添加了一些_iloc。但是为什么呢?这真的很奇怪。 - jezrael
显示剩余4条评论

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