Как справиться с предупреждением SettingWithCopyWarning в Pandas
Описание проблемы
Я только что обновил свою библиотеку Pandas с версии 0.11 до 0.13.0rc1. Теперь в приложении появилось множество новых предупреждений. Одно из них выглядит так:
E:\FinReporter\FM_EXT.py:449: SettingWithCopyWarning: Значение пытается быть записано в копию среза DataFrame.
Попробуйте использовать .loc[row_index,col_indexer] = value вместо
quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE
Я хочу понять, что именно это значит? Нужны ли мне какие-либо изменения?
Как мне приостановить это предупреждение, если я все же настаиваю на использовании quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE
?
Функция, которая вызывает предупреждения
def _decode_stock_quote(list_of_150_stk_str):
"""декодирует веб-страницу и возвращает DataFrame"""
from cStringIO import StringIO
str_of_all = "".join(list_of_150_stk_str)
quote_df = pd.read_csv(StringIO(str_of_all), sep=',', names=list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg'))
quote_df.rename(columns={'A':'STK', 'B':'TOpen', 'C':'TPCLOSE', 'D':'TPrice', 'E':'THigh', 'F':'TLow', 'I':'TVol', 'J':'TAmt', 'e':'TDate', 'f':'TTime'}, inplace=True)
quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]
quote_df['TClose'] = quote_df['TPrice']
quote_df['RT'] = 100 * (quote_df['TPrice']/quote_df['TPCLOSE'] - 1)
quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE
quote_df['TAmt'] = quote_df['TAmt']/TAMT_SCALE
quote_df['STK_ID'] = quote_df['STK'].str.slice(13,19)
quote_df['STK_Name'] = quote_df['STK'].str.slice(21,30)
quote_df['TDate'] = quote_df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10])
return quote_df
Дополнительные сообщения о предупреждениях
E:\FinReporter\FM_EXT.py:449: SettingWithCopyWarning: Значение пытается быть записано в копию среза DataFrame.
Попробуйте использовать .loc[row_index,col_indexer] = value вместо
quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE
E:\FinReporter\FM_EXT.py:450: SettingWithCopyWarning: Значение пытается быть записано в копию среза DataFrame.
Попробуйте использовать .loc[row_index,col_indexer] = value вместо
quote_df['TAmt'] = quote_df['TAmt']/TAMT_SCALE
E:\FinReporter\FM_EXT.py:453: SettingWithCopyWarning: Значение пытается быть записано в копию среза DataFrame.
Попробуйте использовать .loc[row_index,col_indexer] = value вместо
quote_df['TDate'] = quote_df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10])
5 ответ(ов)
В общем, основная цель предупреждения SettingWithCopyWarning
заключается в том, чтобы показать пользователям (особенно новичкам), что они могут работать с копией, а не с оригиналом, как они думают. Существуют ложные срабатывания (иначе говоря, если вы точно знаете, что делаете, это может быть в порядке). Одним из решений является простое отключение предупреждения (по умолчанию warn), как предложил @Garrett.
Вот еще один вариант:
In [1]: df = DataFrame(np.random.randn(5, 2), columns=list('AB'))
In [2]: dfa = df.ix[:, [1, 0]]
In [3]: dfa.is_copy
Out[3]: True
In [4]: dfa['A'] /= 2
/usr/local/bin/ipython:1: SettingWithCopyWarning: Значение пытается быть установлено на копии среза из DataFrame.
Попробуйте использовать .loc[row_index, col_indexer] = значение вместо этого
#!/usr/local/bin/python
Вы можете установить флаг is_copy
в значение False
, что фактически отключит проверку для этого объекта:
In [5]: dfa.is_copy = False
In [6]: dfa['A'] /= 2
Если вы явно создадите копию, то никаких дальнейших предупреждений не будет:
In [7]: dfa = df.ix[:, [1, 0]].copy()
In [8]: dfa['A'] /= 2
Код, который показал автор вопроса, хотя и законный и, вероятно, что я тоже так делаю, технически является примером для этого предупреждения, а не ложным срабатыванием. Другой способ не получать предупреждение — это выполнить операцию выбора с помощью reindex
, например:
quote_df = quote_df.reindex(columns=['STK', ...])
Или:
quote_df = quote_df.reindex(['STK', ...], axis=1) # версия 0.21
Предупреждение о копировании в DataFrame Pandas
Когда вы делаете что-то вроде этого:
quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]
Метод pandas.ix
в этом случае возвращает новый, независимый DataFrame.
Любые значения, которые вы решите изменить в этом DataFrame, не будут изменять исходный DataFrame.
Вот о чем и пытается вас предупредить Pandas.
Почему использование .ix
- плохая идея
Объект .ix
пытается делать больше одного дела, и для любого, кто читал о чистом коде, это является сильным запахом кода.
Рассмотрим этот DataFrame:
df = pd.DataFrame({"a": [1,2,3,4], "b": [1,1,2,2]})
Два различных поведения:
dfcopy = df.ix[:,["a"]]
dfcopy.a.ix[0] = 2
Первое поведение: dfcopy
теперь является независимым DataFrame. Изменение его не повлияет на df
.
df.ix[0, "a"] = 3
Второе поведение: Это изменяет исходный DataFrame.
Используйте .loc
вместо этого
Разработчики Pandas осознали, что объект .ix
довольно непрактичен и создали два новых объекта, которые помогают при доступе к данным и их присвоении (другой - .iloc
).
.loc
работает быстрее, потому что не пытается создать копию данных.
.loc
предназначен для изменения существующего DataFrame на месте, что более эффективно по памяти.
.loc
предсказуем: у него одно поведение.
Решение
То, что вы делаете в вашем примере кода, это загружаете большой файл с множеством столбцов, а затем модифицируете его, делая его меньше.
Функция pd.read_csv
может помочь вам с этим и значительно ускорить загрузку файла.
Так что вместо того, чтобы делать это:
quote_df = pd.read_csv(StringIO(str_of_all), sep=',', names=list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg')) #dtype={'A': object, 'B': object, 'C': np.float64}
quote_df.rename(columns={'A':'STK', 'B':'TOpen', 'C':'TPCLOSE', 'D':'TPrice', 'E':'THigh', 'F':'TLow', 'I':'TVol', 'J':'TAmt', 'e':'TDate', 'f':'TTime'}, inplace=True)
quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]
Сделайте так:
columns = ['STK', 'TPrice', 'TPCLOSE', 'TOpen', 'THigh', 'TLow', 'TVol', 'TAmt', 'TDate', 'TTime']
df = pd.read_csv(StringIO(str_of_all), sep=',', usecols=[0,3,2,1,4,5,8,9,30,31])
df.columns = columns
Это позволит читать только те столбцы, которые вам нужны, и правильно их именовать. Не нужно использовать коварный объект .ix
для выполнения магических манипуляций.
Эта тема действительно вызывает путаницу в Pandas. К счастью, у нее есть относительно простое решение.
Проблема заключается в том, что не всегда ясно, возвращают ли операции фильтрации данных (например, loc
) копию или представление DataFrame. Следующее использование такого отфильтрованного DataFrame может быть поэтому запутанным.
Простое решение (если вам не нужно работать с очень большими наборами данных):
Когда вам нужно обновить любые значения, всегда убедитесь, что вы явно создаете копию DataFrame перед присваиванием.
df # Некоторый DataFrame
df = df.loc[:, 0:2] # Некоторая фильтрация (неясно, возвращается ли представление или копия)
df = df.copy() # Убедимся, что создается копия
df[df["Name"] == "John"] = "Johny" # Присваивание можно теперь выполнить (без предупреждений)
Вы можете использовать следующий код для отключения предупреждений о цепочечной присваивании в Pandas:
import pandas as pd
# ...
pd.set_option('mode.chained_assignment', None)
Этот код устанавливает значение параметра mode.chained_assignment
в None
, что отключает предупреждения при выполнении присваиваний через цепочки.
У меня возникла проблема с использованием метода .apply()
при создании нового DataFrame на основе уже существующего DataFrame, к которому я применял метод .query()
. Например:
prop_df = df.query('column == "value"')
prop_df['new_column'] = prop_df.apply(function, axis=1)
Это вызывало ошибку. Решением, которое помогло бы в этой ситуации, было бы изменить код на следующий:
prop_df = df.copy(deep=True)
prop_df = prop_df.query('column == "value"')
prop_df['new_column'] = prop_df.apply(function, axis=1)
Однако данный подход неэффективен, особенно при работе с большими DataFrame, так как требует создания новой копии.
Если вы используете метод .apply()
для создания нового столбца и его значений, более эффективное решение проблемы заключается в добавлении .reset_index(drop=True)
:
prop_df = df.query('column == "value"').reset_index(drop=True)
prop_df['new_column'] = prop_df.apply(function, axis=1)
Таким образом, вы избавитесь от ошибки и улучшите производительность работы с большими данными.
Как добавить новый столбец к существующему DataFrame
Переименование названий столбцов в Pandas
"Красивая печать всей Series / DataFrame в Pandas"
Преобразование списка словарей в DataFrame pandas
Объединение двух столбцов текста в DataFrame pandas