问答中心分类: DATABASE如何处理 Pandas 中的 SettingWithCopyWarning
0
匿名用户 提问 16分钟 前

Background
我刚刚将我的 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])
Peter Cotton 回复 16分钟 前

这是一个临时设置警告级别的上下文管理器gist.github.com/notbanker/2be3ed34539c86e22ffdd88fd95ad8bc

Peter Cotton 回复 16分钟 前

pandas.pydata.org/pandas-docs/stable/…官方文档详细解释

Peter Cotton 回复 16分钟 前

@leonproudf.set_value已被弃用。熊猫现在建议使用.at[]或者.iat[]反而。文档在这里pandas.pydata.org/pandas-docs/stable/generated/…

Peter Cotton 回复 16分钟 前

使用df.loc[:, foo]避免SettingWithCopyWarning, 然而df[foo]原因SettingWithCopyWarning.

Peter Cotton 回复 16分钟 前

这回答了你的问题了吗?使用索引为 pandas DataFrame 中的特定单元格设置值

19 Answers
0
cs95 回答 16分钟 前

如何处理SettingWithCopyWarning在熊猫?

这篇文章是为读者准备的,

  1. 想了解此警告的含义
  2. 想了解抑制此警告的不同方法
  3. 想了解如何改进他们的代码并遵循良好实践以避免将来出现此警告。

设置

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

是什么SettingWithCopyWarning?
要知道如何处理这个警告,首先要了解它的含义以及为什么会出现它,这一点很重要。
过滤 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

有几种方法可以直接消除此警告:

  1. (推荐的)利用loc对子集进行切片
    df2 = df.loc[:, ['A']]
     df2['A'] /= 2     # Does not raise
  2. 改变pd.options.mode.chained_assignment可以设置为None,"warn", 或者"raise"."warn"是默认值。None将完全抑制警告,并且"raise"会抛出一个SettingWithCopyError,阻止操作通过。
    pd.options.mode.chained_assignment = None
     df2['A'] /= 2
  3. 做一个deepcopy
    df2 = df[['A']].copy(deep=True)
     df2['A'] /= 2

@彼得棉​​花在评论中,提出了一种非侵入式更改模式的好方法(修改自这个要点) 使用上下文管理器,仅在需要时设置模式,并在完成后将其重置回原始状态。

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我有一个数据框

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

我想将 col“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我正在尝试根据某些条件对值进行子集化。我有一个数据框

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我正在尝试从

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, 和以前一样。

cs95 回复 16分钟 前

PS:如果您的情况不在第 3 部分的问题列表中,请告诉我。我会修改我的帖子。

Josiah Yoder 回复 16分钟 前

我认为将问题 2 链接到解决 loc、iloc、at 和 iat 之间差异的问题会很有帮助。您可能比我更了解这样的问题,但如果有帮助,我很乐意寻求一个。

Josiah Yoder 回复 16分钟 前

这个问题解决您想同时使用 loc 和 iloc 的情况,iloc 用于行,loc 用于列

Bryan P 回复 16分钟 前

@cs95:您能否在尝试基于现有列的简单数学运算创建新列的情况下添加 XY 描述。如 df[‘new_col’] = df[‘old_col’]/2。 ‘new_col’ 尚不存在的地方。谢谢

cs95 回复 16分钟 前

@BryanP 除非我弄错了,否则应该或多或少地包含在“告诉我如何抑制警告!”之下。部分。

Bryan P 回复 16分钟 前

