学习 Python 数据分析的同学总是遇到这个警告,查询中文资料,一般只能找到个别的解决办法,不一定适用于自己遇到的情况。查到的最常见解决办法就是直接设置为不显示警告。这实际上并不能解决问题,搜索资料发现这篇英文讲解 SettingWithCopyWarning
原理非常系统的文章,翻译了一下,分享给大家。
太长不看
- 解决方案:学会识别链式索引,不惜一切代价避免使用链式索引
注意:如果你看不懂这里的解决方案,请阅读此文前半部分,直到真正理解如何去做
- 如果要更改原始数据,请使用单一赋值操作(
loc
):data.loc[data.bidder == 'parakeet2004', 'bidderrate'] = 100
- 如果想要一个副本,请确保强制让 Pandas 创建副本:
winners = data.loc[data.bid == data.price].copy() winners.loc[304, 'bidder'] = 'therealname'
- 强烈不推荐直接关闭警告,不过还是提供一下关闭警告的设置方法:
pd.set_option('mode.chained_assignment', None)
- 深度解析底层代码和历史演变(可选阅读)
题图
SettingWithCopyWarning
是人们在学习 Pandas 时遇到的最常见的障碍之一。搜索引擎可以搜索到 Stack Overflow 上的问答、GitHub issues 和一些论坛帖子,分别提供了该警告在某些特定情况下的含义。会有这么多人同样遇到这个警告并不奇怪:有很多方法可以索引 Pandas 数据结构,每种数据结构都有各自的细微差别,甚至 Pandas 本身并不能保证两行代码的运行结果看起来完全相同。
本指南包含了生成警告的原因及解决方案,其中还包括一些底层细节,让你更好地了解代码内部的运行机制,最后提供了有关该话题的一些历史情况,解释代码底层以这样的方式运行的原因。
为了探索 SettingWithCopyWarning
,我们将使用 eBay 3 天拍卖出售的 Xbox 的价格数据集,该数据集出自 Modelling Online Auctions 一书。先来了解下数据的基本结构:
import Pandas as pd
data = pd.read_csv('xbox-3-day-auctions.csv')
data.head()
auctionid | bid | bidtime | bidder | bidderrate | openbid | price | |
---|---|---|---|---|---|---|---|
0 | 8213034705 | 95.0 | 2.927373 | jake7870 | 0 | 95.0 | 117.5 |
1 | 8213034705 | 115.0 | 2.943484 | davidbresler2 | 1 | 95.0 | 117.5 |
2 | 8213034705 | 100.0 | 2.951285 | gladimacowgirl | 58 | 95.0 | 117.5 |
3 | 8213034705 | 117.5 | 2.998947 | daysrus | 10 | 95.0 | 117.5 |
4 | 8213060420 | 2.0 | 0.065266 | donnie4814 | 5 | 1.0 | 120.0 |
如你所见,数据集的每一行都是某一次 eBay Xbox 出价信息。下面是对数据集中每列的简要说明:
auctionid
- 每次拍卖的唯一标识符bid
- 本次拍卖出价bidtime
- 拍卖的时长,以天为单位,从投标开始累计bidder
- 投标人的 eBay 用户名bidderrate
- 投标人的 eBay 用户评级openbid
- 卖方为拍卖设定的开标价price
- 拍卖结束时的中标价
什么是 SettingWithCopyWarning?
首先要理解的是,SettingWithCopyWarning
是一个警告 Warning,而不是错误 Error。
错误表明某些内容是“坏掉”的,例如无效语法(invalid syntax)或尝试引用未定义的变量;警告的作用是提醒编程人员,他们的代码可能存在潜在的错误或问题,但是这些操作在该编程语言中依然合法。在这种情况下,警告很可能表明一个严重但不容易意识到的错误。
SettingWithCopyWarning
告诉你,你的操作可能没有按预期运行,需要检查结果以确保没有出错。
如果代码确实按预期工作,那么我们会很容易忽略该警告,但是 SettingWithCopyWarning
不应该被忽略。在进行下一步操作之前,我们需要花点时间了解这一警告显示的原因。
要了解 SettingWithCopyWarning
,首先要知道,Pandas 中的某些操作会返回数据的视图(View),某些操作会返回数据的副本(Copy)。
View VS Copy
如上所示,左侧的视图 df2
只是原始数据 df1
一个子集,而右侧的副本创建了一个新的对象 df2
。
当我们尝试对数据集进行更改时,这可能会出现问题:
修改视图或副本
根据需求,我们可能想要修改原始 df1
(左),也可能想要修改 df2
(右)。警告让我们知道,代码可能并没有符合需求,修改到的可能并不是我们想要修改的那个数据集。
稍后会深入研究这个问题,但是现在先来了解一下,警告出现的两个主要原因以及对应的解决方案。
链式赋值(Chained Assignment)
当 Pandas 检测到链式赋值(Chained Assignment)时会生成警告。为了方便后续的解释,先来解释一些术语:
- 赋值(Assignment) - 设置某些变量值的操作,例如
data = pd.read_csv('xbox-3-day-auctions.csv')
,有时会将这个操作称之为 设置(Set) 。 - 访问(Access) - 返回某些值的操作,具体参照下方的索引和链式索引示例。有时会将这个操作称之为 获取(Get) 。
- 索引(Indexing) - 任何引用数据子集的赋值或访问方法,例如
data[1:5]
。 - 链式索引(Chaining) - 连续使用多个索引操作,例如
data[1:5][1:3]
。
链式赋值是链式索引和赋值的组合。先快速浏览一下之前加载的数据集,稍后将详细介绍。在这个例子中,假设我们了解到用户 'parakeet2004'
的 bidderrate
值不正确,需要修改这个 bidderrate
值,那么先来查看一下用户 'parakeet2004'
的当前值:
data[data.bidder == 'parakeet2004']
auctionid | bid | bidtime | bidder | bidderrate | openbid | price | |
---|---|---|---|---|---|---|---|
6 | 8213060420 | 3.00 | 0.186539 | parakeet2004 | 5 | 1.0 | 120.0 |
7 | 8213060420 | 10.00 | 0.186690 | parakeet2004 | 5 | 1.0 | 120.0 |
8 | 8213060420 | 24.99 | 0.187049 | parakeet2004 | 5 | 1.0 | 120.0 |
有三行数据需要更新 bidderrate
字段,继续操作:
data[data.bidder == 'parakeet2004']['bidderrate'] = 100
/Library/Frameworks/Python.framework/Versions/36/lib/python3.6/ipykernel/__main__.py:1:SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from aDataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation:http://Pandas.pydata.org/Pandas-docs/stable/indexinghtml#indexing-view-versus-copy
if __name__ == '__main__':
神奇!我们“创造”出了 SettingWithCopyWarning
!
检查一下用户 'parakeet2004'
的相关值,可以看到值没有按预期改变:
data[data.bidder == 'parak