如何应对SettingWithCopyWarning熊猫?
这篇文章适合读者,
想了解这个警告意味着什么
想了解抑制此警告的不同方法
想了解如何改进他们的代码并遵循良好做法以避免将来出现此警告。
建立
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基于整数/位置的赋值,因为规范保证它们始终在原始操作上运行。此外,要设置单个单元格,您应该使用at和iat。
更多信息可以在文档中找到。
注意完成的
所有布尔索引操作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
有几种方法可以直接消除此警告:
做一个 deepcopy
df2 = df[['A']].copy(deep=True)
df2['A'] /= 2
更改pd.options.mode.chained_assignment
可以设置为None,"warn"或"raise"。"warn"是默认值。None将完全抑制警告,"raise"并将抛出一个SettingWithCopyError,以防止操作通过。
pd.options.mode.chained_assignment = None
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
我想在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
问题2 1
我试图将单元格(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,如前。