Как бороться с SettingWithCopyWarning в Pandas?


631

Фон

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

E:\FinReporter\FM_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:\FinReporter\FM_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:\FinReporter\FM_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:\FinReporter\FM_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])

2
Вот контекстный менеджер, чтобы временно установить уровень предупреждения gist.github.com/notbanker/2be3ed34539c86e22ffdd88fd95ad8bc
Питер Коттон

2
Вы можете использовать df.set_valueдокументацию здесь - pandas.pydata.org/pandas-docs/stable/generated/…
leonprou

1
pandas.pydata.org/pandas-docs/stable/… официальный документ подробно объясняет
wyx

3
@leonprou df.set_valueустарела. Панды теперь рекомендует использовать .at[]или .iat[]вместо. документы здесь pandas.pydata.org/pandas-docs/stable/generated/…
Kyle C

Я удивлен, что никто не упомянул option_contextздесь панд : pandas.pydata.org/pandas-docs/stable/user_guide/options.html , используйте какwith pd.option_context("mode.chained_assignment", None): [...]
m-dz

Ответы:


796

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

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

import pandas as pd
pd.options.mode.chained_assignment = None  # default='warn'

34
Я думаю, что в основном я бы за то, чтобы вообще не предупреждать об этом. Если вы работаете с синтаксисом связанного присваивания, вы можете определенно определить порядок индексации, который должен произойти, чтобы он работал должным образом в любой конкретной ситуации. Я думаю, что это чрезмерно параноидально, что есть эти исчерпывающие меры предосторожности. В том же духе, что и «позволять всем быть взрослыми» о «частных» методах или атрибутах класса, я думаю, что для панд лучше, чтобы пользователи были взрослыми о связанных назначениях. Используйте их, только если знаете, что делаете.
августа

48
Немного не в духе пытаться предупредить людей, когда они ходят взламывать альтернативы. Методы доступа Pandas в более новом стиле (улучшенные .ix, улучшенные .ilocи т. Д.) Могут определенно рассматриваться как «основной путь», не предупреждая всех непрерывно о других способах. Вместо этого позвольте им быть взрослыми, и если они хотят выполнять связанное назначение, пусть будет так. Мои два цента в любом случае. Здесь часто встречаются недовольные комментарии разработчиков Pandas, когда цепные задания будут работать для решения проблемы, но не будут рассматриваться как «основной» способ сделать это.
13

8
@EMS проблема в том, что из кода, где делается копия или представление , не всегда ясно, и из-за этой проблемы возникает ряд ошибок / путаницы. Мы рассматривали возможность добавления файла / параметров rc для автоматической настройки, что может быть более полезным, учитывая то, как работает настройка с предупреждением о копировании.
Джефф Тратнер

3
Причина для предупреждения, конечно, для людей, обновляющих старый код. И мне определенно нужно предупреждение, потому что я имею дело с очень уродливым старым кодом.
Томас Эндрюс

16
Кстати, я обнаружил, что отключение предупреждения chained_assignment: pd.options.mode.chained_assignment = Noneмой код работает примерно в 6 раз быстрее. Кто-нибудь еще испытывал подобные результаты?
Мюон

209

Как бороться с SettingWithCopyWarningПандами?

Этот пост предназначен для читателей, которые,

  1. Хотелось бы понять, что означает это предупреждение
  2. Хотелось бы понять разные способы подавления этого предупреждения
  3. Хотелось бы понять, как улучшить свой код и следовать передовой практике, чтобы избежать этого предупреждения в будущем.

Настроить

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

И так далее.


Просто скажи мне, как подавить предупреждение!

Рассмотрим простую операцию над столбцом «А» 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

Есть несколько способов напрямую отключить это предупреждение:

  1. Сделать deepcopy

    df2 = df[['A']].copy(deep=True)
    df2['A'] /= 2
  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
У меня есть датафрейм

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
Я пытаюсь установить значения на основе некоторых условий. У меня есть датафрейм

   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, как и раньше.


7
PS: Дайте мне знать, если ваша ситуация не охвачена списком вопросов раздела 3. Я исправлю свой пост.
cs95

150

В целом, цель SettingWithCopyWarningсостоит в том, чтобы показать пользователям (и особенно новым пользователям), что они могут работать с копией, а не с оригиналом, как они думают. Там являются ложными срабатывания (IOW , если вы знаете , что вы делаете , это может быть хорошо ). Одна из возможностей - просто отключить (по умолчанию предупреждение ) предупреждение, как предлагает @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

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

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

Спасибо за информацию и обсуждение, я просто отключаю предупреждение, чтобы консоль молчала об этом. Это звучит как представление и таблица в базе данных SQL. Мне нужно больше знать о преимуществах введения концепции «копирования», но, по-моему, это немного обременительно, чтобы позаботиться о тонкой семантической разнице в синтаксисе.
bigbug

19
Я согласен с копией (); это ясно, и это исправило мою проблему (которая была ложно положительной).
rdchambers

5
после обновления 0.16я вижу еще много ложных срабатываний, проблема со ложными срабатываниями - это научиться игнорировать это, хотя иногда это и законно.
дашесы

