如何处理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