如何处理Pandas中的SettingWithCopyWarning

如何处理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, :]

避免链式赋值

链式赋值操作通常难以预测返回的是视图还是副本,因此建议使用 lociloc 进行赋值操作。例如,要将列 A 中大于 5 的值对应的列 B 的值设为 4,可以使用以下代码:

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

处理警告的方法

  • 使用 loc 切片子集
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

最佳实践

  • 尽量使用 lociloc 进行数据的索引和赋值操作,以确保操作作用于原始数据。
  • 在需要对数据进行修改时,明确创建副本,避免意外修改原始数据。
  • 对于复杂的操作,可以使用上下文管理器临时更改警告模式,确保代码的可读性和可维护性。

常见问题

问题1:使用链式赋值无法更新数据

1
2
df[df.A > 5]['A'] = 1000  # 无法更新数据
df.loc[df.A > 5, 'A'] = 1000 # 正确更新数据

解决方法:使用 lociloc 进行赋值操作。

问题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 # dfcopy 是独立的 DataFrame,更改不会影响 df
df.ix[0, "a"] = 3 # 更改原始 DataFrame

解决方法:使用 .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 # 直接在原始 DataFrame 上赋值

如何处理Pandas中的SettingWithCopyWarning
https://119291.xyz/posts/how-to-deal-with-settingwithcopywarning-in-pandas/
作者
ww
发布于
2025年6月24日
许可协议