如何避免含列表的Pandas DataFrame深拷贝后修改影响原数据?
Pandas DataFrame含列表列时深拷贝失效的解决办法及原因分析
当Pandas DataFrame的列中每行存储列表时,即便使用copy(deep=True)进行深拷贝,修改新DataFrame中该列的列表元素仍会同步影响原DataFrame。无论通过哪种方式实例化这类列表列,问题都会出现。本文将解答如何避免这种相互影响,以及这是否属于DataFrame使用列表的固有缺陷。
相关代码
import pandas as pd import numpy as np df = pd.DataFrame({'Person': ['Jack', 'Bob', 'Alice'], 'Age': [20, 30, 40]}) df['Flavors'] = np.empty((len(df), 0)).tolist() df['Colors'] = [[] for _ in range(len(df))] df['Friends'] = [[],[],[]] print(df) df2 = df.copy(deep=True) df2.loc[0, 'Flavors'].append('Apple') df2.loc[0, 'Colors'].append('Red') df2.loc[0, 'Friends'].append('Rick') print(df) print(df2)
实际输出
Person Age Flavors Colors Friends 0 Jack 20 [] [] [] 1 Bob 30 [] [] [] 2 Alice 40 [] [] [] Person Age Flavors Colors Friends 0 Jack 20 [Apple] [Red] [Rick] 1 Bob 30 [] [] [] 2 Alice 40 [] [] [] Person Age Flavors Colors Friends 0 Jack 20 [Apple] [Red] [Rick] 1 Bob 30 [] [] [] 2 Alice 40 [] [] []
预期输出
Person Age Flavors Colors Friends 0 Jack 20 [] [] [] 1 Bob 30 [] [] [] 2 Alice 40 [] [] [] Person Age Flavors Colors Friends 0 Jack 20 [Apple] [] [] 1 Bob 30 [] [] [] 2 Alice 40 [] [] [] Person Age Flavors Colors Friends 0 Jack 20 [Apple] [Red] [Rick] 1 Bob 30 [] [] [] 2 Alice 40 [] [] []
原因分析
Pandas的deep=True拷贝并非完全意义上的Python深拷贝。对于DataFrame中的对象类型(比如列表),Pandas只会拷贝对象的引用,而不会递归地拷贝对象内部的元素。也就是说,新DataFrame的列是新的Series,但Series中的每个列表元素仍然和原DataFrame的列表指向同一个内存地址,修改列表元素自然会同步影响两个DataFrame。
解决办法
方法1:使用apply+copy逐个复制列表
对所有含列表的列,手动复制每个列表元素,断开与原DataFrame的引用关联:
df2 = df.copy(deep=True) # 遍历所有列表列,逐个复制内部列表 for col in ['Flavors', 'Colors', 'Friends']: df2[col] = df2[col].apply(lambda x: x.copy()) # 此时修改新DataFrame的列表元素不会影响原DataFrame df2.loc[0, 'Flavors'].append('Apple') df2.loc[0, 'Colors'].append('Red') df2.loc[0, 'Friends'].append('Rick')
方法2:用copy.deepcopy递归复制整个DataFrame
直接使用Python标准库的copy.deepcopy,对整个DataFrame进行完全递归的深拷贝:
import copy df2 = copy.deepcopy(df) df2.loc[0, 'Flavors'].append('Apple') df2.loc[0, 'Colors'].append('Red') df2.loc[0, 'Friends'].append('Rick')
方法3:重构数据结构(推荐)
尽量避免在DataFrame中存储列表这类可变对象,这更符合Pandas处理表格型数据的设计范式。可以将列表拆分为多行(用explode),或者转为JSON字符串存储:
import json # 将列表转为JSON字符串存储 df['Flavors'] = df['Flavors'].apply(json.dumps) df['Colors'] = df['Colors'].apply(json.dumps) df['Friends'] = df['Friends'].apply(json.dumps) # 拷贝后转回列表修改 df2 = df.copy(deep=True) flavors_list = json.loads(df2.loc[0, 'Flavors']) flavors_list.append('Apple') df2.loc[0, 'Flavors'] = json.dumps(flavors_list)
是否是固有缺陷?
这不能算是严格意义上的"缺陷",而是Pandas设计上的权衡。Pandas核心是处理标量值的表格数据,存储可变对象(如列表、字典)不属于常规使用场景,因此其拷贝机制未针对这类场景做特殊处理。如果一定要使用可变对象,需要自行处理深层拷贝的问题。
内容的提问来源于stack exchange,提问作者Tom Kaufman
相关产品推荐
相关产品推荐

