Как бороться с SettingWithCopyWarning в 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])
24 ответа
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'
Этот пост предназначен для читателей, которые,
- Хотелось бы понять, что означает это предупреждение
- Хотелось бы понять разные способы подавления этого предупреждения
- Хотелось бы понять, как улучшить свой код и следовать передовой практике, чтобы избежать этого предупреждения в будущем.
Настроить
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
Есть несколько способов заставить замолчать это предупреждение, показанное ниже.
Делать deepcopy
df2 = df[['A']].copy(deep=True)
df2['A'] /= 2
Задавать is_copy=False
Отключите проверку для этого конкретного DataFrame, установив is_copy=False
,
df2.is_copy = False
df2['A'] /= 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, '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]
, Каков наилучший способ сделать это?
Вы можете использовать любой из следующих методов, чтобы сделать это.
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()
Или же,
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
, как прежде.
В общем то и дело 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
Здесь я отвечаю на вопрос напрямую. Как с этим бороться?
Делать .copy()
после того, как вы нарезаете1.
Подождите, не вернет ли кусок копию? В конце концов, это то, что пытается сказать предупреждающее сообщение? Прочитайте длинный ответ:
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()
df1['foo'] = 'bar'
И то и другое df0
а также df1
являются DataFrame
объекты, но что-то в них отличается, что позволяет пандам напечатать предупреждение. Давайте выясним, что это.
import inspect
test = df[df.x>2]
test_copy = df[df.x>2].copy()
inspect.getmembers(test)
inspect.getmembers(test_copy)
Используя выбранный вами diff-инструмент, вы увидите, что, кроме пары адресов, существенные различия заключаются в следующем:
| | test | test_copy |
| _is_copy | weakref | None |
| _is_view | True | False |
Метод, который решает, следует ли предупредить DataFrame._check_setitem_copy
который проверяет _is_copy
,
Предупреждение предлагает использовать .loc
, но если вы используете .loc
на раме, которая _is_copy
, вы все равно получите то же предупреждение. Ложная? Да. Раздражает? Вы ставите. Полезно? Потенциально, когда используется цепное назначение. Но он не может даже различить этот случай и печатает предупреждение без разбора.
1 Это создаст глубокую копию данных, поэтому вы, вероятно, не захотите использовать этот метод для больших фреймов данных или когда требуется мелкая копия. Предупреждение: если у вас есть объекты Python во фрейме данных, только ссылки будут скопированы в отличие от copy.deepcopy
, Больше информации в док.
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
объект, чтобы делать магические вещи.
Эта тема действительно сбивает с толку Pandas. К счастью, у этого есть относительно простое решение.
Проблема в том, что не всегда ясно, возвращают ли операции фильтрации данных (например, 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)
У меня работали:
import pandas as pd
# ...
pd.set_option('mode.chained_assignment', None)
У меня была эта проблема с .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)
Чтобы устранить любые сомнения, я решил сделать глубокую копию среза вместо обычной копии. Это может быть неприменимо в зависимости от вашего контекста (ограничения памяти / размер фрагмента, возможность снижения производительности, особенно если копирование происходит в цикле, как это было для меня, и т. Д.)
Чтобы быть ясным, вот предупреждение, которое я получил:
/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
Ура!
Это должно работать:
quote_df.loc[:,'TVol'] = quote_df['TVol']/TVOL_SCALE
Некоторые могут хотеть просто подавить предупреждение:
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
Поскольку этот вопрос уже полностью объяснен и обсужден в существующих ответах, я просто предоставлю аккуратный pandas
подход к диспетчеру контекста с использованием pandas.option_context
(ссылки на документацию и пример) - нет абсолютно никакой необходимости создавать собственный класс со всеми дандер-методами и прочими наворотами.
Сначала сам код диспетчера контекста:
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
магия.).
Вы можете избежать всей проблемы, как это, я считаю:
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
это может относиться только к numpy, что означает, что вам может потребоваться его импортировать, но данные, которые я использовал для своих примеров numpy, не были важны для расчетов, но вы можете просто остановить эту настройку с предупреждающим сообщением о копировании, используя эту 1 строку кода ниже ,
np.warnings.filterwarnings('ignore')
Вопрос / замечание для начинающих
Может быть, разъяснение для других начинающих, как я (я из 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
починил это.
Если вы присвоили срез переменной, и хотите установить ее с помощью переменной, как показано ниже:
df2 = df[df['A'] > 2]
df2['B'] = value
И вы не хотите использовать решение Джеффса, потому что ваше состояние вычислений df2
это долго или по какой-то другой причине, тогда вы можете использовать следующее:
df.loc[df2.index.tolist(), 'B'] = value
df2.index.tolist()
возвращает индексы из всех записей в df2, которые затем будут использоваться для установки столбца B в исходном кадре данных.
Я столкнулся с таким же предупреждением, когда выполнял эту часть своего кода:
def scaler(self, numericals):
scaler = MinMaxScaler()
self.data.loc[:, numericals[0]] = scaler.fit_transform(self.data.loc[:, numericals[0]])
self.data.loc[:, numericals[1]] = scaler.fit_transform(self.data.loc[:, numericals[1]])
который
scaler
это MinMaxScaler и
numericals[0]
содержит названия 3 моих числовых столбцов. предупреждение было удалено, когда я изменил код на:
def scaler(self, numericals):
scaler = MinMaxScaler()
self.data.loc[:][numericals[0]] = scaler.fit_transform(self.data.loc[:][numericals[0]])
self.data.loc[:][numericals[1]] = scaler.fit_transform(self.data.loc[:][numericals[1]])
Итак, просто измени
[:, ~]
к
[:][~]
Выбор списка столбцов и присвоение его переменной создает копию, и это по сути напоминает вам, что вы присваиваете копии новые значения. Начиная с версии pandas 1.5.0, в pandas есть режим копирования при записи (CoW) , который заставляет любой кадр данных/серию, полученный из другого, вести себя как копию; поэтому, когда он включен, значения в кадре данных/серии можно изменить только путем изменения самого объекта. Одним из последствий является то, что он никогда не будет поднят. Другой заключается в том, что цепное присвоение никогда не работает. Кроме того, копия создается только в том случае, если данные используются совместно с другим объектом (обычно большинство методов pandas создают копию, что замедляет работу кода), поэтому операции pandas выполняются быстрее с CoW.
Планируется, что это поведение по умолчанию в pandas 3.0, но на данный момент вам необходимо его включить.
Чтобы включить его глобально,
pd.options.mode.copy_on_write = True
или включить его локально с помощью контекстного менеджера:
with pd.option_context("mode.copy_on_write", True):
# do operations
Пример 1 (SettingWithCopyWarning
замолкает):
def func():
df = pd.DataFrame({'A': range(5), 'B': range(5,0,-1)})
df1 = df[['B']] # select a list of columns
df1.loc[0, 'B'] = 1 # assign a value to the copy
func() # <---- SettingWithCopyWarning
pd.options.mode.copy_on_write = True
func() # <---- no warning
Пример 2 (цепное присвоение не работает):
pd.options.mode.copy_on_write = False
df = pd.DataFrame({'A': range(5), 'B': range(5,0,-1)})
df['B'][df['A']<4] = 10 # <---- df changes; no warning
df[df['A']<4]['B'] = 10 # <---- df doesn't change; throws SettingWithCopyWarning
pd.options.mode.copy_on_write = True
df = pd.DataFrame({'A': range(5), 'B': range(5,0,-1)})
df['B'][df['A']<4] = 10 # <---- df doesn't change; no warning
df[df['A']<4]['B'] = 10 # <---- df doesn't change; no warning
Пример 3 (представления возвращаются с помощью связанных методов, что значительно повышает производительность):
df = pd.DataFrame({'A': range(1_000_000), 'B': range(1_000_000)})
%%timeit
with pd.option_context('mode.copy_on_write', False):
df.add_prefix('col ').set_index('col A').rename_axis('index col').reset_index()
# 30.5 ms ± 561 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%%timeit
with pd.option_context('mode.copy_on_write', True):
df.add_prefix('col ').set_index('col A').rename_axis('index col').reset_index()
# 18 ms ± 513 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Эта строка в начале моих «следующих шагов» решила проблему для меня:
df = df.iloc[:] #To avoid SettingWithCopyWarning
Для меня эта проблема произошла в следующем> упрощенном<примере. И я также смог решить ее (надеюсь, с правильным решением):
старый код с предупреждением:
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
, Несмотря на то, что оба работают нормально и результат один и тот же, таким образом мне не нужно отключать предупреждения (= оставляйте их для других проблем индексации цепочек где-то еще).
Я надеюсь, что это может кому-то помочь.
В моем случае я бы создал новый столбец на основе индекса, но получил предупреждение:
df_temp["Quarter"] = df_temp.index.quarter
Я использую вставку () вместо прямого назначения, и это работает для меня:
df_temp.insert(loc=0, column='Quarter', value=df_temp.index.quarter)
Просто создайте копию своего фрейма данных, используя
.copy()
до появления предупреждения, чтобы удалить все предупреждения. Это происходит потому, что мы не хотим вносить изменения в исходный файл quote_df. Другими словами, мы не хотим играть со ссылкой на объект quote_df, который мы создали для quote_df.
quote_df = quote_df.copy()
Я использую.loc
индексатор (свойство) для подмножестваDataFrame
s, чтобы избежатьSettingWithCopyWarning
s при манипулировании результирующими подмножествами (например,new_df
ниже):
# GOOD way to select a columns subset:
new_df = df.loc[:, cols_subset]
#.. vs. bad way:
new_df = df[cols_subset]
# works but gives this warning:
# 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
В моем случае я просто используюPDCsv.loc[index, name]
= NewVal для реализации функции:
PDCsv.loc[0, 'Name'] = 'Anthony Dave'