如何在pandas中使用CopyWarning处理设置

xxslljrj  于 2021-09-08  发布在  Java
关注(0)|答案(5)|浏览(329)

背景

我刚刚把我的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_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])
vfhzx4xs

vfhzx4xs1#

这个 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

很明显,您不关心写操作返回到原始帧(因为您正在覆盖对它的引用),但不幸的是,此模式无法与第一个链式赋值示例区分开来。因此出现了(假阳性)警告。如果您想进一步阅读,关于索引的文档中会提到误报的可能性。您可以通过以下分配安全地禁用此新警告。

import pandas as pd
pd.options.mode.chained_assignment = None  # default='warn'

其他资源

Pandas用户指南:索引和选择数据
python数据科学手册:数据索引和选择
真正的python:settingwithcopywarning在pandas中:视图与副本
dataquest:settingwithcopywarning:如何修复pandas中的此警告
走向数据科学:解释Pandas的复制警告设置

rggaifut

rggaifut2#

如何处理Pandas中的复制警告设置?

这篇文章是为那些,
希望了解此警告的含义
希望了解抑制此警告的不同方式
希望了解如何改进他们的代码并遵循良好实践,以避免将来出现此警告。
设置

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

CopyWarning的设置是什么?

要知道如何处理这一警告,重要的是要理解它的含义以及为什么首先提出这一警告。
过滤 Dataframe 时,根据内部布局和各种实现细节,可以对帧进行切片/索引以返回视图或副本。顾名思义,“视图”是指向原始数据的视图,因此修改视图可能会修改原始对象。另一方面,“副本”是原始数据的复制,修改副本对原始数据没有影响。
正如其他答案所提到的 SettingWithCopyWarning 已创建以标记“链接分配”操作。考虑 df 在上面的设置中。假设您希望选择列“b”中的所有值,其中列“a”中的值大于5。pandas允许您以不同的方式执行此操作,有些方法比其他方法更正确。例如

df[df.A > 5]['B']

1    3
2    6
Name: B, dtype: int64

以及,

df.loc[df.A > 5, 'B']

1    3
2    6
Name: B, dtype: int64

它们返回相同的结果,因此,如果您只读取这些值,则不会产生任何差异。那么,问题是什么?链式赋值的问题是,通常很难预测是否返回视图或副本,因此在尝试重新赋值时,这在很大程度上成为一个问题。要在前面的示例中构建,请考虑解释器如何执行此代码:

df.loc[df.A > 5, 'B'] = 4

# becomes

df.__setitem__((df.A > 5, 'B'), 4)

带着一个 __setitem__ 召唤 df . Otoh,考虑这个代码:

df[df.A > 5]['B'] = 4

# becomes

