当从父数据帧中选择子数据帧时,我注意到一些程序员使用.copy()方法来复制数据帧。例如,

X = my_dataframe[features_list].copy()

...而不仅仅是

X = my_dataframe[features_list]

他们为什么要复制数据帧?如果我不复制一份会怎么样?


当前回答

主要目的是避免链式索引和消除SettingWithCopyWarning。

这里链式索引类似于dfc['A'][0] = 111

该文件指出,在返回视图和返回副本时应避免链式索引。以下是该文档中稍作修改的示例:

In [1]: import pandas as pd

In [2]: dfc = pd.DataFrame({'A':['aaa','bbb','ccc'],'B':[1,2,3]})

In [3]: dfc
Out[3]:
    A   B
0   aaa 1
1   bbb 2
2   ccc 3

In [4]: aColumn = dfc['A']

In [5]: aColumn[0] = 111
SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

In [6]: dfc
Out[6]:
    A   B
0   111 1
1   bbb 2
2   ccc 3

在这里,aColumn是一个视图,而不是原始DataFrame的副本,因此修改aColumn将导致原始dfc也被修改。接下来,如果我们先索引行:

In [7]: zero_row = dfc.loc[0]

In [8]: zero_row['A'] = 222
SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

In [9]: dfc
Out[9]:
    A   B
0   111 1
1   bbb 2
2   ccc 3

这次zero_row是一个副本,因此原始dfc没有被修改。

从上面的两个例子中,我们可以看到是否要更改原始的DataFrame是不明确的。如果你这样写,这是非常危险的:

In [10]: dfc.loc[0]['A'] = 333
SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

In [11]: dfc
Out[11]:
    A   B
0   111 1
1   bbb 2
2   ccc 3

这一次,它根本不管用。这里我们想要更改dfc,但实际上我们修改了一个中间值dfc。Loc[0]是一个副本,立即被丢弃。很难预测中间值是否像dfc。loc[0]或dfc['A']是一个视图或副本,所以不保证原始的DataFrame是否会被更新。这就是为什么应该避免链式索引,pandas为这种链式索引更新生成SettingWithCopyWarning。

现在是.copy()的使用。为了消除这个警告,你可以复制一份明确表达你的意图:

In [12]: zero_row_copy = dfc.loc[0].copy()

In [13]: zero_row_copy['A'] = 444 # This time no warning

因为您正在修改一个副本,所以您知道原始dfc永远不会改变,也不期望它会改变。您的期望与行为匹配,然后SettingWithCopyWarning消失。

注意,如果你想修改原始的DataFrame,文档建议你使用loc:

In [14]: dfc.loc[0,'A'] = 555

In [15]: dfc
Out[15]:
    A   B
0   555 1
1   bbb 2
2   ccc 3

其他回答

有必要提到,返回副本或视图取决于索引的类型。

熊猫的文件说:

返回一个视图和一个副本 关于何时返回数据视图的规则完全是 取决于NumPy。每当一个标签数组或布尔向量 都涉及到索引操作,结果将是一个副本。 使用单标签/标量索引和切片,例如df。第九[3:6]或 df。ix[:, 'A'],将返回一个视图。

一般来说,在副本上工作比在原始数据帧上工作更安全,除非您知道不再需要原始数据并希望继续使用修改过的版本。通常情况下,您仍然可以将原始数据帧与经过处理的版本进行比较,等等。因此,大多数人都在复制,最后合并。

假设您有如下数据帧

df1
     A    B    C    D
4 -1.0 -1.0 -1.0 -1.0
5 -1.0 -1.0 -1.0 -1.0
6 -1.0 -1.0 -1.0 -1.0
6 -1.0 -1.0 -1.0 -1.0

当你想创建另一个df2,它是相同的df1,没有复制

df2=df1
df2
     A    B    C    D
4 -1.0 -1.0 -1.0 -1.0
5 -1.0 -1.0 -1.0 -1.0
6 -1.0 -1.0 -1.0 -1.0
6 -1.0 -1.0 -1.0 -1.0

并想修改df2值仅如下所示

df2.iloc[0,0]='changed'

df2
         A    B    C    D
4  changed -1.0 -1.0 -1.0
5       -1 -1.0 -1.0 -1.0
6       -1 -1.0 -1.0 -1.0
6       -1 -1.0 -1.0 -1.0

同时df1也被改变了

df1
         A    B    C    D
4  changed -1.0 -1.0 -1.0
5       -1 -1.0 -1.0 -1.0
6       -1 -1.0 -1.0 -1.0
6       -1 -1.0 -1.0 -1.0

由于两个df是同一个对象,我们可以使用id来检查它

id(df1)
140367679979600
id(df2)
140367679979600

所以它们是同一个对象,一个改变另一个也会传递相同的值。


如果我们添加副本,现在df1和df2被认为是不同的对象,如果我们对其中一个做相同的改变,另一个将不会改变。

df2=df1.copy()
id(df1)
140367679979600
id(df2)
140367674641232

df1.iloc[0,0]='changedback'
df2
         A    B    C    D
4  changed -1.0 -1.0 -1.0
5       -1 -1.0 -1.0 -1.0
6       -1 -1.0 -1.0 -1.0
6       -1 -1.0 -1.0 -1.0

值得一提的是,当您对原始数据帧进行子集时,添加副本也是安全的,以避免SettingWithCopyWarning

因为如果你不复制,那么即使你把dataFrame赋给一个不同的名字,索引仍然可以在其他地方被操纵。

例如:

df2 = df
func1(df2)
func2(df)

Func1可以通过修改df2来修改df,为了避免:

df2 = df.copy()
func1(df2)
func2(df)

在使用copy()之前,我非常粗心,直到使用下面这行代码 如果不使用copy(), df_genel3中的更改会影响df_genel

df_genel3 = df_genel
df_genel3.loc[(df_genel3['Hareket']=='İmha') , 'Hareket_Tutar'] = tutar 

Copy()解决了这个问题

df_genel3 = df_genel.copy()
df_genel3.loc[(df_genel3['Hareket']=='İmha') , 'Hareket_Tutar'] = tutar