How to deal with SettingWithCopyWarning in Pandas?
我刚把我的熊猫从0.11升级到0.13.0rc1。现在,应用程序弹出了许多新的警告。其中一个是这样的:
1 2 3 | 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 |
我想知道它到底是什么意思?我需要换点什么吗?
如果我坚持使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 |
更多错误消息
1 2 3 4 5 6 7 8 9 | 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 E:\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_SCALE E:\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 | df[df['A'] > 2]['B'] = new_val # new_val not set in df |
警告提供了如下重写建议:
1 | df.loc[df['A'] > 2, 'B'] = new_val |
但是,这不适合您的使用,相当于:
1 2 | df = df[df['A'] > 2] df['B'] = new_val |
虽然很明显,您不关心将其写回原始帧(因为您重写了对它的引用),但不幸的是,此模式无法与第一个链接分配示例区分开来,因此出现(误报)警告。如果您想进一步阅读,可以在索引文档中解决误报的可能性。您可以通过以下分配安全地禁用此新警告。
1 | pd.options.mode.chained_assignment = None # default='warn' |
一般来说,
下面是另一个选项:
1 2 3 4 5 6 7 8 9 10 11 | In [1]: df = DataFrame(np.random.randn(5, 2), columns=list('AB')) In [2]: dfa = df.ix[:, [1, 0]] In [3]: dfa.is_copy Out[3]: True In [4]: dfa['A'] /= 2 /usr/local/bin/ipython:1: 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 #!/usr/local/bin/python |
您可以将
1 2 3 | In [5]: dfa.is_copy = False In [6]: dfa['A'] /= 2 |
如果显式复制,则不会出现进一步的警告:
1 2 3 | In [7]: dfa = df.ix[:, [1, 0]].copy() In [8]: dfa['A'] /= 2 |
上面显示的代码虽然是合法的,而且可能也是我所做的,但从技术上讲,这是一个警告,而不是一个误报。另一种没有警告的方法是通过
1 | quote_df = quote_df.reindex(columns=['STK', ...]) |
或者,
1 | quote_df = quote_df.reindex(['STK', ...], axis=1) # v.0.21 |
How to deal with
SettingWithCopyWarning in Pandas?
这篇文章是为那些,好的。
安装程序好的。
1 2 3 4 5 6 7 | np.random.seed(0) df = pd.DataFrame(np.random.choice(10, (3, 5)), columns=list('ABCDE')) df A B C D E 0 5 0 3 3 7 1 9 3 5 2 4 2 7 6 8 8 1 |
什么是
要知道如何处理这个警告,首先要了解它的含义和为什么会被提出是很重要的。好的。
筛选数据帧时,根据内部布局和各种实现细节,可以对帧进行切片/索引以返回视图或副本。顾名思义,"视图"是将视图转换为原始数据的视图,因此修改视图可能会修改原始对象。另一方面,"副本"是对原始数据的复制,修改副本对原始数据没有影响。好的。
如其他答案所述,创建
1 2 3 4 5 | df[df.A > 5]['B'] 1 3 2 6 Name: B, dtype: int64 |
而且,好的。
1 2 3 4 5 | df.loc[df.A > 5, 'B'] 1 3 2 6 Name: B, dtype: int64 |
这些返回相同的结果,因此如果您只读取这些值,那么它没有任何区别。那么,问题是什么?链式分配的问题是,通常很难预测是否返回视图或副本,因此当您试图重新分配值时,这在很大程度上成为一个问题。要基于前面的示例进行构建,请考虑解释器如何执行此代码:好的。
1 2 3 | df.loc[df.A > 5, 'B'] = 4 # becomes df.__setitem__((df.A > 5, 'B'), 4) |
用一个
1 2 3 | df[df.A > 5]['B'] = 4 # becomes df.__getitem__(df.A > 5).__setitem__('B", 4) |
现在,根据
一般来说,您应该使用
更多信息可以在文档中找到。好的。
Note
All boolean indexing operations done withloc can also be done withiloc . The only difference is thatiloc expects either
integers/positions for index or a numpy array of boolean values, and
integer/position indexes for the columns.Ok.
For example,
Ok.
1 df.loc[df.A > 5, 'B'] = 4Can be written nas
Ok.
1 df.iloc[(df.A > 5).values, 1] = 4And,
Ok.
1 df.loc[1, 'A'] = 100Can be written as
Ok.
1 df.iloc[1, 0] = 100And so on.
Ok.
告诉我如何压制警告!
考虑对
1 2 3 4 5 6 7 8 9 10 11 | df2 = df[['A']] df2['A'] /= 2 /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/IPython/__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 df2 A 0 2.5 1 4.5 2 3.5 |
有两种方法可以直接消除此警告:好的。
制作一个
1 2 | df2 = df[['A']].copy(deep=True) df2['A'] /= 2 |
变更
1 2 | pd.options.mode.chained_assignment = None df2['A'] /= 2 |
@彼得科顿在评论中,想出了一个不错的方法,使用上下文管理器非侵入性地改变模式(从这个要点修改),以设置模式只要是必要的,并在完成时将其重置回原始状态。好的。
1
2
3
4
5
6
7
8
9
10
11
12
13 class ChainedAssignent:
def __init__(self, chained=None):
acceptable = [None, 'warn', 'raise']
assert chained in acceptable,"chained must be in" + str(acceptable)
self.swcw = chained
def __enter__(self):
self.saved_swcw = pd.options.mode.chained_assignment
pd.options.mode.chained_assignment = self.swcw
return self
def __exit__(self, *args):
pd.options.mode.chained_assignment = self.saved_swcw
用法如下:好的。
1 2 3 4 | # some code here with ChainedAssignent(): df2['A'] /= 2 # more code follows |
或者,提出例外好的。
1 2 3 4 5 6 | with ChainedAssignent(chained='raise'): df2['A'] /= 2 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 |
"xy问题":我做错了什么?
很多时候,用户试图寻找抑制这个异常的方法,而不完全理解为什么它首先被提出。这是一个很好的x y问题的例子,用户试图解决一个问题"y",这实际上是一个根深蒂固的问题"x"的症状。将根据遇到此警告的常见问题提出问题,然后提出解决方案。好的。
Question 1
I have a DataFrameOk.
1
2
3
4
5 df
A B C D E
0 5 0 3 3 7
1 9 3 5 2 4
2 7 6 8 8 1I want to assign values in col"A"> 5 to 1000. My expected output is
Ok.
1
2
3
4 A B C D E
0 5 0 3 3 7
1 1000 3 5 2 4
2 1000 6 8 8 1
这样做的方法不对:好的。
1 2 3 | df.A[df.A > 5] = 1000 # works, because df.A returns a view df[df.A > 5]['A'] = 1000 # does not work df.loc[df.A 5]['A'] = 1000 # does not work |
正确使用
1 | df.loc[df.A > 5, 'A'] = 1000 |
< BR>好的。
Question 21
I am trying to set the value in cell (1, 'D') to 12345. My expected output isOk.
1
2
3
4 A B C D E
0 5 0 3 3 7
1 9 3 5 12345 4
2 7 6 8 8 1I have tried different ways of accessing this cell, such as
df['D'][1] . What is the best way to do this?Ok.
1. This question isn't specifically related to the warning, but
it is good to understand how to do this particular operation correctly
so as to avoid situations where the warning could potentially arise in
future.Ok.
您可以使用以下任何方法来执行此操作。好的。
1 2 3 4 | df.loc[1, 'D'] = 12345 df.iloc[1, 3] = 12345 df.at[1, 'D'] = 12345 df.iat[1, 3] = 12345 |
< BR>好的。
Question 3
I am trying to subset values based on some condition. I have a
DataFrameOk.
1
2
3 A B C D E
1 9 3 5 2 4
2 7 6 8 8 1I would like to assign values in"D" to 123 such that"C" == 5. I
triedOk.
1 df2.loc[df2.C == 5, 'D'] = 123Which seems fine but I am still getting the
SettingWithCopyWarning ! How do I fix this?Ok.
这可能是因为您的管道中的代码更高。你是从更大的东西,比如好的。
1 | df2 = df[df.A > 5] |
?在这种情况下,布尔索引将返回一个视图,因此
1 2 3 | df2 = df[df.A > 5].copy() # Or, # df2 = df.loc[df.A > 5, :] |
< BR>好的。< Buff行情>
问题4我想把C栏从好的。
1 2 3 | A B C D E 1 9 3 5 2 4 2 7 6 8 8 1 |
但使用好的。
1 | df2.drop('C', axis=1, inplace=True) |
抛出
这是因为
1 | df2 = df[df.A > 5] |
这里的解决方案是要么制作一个
熊猫数据帧复制警告
当你去做这样的事情时:
1 | quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]] |
在本例中,
您决定在此数据帧中更改的任何值都不会更改原始数据帧。
这就是熊猫试图警告你的。
为什么给定此数据帧:
1 | df = pd.DataFrame({"a": [1,2,3,4],"b": [1,1,2,2]}) |
两种行为:
1 2 | dfcopy = df.ix[:,["a"]] dfcopy.a.ix[0] = 2 |
行为一:
1 | df.ix[0,"a"] = 3 |
行为二:这将更改原始数据帧。
用熊猫开发人员认识到,
在代码示例中,您要做的是加载一个包含大量列的大文件,然后将其修改为较小的文件。
所以不要这样做
1 2 3 | 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]] |
这样做
1 2 3 | columns = ['STK', 'TPrice', 'TPCLOSE', 'TOpen', 'THigh', 'TLow', 'TVol', 'TAmt', 'TDate', 'TTime'] df = pd.read_csv(StringIO(str_of_all), sep=',', usecols=[0,3,2,1,4,5,8,9,30,31]) df.columns = columns |
这将只读取您感兴趣的列,并正确命名它们。不需要用邪恶的
为了消除任何疑问,我的解决方案是对切片进行深度拷贝,而不是常规拷贝。这可能不适用,具体取决于您的上下文(内存限制/切片大小、性能下降的可能性-特别是如果复制发生在循环中,就像对我一样,等等…)
为了清楚起见,这里是我收到的警告:
1 2 3 4 | /opt/anaconda3/lib/python3.6/site-packages/ipykernel/__main__.py:54: 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 |
插图
我怀疑这个警告是因为我在一个切片副本上放了一列而抛出的。虽然技术上并没有试图在切片副本中设置值,但这仍然是对切片副本的修改。以下是我为证实这一怀疑所采取的(简化)步骤,我希望它能帮助我们这些试图理解这一警告的人。
示例1:将列拖到原始列上会影响副本我们已经知道了,但这是一个健康的提醒。这不是警告的内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | >> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]} >> df1 = pd.DataFrame(data1) >> df1 A B 0 111 121 1 112 122 2 113 123 >> df2 = df1 >> df2 A B 0 111 121 1 112 122 2 113 123 # Dropping a column on df1 affects df2 >> df1.drop('A', axis=1, inplace=True) >> df2 B 0 121 1 122 2 123 |
可以避免对df1所做的更改影响df2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | >> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]} >> df1 = pd.DataFrame(data1) >> df1 A B 0 111 121 1 112 122 2 113 123 >> import copy >> df2 = copy.deepcopy(df1) >> df2 A B 0 111 121 1 112 122 2 113 123 # Dropping a column on df1 does not affect df2 >> df1.drop('A', axis=1, inplace=True) >> df2 A B 0 111 121 1 112 122 2 113 123 |
示例2:在副本上删除列可能会影响原始列
这实际上说明了警告。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | >> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]} >> df1 = pd.DataFrame(data1) >> df1 A B 0 111 121 1 112 122 2 113 123 >> df2 = df1 >> df2 A B 0 111 121 1 112 122 2 113 123 # Dropping a column on df2 can affect df1 # No slice involved here, but I believe the principle remains the same? # Let me know if not >> df2.drop('A', axis=1, inplace=True) >> df1 B 0 121 1 122 2 123 |
可以避免对df2所做的更改影响df1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | >> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]} >> df1 = pd.DataFrame(data1) >> df1 A B 0 111 121 1 112 122 2 113 123 >> import copy >> df2 = copy.deepcopy(df1) >> df2 A B 0 111 121 1 112 122 2 113 123 >> df2.drop('A', axis=1, inplace=True) >> df1 A B 0 111 121 1 112 122 2 113 123 |
干杯!
这应该有效:
1 | quote_df.loc[:,'TVol'] = quote_df['TVol']/TVOL_SCALE |
如果已将切片指定给变量,并希望使用变量进行如下设置:
1 2 | df2 = df[df['A'] > 2] df2['B'] = value |
您不想使用Jeffs解决方案,因为您的条件计算
1 | df.loc[df2.index.tolist(), 'B'] = value |
在这里我直接回答这个问题。如何处理?
滑完后做一个
等等,一个切片不是返回一个副本吗?毕竟,这就是警告信息试图表达的内容?阅读长答案:
1 2 3 | import pandas as pd df = pd.DataFrame({'x':[1,2,3]}) df0 = df[df.x>2] |
这会给出警告:
1 | df0['foo'] = 'bar' |
这不:
1 2 | df1 = df[df.x>2].copy() df1['foo'] = 'bar' |
1 2 3 4 5 | import inspect test = df[df.x>2] test_copy = df[df.x>2].copy() inspect.getmembers(test) inspect.getmembers(test_copy) |
使用您选择的diff工具,您将看到除了几个地址之外,唯一的实质性差异是:
1 2 3 | | | test | test_copy | | _is_copy | weakref | None | | _is_view | True | False | |
决定是否发出警告的方法是检查
警告是建议使用
1这将生成数据的深度副本,因此您可能不想对大型数据帧或需要浅层副本时使用此方法。警告:如果数据框中有python对象,那么将只复制引用,而不是复制
对于我来说,这个问题发生在下面的一个>简化<示例中。我也能解决这个问题(希望有一个正确的解决方案):
带警告的旧代码:
1 2 3 4 5 6 7 8 9 | def update_old_dataframe(old_dataframe, new_dataframe): for new_index, new_row in new_dataframe.iterrorws(): old_dataframe.loc[new_index] = update_row(old_dataframe.loc[new_index], new_row) def update_row(old_row, new_row): for field in [list_of_columns]: # line with warning because of chain indexing old_dataframe[new_index][field] old_row[field] = new_row[field] return old_row |
这打印了行
由于update_row方法中的行实际上是
1 | old_row.at[field] = new_row.at[field] |
即访问/查找
我希望这能对某人有所帮助。
我相信你可以避免这样的问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | return ( pd.read_csv(StringIO(str_of_all), sep=',', names=list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg')) #dtype={'A': object, 'B': object, 'C': np.float64} .rename(columns={'A':'STK', 'B':'TOpen', 'C':'TPCLOSE', 'D':'TPrice', 'E':'THigh', 'F':'TLow', 'I':'TVol', 'J':'TAmt', 'e':'TDate', 'f':'TTime'}, inplace=True) .ix[:,[0,3,2,1,4,5,8,9,30,31]] .assign( TClose=lambda df: df['TPrice'], RT=lambda df: 100 * (df['TPrice']/quote_df['TPCLOSE'] - 1), TVol=lambda df: df['TVol']/TVOL_SCALE, TAmt=lambda df: df['TAmt']/TAMT_SCALE, STK_ID=lambda df: df['STK'].str.slice(13,19), STK_Name=lambda df: df['STK'].str.slice(21,30)#.decode('gb2312'), TDate=lambda df: df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10]), ) ) |
使用赋值。从文档中:将新列分配给一个数据框架,返回一个新对象(一个副本),除了新列之外,还返回所有原始列。
参见Tom Augspurger关于熊猫中方法链接的文章:https://tom augspurger.github.io/method-chaining
跟进初学者问题/评论
也许对像我这样的初学者有一个解释(我来自R,它在引擎盖下的工作方式有点不同)。下面的无害的外观和功能代码不断生成带有复制警告的设置,我不知道为什么。我已经阅读并理解了用"链接索引"发布的,但我的代码不包含任何:
1 2 3 | def plot(pdb, df, title, **kw): df['target'] = (df['ogg'] + df['ugg']) / 2 # ... |
但后来,太晚了,我看了一下plot()函数的调用位置:
1 2 | df = data[data['anz_emw'] > 0] pixbuf = plot(pdb, df, title) |
所以"df"不是一个数据帧,而是一个对象,它以某种方式记住它是通过索引一个数据帧创建的(这是一个视图吗?)这将使plot()中的行
1 | df['target'] = ... |
相当于
1 | data[data['anz_emw'] > 0]['target'] = ... |
这是一个链接索引。我说得对吗?
不管怎样,
1 2 | def plot(pdb, df, title, **kw): df.loc[:,'target'] = (df['ogg'] + df['ugg']) / 2 |
修理它。