一、SettingWithCopyWarning 是什么?
在使用 numpy 和 pandas 的过程当中,如果使用向量化的方法来回测的话,由于经常需要用到赋值操作,很多的操作都会触发 SettingWithCopyWarning 的警告
“ SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead ”
给这个警告搞得不胜其烦,所以好好研究一下,到底应该怎么解决这个问题。
在 pandas 的一些操作中,有的时候会返回一个 view of data 而有一些会返回一个 copy.
view 和 copy 的区别如下图所示:
如上图所示:如果是 df2 是view 的话,实际上df2是df1的一个子集,而如果df2是copy的话,实际上df1和df2是两个完全不同的 DataFrame。
所以当我们尝试去改变df2的值的时候,我们会发现:修改view和修改copy,会得到完全不同的结果,因此 pandas就会出现警告。
二、解决办法
1.直接关闭 Warning
直接在pandas关闭warning:
import warnings
import pandas as pd
from pandas.core.common import SettingWithCopyWarning
warnings.simplefilter(action="ignore", category=SettingWithCopyWarning)
df = pd.DataFrame(dict(A=[1, 2, 3], B=[2, 3, 4]))
df[df['A'] > 2]['B'] = 5
2.使用copy来更改
我们先来看一下一般是什么时候出现 SettingWithCopyWarning。下图是 300905.SZ 的 2021年未复权的K线图,可以看到在2021年5月17日之后复权有一个跳空,因此在计算的时候,需要乘上一个复权因子。
我们先把数据取出来看一看
year_lst = ['2021']
data_dic = assistant.ReadDayBarYear(year_lst=year_lst)
stock_name = '300905.SZ'
df = data_dic[stock_name][['Open','High','Low','Close','Volume','Adj_Factor']]
tmp_df = df.loc['20210515':'20210520']
print(tmp_df)
现在对开盘价复权
df['Open'] = df['Open']*df['Adj_Factor']
则此时即会发出警告:
这时候我们再看
tmp_df = df.loc['20210515':'20210520']
print(tmp_df)
可以看到此时的开盘价已经改变
由上面的分析可以看出,由于df是 data_dic 的一个 view 因此我们在改变df的值的时候,pandas 就会疑惑,到底是连data_dic 中的数据也改变呢?还是只改变 df 中的值呢?因此我们让df成为 data_dic 的一个copy,这样子就不会出现 warning 了
import copy
year_lst = ['2021']
data_dic = assistant.ReadDayBarYear(year_lst=year_lst)
stock_name = '300905.SZ'
df = copy.copy(data_dic[stock_name][['Open','High','Low','Close','Volume','Adj_Factor']])
# 此时给价格复权,就不会有警告了
df['Open'] = df['Open']*df['Adj_Factor']
tmp_df = df.loc['20210515':'20210520']
print(tmp_df)
总结
不得不说出现这个warning的时候,下面的一句话很具有迷惑性:
“ Try using .loc[row_indexer,col_indexer] = value instead ”
最开始的时候,还想尝试使用 df.loc 来解决问题,结果怎么搞都不行。
另外在给 DataFrame 的单独的行列赋值的时候,这个问题也会导致出现赋值失败的情况,这个等下一次有机会的时候再讨论吧。