Як боротися з 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
І так далі.
Просто скажіть, як придушити попередження!
Розглянемо просту операцію на стовпці "A" 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 У
мене є DataFrame
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
Я намагаюся підмножити значення, виходячи з якоїсь умови. У мене є DataFrame
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
, як і раніше.