这段时间一直在用pandas,今天运行前人代码发现报了一个warning:
SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
网上查了下,发现有这个问题的人还不少,但是感觉大家都没太说到点上,到底这个错误是如何产生的以及如何避免。不少方法基本上都是想办法绕过这个warning或者直接禁用掉warning提示,这样并不知道其中的原理,只是避而不见,确实不是一个好的变成习惯。后来上google上搜,看到了一个youtube视频和一篇外文Blog解释得还是非常清楚的。Youtube视频解释的比较简略,blog解释的更详细了一些。
现在总结如下:
SettingWithCopyWarning会在什么时候出现呢,简而言之就是在链式赋值的时候出现。
以下例子数据以此为例:
df1 = pd.DataFrame(np.random.random(20).reshape((10,2)), columns=list('AB'))
df1
A | B | |
---|---|---|
0 | 0.407007 | 0.286344 |
1 | 0.140339 | 0.036872 |
2 | 0.450920 | 0.320719 |
3 | 0.783196 | 0.987610 |
4 | 0.011362 | 0.263995 |
5 | 0.968380 | 0.628029 |
6 | 0.465733 | 0.618144 |
7 | 0.441445 | 0.426087 |
8 | 0.831295 | 0.911736 |
9 | 0.447908 | 0.653442 |
链式就是进行多次同类型的操作,比如a = b = c = 4
就是一个链式操作。在这里的链式操作主要是指,对于一个pandas格式的表,选取两次或者以上次数的其中结果。
比如选取其中A值小于0.3的行得到:
df1[df1.A < 0.3]
结果如下:
A | B | |
---|---|---|
1 | 0.140339 | 0.036872 |
4 | 0.011362 | 0.263995 |
那么选取其中所有A<0.3的B列值可以写为:
df1[df1.A < 0.3].B
得到:
1 0.036872
4 0.263995
Name: B, dtype: float64
以上中,先选取左右A<0.3
的行,其次再从中选取B列,上述操作将其分为两部,那么这样就是链式操作。
如果此时要进行:选取其中所有A<0.3
的B列值并将其赋值为1,如果进行:
df1[df1.A < 0.3].B = 1
此时就会报错SettingWithCopyWarning的Warning
如果此时再查看df1里面的值,会发现完全没有改变。
【所以此时这个爆warning是非常有意义的,如果单纯的忽略掉则会导致程序错误。】
根据会提示用loc函数。
用loc函数如下:
df1.loc[df1.A<0.3, 'B'] = 1
运行完后再查看就会发现df1里面的对应着都变为1了。
官方的解释是,pandas这个机制设计如此,凡事出现链式赋值的情况,pandas都是不能够确定到底返回的是一个引用还是一个拷贝。所以遇到这种情况就干脆报warning
有些时候比如将链式给拆解成为多步的时候,就是一些隐式的情况。
比如:
df2 = df1.loc[df1.A<0.3]
df2.loc[1,'B'] = 2
虽然这两步每步都用了loc,但是凡是把取值(GET)操作分为两步的,仍然是链式赋值的状态,所以仍然会报warning。
不过再次查看df2发现df2的值确实已经改变过来了,查看df1的值,发现df1的值没有变。
所以之前那次用loc取出的就是引用,这次就变成拷贝了。也就是说链式赋值是一个要避免的状态。
如果明确说要用拷贝怎么办,就是如下:
df2 = df1.loc[df1.A<0.3].copy()
有些情况下,出现了链式拷贝但是不会报错,所以会出现假阴性【相对应的,也会出现假阳性,即报错了,但是实际上没有链式赋值出现,但是这种一般出现在早起pandas版本中,现在新版本应该不会有了】
比如下面个:
df1.loc[df1.A<0.3, ('A','B')].B = 3
df1
此时没有报warning,但是再查看df1发现仍然没有任何改变。
这里总结一下pandas的这个问题: