Как бороться с SettingWithCopyWarning в панд?

фон

Я только что обновил своих панд с 0.11 до 0.13.0rc1. Теперь, приложение выскакивает много новых предупреждений. Один из них такой:--7-->

E:FinReporterFM_EXT.py:449: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
  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):
    """decode the webpage and return 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')) #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]]
    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)#.decode('gb2312')
    quote_df['TDate']  = quote_df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10])

    return quote_df

больше сообщений об ошибках

E:FinReporterFM_EXT.py:449: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
  quote_df['TVol']   = quote_df['TVol']/TVOL_SCALE
E:FinReporterFM_EXT.py:450: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
  quote_df['TAmt']   = quote_df['TAmt']/TAMT_SCALE
E:FinReporterFM_EXT.py:453: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
  quote_df['TDate']  = quote_df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10])

4 ответов


на SettingWithCopyWarning был создан, чтобы отметить потенциально запутанные" цепные " назначения, такие как следующие, которые не всегда работают так, как ожидалось, особенно когда первый выбор возвращает скопировать. [см.GH5390 и GH5597 на фоне обсуждения.]

df[df['A'] > 2]['B'] = new_val  # new_val not set in df

предупреждение предлагает предложение переписать следующим образом:

df.loc[df['A'] > 2, 'B'] = new_val

однако это не соответствует вашему использованию, что эквивалентно кому:

df = df[df['A'] > 2]
df['B'] = new_val

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

pd.options.mode.chained_assignment = None  # default='warn'

в общем смысл SettingWithCopyWarning показать пользователям (и esp новым пользователям), что они мая работать на копии, а не на оригинале, как они думают. Там are ложные срабатывания (IOW вы знаете, что делаете, так что это ok). Одна из возможностей-просто отключить (по умолчанию предупредить) предупреждение, как предлагает @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: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
  #!/usr/local/bin/python

вы можете установить is_copy флаг False, который эффективно отключит проверку, *для этого объекта"

In [5]: dfa.is_copy = False

In [6]: dfa['A'] /= 2

если вы explicity копировать, то вы знаете, что вы делаете, поэтому никакого дальнейшего предупреждения не произойдет.

In [7]: dfa = df.ix[:,[1,0]].copy()

In [8]: dfa['A'] /= 2

код, который OP показывает выше, в то время как законный, и, вероятно, что-то я делаю, технически является случаем для этого предупреждения, а не ложным положительным. Другой способ не имейте предупреждение, чтобы сделать операцию выбора через reindex, например,

quote_df = quote_df.reindex(columns=['STK',.......])

или

quote_df = quote_df.reindex(['STK',.......], axis=1) # v.0.21

Pandas dataframe копировать предупреждение

когда вы идете и делаете что-то вроде этого:

quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]

pandas.ix в этом случае возвращает новый автономный фрейм данных.

любые значения, которые вы решите изменить в этом фрейме данных, не изменят исходный фрейм данных.

это то, о чем панды пытаются предупредить вас.

почему .ix - это плохая идея

на .ix объект пытается сделать более одной вещи, и для любого, кто читал что-либо о чистом коде, это сильный запах.

учитывая этот фрейм данных:

df = pd.DataFrame({"a": [1,2,3,4], "b": [1,1,2,2]})

два варианта:

dfcopy = df.ix[:,["a"]]
dfcopy.a.ix[0] = 2

поведение одного: dfcopy теперь является автономным фреймом данных. Изменение его не изменится df

df.ix[0, "a"] = 3

поведение два: это изменяет исходный фрейм данных.

использовать .loc вместо

разработчики панд признали, что .ix "объект" был довольно вонючий[спекулятивно] и, таким образом, создал два новых объекта, которые помогают в присоединении и присвоении данных. (Другой .iloc)

.loc быстрее, потому что он не пытается создать копию данных.

.loc предназначен для изменения существующего фрейма данных на месте, что более эффективно для памяти.

.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

это будет только читать столбцы, которые вас интересуют, и назвать их правильно. Нет необходимости использовать зло


чтобы устранить любые сомнения, моим решением было сделать глубокую копию среза вместо обычной копии. Это может быть неприменимо в зависимости от вашего контекста (ограничения памяти / размер среза, потенциал для снижения производительности - особенно если копия происходит в цикле, как это было для меня, и т. д...)

чтобы быть ясным, вот предупреждение, которое я получил:

/opt/anaconda3/lib/python3.6/site-packages/ipykernel/__main__.py:54:
SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation:
http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy

иллюстрации

у меня были сомнения, что предупреждение было выдано из-за колонны был падение на копию среза. Хотя технически не пытались установить значение в копии среза, это все еще было модификацией копии среза. Ниже приведены (упрощенные) шаги, которые я предпринял, чтобы подтвердить подозрение, я надеюсь, что это поможет тем из нас, кто пытается понять предупреждение.

Пример 1: падение столбца на оригинал влияет на копию

мы знали, что уже, но это здоровое напоминание. Это не что предупреждение об этом.

>> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]}
>> df1 = pd.DataFrame(data1)
>> df1

    A   B
0   111 121
1   112 122
2   113 123


>> df2 = df1
>> df2

A   B
0   111 121
1   112 122
2   113 123

# Dropping a column on df1 affects df2
>> df1.drop('A', axis=1, inplace=True)
>> df2
    B
0   121
1   122
2   123

можно избежать изменений, внесенных в df1, чтобы повлиять на df2

>> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]}
>> df1 = pd.DataFrame(data1)
>> df1

A   B
0   111 121
1   112 122
2   113 123

>> import copy
>> df2 = copy.deepcopy(df1)
>> df2
A   B
0   111 121
1   112 122
2   113 123

# Dropping a column on df1 does not affect df2
>> df1.drop('A', axis=1, inplace=True)
>> df2
    A   B
0   111 121
1   112 122
2   113 123

Пример 2: Удаление столбца на копии может повлиять на оригинал

Это на самом деле иллюстрирует предупреждение.

>> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]}
>> df1 = pd.DataFrame(data1)
>> df1

    A   B
0   111 121
1   112 122
2   113 123

>> df2 = df1
>> df2

    A   B
0   111 121
1   112 122
2   113 123

# Dropping a column on df2 can affect df1
# No slice involved here, but I believe the principle remains the same?
# Let me know if not
>> df2.drop('A', axis=1, inplace=True)
>> df1

B
0   121
1   122
2   123

можно избежать изменений, внесенных в df2, чтобы повлиять на df1

>> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]}
>> df1 = pd.DataFrame(data1)
>> df1

    A   B
0   111 121
1   112 122
2   113 123

>> import copy
>> df2 = copy.deepcopy(df1)
>> df2

A   B
0   111 121
1   112 122
2   113 123

>> df2.drop('A', axis=1, inplace=True)
>> df1

A   B
0   111 121
1   112 122
2   113 123

Ура!