不要对链式索引赋值!趟过了这个坑,才能真正迈入Pandas高手之列

实话实说,我一向不太喜欢Pandas,因为它的功能实在太过强大了,想要熟练地驾驭它,对于我这样的中老年人来说,学习成本偏高。不过,对于接受能力超强的年轻人而言,Pandas确实是数据处理方面不可或缺的利器,我的子侄辈中就有多人喜欢使用。正是因为他们在Pandas的使用过程中,不断地向我咨询问题,我在帮他们解决问题的过程中,也逐渐熟悉了Pandas。这不,今天中午又有问题提出来了,这次是一个非常经典的问题,几乎每个人都会遇到。可以说,学会了解决这个问题,才算真正理解了Pandas。我把这个问题产生的背景、原因和解决方案,尽可能用浅显的文字完整地记录在这里,希望这一篇文章能够成为Pandas的入门读物。更详细的教程,请参考我的另一篇博客《Pandas简明教程》

1. 问题产生的背景

1.1 构造一个测试用的DataFrame

DataFrame是Pandas最核心最常用的数据结构,可以理解为二维的异构表格。所谓异构,是指DataFrame的每一列都可以拥有独立的数据类型,而不必像Numpy的多维数组(ndarray)那样所有元素必须是同一种数据类型。DataFrame的每一列都有一个列名,每一行都有一个索引。

有多种方式可以创建DataFrame对象,将字典数据转换为DataFrame对象是最常见的创建方法,字典的键对应的是DataFrame的列,键名自动称为列名。如果没有指定索引,则使用默认索引(从0开始的连续整数)。

>>> import pandas as pd
>>> data = {
	'华东科技': [1.91, 1.90, 1.86, 1.84],
	'长安汽车': [11.27, 11.14, 11.28, 11.71],
	'西藏矿业': [7.89, 7.79, 7.61, 7.50],
	'重庆啤酒': [50.46, 50.17, 50.28, 50.28]
}
>>> df = pd.DataFrame(data)
>>>> df
   华东科技   长安汽车  西藏矿业   重庆啤酒
0  1.91  11.27  7.89  50.46
1  1.90  11.14  7.79  50.17
2  1.86  11.28  7.61  50.28
3  1.84  11.71  7.50  50.28

1.2 条件检索

Pandas的条件检索非常灵活,下面的代码演示了最常用的几种方式。

>>> df[df.长安汽车 > 11.2] # 检索长安汽车股价大于11.2的所有行
   华东科技   长安汽车  西藏矿业   重庆啤酒
0  1.91  11.27  7.89  50.46
2  1.86  11.28  7.61  50.28
3  1.84  11.71  7.50  50.28
>>> df[(df.长安汽车 > 11.2) & (df.华东科技 < 1.9)] # 检索满足“与”条件的所有行
   华东科技   长安汽车  西藏矿业   重庆啤酒
2  1.86  11.28  7.61  50.28
3  1.84  11.71  7.50  50.28
>>> df[df.西藏矿业.isin([7.61, 7.89])] # 检索西藏矿业股价等于多个指定值的所有行
   华东科技   长安汽车  西藏矿业   重庆啤酒
0  1.91  11.27  7.89  50.46
2  1.86  11.28  7.61  50.28
>>> df[df.index.isin([1,3])] # 检索索引号等于指定值的所有行
   华东科技   长安汽车  西藏矿业   重庆啤酒
1  1.90  11.14  7.79  50.17
3  1.84  11.71  7.50  50.28

Pandas的条件检索,本质上和Numpy是一样的,返回的是布尔型的结果,我们再用这个布尔型的结果去索引,得到了检索结果。

>>> df.长安汽车 > 11.2
0     True
1    False
2     True
3     True
Name: 长安汽车, dtype: bool

同样,使用Numpy的取反符号(~)可以反向选取检索结果。

>>> df[~df.index.isin([1,3])] # 取反
   华东科技   长安汽车  西藏矿业   重庆啤酒
0  1.91  11.27  7.89  50.46
2  1.86  11.28  7.61  50.28

