Як боротися з SettingWithCopyWarning у Pandas?


629

Фон

Я щойно оновив мої Панди з 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застарілий. Зараз Pandas рекомендує використовувати .at[]або .iat[]замість цього. docs тут 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

Відповіді:


793

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
Думаю, я б переважно виступав за те, щоб не попереджати про це взагалі. Якщо ви працюєте з синтаксисом ланцюгового призначення, ви можете точно визначити порядок індексації, який повинен відбутися, щоб він працював так, як очікувалося в будь-якій ситуації. Я думаю, що це надто параноїчно, що щодо цього є ці вичерпні запобіжні заходи. У тому ж дусі, як "дозволяти всім дорослим" щодо методів або атрибутів "приватного" класу, я думаю, що пандам краще дозволяти користувачам дорослих людей щодо прикованих завдань. Використовуйте їх, лише якщо ви знаєте, що робите.
ely

48
Трохи нетипово намагатись попередити людей, коли вони хакують альтернативи. Новіші способи доступу Pandas (удосконалені .ix, вдосконалені .ilocтощо), безумовно, можуть розглядатися як "основний спосіб", не попереджаючи всіх про інші способи. Натомість нехай вони будуть дорослими, і якщо вони хочуть виконувати прикуті завдання, так і нехай буде. Мої два центи все одно. Тут часто бачать незадоволені коментарі від Pandas devs, коли ланцюгові завдання працюватимуть на вирішенні проблеми, але їх не вважають "основним" способом.
ely

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

3
Причина застерегти, звичайно, люди, які оновлюють старий код. І мені, безумовно, потрібне попередження, бо я маю справу з якимсь дуже некрасивим старим кодом.
Томас Ендрюс

15
Зі сторони, я виявив, що відключення попередження прикованого присвоєння: 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

І так далі.


Просто скажіть, як придушити попередження!

Розглянемо просту операцію на стовпці "A" 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 У
мене є DataFrame

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
Я намагаюся підмножити значення, виходячи з якоїсь умови. У мене є DataFrame

   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

Код, який показує ОП, наведено вище, в той час як законне, і, мабуть, те, що я також роблю, технічно є справжнім попередженням, а не хибним позитивом. Ще один спосіб не мати попередження - зробити операцію вибору через reindex, наприклад,

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

Або,

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

Дякую за інформацію та обговорення, я просто відключаю попередження, щоб консоль мовчала про це. Це звучить як перегляд та таблиця в базі даних SQL. Мені потрібно більше дізнатися про користь введення концепції "копіювати", але IMHO - це певна
нагода

19
Я згоден з копією (); це зрозуміло, і він виправив мою проблему (що було помилковим позитивом).
rdchambers

5
Після оновлення 0.16я бачу ще багато помилкових позитивів, проблема з помилковими позитивами - це вчиться ігнорувати це, хоча іноді це є законним.
тире

3
@dashesy Ви пропустите точку. іноді, навіть, більшу частину часу це може спрацювати. Але це може статися, наприклад, якщо кадр більший / менший або ви додаєте стовпець, який говорить про інший тип, що він не працює. В тім-то й річ. Ви робите щось, що може працювати, але це не гарантується. Це дуже відрізняється від попереджень про депресію. Якщо ви хочете продовжувати його використовувати, і це працює, чудово. Але будьте попереджені.
Джефф

3
@Jeff має сенс зараз, тому це undefinedповедінка. Якщо що-небудь, це повинно викликати помилку (щоб уникнути підводних каменів C), оскільки apiзаморожена поточна поведінка попередження має сенс для зворотної сумісності. І я змушу їх кидати, щоб сприймати їх як помилки у своєму виробничому коді ( warnings.filterwarnings('error', r'SettingWithCopyWarning). Також пропозиція використовувати .locіноді також не допомагає (якщо вона є в групі).
тире

41

Попередження про копіювання фрейму даних Pandas

Коли ти йдеш і робиш щось подібне:

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. Це два основні методи індексації структур даних панд. Детальніше читайте в документації.
Ninjakannon

Як слід замінити стовпчик 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)

Використовуючи свій інструмент різного вибору, ви побачите, що за межами пари адрес єдина відмінність матеріалу:

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

Метод, який вирішує, чи потрібно попереджати, - це те, DataFrame._check_setitem_copyщо перевіряє _is_copy. Тож ось ви йдете. Зробіть copyтак, щоб ваш DataFrame не був_is_copy .

Попередження пропонує використовувати .loc, але якщо ви використовуєте його .locна кадрі _is_copy, ви все одно отримаєте те саме попередження. Зман? Так. Дратівливий? Будьте впевнені. Корисні? Потенційно, коли використовується ланцюгове призначення. Але він не може правильно визначити призначення ланцюга і друкує попередження без розбору.


11

Ця тема насправді плутає з Пандами. На щастя, він має відносно просте рішення.

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

Є помилка
друку

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

І ви не хочете використовувати рішення Джеффа, оскільки обчислення ваших умов 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]

тобто метод доступу / пошуку для a 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(посилання на документи та приклад ) - абсолютно немає необхідності створювати спеціальний клас із усіма методами dnder та іншими дзвонами і свисти.

Спочатку сам код менеджера контексту:

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.