今天发现,全局定义的pandas的DataFrame被传入到函数中的时候,如果函数改变了slice,比如df.loc[…,…] = x,全局变量df也会跟着变。险些造成错误计算,幸好发现及时。这里探讨一下这个问题的规避方法。
tl;dr:
使用的dataframe很大的情况下,复制列,在复制的列上进行想要的操作,如果很小,使用df.copy()。
更新,复制列有时会有bug,会弹出view vs copy警告,原因未知,建议使用df.copy()
更新,原因已查明。
当在函数中对df进行切片,即使使用了loc:
new_df = df.loc[cond]
new_df.loc[0,'col_name'] = 1
也会弹出警告,原因是new_df本质上还是df的切片,
包括像是(df.drop_duplicates这些方法都是切片)
new_df.loc[0,‘col_name’] = 1相当于 df.loc[cond].loc[0,‘col_name’] = 1
一旦出现给双重切片赋值,就会弹出警告,规避方法:
new_df = df.loc[cond].copy()
new_df.loc[0,'col_name'] = 1
takeaway:避免给双重切片赋值
首先是实验环节:定义dataframe
import pandas as pd
df = pd.DataFrame({'a':['z','x','c'],'b':[4,5,6]})
- 改变整个df,不改变原df:
def change_df(df):
df = df * 2
print('change_df')
change_df(df)
print(df)
输出:
change_df
a b
0 z 4
1 x 5
2 c 6
- 改变一列,原df发生变化:
def change_b(df):
df.loc[:,'b'] = df.b*2
print('change_b')
change_b(df)
print(df)
输出:
change_b
a b
0 z 8
1 x 10
2 c 12
- 在func里面复制一个新的col,对新col赋值,不改变被复制的col,但是df会出现新加入的col:
def change_nCol(df):
df.loc[:,'c'] = df.b
df.loc[:,'c'] = df.c*2
print('\nchange_nCol')
change_nCol(df)
print(df)
输出:
change_nCol
a b c
0 z 8 16
1 x 10 20
2 c 12 24
- 使用copy功能,原df不变:
def change_copy(df):
df_copy = df.copy(deep = True) #默认为True
df_copy.loc[:,'a'] = df_copy.a*2
print('\nchange_copy')
change_copy(df)
print(df)
输出:
change_copy
a b c
0 z 8 16
1 x 10 20
2 c 12 24
- 但也有例外情况,如官网上讲的:
note that when copying an object containing Python objects, a deep copy will copy the data, but will not do so recursively. Updating a nested data object will be reflected in the deep copy.
>>> s = pd.Series([[1, 2], [3, 4]])
>>> deep = s.copy()
>>> s[0][0] = 10
>>> s
0 [10, 2]
1 [3, 4]
dtype: object
>>> deep
0 [10, 2]
1 [3, 4]
dtype: object
- 讲一下我的看法
传入函数之后,如果是对dataframe只是进行切片操作,那么原来的dataframe是不会变的。
如果涉及到改变数值,包括添加新列新行,以及改变原数值,那么原df会跟着变。有时会需要新加一列方便计算,结尾删除该列一定要inplace,不然普通的drop也其实只是切片操作,不会删除新加列。之前一直不知道inplace的用处,这里就能看出inplace的作用了。
def test(data):
data['test'] = 0
data = data.drop(columns=['test'])
print('test done df里面多一个test列,并且没有被删掉')
def test2(data):
data['test'] = 0
data.drop(columns=['test'], inplace=True)
print('test done df里面多出来的test列被删掉了')
如果你在切片上改变数值,则会弹出set value on a copy of view警告,也是终于看懂这句话的意思,new是data的一个切片的copy。具体原始df会不会改,读者可以自行实验。
def test_change(data):
new = data.iloc[:,0:2]
new['test'] = 1