1.3 修改检索到的数据

对于检索到的结果数据,如果想修改的话,比如改成无效值nan(需要提前导入Numpy),一般会写成这样:

>>> import numpy as np
>>> df[~df.index.isin([1,3])].iloc[:,:] = np.nan

然而,这样却是行不通的。有趣的是,这不是一个错误,而是警告。到这里,恭喜你,经典的SettingWithCopyWarning问题终于出现了!解决了它,你就可以步入Pandas高手之列了。

Warning (from warnings module):
  File "C:\Users\xufive\AppData\Local\Programs\Python\Python37\lib\site-packages\pandas\core\indexing.py", line 671
    self._setitem_with_indexer(indexer, value)
SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy

Warning (from warnings module):
  File "__main__", line 1
SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy

2. 原因分析

是使用loc选取数据的方法错误吗?显然不是,因为用loc直接选取df的数据做修改是没有任何问题的。

>>> df.iloc[:,:] = np.nan
>>> df
   华东科技  长安汽车  西藏矿业  重庆啤酒
0   NaN   NaN   NaN   NaN
1   NaN   NaN   NaN   NaN
2   NaN   NaN   NaN   NaN
3   NaN   NaN   NaN   NaN

虽然检索结果看起来也是一个DataFrame,但对检索结果再使用loc选取并修改数据,就出现了问题。原始的DataFrame和作为检索结果的DataFrame有什么不同呢?

原来,这是Pandas的针对链式赋值(Chained Assignment)的保护机制导致的结果。所谓链式赋值,就是对索引的索引结果赋值。当我们使用条件检索时,相当于一次索引,在对这个结果做loc选取,就是二次索引,也就是链式索引,而链式索引在Pandas体系中被禁止赋值。简单理解,就是我们无法对通过两个方括号选取的数据赋值。

3. 解决方案

了解问题产生的原因,就容易找到解决方案了:用检索所条件作为loc的行参数,将两次索引变成一次,自然就没有链式索引,赋值也就不再受限了。以下是完整代码。

>>> import pandas as pd
>>>> import numpy as np
>>> data = {
	'华东科技': [1.91, 1.90, 1.86, 1.84],
	'长安汽车': [11.27, 11.14, 11.28, 11.71],
	'西藏矿业': [7.89, 7.79, 7.61, 7.50],
	'重庆啤酒': [50.46, 50.17, 50.28, 50.28]
}
>>> df = pd.DataFrame(data)
>>> df
   华东科技   长安汽车  西藏矿业   重庆啤酒
0  1.91  11.27  7.89  50.46
1  1.90  11.14  7.79  50.17
2  1.86  11.28  7.61  50.28
3  1.84  11.71  7.50  50.28
>>> df.loc[~df.index.isin([1,3]), :] = np.nan
>>> df
   华东科技   长安汽车  西藏矿业   重庆啤酒
0   NaN    NaN   NaN    NaN
1  1.90  11.14  7.79  50.17
2   NaN    NaN   NaN    NaN
3  1.84  11.71  7.50  50.28

loc的列参数,除了冒号(:)指定全部列,也可以用列名指定单列,或者用元组指定多列。

>>> df = pd.DataFrame(data)
>>> df
   华东科技   长安汽车  西藏矿业   重庆啤酒
0  1.91  11.27  7.89  50.46
1  1.90  11.14  7.79  50.17
2  1.86  11.28  7.61  50.28
3  1.84  11.71  7.50  50.28
>>> df.loc[df.长安汽车 > 11.2, ('华东科技', '西藏矿业', '重庆啤酒')] = np.nan
>>> df
   华东科技   长安汽车  西藏矿业   重庆啤酒
0   NaN  11.27   NaN    NaN
1   1.9  11.14  7.79  50.17
2   NaN  11.28   NaN    NaN
3   NaN  11.71   NaN    NaN

玩转Pandas就是这么简单,你get到了吗?

相关推荐
©️2020 CSDN 皮肤主题: 代码科技 设计师:Amelia_0503 返回首页
实付 15.20元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值