@cs95:谢谢。在创建数据框时制作副本有效(在我的情况下,结构很简单,.copy()就够了。但是,这似乎不仅仅是压制警告。我最初跳过了该部分,因为我的目标是解决问题而不是告诉 Pandas 忽略它。似乎使用copy()使原始数据框成为自己的东西(而不是视图并假设它来自其他地方)似乎实际上解决了根本问题,对吧?不只是压制警告?

Ciprian Tomoiagă 回复 16分钟 前

有没有办法进行布尔索引,显式返回一个新对象,而不调用.copy()?因为我只想在精简数据框后创建新列

cs95 回复 16分钟 前

@CiprianTomoiagă loc 在切片数据时总是返回一个副本。

Mark 回复 16分钟 前

@cs95 – 谢谢。几乎每个人都关注您的问题 1 和 2。但是,我遇到了问题 3 中的子集问题。我发现在创建子网后,_is_copy不再是 None 导致我使用 .copy()。

Pythonist 回复 16分钟 前

非常感谢!我总是用.loc在我的代码中,因为我知道“链式分配”问题,但我仍然几乎随机烦人SettingWithCopyWarning时不时。事实证明,我的问题是您在“问题 3”中涵盖的内容。这花了我几个月的时间才意识到,所以也许应该在文档中更清楚地指出……无论如何,再次感谢!

clime 回复 16分钟 前

好吧,“问题 3”抛出“SettingWithCopyWarning”似乎有点随机,因为它是一个不同的问题(在这里您可能会惊讶于您正在更改 df,在其他情况下您可能会惊讶于您的更改丢失)。这个警告很烦人。

henning 回复 16分钟 前

@cs95 怎么样df["widget"].replace("foo", "bar", inplace=True)?

0
Jeff 回答 16分钟 前

一般来说,要点SettingWithCopyWarning是向用户(尤其是新用户)展示他们可能是在副本上操作,而不是他们认为的原件。那里误报(IOW,如果你知道你在做什么,它可能是好的)。一种可能性是简单地关闭(默认情况下警告) 警告正如@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_copy标记为False,这将有效地关闭检查,对于那个对象

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
Marses 回复 16分钟 前

我认为说存在误报是轻描淡写的。我认为我从来没有得到过这个警告帮助我,而且我让它阻塞我的输出的次数是疯狂的。这也是一种不好的编程习惯:如果你开始忽略输出中的警告,因为你知道它们是纯粹的垃圾,你可能会开始错过真正的问题。不得不一直关闭相同的警告也很烦人。

0
user443854 回答 16分钟 前

这里我直接回答问题。我们该如何应对?
做一个.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对象,但它们的某些方面有所不同,这使得 pandas 能够打印警告。让我们找出它是什么。

import inspect
slice= df[df.x>2]
slice_copy = df[df.x>2].copy(deep=False)
inspect.getmembers(slice)
inspect.getmembers(slice_copy)

使用您选择的差异工具,您会看到除了几个地址之外,唯一的实质性区别是:

|          | slice   | slice_copy |
| _is_copy | weakref | None       |

决定是否警告的方法是DataFrame._check_setitem_copy检查_is_copy.所以给你。做一个copy这样你的 DataFrame 就不是_is_copy.
警告建议使用.loc,但如果你使用.loc在一个框架上_is_copy,您仍然会收到相同的警告。误导?是的。恼人的?你打赌。有帮助吗?潜在地,当使用链式赋值时。但它无法正确检测链分配并不加选择地打印警告。

user650654 回复 16分钟 前

很好的侦查。 FWIW我还发现_is_copyNone对于原始的df和切片的weakref。更远,_is_copy()在切片上返回原始 df 的所有行。但是参考印刷的_is_copy和原df的id不一样。切片是否以某种方式复制?另外,我想知道浅拷贝是否会导致其他问题或更新版本的熊猫?

mirekphd 回复 16分钟 前

这个答案肯定值得一个单独的写作风格徽章。

waykiki 回复 16分钟 前

放下对问题的最具体和直接的答案。说得很好。

0
firelynx 回答 16分钟 前

熊猫数据框复制警告
当你去做这样的事情时:

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

pandas.ix 在这种情况下返回一个新的独立数据框。
您决定在此数据框中更改的任何值都不会更改原始数据框。
这是 pandas 试图警告你的。

为什么.ix是个坏主意
.ixobject 试图做的不止一件事,对于任何读过干净代码的人来说,这是一种强烈的气味。
鉴于此数据框:

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

两种行为:

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

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

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

行为二:这会更改原始数据框。

利用.loc反而
pandas 开发人员认识到.ix对象很臭[推测性地],因此创建了两个新对象,有助于数据的访问和分配。 (另一个存在.iloc)
.loc更快,因为它不会尝试创建数据的副本。
.loc旨在就地修改您现有的数据框,这样可以提高内存效率。
.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反对做神奇的事情。

0
Mikulas 回答 16分钟 前

这个话题真的让 Pandas 很困惑。幸运的是,它有一个相对简单的解决方案。
问题在于,数据过滤操作(例如 loc)是否返回 DataFrame 的副本或视图并不总是很清楚。因此,进一步使用这种过滤的 DataFrame 可能会令人困惑。
简单的解决方案是(除非您需要处理非常大的数据集):
每当您需要更新任何值时,请始终确保在分配之前明确复制 DataFrame。

df  # Some DataFrame
df = df.loc[:, 0:2]  # Some filtering (unsure whether a view or copy is returned)
df = df.copy()  # Ensuring a copy is made
df[df["Name"] == "John"] = "Johny"  # Assignment can be done now (no warning)
memeplex 回复 16分钟 前

对于大型数据集,您可以制作浅 (deep=False) 副本。压制警告似乎还是太多了。