У меня есть базовое понимание SettingWithCopyWarning, но я не могу понять, почему я получаю предупреждение для этого конкретного случая.
Я следую коду из https://github.com/ageron/handson-ml/blob/master/02_end_to_end_machine_learning_project.ipynb
Когда я запускаю код, как показано ниже (используя .loc), я не получаю SettingWithCopyWarning
Однако, если вместо этого я запускаю код с .iloc, я получаю предупреждение.
Может ли кто-нибудь помочь мне понять это?
from sklearn.model_selection import StratifiedShuffleSplit
split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)
for train_index, test_index in split.split(housing, housing["income_cat"]):
strat_train_set = housing.loc[train_index]
strat_test_set = housing.loc[test_index]
for set_ in (strat_train_set, strat_test_set):
set_.drop("income_cat", axis=1, inplace=True)
Проблема здесь не из-за индексации, iloc
и loc
будут работать для вас одинаково. Проблема в set_.drop("income_cat", axis=1, inplace=True)
. Похоже, что между фреймом данных set_
и strat_train_set
и strat_test_set
есть слабая ссылка.
for set_ in (strat_train_set, strat_test_set):
print(set_._is_copy)
При этом вы получаете:
<weakref at 0x128b30598; to 'DataFrame' at 0x128b355c0>
<weakref at 0x128b30598; to 'DataFrame' at 0x128b355c0>
Это может привести к SettingWithCopyWarning
, поскольку он пытается преобразовать копию фрейма данных и применить эти изменения к исходным.
Я провел некоторое исследование, и, насколько я понимаю, это то, что находится под капотом SettingWithCopyWarning
: каждый раз, когда фрейм данных df
создается из другого фрейма df_orig
, pandas
использует некоторые эвристики, чтобы определить, скопированы ли данные может быть неявно из df_orig
, что менее опытный пользователь может не знать. Если да, то в поле _is_copy
df
устанавливается значение слабая ссылкаdf_orig
. Позже, когда будет предпринята попытка обновить df
на месте, pandas
определит, следует ли отображать SettingWithCopyWarning
, на основе df._is_copy
, а также некоторых других полей df
(обратите внимание, что 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"]=...
.
Пробовали ли вы сбросить индекс после использования
iloc
? Если вы анализируете подмножество, особенно если вы создаете/обновляете/вычисляете новые значения в том же фрейме данных, который был вырезан в подмножество оригинала, это предупреждение, как правило, появляется.