df.__getitem__(df.A > 5).__setitem__('B", 4)

现在,取决于 __getitem__ 返回了一个视图或副本 __setitem__ 操作可能不起作用。
一般来说,您应该使用 loc 用于基于标签的分配,以及 iloc 对于基于整数/位置的指定,因为规范保证它们始终对原始对象进行操作。此外,对于设置单个单元格,应使用 atiat .
更多信息可在文档中找到。
笔记
使用完成的所有布尔索引操作 loc 也可以用 iloc . 唯一的区别是 iloc 索引需要整数/位置或布尔值的numpy数组,列需要整数/位置索引。
例如

df.loc[df.A > 5, 'B'] = 4

可以写入nas

df.iloc[(df.A > 5).values, 1] = 4

以及,

df.loc[1, 'A'] = 100

可以写成

df.iloc[1, 0] = 100

等等

请告诉我如何抑制警告!

考虑“A”列的简单操作 df . 选择“a”并除以2将发出警告,但操作将起作用。

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

有两种方法可以直接消除此警告:
(推荐)使用 loc 要切片子集,请执行以下操作:

df2 = df.loc[:, ['A']]
 df2['A'] /= 2     # Does not raise

改变 pd.options.mode.chained_assignment 可以设置为 None , "warn""raise" . "warn" 是默认值。 None 将完全抑制警告,并且 "raise" 将抛出一个 SettingWithCopyError ,防止操作通过。

pd.options.mode.chained_assignment = None
 df2['A'] /= 2

制造 deepcopy ```
df2 = df'A'.copy(deep=True)
df2['A'] /= 2

@peter cotton在评论中提出了一种很好的方法,可以使用上下文管理器非侵入式地更改模式(根据本要点进行修改),只在需要时设置模式,并在完成后将其重置回原始状态。

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
用法如下:

some code here

with ChainedAssignent():
df2['A'] /= 2

more code follows

或者,提出例外

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问题”:我做错了什么?

很多时候,用户试图寻找抑制此异常的方法,却没有完全理解它最初提出的原因。这是xy问题的一个很好的例子,用户试图解决一个问题“y”,这实际上是一个深层次问题“x”的症状。将根据遇到此警告的常见问题提出问题,然后提出解决方案。
问题1
我有一个 Dataframe 

df
A B C D E
0 5 0 3 3 7
1 9 3 5 2 4
2 7 6 8 8 1

我想分配列“a”>5到1000中的值。我的预期输出为

A B C D E
0 5 0 3 3 7
1 1000 3 5 2 4
2 1000 6 8 8 1

错误的做法:

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

正确使用 `loc` :

df.loc[df.A > 5, 'A'] = 1000

问题21
我正在尝试将单元格(1,'d')中的值设置为12345。我的预期输出为

A B C D E
0 5 0 3 3 7
1 9 3 5 12345 4
2 7 6 8 8 1

我尝试了不同的方法来访问此单元格,例如 `df['D'][1]` . 最好的方法是什么?
1.此问题与警告没有具体关系,但最好了解如何正确执行此特定操作,以避免将来可能出现警告的情况。
您可以使用以下任何方法来执行此操作。

df.loc[1, 'D'] = 12345
df.iloc[1, 3] = 12345
df.at[1, 'D'] = 12345
df.iat[1, 3] = 12345

问题3
我试图根据某些条件对值进行子集划分。我有一个 Dataframe 

A B C D E
1 9 3 5 2 4
2 7 6 8 8 1

我想把“d”中的值赋给123,这样“c”==5

df2.loc[df2.C == 5, 'D'] = 123

这似乎很好,但我仍然得到 `SettingWithCopyWarning` ! 我该如何解决这个问题?
这实际上可能是因为管道中的代码更高。你创造了什么 `df2` 来自更大的东西,比如

df2 = df[df.A > 5]

? 在这种情况下,布尔索引将返回一个视图,因此 `df2` 我将引用原文。你需要做的是分配 `df2` 复制:

df2 = df[df.A > 5].copy()

Or,

df2 = df.loc[df.A > 5, :]

问题4
我正试图将“c”列从

A B C D E
1 9 3 5 2 4
2 7 6 8 8 1

但是使用

df2.drop('C', axis=1, inplace=True)

投掷 `SettingWithCopyWarning` . 为什么会这样?
这是因为 `df2` 必须已创建为来自某些其他切片操作的视图,例如

df2 = df[df.A > 5]

这里的解决方案是 `copy()` 属于 `df` ,或使用 `loc` ,一如以往。
drkbr07n

drkbr07n3#

总的来说 SettingWithCopyWarning 是向用户(尤其是新用户)显示,他们可能是在拷贝上操作,而不是他们认为的原始操作。有误报(如果你知道你在做什么,那就可以了)。一种可能是按照@garrett的建议关闭(默认警告)警告。
这是另一个选择:

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_copyFalse ,这将有效地关闭该对象的检查:

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 ,例如。

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

quote_df = quote_df.reindex(['STK', ...], axis=1)  # v.0.21
bxgwgixi

bxgwgixi4#

Dataframe 复制警告

当你去做这样的事情时:

quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]
``` `pandas.ix` 在本例中,返回一个新的独立 Dataframe 。
您决定在此 Dataframe 中更改的任何值都不会更改原始 Dataframe 。
这就是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` 现在是一个独立的 Dataframe 。改变它不会改变 `df` ```
df.ix[0, "a"] = 3

行为二:这将更改原始 Dataframe 。

改为使用.loc

Pandas开发商认识到 .ix 对象相当臭[推测],因此创建了两个新对象,这有助于数据的存取和分配(另一个存在 .iloc ) .loc 速度更快,因为它不尝试创建数据的副本。 .loc 是为了修改现有的 Dataframe ,这更节省内存。 .loc 是可预测的,它有一个行为。

解决方案

在代码示例中,您正在加载一个包含许多列的大文件,然后将其修改为更小的文件。
这个 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 反对做神奇的事情。

xfyts7mz

xfyts7mz5#

在这里,我直接回答这个问题。如何处理?
制造 .copy(deep=False) 在你切完之后。请参阅pandas.dataframe.copy。
等等,切片不返回副本吗?毕竟,这就是警告信息试图表达的意思?阅读长答案:

import pandas as pd
df = pd.DataFrame({'x':[1,2,3]})

这是一个警告:

df0 = df[df.x>2]
df0['foo'] = 'bar'

这并不是:

df1 = df[df.x>2].copy(deep=False)
df1['foo'] = 'bar'

二者都 df0df1DataFrame 对象,但它们的某些方面有所不同

相关问题