如何处理Pandas中的SettingWithCopyWarning
技术背景
在使用Pandas进行数据处理时,SettingWithCopyWarning
警告经常出现。该警告旨在标记可能令人困惑的“链式”赋值操作,因为在对DataFrame进行切片或索引操作时,返回的可能是原始数据的视图(view)或副本(copy),这会导致赋值操作的结果难以预测。例如,当使用链式索引进行赋值时,无法确定操作是作用于原始数据还是其副本,这可能会引发意外的结果。
实现步骤
理解视图和副本
在过滤DataFrames时,切片或索引操作可能返回视图或副本。视图是对原始数据的引用,修改视图可能会影响原始对象;而副本是原始数据的复制,修改副本不会影响原始对象。例如:
1 2 3 4 5 6 7 8 9 10
| import pandas as pd import numpy as np
np.random.seed(0) df = pd.DataFrame(np.random.choice(10, (3, 5)), columns=list('ABCDE'))
slice1 = df[df.A > 5]
slice2 = df.loc[df.A > 5, :]
|
避免链式赋值
链式赋值操作通常难以预测返回的是视图还是副本,因此建议使用 loc
或 iloc
进行赋值操作。例如,要将列 A
中大于 5 的值对应的列 B
的值设为 4,可以使用以下代码:
1
| df.loc[df.A > 5, 'B'] = 4
|
处理警告的方法
1 2
| df2 = df.loc[:, ['A']] df2['A'] /= 2
|
- 更改
pd.options.mode.chained_assignment
:可以将其设置为 None
、"warn"
或 "raise"
。"warn"
是默认值,None
会完全抑制警告,"raise"
会抛出 SettingWithCopyError
阻止操作进行。
1 2
| pd.options.mode.chained_assignment = None df2['A'] /= 2
|
1 2
| df2 = df[['A']].copy(deep=True) df2['A'] /= 2
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import pandas as pd
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
with ChainedAssignent(): df2['A'] /= 2
|
核心代码
正确使用 loc
进行赋值
1 2 3 4 5 6 7 8
| import pandas as pd import numpy as np
np.random.seed(0) df = pd.DataFrame(np.random.choice(10, (3, 5)), columns=list('ABCDE'))
df.loc[df.A > 5, 'A'] = 1000
|
使用上下文管理器临时更改警告模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import pandas as pd
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
df2 = df[['A']] with ChainedAssignent(chained=None): df2['A'] /= 2
|
最佳实践
- 尽量使用
loc
或 iloc
进行数据的索引和赋值操作,以确保操作作用于原始数据。 - 在需要对数据进行修改时,明确创建副本,避免意外修改原始数据。
- 对于复杂的操作,可以使用上下文管理器临时更改警告模式,确保代码的可读性和可维护性。
常见问题
问题1:使用链式赋值无法更新数据
1 2
| df[df.A > 5]['A'] = 1000 df.loc[df.A > 5, 'A'] = 1000
|
解决方法:使用 loc
或 iloc
进行赋值操作。
问题2:使用 .ix
引发的问题
.ix
既可以按标签索引,也可以按位置索引,行为不明确,容易引发问题。
1 2 3 4
| df = pd.DataFrame({"a": [1, 2, 3, 4], "b": [1, 1, 2, 2]}) dfcopy = df.ix[:, ["a"]] dfcopy.a.ix[0] = 2 df.ix[0, "a"] = 3
|
解决方法:使用 .loc
或 .iloc
替代 .ix
。
问题3:在过滤后的 DataFrame 上赋值引发警告
1 2
| df2 = df[df.A > 5] df2['A'] = 1000
|
解决方法:可以创建副本或使用 loc
进行赋值。
1 2 3 4
| df2 = df[df.A > 5].copy() df2['A'] = 1000
df.loc[df.A > 5, 'A'] = 1000
|