如何处理 SettingWithCopyWarning
and ChainedAssignmentError
?
这篇文章适合以下读者:
-
想了解这个警告的含义
-
想要了解抑制此警告的不同方法
-
想要了解如何改进他们的代码并遵循良好的做法以避免将来出现此警告。
设置
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
?
要知道如何处理此警告,首先重要的是了解它的含义以及为什么会出现此警告。
过滤 DataFrames 时,可以对帧进行切片/索引以返回 视图 或 副本 ,具体取决于内部布局和各种实现细节。顾名思义,“视图”是原始数据的视图,因此修改视图可能会修改原始对象。另一方面,“副本”是对原始数据的复制,修改副本不会对原始数据产生影响。
正如其他答案所提到的, 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
and 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
等等。
从 pandas >= 2.0 开始,您可以启用 写时复制优化 以节省内存并避免在写入之前复制数据(如果可能)。
可以通过以下方式启用
pd.options.mode.copy_on_write = True
在此之后,尝试进行链式赋值将导致
ChainedAssignmentError: A value is trying to be set on a copy of a DataFrame or Series through chained assignment.
When using the Copy-on-Write mode, such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy.
Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step.
错误出现在与 SettingWithCopyWarning
.
只要告诉我如何抑制警告!
考虑对 的 \'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
有几种方法可以直接消除此警告:
-
第22页
df2 = df.loc[:, ['A']] df2['A'] /= 2 # Does not raise
-
p23
pd.options.mode.chained_assignment = None df2['A'] /= 2
-
p24
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
我想将 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
Question 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
,像以前一样。