3
@dashesy вы упускаете суть. иногда, может быть, даже большую часть времени это может работать. Но это может произойти, например, если кадр больше / меньше, или вы добавляете столбец, скажем, другого типа d, что он не работает. В этом-то и дело. Вы делаете то, что может работать, но это не гарантировано. Это очень отличается от предупреждений об устаревании. Если вы хотите продолжать использовать его, и он работает, отлично. Но будь предупрежден.
Джефф

3
@Джефф имеет смысл сейчас, так что это undefinedповедение. Во всяком случае, это должно привести к ошибке (чтобы избежать ошибок, замеченных в C), поскольку apiтекущее поведение предупреждения имеет смысл для обратной совместимости. И я заставлю их бросать, чтобы ловить их как ошибки в моем производственном коде ( warnings.filterwarnings('error', r'SettingWithCopyWarning). Кроме того, предложение использовать .locиногда также не помогает (если оно в группе).
дашесы

41

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

Это будет читать только интересующие вас столбцы и правильно называть их. Нет необходимости использовать злой .ixобъект, чтобы делать магические вещи.


«Разработчики панд признали, что объект .ix был довольно вонючим [спекулятивно] и, таким образом, создали два новых объекта» - что это за другой?
jf328

3
@ jf328 .iloc Я думаю
Брайан Бьен,

1
Да, это так .iloc. Это два основных метода индексации структур данных панд. Подробнее читайте в документации.
Ниндзяканнон

Как заменить столбец DataFrame временными метками на столбец с указанием даты или времени или строки?
болдник

@boldnik Проверьте этот ответ stackoverflow.com/a/37453925/3730397
firelynx

20

Здесь я отвечаю на вопрос напрямую. Как с этим бороться?

Сделайте .copy(deep=False)после того, как вы нарежете. Смотрите pandas.DataFrame.copy .

Подождите, не вернет ли кусок копию? В конце концов, это то, что пытается сказать предупреждающее сообщение? Прочитайте длинный ответ:

import pandas as pd
df = pd.DataFrame({'x':[1,2,3]})

Это дает предупреждение:

df0 = df[df.x>2]
df0['foo'] = 'bar'

Это не:

df1 = df[df.x>2].copy(deep=False)
df1['foo'] = 'bar'

Оба df0и df1являются DataFrameобъектами, но что-то в них отличается, что позволяет пандам распечатать предупреждение. Давайте выясним, что это.

import inspect
slice= df[df.x>2]
slice_copy = df[df.x>2].copy(deep=False)
inspect.getmembers(slice)
inspect.getmembers(slice_copy)

Используя выбранный вами diff-инструмент, вы увидите, что кроме пары адресов, единственное существенное различие заключается в следующем:

|          | slice   | slice_copy |
| _is_copy | weakref | None       |

Метод, который решает, предупреждать ли, - это DataFrame._check_setitem_copyкакие проверки _is_copy. Итак, поехали. Сделайте copyтак, чтобы ваш DataFrame не был _is_copy.

Предупреждение предлагает использовать .loc, но если вы используете .locна кадре _is_copy, вы все равно получите то же предупреждение. Вводя в заблуждение? Да. Раздражает? Вы ставите. Полезно? Потенциально, когда используется цепное назначение. Но он не может правильно определить назначение цепи и распечатывает предупреждение без разбора.


11

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

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

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

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

df  # Some DataFrame
df = df.loc[:, 0:2]  # Some filtering (unsure whether a view or copy is returned)
df = df.copy()  # Ensuring a copy is made
df[df["Name"] == "John"] = "Johny"  # Assignment can be done now (no warning)

Есть опечатка: косвенно должно быть явно
s9527

7

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

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

/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

Ура!



4

Некоторые могут хотеть просто подавить предупреждение:

class SupressSettingWithCopyWarning:
    def __enter__(self):
        pd.options.mode.chained_assignment = None

    def __exit__(self, *args):
        pd.options.mode.chained_assignment = 'warn'

with SupressSettingWithCopyWarning():
    #code that produces warning

3

Если вы присвоили срез переменной, и хотите установить ее, как показано ниже:

df2 = df[df['A'] > 2]
df2['B'] = value

И вы не хотите использовать решение Jeffs, потому что ваши условия вычислений слишком df2длинные или по какой-то другой причине, тогда вы можете использовать следующее:

df.loc[df2.index.tolist(), 'B'] = value

df2.index.tolist() возвращает индексы из всех записей в df2, которые затем будут использоваться для установки столбца B в исходном кадре данных.


это в 9 раз дороже, чем df ["B"] = значение
Claudiu Creanga

Можете ли вы объяснить это более глубоко @ClaudiuCreanga?
gies0r

2

Для меня эта проблема произошла в следующем> упрощенном <примере. И я также смог решить ее (надеюсь, с правильным решением):

старый код с предупреждением:

def update_old_dataframe(old_dataframe, new_dataframe):
    for new_index, new_row in new_dataframe.iterrorws():
        old_dataframe.loc[new_index] = update_row(old_dataframe.loc[new_index], new_row)

def update_row(old_row, new_row):
    for field in [list_of_columns]:
        # line with warning because of chain indexing old_dataframe[new_index][field]
        old_row[field] = new_row[field]  
    return old_row

Это напечатало предупреждение для строки old_row[field] = new_row[field]

Поскольку строки в методе update_row на самом деле являются типами Series, я заменил строку на:

old_row.at[field] = new_row.at[field]

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

Я надеюсь, что это может кому-то помочь.


2

Вы можете избежать всей проблемы, как это, я считаю:

return (
    pd.read_csv(StringIO(str_of_all), sep=',', names=list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg')) #dtype={'A': object, 'B': object, 'C': np.float64}
    .rename(columns={'A':'STK', 'B':'TOpen', 'C':'TPCLOSE', 'D':'TPrice', 'E':'THigh', 'F':'TLow', 'I':'TVol', 'J':'TAmt', 'e':'TDate', 'f':'TTime'}, inplace=True)
    .ix[:,[0,3,2,1,4,5,8,9,30,31]]
    .assign(
        TClose=lambda df: df['TPrice'],
        RT=lambda df: 100 * (df['TPrice']/quote_df['TPCLOSE'] - 1),
        TVol=lambda df: df['TVol']/TVOL_SCALE,
        TAmt=lambda df: df['TAmt']/TAMT_SCALE,
        STK_ID=lambda df: df['STK'].str.slice(13,19),
        STK_Name=lambda df: df['STK'].str.slice(21,30)#.decode('gb2312'),
        TDate=lambda df: df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10]),
    )
)

Используя Назначить. Из документации : Назначьте новые столбцы в DataFrame, возвращая новый объект (копию) со всеми исходными столбцами в дополнение к новым.

См. Статью Тома Аугспургера о цепочках методов в пандах: https://tomaugspurger.github.io/method-chaining.


2

Вопрос / замечание для начинающих

Может быть, разъяснение для других начинающих, как я (я из R, который, кажется, работает несколько иначе под капотом). Следующий безвредный на вид и функциональный код продолжал выдавать предупреждение SettingWithCopy, и я не мог понять, почему. Я и прочитал, и понял, что было получено с помощью «цепной индексации», но мой код не содержит ничего:

def plot(pdb, df, title, **kw):
    df['target'] = (df['ogg'] + df['ugg']) / 2
    # ...

Но потом, слишком поздно, я посмотрел, где вызывается функция plot ():

    df = data[data['anz_emw'] > 0]
    pixbuf = plot(pdb, df, title)

Таким образом, "df" - это не фрейм данных, а объект, который каким-то образом запоминает, что он был создан путем индексации фрейма данных (так что это представление?), Которое сделает линию в plot ()

 df['target'] = ...

эквивалентно

 data[data['anz_emw'] > 0]['target'] = ...

которая является цепной индексацией. Я правильно понял?

Тем не мение,

def plot(pdb, df, title, **kw):
    df.loc[:,'target'] = (df['ogg'] + df['ugg']) / 2

починил это.


1

Поскольку этот вопрос уже полностью объяснен и обсужден в существующих ответах, я просто предоставлю аккуратный pandasподход к использованию менеджера контекста pandas.option_context(ссылки на документы и пример). ) - абсолютно не нужно создавать собственный класс со всеми методами dunder и другими функциями. и свистит.

