如何处理Pandas中的SettingWithCopyWarning?

背景

我只是把我的pandas从0.11升级到了0.13.0rc1。 现在,应用程序正在popup很多新的警告。 其中之一是这样的:

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应该如何暂停警告?

提供错误的function

 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_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]) 

从我所收集的信息中, SettingWithCopyWarning被创build来标记潜在的令人困惑的“链接”分配,例如下面的分配,这些分配并不总是按预期工作,特别是当第一个select返回副本时 。 [参见GH5390和GH5597进行背景讨论。]

 df[df['A'] > 2]['B'] = new_val # new_val not set in df 

警告提供了一个改写如下的build议:

 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' 

一般而言, SettingWithCopyWarning是向用户(并且特别是新用户)展示他们可能在副本上操作而不是他们所想的原始操作。 有错误的肯定(你知道你在做什么,所以可以)。 一种可能性就是简单地按照@Garrett的build议closures(默认警告 )警告。

这是一个nother,每个选项。

 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 

您可以将is_copy标志设置为False ,这将有效地closures对该对象的检查*

 In [5]: dfa.is_copy = False In [6]: dfa['A'] /= 2 

如果你明确地复制,那么你知道你在做什么 ,所以不会有进一步的警告发生。

 In [7]: dfa = df.ix[:,[1,0]].copy() In [8]: dfa['A'] /= 2 

OP在上面显示的代码虽然是合法的,也可能是我所做的,但在技术上这是警告的一种情况,而不是一个错误的肯定。 另一种警告的方法是通过reindex进行select操作,例如

 quote_df = quote_df(columns=['STK',.......]) 

pandas数据框复制警告

当你去做这样的事情:

 quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]] 

在这种情况下, pandas.ix返回一个新的独立数据pandas.ix

您决定在此数据框中更改的任何值都不会更改原始数据框。

pandas试图警告你。

为什么.ix是一个坏主意

.ix对象试图做不止一件事,对于任何读过干净代码的人来说,这是一种强烈的气味。

鉴于这个dataframe:

 df = pd.DataFrame({"a": [1,2,3,4], "b": [1,1,2,2]}) 

两种行为:

 dfcopy = df.ix[:,["a"]] dfcopy.a.ix[0] = 2 

行为之一: dfcopy现在是一个独立的数据dfcopy 。 改变它不会改变df

 df.ix[0, "a"] = 3 

行为二:这改变了原始的数据框。

改用.loc

pandas开发者认识到, .ix对象是非常臭的[推测],从而创build了两个新的对象,有助于数据的join和分配。 (另一个是.iloc

.loc更快,因为它不会尝试创build数据的副本。

.loc是为了修改现有的dataframe,这是更有效的内存。

.loc是可预测的,它有一个行为。

解决scheme

你在你的代码示例中正在做的是加载一个有大量列的大文件,然后将其修改为更小。

pd.read_csv函数可以帮助你解决很多问题,并且使文件的加载速度更快。

所以,而不是这样做

 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]] 

做这个

 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 

这将只读取您感兴趣的列,并正确命名。 没有必要使用邪恶的.ix对象来做神奇的东西。

如果已将切片分配给一个variables,并想使用如下所示的variables进行设置:

 df2 = df[df['A'] > 2] df2['B'] = value 

而且您不想使用Jeffs解决scheme,因为您的计算df2的条件过长或出于其他原因,则可以使用以下内容:

 df.loc[df2.index.tolist(), 'B'] = value 

df2.index.tolist()返回来自df2中所有条目的索引,然后将被用于在原始dataframe中设置列B.

为了消除任何疑问,我的解决scheme是制作切片的深层副本而不是常规副本。 这可能不适用,具体取决于你的上下文(内存约束/片的大小,潜在的性能下降 – 尤其是如果复制发生在像我这样的循环中,等等…)

要清楚,这是我收到的警告:

 /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:在原件上放置一列会影响副本

我们知道,但这是一个健康的提醒。 这不是什么警告。

 >> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]} >> df1 = pd.DataFrame(data1) >> df1 AB 0 111 121 1 112 122 2 113 123 >> df2 = df1 >> df2 AB 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

 >> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]} >> df1 = pd.DataFrame(data1) >> df1 AB 0 111 121 1 112 122 2 113 123 >> import copy >> df2 = copy.deepcopy(df1) >> df2 AB 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 AB 0 111 121 1 112 122 2 113 123 

示例2:在副本上放置一列可能会影响原件

这实际上说明了这个警告。

 >> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]} >> df1 = pd.DataFrame(data1) >> df1 AB 0 111 121 1 112 122 2 113 123 >> df2 = df1 >> df2 AB 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

 >> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]} >> df1 = pd.DataFrame(data1) >> df1 AB 0 111 121 1 112 122 2 113 123 >> import copy >> df2 = copy.deepcopy(df1) >> df2 AB 0 111 121 1 112 122 2 113 123 >> df2.drop('A', axis=1, inplace=True) >> df1 AB 0 111 121 1 112 122 2 113 123 

干杯!

你可以避免这样的问题,我相信:

 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关于大pandas方法链接的文章: https ://tomaugspurger.github.io/method-chaining