Как бороться с SettingWithCopyWarning
Пандами?
Этот пост предназначен для читателей, которые,
- Хотелось бы понять, что означает это предупреждение
- Хотелось бы понять разные способы подавления этого предупреждения
- Хотелось бы понять, как улучшить свой код и следовать передовой практике, чтобы избежать этого предупреждения в будущем.
Настроить
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
и iat
.
Больше можно найти в документации .
Примечание.
Все операции логического индексирования, выполненные с loc
помощью, также можно выполнить с помощью iloc
. Единственное отличие состоит в том, что iloc
ожидаются либо целые числа / позиции для индекса, либо пустой массив логических значений, а также целочисленные / позиционные индексы для столбцов.
Например,
df.loc[df.A > 5, 'B'] = 4
Может быть написано НАС
df.iloc[(df.A > 5).values, 1] = 4
А также,
df.loc[1, 'A'] = 100
Может быть написано как
df.iloc[1, 0] = 100
И так далее.
Просто скажи мне, как подавить предупреждение!
Рассмотрим простую операцию над столбцом «А» df
. Выбор «А» и деление на 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
Есть несколько способов напрямую отключить это предупреждение:
Сделать deepcopy
df2 = df[['A']].copy(deep=True)
df2['A'] /= 2
Изменениеpd.options.mode.chained_assignment
Может быть установлено None
, "warn"
или "raise"
. "warn"
по умолчанию. None
полностью отключит предупреждение и "raise"
выдаст сообщение SettingWithCopyError
, не позволяющее завершить операцию.
pd.options.mode.chained_assignment = None
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
У меня есть датафрейм
df
A B C D E
0 5 0 3 3 7
1 9 3 5 2 4
2 7 6 8 8 1
Я хочу присвоить значения в столбце "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
Вопрос 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
Я пытаюсь установить значения на основе некоторых условий. У меня есть датафрейм
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
, как и раньше.