对于想了解Python中如何处理Pandas中的SettingWithCopyWarning?的读者,本文将是一篇不可错过的文章,我们将详细介绍pandas.set_option,并且为您提供关于/o
对于想了解Python中如何处理Pandas中的SettingWithCopyWarning?的读者,本文将是一篇不可错过的文章,我们将详细介绍pandas.set_option,并且为您提供关于/opt/conda/lib/python3.6/site-packages/pandas/core/ops.py:816: pandas 处理 NaN、Deep-Learning-With-Python/6_1_4_EDEN.ipynb at master · ChileWang0228/Deep-Learning-With-Python · ...、Pandas 中 SettingwithCopyWarning 的原理和解决方案、Pandas 给了我一个 SettingWithCopyWarning的有价值信息。
本文目录一览:- Python中如何处理Pandas中的SettingWithCopyWarning?(pandas.set_option)
- /opt/conda/lib/python3.6/site-packages/pandas/core/ops.py:816: pandas 处理 NaN
- Deep-Learning-With-Python/6_1_4_EDEN.ipynb at master · ChileWang0228/Deep-Learning-With-Python · ...
- Pandas 中 SettingwithCopyWarning 的原理和解决方案
- Pandas 给了我一个 SettingWithCopyWarning
Python中如何处理Pandas中的SettingWithCopyWarning?(pandas.set_option)
背景
我刚刚将Pandas从0.11升级到0.13.0rc1。现在,该应用程序弹出许多新警告。其中之一是这样的:
E:\FinReporter\FM_EXT.py:449: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.Try using .loc[row_index,col_indexer] = value instead quote_df[''TVol''] = quote_df[''TVol'']/TVOL_SCALE
我想知道到底是什么意思?我需要改变什么吗?
如果我坚持使用该如何警告quote_df[''TVol''] = quote_df[''TVol'']/TVOL_SCALE?
产生错误的函数
def _decode_stock_quote(list_of_150_stk_str): """decode the webpage and return dataframe""" from cStringIO import StringIO str_of_all = "".join(list_of_150_stk_str) quote_df = pd.read_csv(StringIO(str_of_all), sep='','', names=list(''ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg'')) #dtype={''A'': object, ''B'': object, ''C'': np.float64} quote_df.rename(columns={''A'':''STK'', ''B'':''TOpen'', ''C'':''TPCLOSE'', ''D'':''TPrice'', ''E'':''THigh'', ''F'':''TLow'', ''I'':''TVol'', ''J'':''TAmt'', ''e'':''TDate'', ''f'':''TTime''}, inplace=True) quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]] quote_df[''TClose''] = quote_df[''TPrice''] quote_df[''RT''] = 100 * (quote_df[''TPrice'']/quote_df[''TPCLOSE''] - 1) quote_df[''TVol''] = quote_df[''TVol'']/TVOL_SCALE quote_df[''TAmt''] = quote_df[''TAmt'']/TAMT_SCALE quote_df[''STK_ID''] = quote_df[''STK''].str.slice(13,19) quote_df[''STK_Name''] = quote_df[''STK''].str.slice(21,30)#.decode(''gb2312'') quote_df[''TDate''] = quote_df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10]) return quote_df
更多错误讯息
E:\FinReporter\FM_EXT.py:449: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.Try using .loc[row_index,col_indexer] = value instead quote_df[''TVol''] = quote_df[''TVol'']/TVOL_SCALEE:\FinReporter\FM_EXT.py:450: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.Try using .loc[row_index,col_indexer] = value instead quote_df[''TAmt''] = quote_df[''TAmt'']/TAMT_SCALEE:\FinReporter\FM_EXT.py:453: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.Try using .loc[row_index,col_indexer] = value instead quote_df[''TDate''] = quote_df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10])
答案1
小编典典在SettingWithCopyWarning
被创造的标志可能造成混淆的“链接”的任务,比如下面这并不总是如预期的工作,特别是当第一选择返回一个副本。[ 有关背景讨论,请参见GH5390和GH5597。]
df[df[''A''] > 2][''B''] = new_val # new_val not set in df
该警告提出了如下重写建议:
df.loc[df[''A''] > 2, ''B''] = new_val
但是,这不适合您的用法,相当于:
df = df[df[''A''] > 2]df[''B''] = new_val
很明显,您不必在意将其写回到原始帧的写操作(因为您重写了对它的引用),但不幸的是,这种模式无法与第一个链式分配示例区分开,因此(误报)警告。如果您想进一步阅读,可能会在建立索引的文档中解决误报的可能性。您可以通过以下分配安全地禁用此新警告。
pd.options.mode.chained_assignment = None # default=''warn''
/opt/conda/lib/python3.6/site-packages/pandas/core/ops.py:816: pandas 处理 NaN
这里记录一下犯过的及其傻帽的错误!!!!哈哈,无语,同时讨论一下NaN这个数据类型的处理
/opt/conda/lib/python3.6/site-packages/pandas/core/ops.py:816: FutureWarning: elementwise comparison Failed; returning scalar instead,but in the future will perform elementwise comparison result = getattr(x,name)(y)
....................
TypeError: invalid type comparison
这里有一个优惠券的scv表:
import numpy as np
import pandas as pd
dfoff = pd.read_csv("datalab/4901/ccf_offline_stage1_train.csv")
dfofftest = pd.read_csv("datalab/4901/ccf_offline_stage1_test_revised.csv")
dfoff.head()
笔者这里的目的是想统计出 Coupon_id是非NaN(非空)且Date是NaN(空)的用户数(行数)
----------------------------------------------------------------------------------------------------------------------------------------------------------------
一般来说比如我们想筛选出 discount_rate是20:1且distance不是1.0的行数可以这么做:
dfoff.info()
print('数目是:',dfoff[(dfoff['discount_rate']=='20:1')&(dfoff['Date']!=1.0)].shape[0])
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
于是笔者这样做了筛选:
dfoff.info()
print('有优惠券,但是没有使用优惠券购买的客户有',dfoff[(dfoff['Coupon_id']!='NaN')&(dfoff['Date']=='NaN')].shape[0])
结果报错:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1754884 entries,0 to 1754883
Data columns (total 7 columns):
User_id int64
Merchant_id int64
Coupon_id float64
discount_rate object
distance float64
Date_received float64
Date float64
dtypes: float64(4),int64(2),object(1)
memory usage: 93.7+ MB
/opt/conda/lib/python3.6/site-packages/pandas/core/ops.py:816: FutureWarning: elementwise comparison Failed; returning scalar instead,but in the future will perform elementwise comparison
result = getattr(x,name)(y)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-24-c27c94978405> in <module>()
1 dfoff.info()
----> 2 print('有优惠券,但是没有使用优惠券购买的客户有',dfoff[(dfoff['Coupon_id']!='NaN')&(dfoff['Date']=='NaN')].shape[0])
/opt/conda/lib/python3.6/site-packages/pandas/core/ops.py in wrapper(self,other,axis)
877
878 with np.errstate(all='ignore'):
--> 879 res = na_op(values,other)
880 if is_scalar(res):
881 raise TypeError('Could not compare {typ} type with Series'
/opt/conda/lib/python3.6/site-packages/pandas/core/ops.py in na_op(x,y)
816 result = getattr(x,name)(y)
817 if result is NotImplemented:
--> 818 raise TypeError("invalid type comparison")
819 except AttributeError:
820 result = op(x,y)
TypeError: invalid type comparison
其实吧原因很简单,注意看上面笔者故意标红的地方,Coupon_id 和Date的数据类型都是float64,而代码中却用了dfoff['Coupon_id']!='NaN',这不是字符串嘛!!!!!!
print(type('NaN'))
<class 'str'>
float和str比较当然报错了是吧,哎!能这样直接去比较我也算是极品啦哈哈哈
于是可以使用其内置的方法解决:
dfoff.info()
print('有优惠券,但是没有使用优惠券购买的客户有',dfoff[(dfoff['Coupon_id'].notnull())&(dfoff['Date'].isnull())].shape[0])
即使用了如下两个方法
.notnull()
.isnull()
其作用就是判断是否是空值,如果csv中的NaN的地方换成null同样适用
同时这里说一下怎么将NaN替换掉:例如替换成0.0
dfoff['Coupon_id']=dfoff['Coupon_id'].replace(np.nan,0.0)
-----------------------------------------------------------------------------------------------------------------------------------------------------------
下面来说一下NaN这个数据类型,它的全称应该是not a number,说到这里不得不提到另外一个数据类型inf
相同点:都是代表一个无法表示的数
不同点:inf代表无穷大,是一个超过浮点表示范围的浮点数,而NaN可以看成是缺少值或者是无理数
假设现在有一段程序:
def ConvertRate(row):
if row.isnull():
return 0
elif ':' in str(row):
rows = str(row).split(':')
return 1.0-float(rows[1])/float(rows[0])
else:
return float(row)
dfoff['discount_rate'] = dfoff['discount_rate'].apply(ConvertRate)
print(dfoff.head(3))
会发现报错:
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-3-0aa06185ee75> in <module>()
7 else:
8 return float(row)
----> 9 dfoff['discount_rate'] = dfoff['discount_rate'].apply(ConvertRate)
10 print(dfoff.head(3))
/opt/conda/lib/python3.6/site-packages/pandas/core/series.py in apply(self,func,convert_dtype,args,**kwds)
2549 else:
2550 values = self.asobject
-> 2551 mapped = lib.map_infer(values,f,convert=convert_dtype)
2552
2553 if len(mapped) and isinstance(mapped[0],Series):
pandas/_libs/src/inference.pyx in pandas._libs.lib.map_infer()
<ipython-input-3-0aa06185ee75> in ConvertRate(row)
1 def ConvertRate(row):
----> 2 if row.isnull():
3 return 0
4 elif ':' in str(row):
5 rows = str(row).split(':')
AttributeError: 'float' object has no attribute 'isnull'
那它到底是什么数据类型呢?
print(type(np.nan))
print(type(np.inf))
<class 'float'>
<class 'float'>
NaN'就是表示一个普通的字符串,而np.nan就是代表真真的nan,那我们可不可以使用这样:
def ConvertRate(row):
if row==np.nan:
return 0
elif ':' in str(row):
rows = str(row).split(':')
return 1.0-float(rows[1])/float(rows[0])
else:
return float(row)
dfoff['discount_rate'] = dfoff['discount_rate'].apply(ConvertRate)
print(dfoff.head(3))
User_id Merchant_id Coupon_id discount_rate distance Date_received \
0 1439408 2632 NaN NaN 0.0 NaN
1 1439408 4663 11002.0 150:20 1.0 20160528.0
2 1439408 2632 8591.0 20:1 0.0 20160217.0
Date discount_rate
0 20160217.0 NaN
1 NaN 0.866667
2 NaN 0.950000
可以看到这里还是NaN,并不是0,说明还是不对
那试一下:
def ConvertRate(row):
if row==float('NaN'):
return 0
elif ':' in str(row):
rows = str(row).split(':')
return 1.0-float(rows[1])/float(rows[0])
else:
return float(row)
dfoff['discount_rate'] = dfoff['discount_rate'].apply(ConvertRate)
print(dfoff.head(3))
结果还是如上面,其实NaN数据类型就是一种特殊的float,这里相当于强制类型转化
那到底怎么办呢?其实判断是否是NaN可以使用如下方法:
row!=row
如果结果是真,那么就是NaN,假就代表不是NaN
可以看一下结果:
def ConvertRate(row):
if row!=row:
return 0
elif ':' in str(row):
rows = str(row).split(':')
return 1.0-float(rows[1])/float(rows[0])
else:
return float(row)
dfoff['discount_rate'] = dfoff['discount_rate'].apply(ConvertRate)
print(dfoff.head(3))
print(dfoff.head(3))
User_id Merchant_id Coupon_id discount_rate distance Date_received \
0 1439408 2632 NaN NaN 0.0 NaN
1 1439408 4663 11002.0 150:20 1.0 20160528.0
2 1439408 2632 8591.0 20:1 0.0 20160217.0
Date discount_rate
0 20160217.0 0.000000
1 NaN 0.866667
2 NaN 0.950000
于是笔者最开始的那个问题也可以这样解决:
print('有优惠券,但是没有使用优惠券购买的客户有',dfoff[(dfoff['Coupon_id']==dfoff['Coupon_id'])&(dfoff['Date']!=dfoff['Date'])].shape[0])
有优惠券,但是没有使用优惠券购买的客户有 977900
---------------------------------------------------------------------------------------------------------------------------------------------------------------
有时候在使用apply的时候会报错,所以最好加一下:axis = 1意思是按列处理的
对应到上面就是吧:
dfoff['discount_rate'] = dfoff['discount_rate'].apply(ConvertRate)
改为:
dfoff['discount_rate'] = dfoff['discount_rate'].apply(ConvertRate,axis = 1)
------------------------------------------------------------------------------------------------------------------------------------------------------------
所以最后总结一下:
NaN和inf都是一种特殊的float数据类型
可以使用row!=row类似的形式来判断是否是NaN,如果是真就代表是NaN,假就代表不是NaN,换句话说也可以使用row==row来判断是否是NaN,只不过逻辑相反而已
报错记得加axis = 1
------------------------------------------------------------------------------------------------------------------------------------------------------
在使用pands加载数据的时候,其实我们是可以控制数据类型的,比如让缺省值变为null,而不是NAN,即让字段的数据类型不再是float,而是object,这里有一个例子:https://blog.csdn.net/weixin_42001089/article/details/85013073
Deep-Learning-With-Python/6_1_4_EDEN.ipynb at master · ChileWang0228/Deep-Learning-With-Python · ...
Pandas 中 SettingwithCopyWarning 的原理和解决方案
原文链接:https://www.dataquest.io/blog/settingwithcopywarning/
原文标题:Understanding SettingwithCopyWarning in pandas
原文发布时间:5 JULY 2017(需要注意时效性,文中有一些方法已经弃用,比如 ix
)
作者:Benjamin Pryke
译者:Ivy Lee
学习 Python 数据分析的同学总是遇到这个警告,查询中文资料,一般只能找到个别的解决办法,不一定适用于自己遇到的情况。查到的最常见解决办法就是直接设置为不显示警告。搜索资料发现这篇英文讲解 SettingWithCopyWarning
原理非常系统的文章,翻译了一下,分享给大家。
SettingWithCopyWarning
是人们在学习 Pandas 时遇到的最常见的障碍之一。快速的网络搜索可以搜索到 Stack Overflow 问题,GitHub issues 和程序员的论坛帖子,试图解释这个警告在他们的特定情况下意味着什么。这么多人为此困扰并不奇怪:有很多方法可以索引 Pandas 数据结构,每种数据结构都有自己独特的细微差别,甚至 Pandas 本身并不能保证两行代码的运行结果看起来完全相同。
本指南解释了生成警告的原因并展示了如何解决这一警告。它还包括一些底层的细节,让你更好地了解代码内部发生了什么,提供了有关该话题的一些历史记录,让你了解为什么代码底层以这样的方式运作。
为了探索 SettingWithCopyWarning
,我们将使用 Modelling Online Auctions 一书中的 eBay 3 天拍卖出售的 Xbox 的价格数据集。让我们来看看:
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
是一个警告,而不是错误 Error。
错误表明某些内容是 “坏掉” 的,例如无效语法(invalid syntax)或尝试引用未定义的变量。警告的作用是提醒程序员,他们的代码可能存在潜在的错误或问题,但是这些操作仍然是该编程语言中的合法操作。在这种情况下,警告很可能表明一个严重但不容易意识到的错误。
SettingWithCopyWarning
告诉你,你的操作可能没有按预期运行,你应该检查结果以确保没有出错。
如果你的代码仍然按预期工作,那么很容易忽略警告。这不是良好的实践,SettingWithCopyWarning
不应该被忽略。在采取下一步行动之前,花点时间了解为什么会获得这一警告。
要了解 SettingWithCopyWarning
,首先需要了解 Pandas 中的某些操作可以返回数据的视图(View),而某些其他操作将返回数据的副本(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,首先,查看一下当前的值。
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
!
如果检查一下,可以看到在这种情况下,值没有按预期改变:
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
、.loc[]
、.iloc[]
、.ix[]
,也是如此,我们的链式操作是:
data[data.bidder == ''parakeet2004'']
[''bidderrate''] = 100
这两个链式操作一个接一个地独立执行。第一次是访问操作(get),返回一个 DataFrame
,其中包含所有 bidder
等于 ''parakeet2004''
的行。第二个是赋值操作(set),是在这个新的 DataFrame
上运行的,我们压根没有在原始 DataFrame
上运行。
这个解决方案很简单:使用 loc
将链式操作组合到一个操作中,以便 Pandas 可以确保 set 的是原始 DataFrame
。Pandas 会始终确保下面这样的非链式 set 操作起作用。
# 设置新值
data.loc[data.bidder == ''parakeet2004'', ''bidderrate''] = 100
# 检查结果 data[data.bidder == ''parakeet2004''][''bidderrate''] 6 100 7 100 8 100 Name: bidderrate, dtype: int64
这就是警告中建议我们做的操作,在这种情况下它完美地适用。
隐蔽的链式操作(Hidden chaining)
现在来看一下遇到 SettingWithCopyWarning
的第二种最常见的方式。我们来探索中标者的数据,我们将为此创建一个新的 DataFrame,现在已经学习了关于链式赋值的内容,因此请注意使用 loc
。
winners = data.loc[data.bid == data.price]
winners.head()
auctionid | bid | bidtime | bidder | bidderrate | openbid | price | |
---|---|---|---|---|---|---|---|
3 | 8213034705 | 117.5 | 2.998947 | daysrus | 10 | 95.00 | 117.5 |
25 | 8213060420 | 120.0 | 2.999722 | djnoeproductions | 17 | 1.00 | 120.0 |
44 | 8213067838 | 132.5 | 2.996632 | * champaignbubbles* |
202 | 29.99 | 132.5 |
45 | 8213067838 | 132.5 | 2.997789 | * champaignbubbles* |
202 | 29.99 | 132.5 |
66 | 8213073509 | 114.5 | 2.999236 | rr6kids | 4 | 1.00 | 114.5 |
我们可能会使用 winners
变量编写一些后续的代码行。
mean_win_time = winners.bidtime.mean()
... # 20 lines of code
mode_open_bid = winners.openbid.mode()
偶然的机会,我们在该 DataFrame
发现了另一个错误:标记为 304
的行中缺少了 bidder
值。
winners.loc[304, ''bidder'']
nan
对这个例子来说,假设我们知道这个投标人的真实用户名,并以此更新数据:
winners.loc[304, ''bidder''] = ''therealname''
/Library/Frameworks/Python.framework/Versions/36/lib/python3.6/Pandas/core/indexing.py:517: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/indexing.html#indexing-view-versus-copy
self.obj[item] = s
另一个 SettingWithCopyWarning
!但我们使用了 loc
,这又是怎么回事?为了研究这一点,我们来看看代码的结果:
print(winners.loc[304, ''bidder''])
therealname
代码按预期工作了,为什么我们还是得到警告?
链式索引可能跨越两行代码发生,也可能在一行代码内发生。因为 winners
是作为 get 操作的输出创建的(data.loc[data.bid == data.price]
),它可能是原始 DataFrame
的副本,也可能不是,但除非我们检查,否则我们不能了解到。当我们对 winners
进行索引时,我们实际上使用的是链式索引。
这意味着当我们尝试修改 winners
时,我们可能也修改了 data
。
在实际的代码中,这些行可能会跨越很大的距离,因此追踪问题可能会更困难,但情况是与示例类似的。
为了防止这种情况下的警告,解决方案是在创建新 DataFrame 时明确告知 Pandas 制作一个副本:
winners = data.loc[data.bid == data.price].copy()
winners.loc[304, ''bidder''] = ''therealname''
print(winners.loc[304, ''bidder'']) print(data.loc[304, ''bidder'']) therealname nan
就这么简单!
窍门就是,学会识别链式索引,不惜一切代价避免使用链式索引。如果要更改原始数据,请使用单一赋值操作。如果你想要一个副本,请确保你强制让 Pandas 制作副本。这样可以节省时间,也可以使代码保持严密的逻辑。
另外请注意,即使 SettingWithCopyWarning
只在你进行 set 时才会发生,但在进行 get 操作时,最好也避免使用链式索引。链式操作较慢,而且只要你稍后决定进行赋值操作,就会导致问题。
处理 SettingWithCopyWarning 的提示和技巧
在我们进行下面更深入的分析之前,让我们 “拿出显微镜”,看看 SettingWithCopyWarning
的更多细节。
关闭警告
首先,如果不讨论如何明确地控制 SettingWithCopy
设置,那么本文则不完整。Pandas 的 mode.chained_assignment
选项可以采用以下几个值之一:
''raise''
- 抛出异常(exception)而不是警告''warn''
- 生成警告(默认)None
- 完全关闭警告
例如,如果要关闭警告:
pd.set_option(''mode.chained_assignment'', None)
data[data.bidder == ''parakeet2004''][''bidderrate''] = 100
因为这样没有给我们任何警告,除非你完全了解自己在做什么,否则不建议这样做。如果你对想要实现的操作有任何一丁点的疑问,关闭警告都不被推荐。有些开发者非常重视 SettingWithCopy
甚至选择将其提升为异常,如下所示:
pd.set_option(''mode.chained_assignment'', ''raise'')
data[data.bidder == ''parakeet2004''][''bidderrate''] = 100
---------------------------------------------------------------------------
SettingWithCopyError Traceback (most recent call last)
<ipython-input-13-80e3669cab86> in <module>()
1 pd.set_option(''mode.chained_assignment'', ''raise'')
----> 2 data[data.bidder == ''parakeet2004''][''bidderrate''] = 100
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/Pandas/core/frame.py in __setitem__(self, key, value)
2427 else:
2428 # set column
-> 2429 self._set_item(key, value) 2430 2431 def _setitem_slice(self, key, value): /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/Pandas/core/frame.py in _set_item(self, key, value) 2500 # value exception to occur first 2501 if len(self): -> 2502 self._check_setitem_copy() 2503 2504 def insert(self, loc, column, value, allow_duplicates=False): /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/Pandas/core/generic.py in _check_setitem_copy(self, stacklevel, t, force) 1758 1759 if value == ''raise'': -> 1760 raise SettingWithCopyError(t) 1761 elif value == ''warn'': 1762 warnings.warn(t, SettingWithCopyWarning, stacklevel=stacklevel) SettingWithCopyError: 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 See the caveats in the documentation: http://Pandas.pydata.org/Pandas-docs/stable/indexing.html#indexing-view-versus-copy
如果你正在与缺乏经验的 Pandas 开发人员合作开发项目,或者正在开发需要高度严谨的项目,这可能特别有用。
使用此设置的更精确方法是使用 上下文管理器 context manager 。
# resets the option we set in the previous code segment
pd.reset_option(''mode.chained_assignment'')
with pd.option_context(''mode.chained_assignment'', None): data[data.bidder == ''parakeet2004''][''bidderrate''] = 100
正如你所看到的,这种方法可以实现针对性的警告设置,而不是影响整个环境。
is_copy 属性
避免警告的另一个技巧是修改 Pandas 用于解释 SettingWithCopy
的工具之一。每个 DataFrame
都有一个 is_copy
属性,默认情况下为 None
,但如果它是副本,则会使用 weakref
引用原始 DataFrame
。通过将 is_copy
设置为 None
,可以避免生成警告。
winners = data.loc[data.bid == data.price]
winners.is_copy = None
winners.loc[304, ''bidder''] = ''therealname''
但是请注意,这并不会奇迹般地解决问题,反而会使错误检测变得非常困难。
单类型 VS 多类型对象
值得强调的另一点是单类型对象和多类型对象之间的差异。如果 DataFrame
所有列都具有相同的 dtype,则它是单类型的,例如:
import numpy as np
single_dtype_df = pd.DataFrame(np.random.rand(5,2), columns=list(''AB'')) print(single_dtype_df.dtypes) single_dtype_df A float64 B float64 dtype: object
A | B | |
---|---|---|
0 | 0.383197 | 0.895652 |
1 | 0.077943 | 0.905245 |
2 | 0.452151 | 0.677482 |
3 | 0.533288 | 0.768252 |
4 | 0.389799 | 0.674594 |
如果 DataFrame
的列不是全部具有相同的 dtype,那么它是多类型的,例如:
multiple_dtype_df = pd.DataFrame({''A'': np.random.rand(5),''B'': list(''abcde'')})
print(multiple_dtype_df.dtypes)
multiple_dtype_df
A float64
B object
dtype: object
A | B | |
---|---|---|
0 | 0.615487 | a |
1 | 0.946149 | b |
2 | 0.701231 | c |
3 | 0.756522 | d |
4 | 0.481719 | e |
由于下面历史部分中所述的原因,对多类型对象的索引 get 操作将始终返回副本。然而,为了提高效率,索引器对单类型对象的操作几乎总是返回一个视图,这里需要注意的是,这取决于对象的内存布局,并不能完全保证。
误报
误报,即无意中报告链式赋值的情况,曾经在早期版本的 Pandas 中比较常见,但此后大部分都被解决了。为了完整起见,在此处包括一些已修复的误报示例也是有用的。如果你在使用早期版本的 Pandas 时遇到以下任何情况,则可以安全地忽略或抑制警告(或通过升级完全避免警告!)
使用当前列的值,将新列添加到 DataFrame
会生成警告,但这已得到修复。
data[''bidtime_hours''] = data.bidtime.map(lambda x: x * 24)
data.head(2)
auctionid | bid | bidtime | bidder | bidderrate | openbid | price | bidtime_hours | |
---|---|---|---|---|---|---|---|---|
0 | 8213034705 | 95.0 | 2.927373 | jake7870 | 0 | 95.0 | 117.5 | 70.256952 |
1 | 8213034705 | 115.0 | 2.943484 | davidbresler2 | 1 | 95.0 | 117.5 | 70.643616 |
当在一个 DataFrame
切片上使用 apply
方法进行设置时,也会出现误报,不过这也已得到修复。
data.loc[:, ''bidtime_hours''] = data.bidtime.apply(lambda x: x * 24)
data.head(2)
auctionid | bid | bidtime | bidder | bidderrate | openbid | price | bidtime_hours | |
---|---|---|---|---|---|---|---|---|
0 | 8213034705 | 95.0 | 2.927373 | jake7870 | 0 | 95.0 | 117.5 | 70.256952 |
1 | 8213034705 | 115.0 | 2.943484 | davidbresler2 | 1 | 95.0 | 117.5 | 70.643616 |
最后,直到 0.17.0 版本前,DataFrame.sample
方法中存在一个错误,导致 SettingWithCopy
警告误报。现在,sample
方法每次都会返回一个副本。
sample = data.sample(2)
sample.loc[:, ''price''] = 120
sample.head()
auctionid | bid | bidtime | bidder | bidderrate | openbid | price | bidtime_hours | |
---|---|---|---|---|---|---|---|---|
481 | 8215408023 | 91.01 | 2.990741 | sailer4eva | 1 | 0.99 | 120 | 71.777784 |
503 | 8215571039 | 100.00 | 1.965463 | lambonius1 | 0 | 50.00 | 120 | 47.171112 |
链式赋值深度解析
让我们重用之前的例子:试图更新 data
中 bidder
值为 ''parakeet2004''
的所有行的 bidderrate
字段。
data[data.bidder == ''parakeet2004''][''bidderrate''] = 100
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/ipykernel/__main__.py:1: 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
See the caveats in the documentation: http://Pandas.pydata.org/Pandas-docs/stable/indexing.html#indexing-view-versus-copy
if __name__ == ''__main__'':
Pandas 用这个 SettingWithCopyWarning
告诉我们的是,代码的行为是模棱两可的,但要理解为什么这样做以及警告的措辞,以下概念将会有所帮助。
我们之前简要地谈过了视图(View)和副本(Copy)。有两种方法可以访问 DataFrame
的子集:可以创建对内存中原始数据的引用(视图),也可以将子集复制到新的较小的 DataFrame
中(副本)。视图是查看 原始 数据特定部分的一种方式,而副本是将该数据克隆到内存中的新位置。正如我们之前的图表所示,修改视图将修改原始变量,但修改副本则不会。
由于某些我们将在稍后介绍的原因,Pandas 中 get 操作的输出无法保证。索引 Pandas 数据结构时,视图或副本都可能被返回,这意味着对某一 DataFrame
进行 get 操作返回一个新的 DataFrame
,这个新的数据可能是:
- 来自原始对象的数据副本。
- 没有复制,而是直接对原始对象的引用。
因为我们不知道将会发生什么,并且每种可能性都有非常不同的行为,所以忽略警告就是 “玩火”。
为了更清楚地解释视图、副本和其中的歧义,让我们创建一个简单的 DataFrame
并对其进行索引:
df1 = pd.DataFrame(np.arange(6).reshape((3,2)), columns=list(''AB''))
df1
A | B | |
---|---|---|
0 | 0 | 1 |
1 | 2 | 3 |
2 | 4 | 5 |
将 df1
的子集赋值给 df2
:
df2 = df1.loc[:1]
df2
A | B | |
---|---|---|
0 | 0 | 1 |
1 | 2 | 3 |
根据刚才学到的知识,我们知道 df2
可能是 df1
的视图或 df1
子集的副本。
在解决问题之前,我们还需要再看一下链式索引。扩展一下 ''parakeet2004''
示例,我们将两个索引操作链接在一起:
data[data.bidder == ''parakeet2004'']
__intermediate__[''bidderrate''] = 100
__intermediate__
表示第一个调用的输出,对我们是完全不可见的。请记住,如果我们使用了属性访问,会得到相同的有问题的结果:
data[data.bidder == ''parakeet2004''].bidderrate = 100
这同样适用于任何其他形式的链式调用,因为我们正在生成中间对象 。
在底层代码中,链式索引意味着对 __getitem__
或 __setitem__
进行多次调用以完成单个操作。这些是 特殊的 Python 方法,通过在实现它们的类的实例上使用方括号,可以调用这些方法,这是语法糖的一种示例。让我们看一下 Python 解释器如何执行我们示例中的内容。
# Our code
data[data.bidder == ''parakeet2004''][''bidderrate''] = 100
# Code executed data.__getitem__(data.__getitem__(''bidder'') == ''parakeet2004'').__setitem__(''bidderrate'', 100)
正如你可能已经意识到的那样,SettingWithCopyWarning
是由此链式 __setitem__
调用生成的。你可以自己尝试一下 - 上面这些代码的功能相同。为清楚起见,请注意第二个 __getitem__
调用(对 bidder
列)是嵌套的,而不是链式问题的所有部分。
通常,如上面所述,Pandas 不保证 get 操作是返回视图还是副本。如果在我们的示例中返回了一个视图,则链式赋值中的第二个表达式将是对原始对象 __setitem__
的调用。但是,如果返回一个副本,那么将被修改的是副本 - 原始对象不会被修改。
这就是警告中 “a value is trying to be set on a copy of a slice from a DataFrame” 的含义。由于没有对此副本的引用,它最终将被回收 。SettingWithCopyWarning
让我们知道 Pandas 无法确定第一个 __getitem__
调用是否返回了视图或副本,因此不清楚该赋值是否更改了原始对象。换一种说法就是:“我们是否正在修改原始数据?” 这一问题的答案是未知的。
如果我们确实想要修改原始文件,警告建议的解决方案是使用 loc
将这两个单独的链式操作转换为单个赋值操作。这样我们的代码中没有了链式索引,就不会再收到警告。我们修改后的代码及其扩展版本如下所示:
# Our code
data.loc[data.bidder == ''parakeet2004'', ''bidderrate''] = 100
# Code executed data.loc.__setitem__((data.__getitem__(''bidder'') == ''parakeet2004'', ''bidderrate''), 100)
DataFrame 的 loc
属性保证是原始 DataFrame
本身,具有扩展的索引功能。
假阴性(False negatives)
使用 loc
并没有结束我们的问题,因为使用 loc
的 get 操作仍然可以返回一个视图或副本。让我们快速过一下,一个有点复杂的例子。
data.loc[data.bidder == ''parakeet2004'', (''bidderrate'', ''bid'')]
bidderrate | bid | |
---|---|---|
6 | 100 | 3.00 |
7 | 100 | 10.00 |
8 | 100 | 24.99 |
我们这次拉出了两列而不是一列。让我们尝试 set 所有的 bid
值。
data.loc[data.bidder == ''parakeet2004'', (''bidderrate'', ''bid'')][''bid''] = 5.0 data.loc[data.bidder == ''parakeet2004'', (''bidderrate'', ''bid'')]
bidderrate | bid | |
---|---|---|
6 | 100 | 3.00 |
7 | 100 | 10.00 |
8 | 100 | 24.99 |
没有效果,也没有警告!我们在切片的副本上 set 了一个值但是 Pandas 没有检测到它 - 这就是假阴性。这是因为,使用 loc
之后并不意味着我们可以再次使用链式赋值。这个特定的 bug,有一个未解决的 GitHub issue 。
正确的解决方法如下:
data.loc[data.bidder == ''parakeet2004'', ''bid''] = 5.0
data.loc[data.bidder == ''parakeet2004'', (''bidderrate'', ''bid'')]
bidderrate | bid | |
---|---|---|
6 | 100 | 5 |
7 | 100 | 5 |
8 | 100 | 5 |
你可能怀疑,是否有人会在实践中遇到这样的问题。其实这比你想象的更容易出现。当我们像下一节中这样做:将 DataFrame
查询的结果赋值给变量。
隐藏的链式索引
让我们再看一下之前隐藏的链式索引示例,我们试图设置 winners
变量中,标记为 304
行的 bidder
字段。
winners = data.loc[data.bid == data.price]
winners.loc[304, ''bidder''] = ''therealname''
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/Pandas/core/indexing.py:517: 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
See the caveats in the documentation: http://Pandas.pydata.org/Pandas-docs/stable/indexing.html#indexing-view-versus-copy
self.obj[item] = s
我们得到了另一个 SettingWithCopyWarning
尽管我们使用了 loc
。这个问题可能令人非常困惑,因为警告信息建议我们的方法,我们已经做过了。
不过,想一下 winners
变量。它究竟是什么?由于我们通过 data.loc[data.bid == data.price]
将它初始化,我们无法知道它是原始 data
DataFrame
的视图还是副本(因为 get 操作返回视图或副本)。将初始化与生成警告的行组合在一起可以清楚地表明我们的错误。
data.loc[data.bid == data.price].loc[304, ''bidder''] = ''therealname''
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/Pandas/core/indexing.py:517: 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
See the caveats in the documentation: http://Pandas.pydata.org/Pandas-docs/stable/indexing.html#indexing-view-versus-copy
self.obj[item] = s
我们再次使用了链式赋值,只是这次它被分在了两行代码中。思考这个问题的另一种方法是,问一个问题 “这个操作会修改一个对象,还是两个对象?” 在我们的示例中,答案是未知的:如果 winners
是副本,那么只有 winners
受到影响,但如果是视图,则 winners
和 data
都将被更新。这种情况可能发生在脚本或代码库中相距很远的行之间,这使问题很难被追根溯源。
此处警告的意图是让我们意识到,我们以为代码将修改原始 DataFrame
,实际没有修改成功,或者说我们将修改副本而不是原始数据。深入研究 Pandas GitHub repo 中的 issue,你可以看到开发人员自己对这个问题的解释。
如何解决这个问题在很大程度上取决于我们自己的意图。如果我们想要使用原始数据的副本,解决方案就是强制 Pandas 制作副本。
winners = data.loc[data.bid == data.price].copy()
winners.loc[304, ''bidder''] = ''therealname''
print(data.loc[304, ''bidder'']) # Original print(winners.loc[304, ''bidder'']) # Copy nan therealname
另一方面,如果你需要更新原始 DataFrame
,那么你应该使用原始 DataFrame
而不是重新赋值一些具有未知行为的其他变量。我们之前的代码将修改为:
# Finding the winners
winner_mask = data.bid == data.price
# Taking a peek
data.loc[winner_mask].head()
# Doing analysis
mean_win_time = data.loc[winner_mask, ''bidtime''].mean()
... # 20 lines of code mode_open_bid = data.loc[winner_mask, ''openbid''].mode() # Updating the username data.loc[304, ''bidder''] = ''therealname''
在更复杂的情况下,例如修改 DataFrame
子集的子集,不要使用链式索引,可以在原始 DataFrame
上通过 loc
进行修改。例如,你可以更改上面的新 winner_mask
变量或创建一个选择中标者子集的新变量,如下所示:
high_winner_mask = winner_mask & (data.price > 150)
data.loc[high_winner_mask].head()
auctionid | bid | bidtime | bidder | bidderrate | openbid | price | bidtime_hours | |
---|---|---|---|---|---|---|---|---|
225 | 8213387444 | 152.0 | 2.919757 | uconnbabydoll1975 | 15 | 0.99 | 152.0 | 70.074168 |
328 | 8213935134 | 207.5 | 2.983542 | toby2492 | 0 | 0.10 | 207.5 | 71.605008 |
416 | 8214430396 | 199.0 | 2.990463 | volpendesta | 4 | 9.99 | 199.0 | 71.771112 |
531 | 8215582227 | 152.5 | 2.999664 | ultimatum_man | 2 | 60.00 | 152.5 | 71.991936 |
这种技术会使得未来的代码库维护和扩展更加稳健。
历史
你可能想知道为什么要造成这么混乱的现状,为什么不明确指定索引方法是返回视图还是副本,来完全避免 SettingWithCopy
问题。要理解这一点,我们必须研究 Pandas 的过去。
Pandas 确定返回一个视图还是一个副本的逻辑,源于它对 NumPy 库的使用,这是 Pandas 库的基础。视图实际上是通过 NumPy 进入 Pandas 的词库的。实际上,视图在 NumPy 中很有用,因为它们能够可预测地返回。由于 NumPy 数组是单一类型的,因此 Pandas 尝试使用最合适的 dtype 来最小化内存处理需求。因此,包含单个 dtype 的 DataFrame
切片可以作为单个 NumPy 数组的视图返回,这是一种高效处理方法。但是,多类型的切片不能以相同的方式存储在 NumPy 中。Pandas 兼顾多种索引功能,并且保持高效地使用其 NumPy 内核的能力。
最终,Pandas 中的索引被设计为有用且通用的方式,其核心并不完全与底层 NumPy 数组的功能相结合。随着时间的推移,这些设计和功能元素之间的相互作用,导致了一组复杂的规则,这些规则决定了返回视图还是副本。经验丰富的 Pandas 开发者通常都很满意 Pandas 的做法,因为他们可以轻松地浏览其索引行为。
不幸的是,对于 Pandas 的新手来说,链式索引几乎是不可避免的,因为 get 操作返回的就是可索引的 Pandas 对象。此外,用 Pandas 的核心开发人员之一 Jeff Reback 的话来说,“从语言的角度来看,直接检测链式索引是不可能的,必须经过推断才能了解”。
因此,在 2013 年底的 0.13.0 版本中引入了警告,作为许多开发者遇到链式赋值导致的无声失败的解决方案。
在 0.12 版本之前,ix
索引器是最受欢迎的(在 Pandas 术语中,“索引器” 比如 ix
,loc
和 iloc
,是一种简单的结构,允许使用方括号来索引对象,就像数组一样,但具有一些特殊的用法)。但是大约在 2013 年中 ,Pandas 项目开始意识到日益增加的新手用户的重要性,有动力开始提高新手用户的使用体验。自从此版本发布以来,loc
和 iloc
索引器因其更明确的性质和更易于解释的用法而受到青睐。(译者注:pandas v0.23.3 (July 7, 2018),其中 ix
方法已经被弃用)
SettingWithCopyWarning
在推出后持续改进,多年来在许多 GitHub issue 中得到了热烈的讨论 ,甚至还在不断更新 ,但是要理解它,仍然是成为 Pandas 专家的关键。
总结
SettingWithCopyWarning
的基础复杂性是 Pandas 库中为数不多的坑。这个警告的源头深深嵌在库的底层中,不应被忽视。Jeff Reback 自己的话 ,“我没有找到任何你应该忽略这个警告的情况。如果你做某些类型的索引时不起作用,而其他情况下起作用,你是在玩火。”
幸运的是,解决警告只需要识别链式赋值并修复。如果整篇文章你只了解到了一件事,那么就应该是这一点。
作者:笨熊不紧张
链接:https://www.jianshu.com/p/72274ccb647a
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
Pandas 给了我一个 SettingWithCopyWarning
检查代码中df创建的位置。
最有可能的是,它是另一个 DataFrame 的视图,例如:
df = df_src[...]
然后任何试图在 df 中保存某些东西都会导致这个警告。
为了避免它,创建df作为一个真正独立的DataFrame,它的 自己的数据缓冲区。类似的东西:
df = df_src[...].copy()
现在 df 有自己的数据缓冲区,可以在没有 以上警告。
,如果您打算稍后在代码中使用相同的 df,那么在进行任何迭代之前创建 df 的深层副本有时会很有用。
Pandas 的原生 copy
方法并不总是像人们预期的那样——这里有一个类似的 question 可能会提供更多见解。
您可以使用python自带的copy
模块来复制整个对象并确保2个数据帧之间没有链接。
import copy
df_copy = copy.deepcopy(df)
关于Python中如何处理Pandas中的SettingWithCopyWarning?和pandas.set_option的介绍已经告一段落,感谢您的耐心阅读,如果想了解更多关于/opt/conda/lib/python3.6/site-packages/pandas/core/ops.py:816: pandas 处理 NaN、Deep-Learning-With-Python/6_1_4_EDEN.ipynb at master · ChileWang0228/Deep-Learning-With-Python · ...、Pandas 中 SettingwithCopyWarning 的原理和解决方案、Pandas 给了我一个 SettingWithCopyWarning的相关信息,请在本站寻找。
本文标签: