为什么熊猫数据框单元格的ID每次执行都会改变?


问题内容

当我尝试确保数据框视图的某些属性时遇到了这个问题。

假设我有一个定义为:的数据框,df = pd.DataFrame(columns=list('abc'), data=np.arange(18).reshape(6, 3))并且此数据框的视图定义为:df1 = df.iloc[:3, :]。现在,我们有两个数据框,如下所示:

print(df)
    a   b   c
0   0   1   2
1   3   4   5
2   6   7   8
3   9  10  11
4  12  13  14
5  15  16  17

print(df1)

   a  b  c
0  0  1  2
1  3  4  5
2  6  7  8

现在,我要输出这两个数据帧的特定单元格的ID:

print(id(df.loc[0, 'a']))
print(id(df1.loc[0, 'a']))

我的输出为:

140114943491408
140114943491408

奇怪的是,如果我连续执行这两行“打印ID”代码,则ID也会发生变化:

140114943491480
140114943491480

我必须强调,当我执行这两个“打印ID”代码时,我没有执行“
df定义”代码,因此df和df1没有重新定义。然后,我认为,数据帧中每个元素的内存地址应该是固定的,那么输出将如何变化?

当我继续执行这两行“打印ID”代码时,发生了一件更奇怪的事情。在极少数情况下,这两个id甚至彼此不相等:

140114943181088
140114943181112

但是,如果我同时执行id(df.loc[0, 'a']) == id(df1.loc[0, 'a']),python仍然输出True。我知道,由于df1是df的视图,因此它们的单元格应该共享一个内存,但是其id的输出有时会有所不同?

那些奇怪的行为使我完全迷失了。谁能解释这些行为?它们是由于数据帧或python中的id函数的特性引起的吗?谢谢!

仅供参考,我正在使用Python 3.5.2


问题答案:

您没有获得“单元格”的ID,而是获得id.loc访问器返回的对象的ID,该访问器是基础数据的盒装版本。

所以,

>>> import pandas as pd
>>> df = pd.DataFrame(columns=list('abc'), data=np.arange(18).reshape(6, 3))
>>> df1 = df.iloc[:3, :]
>>> df.dtypes
a    int64
b    int64
c    int64
dtype: object
>>> df1.dtypes
a    int64
b    int64
c    int64
dtype: object

但是由于Python中的 所有内容 都是对象,因此您的loc方法必须返回一个对象:

>>> x = df.loc[0, 'a']
>>> x
0
>>> type(x)
<class 'numpy.int64'>
>>> isinstance(x, object)
True

但是,实际的基础缓冲区是C个固定大小的64位有符号整数的原始数组。它们不是Python对象,它们被“装箱”以从其他将原始类型与对象混合在一起的语言中借用一个术语。

现在,所有对象都具有相同现象的现象id

>>> id(df.loc[0, 'a']), id(df.loc[0, 'a'])
(4539673432, 4539673432)
>>> id(df.loc[0, 'a']), id(df.loc[0, 'a']), id(df1.loc[0,'a'])
(4539673432, 4539673432, 4539673432)

发生是因为在Python中,对象可以自由地重用最近回收的对象的内存地址。确实,当您创建的元组时id,对象的返回loc仅存在足够长的时间,以通过第一次调用进行传递和处理id,第二次使用时loc,已经释放的对象仅会重复使用相同的内存。您可以在任何Python对象中看到相同的行为,例如list

>>> id([]), id([])
(4545276872, 4545276872)

从根本上讲,id只能保证在对象的 生存期内
唯一的。在此处阅读有关此现象的更多信息。但是请注意,在以下情况下,它将始终是不同的:

>>> x = df.loc[0, 'a']
>>> x2 = df.loc[0, 'a']
>>> id(x), id(x2)
(4539673432, 4539673408)

由于您维护引用,因此不会回收对象,并且需要新的内存。

注意,对于许多不可变的对象,解释器可以自由优化并返回 相同的精确对象 。在CPython中,“小整数”就是这种情况,所谓的小整数缓存:

>>> x = 2
>>> y = 2
>>> id(x), id(y)
(4304820368, 4304820368)

但这是不应该依赖的实现细节。

如果您想证明自己的数据帧正在共享相同的基础缓冲区,只需对其进行突变,便会在视图之间看到相同的变化:

>>> df
    a   b   c
0   0   1   2
1   3   4   5
2   6   7   8
3   9  10  11
4  12  13  14
5  15  16  17
>>> df1
   a  b  c
0  0  1  2
1  3  4  5
2  6  7  8
>>> df.loc[0, 'a'] = 99
>>> df
    a   b   c
0  99   1   2
1   3   4   5
2   6   7   8
3   9  10  11
4  12  13  14
5  15  16  17
>>> df1
    a  b  c
0  99  1  2
1   3  4  5
2   6  7  8