这里仅以DataFrame为例进行说明。
Pandas版本:1.5.3
1 问题描述
现有一DataFrame型变量A,现在需要对变量A做一些处理操作,但又希望保存A的一个原始副本。所以考虑将变量A赋值给变量B,然后再变量B上进行处理操作。但是处理完却发现A中的值也跟着发生了变动。具体举例如下:
很显然,上述代码中虽然没有直接对A进行变换,但A的值还是发生了变化。这就涉及到了Python的深浅拷贝问题。本文主要关注Pandas对象的深浅拷贝,其他类型变量的深浅拷贝可以参考:https://blog.csdn.net/yeshang_lady/article/details/80755061
2 Pandas对象深浅拷贝
Pandas中提供了copy()方法来对其对象进行拷贝,其内设的bool型参数deep可以设置是进行深拷贝。另外,copy标准库中deepcopy()方法也可以完成Pandas对象的深拷贝。这三者的区别如下:
- deep=False:只对Pandas对象的数据和元素进行浅拷贝,即只复制数据和索引的引用,对副本变量的修改会影响原始数据。
- deep=True:会对数据和索引进行复制。在副本变量上的修改不会影响原始变量。但这种复制不能递归(即当Pandas中的数据也是一个引用时,该方法不会对该引用的具体内容也进行复制)。
- copy.deepcopy: 会对数据和索引进行复制,并且这种复制是可递归的。
2.1 对索引/列名的修改
先创建对象A,并使用不同拷贝方法创建A的副本对象。先用id()方法来查看各个副本对象的列名/索引信息存放的位置是否相同。具体代码如下:
import pandas as pd
from copy import deepcopy
A=pd.DataFrame([['A',[3000,3002],22],
['B',[3010,2900],29]],columns=['col_1','col_2','col_3'])
A_deep=A.copy(deep=True)
A_shallow=A.copy(deep=False)
A_copy=deepcopy(A)
#查看各个对象的索引和列名的存放地址
print("对象A的索引存放地址:{},列名存放地址:{}".format(id(A.index),id(A.columns)))
print("对象A_deep的索引存放地址:{},列名存放地址:{}".format(id(A_deep.index),id(A_deep.columns)))
print("对象A_shallow的索引存放地址:{},列名存放地址:{}".format(id(A_shallow.index),id(A_shallow.columns)))
print("对象A_copy的索引存放地址:{},列名存放地址:{}".format(id(A_copy.index),id(A_copy.columns)))
其结果如下:
从图上可以看出,浅拷贝得到的对象A_shallow的索引和列名存放地址与原始变量A的存放地址相同。而其他两种方法得到的存放地址与原始变量A不同。但由于DataFrame型变量的索引和列名都是不可变对象,所以对A_shallow中的列名或索引的修改不会导致原始变量A的改变。具体如下:
#A_shallow.columns[0]='A1' 无法运行,提示其为不可变类型
A_shallow.columns=['A1','B1','C1'] #这种写法相当于将A_shallow的列名指向内存中另外一个地址
print("修改后的A_shallow的列名:",list(A_shallow.columns))
print("修改A_shallow的列名后A的列名:",list(A.columns))
print("修改后的A_shallow的列名存放地址:",id(A_shallow.columns))
其结果如下:
从图中可以看到,A_shallow的列名改名并没有导致A跟着变动。(修改索引也是)
2.2 数据修改
- 第1种情况:浅拷贝(deep=False)。具体如下:
import pandas as pd
from copy import deepcopy
A=pd.DataFrame([['A',[3000,3002],22],
['B',[3010,2900],29]],columns=['col_1','col_2','col_3'])
A_shallow=A.copy(deep=False)
A_shallow.loc[0,'col_1']='C'
A_shallow['col_3']=[34,27]
A_shallow和A的值如下:
从结果上可以看到奇怪的现象,就是对A_shallow中col_3整列的修改并没有影响到A中的相应列,好似与浅拷贝的定义不符合。这是因为A_shallow中的col_3指向了新的内存地址,而A中col_3列指向的位置不变。这与下述代码同义:
a=[1,2]
print(a,id(a))
a[0]=0
print(a,id(a))
a=[2,3,5]
print(a,id(a))
其结果如下:
- 第2种情况:深拷贝(deep=True)。具体如下:
import pandas as pd
from copy import deepcopy
A=pd.DataFrame([['A',[3000,3002],4002],
['B',[3010,2900],3009]],columns=['col_1','col_2','col_3'])
A_deep=A.copy(deep=True)
A_deep.loc[0,'col_1']='C'
A_deep.loc[0,'col_2'][0]=3
A_deep.loc[1,'col_2']=[2020]
其结果如下:
- 第3种情况:deepcopy()方法。具体如下:
import pandas as pd
from copy import deepcopy
A=pd.DataFrame([['A',[3000,3002],4002],
['B',[3010,2900],3009]],columns=['col_1','col_2','col_3'])
A_copy=deepcopy(A)
A_copy['col_2']=[[2002,2012],[3030,2020]]
A_copy.loc[0,'col_1']='E'
其结果如下: