Почему важно явно использовать copy и стараться избегать связанных операций при изменении в выборках датасета.
Разберемся сначала с понятиями ‘view‘ (представление) и ‘copy‘ (копия).
view – срез, сформированный из оригинального объекта, а copy (копия) – новый объект. Когда мы меняем представления (view), мы меняем исходный объект, а изменения копии меняют новый объект.
Важным моментом для работы с pandas является понимание работы связанных (chained) операций и что получается в результате.
Допустим, имеем
1 2 3 4 5 6 7 8 |
print(df) A B C 0 1 1 5 1 2 2 6 2 3 3 7 3 4 4 8 df[df['A'] > 1]['C'] = 444 |
В результате выполнения, мы не получим изменения в оригинальном датафрейме, а получим изменения в копии и скорее всего предупреждение SettingWithCopyWarning (это зависит от ваших настроек mode.chained_assignment).
Что бы понять, почему мы получаем копию, стоит вспомнить, что фактически здесь выполняется два действия:
1) получение (get) операция, где в результате получаем датафрейм, удовлетворяющий условию df[df['A'] > 1] – все строки, где значение A больше 1
2) операция установки (set), которая происходит с новым датафреймом
Такой же результат будет и при использовании loc.
1 |
df.loc[df['A'] > 1]['C'] = 444 |
Для замены в оригинальном датафрейме следует воспользоваться полной записью loc (об этом и сообщает предупрждение SettingWithCopyWarning), т.е. присваиванием в одну операцию.
1 2 |
#.loc[row_indexer,col_indexer] = value instead df.loc[df['A'] > 1, 'C'] = 444 |
Но на этом приключения с копией и представлением не заканчиваются, так как имеет место еще и скрытые связанные операции (Hidden Chaining).
Получим новый датафрейм на база оригинального
1 2 3 4 5 6 7 8 9 10 11 |
temp = df.loc[df['A'] > 1] A B C 1 2 2 444 2 3 3 444 3 4 4 444 print(temp.shape) # (3, 3) # поменяем значение temp.loc[2, 'A'] = 77 |
и получим SettingWithCopyWarning
А почему? Потому что связанная операция может выполняться как в одной строке, так и в двух! И между этими строками может быть какой-то код.
Результат выполнения в этом случае будет ожидаем:
1 2 3 4 |
print(temp.loc[2, 'A']) # 77 print(df.loc[2, 'A']) # 3 |
Но для pandas мы все так же пытались изменить оригинальный датасет, и случайно сделали копию, хотя это не так.
Во избежания этих ситуаций стоит явно использовать метод copy.
1 2 3 4 5 6 7 8 9 10 11 |
new_temp = df.loc[df['A'] > 1].copy() # A B C 1 2 2 444 2 3 3 444 3 4 4 444 new_temp.loc[2, 'A'] = 77 print(new_temp.loc[2, 'A']) # 77 print(df.loc[2, 'A']) # 3 |
И никаких SettingWithCopyWarning предупреждений!
Для работы над view, стоит избегать связанных операций и использовать присваивание в одну операцию с использованием loc.
1 |
df.loc[df['A'] > 1, 'C'] = 444 |
Для работы с SettingWithCopyWarning можно настроить варианты работы через опцию mode.chained_assignment:
‘raise’ — вызвать исключение вместо предупреждения
‘warn’ — выдать предупреждение (по умолчанию)
None — полностью отключить предупреждение
1 2 3 4 |
pd.set_option('mode.chained_assignment','raise') pd.set_option('mode.chained_assignment','warn') pd.option_context('mode.chained_assignment', None) pd.options.mode.chained_assignment = None # None|'warn'|'raise' |