Сначала код самого менеджера контекста:

from contextlib import contextmanager

@contextmanager
def SuppressPandasWarning():
    with pd.option_context("mode.chained_assignment", None):
        yield

Тогда пример:

import pandas as pd
from string import ascii_letters

a = pd.DataFrame({"A": list(ascii_letters[0:4]), "B": range(0,4)})

mask = a["A"].isin(["c", "d"])
# Even shallow copy below is enough to not raise the warning, but why is a mystery to me.
b = a.loc[mask]  # .copy(deep=False)

# Raises the `SettingWithCopyWarning`
b["B"] = b["B"] * 2

# Does not!
with SuppressPandasWarning():
    b["B"] = b["B"] * 2

Стоит заметить, что оба подхода не изменяются a, что немного удивляет меня, и даже мелкая копия df .copy(deep=False)помешает появлению этого предупреждения (насколько я понимаю, мелкая копия должна хотя бы изменить a, но это не так). т. pandasволшебство.).


хммм, я понимаю это, если предупреждение подняло что-то не так, очевидно, поэтому лучше избегать предупреждения, как подавить это, что вы думаете?
jezrael

Нет, предупреждение - это просто предупреждение. Как и здесь, он предупреждает вас о том, что что-то может быть не так, что приятно знать, но если вы знаете, что и почему вы делаете, совершенно нормально подавить некоторые из них. См. Объяснение в переназначении ссылок в stackoverflow.com/a/20627316/4272484 .
m-dz

1

Я получил эту проблему .apply()при назначении нового кадра данных из ранее существующего кадра данных, на котором я использовал .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)

Однако это НЕ эффективно, особенно при использовании больших фреймов данных, из-за необходимости делать новую копию.

Если вы используете .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)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.