pandas DataFrame的get与set
SettingwithCopyWarning警告
warning实在是一个讨厌的东东,即便我们知道我们的程序没错,但看着黄橙橙的warning实在是没有食欲…
上面的警告来自这段操作:
import pandas as pd
data = pd.DataFrame(data=np.zeros([3,3]), columns=['a', 'b', 'c'])
data.loc[0:1]['a'] = 1
data
Just Look Look
我们来看一下这个警告:
SettingwithCopyWarning:试图对dataframe的切片的拷贝进行赋值。
我们的目的是将data的0,1行,a列赋值为1.
我们来看看结果如何呢,
a | b | c | |
---|---|---|---|
0 | 0.0 | 0.0 | 0.0 |
1 | 0.0 | 0.0 | 0.0 |
2 | 0.0 | 0.0 | 0.0 |
可以看到,上面的代码并没有实现我们想要的效果。
我们来看看pandas官方的解释,
我们知道,上面这种操作方式被称为链式操作(chained []),使用链式[]时,两次操作是顺序发生的,data.loc[0:1]返回的是一个DataFrame,而后对返回的DataFrame进行赋值。
问题在于这种链式操作的结果是不确定的。在复杂的操作中,我们很难确定 [] 操作返回的是view还是copy,这取决于数组在内存中的布局,而这是pandas无法保证的。因此随后的赋值操作是对data本身还是前一 [] 返回的临时拷贝是不确定的。这就是上面的现象发生的原因啦~(参考自pandas官方文档)
怎么解决呢?
知道了这个警告发生的原因以及后果以后,我们来看看这个问题要如何解决。
我们先要过来再赋值会出现问题,那我们为什么不尝试一下直接set呢?
import pandas as pd
data = pd.DataFrame(data=np.zeros([3,3]), columns=['a', 'b', 'c'])
data.loc[0:1, 'a'] = 1
data
a | b | c | |
---|---|---|---|
0 | 1 | 0 | 0 |
1 | 1 | 0 | 0 |
2 | 0 | 0 | 0 |
看看~结果对了呢,而且没有警告哟,我们只是去掉了 ][ ,加了个逗号,问题完美地解决了。
这也是pandas推荐的方法,在赋值时使用单次索引。
data.loc 可以保证指向的是data本身,因此,对 loc 进行get和set针对的是data本身,当然,data.loc[idx]可能是view或者copy。
Get and Set
import pandas as pd
data = pd.DataFrame(data=np.array([[0,0,0],[0,0,0],[0,0,0]]), columns=[['a', 'a', 'b'], [1, 2, '']])
data['a'][1]= 1
data
多级索引中,data[‘a’] 返回一个DataFrame,这个DataFrame是view还是copy呢?所以这里还是会警告哒~
import pandas as pd
data = pd.DataFrame(data=np.array([[0,0,0],[0,0,0],[0,0,0]]), columns=[['a', 'a', 'b'], [1, 2, '']])
data['a', 1]= 1
# or
# data.loc[:, ('a', 1)] = 1
data
正确的姿态应该是这样哟~(分开这是个标点不是取反)
data[‘a’, 1] 或者data.loc[:, (‘a’, 1)] 将多级索引通过一次操作完成赋值。
import pandas as pd
data = pd.DataFrame(data=np.array([[0,0,0],[0,0,0],[0,0,0]]), columns=[['a', 'a', 'b'], [1, 2, '']])
filtered_data = data['a']
filtered_data[1] = 1
filtered_data
这里呢?我们将data[‘a’] 返回的DataFrame赋值给filtered_data,我们试图对新数据进行操作,但pandas还是无法确定filtered_data是view还是copy,即我们无法保证filtered_data是新数据还是原数据,这种操作称为隐式链(hidden chaining),实际相当于data[‘a’][1] = 1,因此,这里还是会警告的。
import pandas as pd
data = pd.DataFrame(data=np.array([[0,0,0],[0,0,0],[0,0,0]]), columns=[['a', 'a', 'b'], [1, 2, '']])
filtered_data = (data['a']).copy()
filtered_data[1] = 1
print(filtered_data)
print(data)
那么我们如何保证我们获取到的是新数据,即拷贝呢,我们可以调用DataFrame的copy方法,获取数据的拷贝,然后再继续我们的操作。
通过上面的几段小例子,我们可以总结如下操作规律:
- 对于作为view的[]操作,我们可以直接取值,并无什么需要注意的。
- 对于要修改原数据的操作,注意不要使用链式操作,因为[]的返回既可能是view,也可能是copy。我们可以一次性从篮子里拿出我们想要的东西进行修改。
- 对于要获取新数据并对新数据进行修改的,保险起见,调用copy方法产生copy,从而在不影响原数据的基础上进行修改。
关闭警告
当我们清楚地知道我们在做什么时,我们可以关闭警告~
pandas的配置”mode.chained_assignment“可以取这些值:
- ‘warn’, 默认哒,就是那个SettingWithCopyWarning警告啦。
- ‘raise’ 不再警告,而是抛出异常让我们处理。
- 完全不再警告,不理你啦哼~
import pandas as pd
pd.set_option('mode.chained_assignment', None)
data = pd.DataFrame(data=np.zeros([3,3]), columns=['a', 'b', 'c'])
data.loc[0:1]['a'] = 1
data
重要的是,当我们关闭警告时,我们一定要知道我们的操作是没有问题的哟~
Something Else
土豆在尝试上面的操作时,发现了一个好玩的事情~
import pandas as pd
data = pd.DataFrame(data=np.zeros([3,3]), columns=['a', 'b', 'c'])
data.loc[0:1]['a'] = 1
data
a | b | c | |
---|---|---|---|
0 | 0.0 | 0.0 | 0.0 |
1 | 0.0 | 0.0 | 0.0 |
2 | 0.0 | 0.0 | 0.0 |
还记得这段吗,对,就是我们之前的警告发出者。
而修改data.loc[0:1][‘a’] = 1变成data.loc[0:1][‘a’] = 1.0呢?
a | b | c | |
---|---|---|---|
0 | 1.0 | 0.0 | 0.0 |
1 | 1.0 | 0.0 | 0.0 |
2 | 0.0 | 0.0 | 0.0 |
虽然警告还在,但结果变成正确的了。
import pandas as pd
data = pd.DataFrame(data=np.zeros([3,3]), columns=['a', 'b', 'c'])
filtered_data = data.loc[0:1]
filtered_data['a'] = 1
print('切片:\n', filtered_data.dtypes)
print('原数据:\n', data.dtypes)
data
切片:
a int64
b float64
c float64
dtype: object
原数据:
a float64
b float64
c float64
dtype: object
我们发现在赋值1时,a列的数据类型发生了变化,土豆猜想是因为1为整形,因数据类型发生变化而重新创建了数组,更进一步,我们来看看此时对其他列赋值。
import pandas as pd
data = pd.DataFrame(data=np.zeros([3,3]), columns=['a', 'b', 'c'])
filtered_data = data.loc[0:1]
filtered_data['b'] = 2.0
filtered_data['a'] = 1
print('切片:\n', filtered_data.dtypes)
print('原数据:\n', data.dtypes)
data
a | b | c | |
---|---|---|---|
0 | 0.0 | 2.0 | 0.0 |
1 | 0.0 | 2.0 | 0.0 |
2 | 0.0 | 0.0 | 0.0 |
我们在a列赋值前对b列赋值为浮点数2.0,可以看到b列的赋值对data同样生效了,而之后对a列赋值为整形没有生效。
import pandas as pd
data = pd.DataFrame(data=np.zeros([3,3]), columns=['a', 'b', 'c'])
filter_data = data.loc[0:1]
filter_data['a'] = 1
filter_data['b'] = 2.0
print('切片:\n', filter_data.dtypes)
print('原数据:\n', data.dtypes)
data
a | b | c | |
---|---|---|---|
0 | 0.0 | 0.0 | 0.0 |
1 | 0.0 | 0.0 | 0.0 |
2 | 0.0 | 0.0 | 0.0 |
我们对换a列和b列的赋值顺序,这次所有的赋值都只对filtered_data起作用而对data无效。
从上面我们基本可以验证我们的结论,那就是这里切片得到应该是一个指向原数据的引用而不是复制,而在数据类型改变时会复制原数组而不再指向原数组。
当然这些还是土豆的猜想,对我们使用panda不会产生影响,我们只需要牢记避免使用链式操作来进行赋值就可以啦~
之后,土豆会仔细阅读pandas源码来弄明白这些问题哒~~~
小结
- 链式操作(chained [])在赋值时应该尽量避免除非我们清晰地知道这样带来的结果是什么。
- 对于view(取值)的操作,链式操作没有任何问题。
- 当我们明确是要对切片或过滤后新数据进行改动而又不想使原数据发生变化时,我们可以使用copy方法强制生成拷贝。
- 当我们仅仅需要对新数据进行改动而不在乎原数据时,可以通过隐式链覆盖原变量而不关心他的死活。
- 总之,对于要改动的数据,牢记永远不要使用链式操作来进行赋值。
没警告就正确了吗?
import pandas as pd
data = pd.DataFrame(data=np.zeros([3,3]), columns=['a', 'b', 'c'])
data.loc[0:1, ['a', 'b']]['a'] = 1
data
a | b | c | |
---|---|---|---|
0 | 0.0 | 0.0 | 0.0 |
1 | 0.0 | 0.0 | 0.0 |
2 | 0.0 | 0.0 | 0.0 |
木有警告~木有警告~木有警告~
我们永远要清楚自己在做什么,而不是根据报错来修改我们的代码,我们要做代码的好朋友,要足够了解他才行呢~
最后
最重要的当然还是在赋值时跟链式操作say no啦
取新数据用copy
view时无所谓,我们开心就好啦
警告可以关,思考不能断
没有警告非常好,小心警惕不能少
时刻牢记这几条,pandas大法用得妙~
扩展阅读:SettingwithCopyWarning: How to Fix This Warning in Pandas
土豆第一次写,如有错误请大家批评指正~
这几天我们的祖国面临了一些问题,我们每一个中国人都有责任为我们的祖国出一份力,少出门,不聚会,出门定要戴口罩,保护自己就是保护祖国~殷忧启圣,多难兴邦,不管眼下中华儿女面临怎样的困难,只要我们一起努力,必将取得最后的胜利,中华民族数千年来曾经经历过无数的苦难,但我们从未倒下!武汉加油!中国加油!当然啦,在家好好学习也是很愉快的呢~我们一起加油!!!