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

Фон

Я только что обновил свой Pandas от 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])

Ответ 1

SettingWithCopyWarning был создан для 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'

Ответ 2

В общем, смысл 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

Ответ 3

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

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

  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 был создан для SettingWithCopyWarning " 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 и 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 не SettingWithCopyError предупреждение полностью, а "raise" вызовет SettingWithCopyError, не позволяющую 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, как и раньше.

Ответ 4

Pandas DataFrame предупреждение о копировании

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

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

pandas.ix этом случае 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: dfcopy теперь является отдельным фреймом данных. Изменение не изменит df

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

Поведение два: Это меняет исходный фрейм данных.


.loc этого используйте .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 магических вещей.

Ответ 5

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

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

/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

Ура!

Ответ 6

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

Сделайте .copy(deep=False) после .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)

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

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

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

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

Ответ 7

Если вы назначили срез переменной и хотите установить ее с помощью следующей переменной:

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

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

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

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

Ответ 8

Это должно работать:

quote_df.loc[:,'TVol'] = quote_df['TVol']/TVOL_SCALE

Ответ 9

Я думаю, вы могли бы избежать всей этой проблемы:

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]),
    )
)

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

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

Ответ 10

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

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

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. Несмотря на то, что обе работают очень хорошо, и результат такой же, таким образом мне не нужно отключать предупреждения (= хранить их для других проблем с индексацией цепочки где-то еще).

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

Ответ 11

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

Может быть, разъяснение для других начинающих, как я (я из 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

починил это.

Ответ 12

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

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

Ответ 13

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

Проблема в том, что не всегда понятно, возвращают ли операции фильтрации данных (например, 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)