我已经用pd做了一段时间的实验。系列和 pd。数据帧并面临一些奇怪的问题。假设我有以下 pd。数据帧:
df = pd.DataFrame({'col':[[1,2,3]]})
请注意,此数据帧包含包含列表的列。我想修改此数据帧的副本并返回其修改后的版本,以便初始版本保持不变。为了简单起见,假设我想在其单元格中添加整数"4"。
我尝试了以下代码:
def modify(df):
dfc = df.copy(deep=True)
dfc['col'].iloc[0].append(4)
return dfc
modify(df)
print(df)
问题是,除了新的复制dfc
,初始数据帧df
也被修改。为什么?我应该怎么做才能防止初始数据帧被修改?我的熊猫版本是0.25.0
从此处的文档,在注释部分:
当 deep=True 时,将复制数据,但不会递归复制实际的 Python 对象,只会复制对该对象的引用。这与标准库中的copy.deepcopy形成对比,后者递归复制对象数据(请参阅下面的示例(。
在GitHub 上的本期中再次引用了这一点,开发人员指出:
在 a 中嵌入可变对象。 数据帧是一种反模式
因此,此函数正在按照开发人员的意图工作 - 列表等可变对象不应嵌入到数据帧中。
我找不到一种方法可以让copy.deepcopy
在数据帧上按预期工作,但我确实使用 pickle 发现了一个相当糟糕的解决方法:
import pandas as pd
import pickle
df = pd.DataFrame({'col':[[1,2,3]]})
def modify(df):
dfc = pickle.loads(pickle.dumps(df))
print(dfc['col'].iloc[0] is df['col'].iloc[0]) #Check if we've succeeded in deepcopying
dfc['col'].iloc[0].append(4)
print(dfc)
return dfc
modify(df)
print(df)
输出:
False
col
0 [1, 2, 3, 4]
col
0 [1, 2, 3]
这是一个有趣的问题。发生这种情况的原因是,即使您正在创建数据帧的副本,这也不会改变内部列表仍在引用同一对象的事实,或者换句话说,这些对象不会递归复制。这可以通过检查列表id
看到:
df = pd.DataFrame({'col':[[1,2]]})
dfc = df.copy()
dfc['col'].iloc[0].append(4)
id(df.iloc[0,0])
# 1734189849288
id(dfc.iloc[0,0])
# 1734189849288
我能想到的一种解决方法是沿感兴趣的列应用list.copy
:
id(df.iloc[0,0])
# 1734174279432
dfc = df.copy()
dfc['col'] = df.col.apply(list.copy)
id(dfc.iloc[0,0])
# 1734186015688