14

Как справиться с предупреждением SettingWithCopyWarning в Pandas

23

Описание проблемы

Я только что обновил свою библиотеку 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 ответ(ов)

1

В общем, основная цель предупреждения 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
0

Предупреждение о копировании в 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 для выполнения магических манипуляций.

0

Эта тема действительно вызывает путаницу в Pandas. К счастью, у нее есть относительно простое решение.

Проблема заключается в том, что не всегда ясно, возвращают ли операции фильтрации данных (например, loc) копию или представление DataFrame. Следующее использование такого отфильтрованного DataFrame может быть поэтому запутанным.

Простое решение (если вам не нужно работать с очень большими наборами данных):

Когда вам нужно обновить любые значения, всегда убедитесь, что вы явно создаете копию DataFrame перед присваиванием.

df  # Некоторый DataFrame
df = df.loc[:, 0:2]  # Некоторая фильтрация (неясно, возвращается ли представление или копия)
df = df.copy()  # Убедимся, что создается копия
df[df["Name"] == "John"] = "Johny"  # Присваивание можно теперь выполнить (без предупреждений)
0

Вы можете использовать следующий код для отключения предупреждений о цепочечной присваивании в Pandas:

import pandas as pd
# ...
pd.set_option('mode.chained_assignment', None)

Этот код устанавливает значение параметра mode.chained_assignment в None, что отключает предупреждения при выполнении присваиваний через цепочки.

0

У меня возникла проблема с использованием метода .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)

Таким образом, вы избавитесь от ошибки и улучшите производительность работы с большими данными.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь