我做了一些探索,根据我的理解,这就是SettingWithCopyWarning的底层内容:每次从另一个帧df_orig创建一个数据帧df时,pandas采用一些启发式方法来确定数据是否可能隐含从df_orig 复制,经验不足的用户可能不知道。如果是这样,则将df 的_is_copy 字段设置为df_orig 的weak reference。稍后,当尝试就地更新df 时,pandas 将根据df._is_copy 以及df 的一些其他字段确定是否应显示SettingWithCopyWarning(注意df._is_copy 是这里不是唯一的标准)。但是,由于某些方法在不同场景之间共享,因此启发式方法并不完美,并且某些情况可能处理不当。
在帖子中的代码中,housing.loc[train_index] 和 housing.iloc[train_index] 都返回了 housing 数据帧的隐式副本。
for df in (housing.loc[train_index], housing.iloc[train_index]):
print(df._is_view, df._is_copy)
上述检查产生以下结果:
False None
False <weakref at 0x0000019BFDF37958; to 'DataFrame' at 0x0000019BFDF26550>
这里,_is_view 是另一个字段,它显示对 df 的更新是否会影响原始数据框 housing。 False 结果表明基础数据已被复制。但是,对于housing.loc[train_index],df._is_copy 字段未设置,在我看来应该在这种情况下,导致SettingWithCopyWarning 丢失,当df 的就地修改由语句df.drop("income_cat", axis=1, inplace=True) 执行时.
为了避免SettingWithCopyWarning,您需要(1)在切片之前执行就地更新; (2) 如果可能,将更新逻辑构建到切片中;或 (3) 当需要就地更新时,在切片后制作数据的“显式”副本。在您的示例中,方法 (1) 如下所示:
# Updates the housing data frame in-place before slicing
income_cat = housing["income_cat"]
housing.drop("income_cat", axis=1, inplace=True)
for train_index, test_index in split.split(housing, income_cat):
strat_train_set = housing.loc[train_index]
strat_test_set = housing.loc[test_index]
方法(2)如下所示:
feature_cols = housing.columns.difference(["income_cat"])
for train_index, test_index in split.split(housing, housing["income_cat"]):
# Filter columns at the same time as slicing the rows
strat_train_set = housing.loc[train_index, feature_cols]
strat_test_set = housing.loc[test_index, feature_cols]
方法(3)如下所示:
for train_index, test_index in split.split(housing, housing["income_cat"]):
...
for set_ in (strat_train_set, strat_test_set):
# Remove "inplace=True" results in a copy being made
set_.drop("income_cat", axis=1)
除了更改更新方法的inplace 设置之外,df.copy() 是另一种可用于制作“显式”副本的方法。如果您打算更改df 的一列或多列,请使用df.assign(col=...) 而不是df["col"]=... 创建副本。