Pandas: .loc [...] = value возвращает SettingWithCopyWarning

Созданный на 8 сент. 2017  ·  8Комментарии  ·  Источник: pandas-dev/pandas

Образец кода

# My code
df.loc[0, 'column_name'] = 'foo bar'

Описание проблемы

Этот код в Pandas 20.3 выдает SettingWithCopyWarning и предлагает

«Попробуйте вместо этого использовать .loc[row_indexer,col_indexer] = value ».

Я уже так делаю, похоже есть небольшая ошибка. Я использую Jupyter.
Спасибо! :)

Вывод pd.show_versions()


коммит: Нет
питон: 3.6.1.final.0
биты Python: 64
ОС: Windows
ОС-релиз: 8.1
машина: AMD64
процессор: Intel64 Family 6 Model 61 Stepping 4, GenuineIntel
byteorder: маленький
LC_ALL: Нет
ЯЗЫК: Нет
МЕСТО: Нет.

панды: 0.20.1
pytest: 3.0.7
пункт: 9.0.1
setuptools: 35.0.2
Cython: 0,25,2
число: 1.12.1
scipy: 0.19.0
xarray: Нет
IPython: 5.3.0
сфинкс: 1.5.6
Пэтси: 0.4.1
dateutil: 2.6.0
pytz: 2017.2
blosc: Нет
узкое место: 1.2.1
таблицы: 3.2.2
numexpr: 2.6.2
перо: Нет
matplotlib: 2.0.2
openpyxl: Нет
xlrd: 1.0.0
xlwt: 1.2.0
xlsxwriter: 0.9.6
лхмл: 3.7.3
BS4: 4.6.0
html5lib: 0,999
sqlalchemy: 1.1.9
pymysql: Нет
psycopg2: Нет
jinja2: 2.9.6
s3fs: Нет
pandas_gbq: Нет
pandas_datareader: Нет

Indexing Usage Question

Самый полезный комментарий

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

df_c = df.loc[df.encountry == country, :]

Pandas не уверен на 100%, хотите ли вы присвоить значения только своему df_c срезу или полностью распространить его до исходного df . Чтобы избежать этого, когда вы впервые назначаете df_c убедитесь, что вы сообщите pandas, что это его собственный фрейм данных (а не срез), используя

df_c = df.loc[df.encountry == country, :].copy()

Это исправит вашу ошибку. Я приведу краткий пример, чтобы объяснить вышесказанное, поскольку я заметил, что многие пользователи путаются с пандами в этом аспекте.

Пример с выдуманными данными

>>> import pandas as pd
>>> df = pd.DataFrame({'A':[1,2,3,4,5], 'B':list('QQQCC')})
>>> df
   A  B
0  1  Q
1  2  Q
2  3  Q
3  4  C
4  5  C
>>> df.loc[df['B'] == 'Q', 'new_col'] = 'hello'
>>> df
   A  B new_col
0  1  Q   hello
1  2  Q   hello
2  3  Q   hello
3  4  C     NaN
4  5  C     NaN

Итак, все вышесказанное работает так, как мы и ожидали! Теперь давайте попробуем пример, который отражает то, что вы пытались сделать со своими данными.

>>> df = pd.DataFrame({'A':[1,2,3,4,5], 'B':list('QQQCC')})
>>> df_q = df.loc[df['B'] == 'Q']
>>> df_q
   A  B
0  1  Q
1  2  Q
2  3  Q
>>> df_q.loc[df['A'] < 3, 'new_col'] = 'hello'
/Users/riddellcd/anaconda/lib/python3.6/site-packages/pandas/core/indexing.py:337: 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

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  self.obj[key] = _infer_fill_value(value)

>>> df_q
   A  B new_col
0  1  Q   hello
1  2  Q   hello
2  3  Q     NaN

Похоже, мы получили ту же ошибку! Но это изменило df_q как мы и ожидали! Это потому, что df_q - это часть df поэтому, даже если мы используем .loc [] df_q pandas предупреждает нас, что он не будет распространять изменения вверх в df . Чтобы избежать этого, нам нужно быть более явным и сказать, что df_q - это собственный фрейм данных, отдельный от df , явным образом объявив это так.

Давайте начнем с df_q но .copy() этот раз используем

>>> df_q = df.loc[df['B'] == 'Q'].copy()
>>> df_q
   A  B
0  1  Q
1  2  Q
2  3  Q

Lets try to reassign our value now!
>>> df_q.loc[df['A'] < 3, 'new_col'] = 'hello'
>>> df_q
   A  B new_col
0  1  Q   hello
1  2  Q   hello
2  3  Q     NaN

Это работает без ошибок, потому что мы сказали пандам, что df_q отделен от df

Если вы действительно хотите, чтобы эти изменения в df_c распространялись до df другой пункт и вы ответите, если хотите.

Все 8 Комментарий

@NadiaRom Можете привести полный пример? Трудно сказать наверняка, но я подозреваю, что df возникло в результате операции, которая может быть просмотром или копированием. Например:

In [8]: df = pd.DataFrame({"A": [1, 2], "B": [3, 4], "C": [4, 5]})

In [9]: df1 = df[['A', 'B']]

In [10]: df1.loc[0, 'A'] = 5
/Users/taugspurger/Envs/pandas-dev/lib/python3.6/site-packages/pandas/pandas/core/indexing.py:180: 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
  self._setitem_with_indexer(indexer, value)
/Users/taugspurger/Envs/pandas-dev/bin/ipython:1: 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
  #!/Users/taugspurger/Envs/pandas-dev/bin/python3.6

Итак, мы правильно обновляем df1 . Неясно, будет ли также обновляться df . Я думаю, что то же самое происходит и с вами, но без воспроизводимого примера трудно сказать наверняка.

@TomAugspurger Вот код, в общем, я никогда не присваиваю значения пандам без .loc

df = pd.read_csv('df_unicities.tsv', sep='\t')
df.replace({'|': '--'}, inplace=True)

df_c = df.loc[df.encountry == country, : ]

df_c['sort'] = (df_c.encities_ua == 'all').astype(int) # new column
df_c['sort'] += (df_c.encities_foreign == 'all').astype(int)
df_c.sort_values(by='sort', inplace=True)

# ---end of chunk, everything is fine ---

if df_c.encities_foreign.str.contains('all').sum() < len(df_c):
    df_c.loc[df_c.encities_foreign.str.contains('all'), 'encities_foreign'] = 'other'
    df_c.loc[df_c.cities_foreign.str.contains('всі'), 'cities_foreign'] = 'інші'
else:
    df_c.loc[df_c.encities_foreign.str.contains('all'), 'encities_foreign'] = country
    df_c.loc[df_c.cities_foreign.str.contains('всі'), 'cities_foreign'] = df_c.country.iloc[0]

if df_c.encities_ua.str.contains('all').sum() < len(df_c):
    df_c.loc[df_c.encities_ua.str.contains('all'), 'encities_ua'] = 'other'
    df_c.loc[df_c.cities_ua.str.contains('всі'), 'cities_ua'] = 'інші'
else:
    df_c.loc[df_c.encities_ua.str.contains('all'), 'encities_ua'] = 'Ukraine'
    df_c.loc[df_c.cities_ua.str.contains('всі'), 'cities_ua'] = 'Україна'

# Warning after it

Спасибо за быстрый ответ!

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

df_c = df.loc[df.encountry == country, :]

Pandas не уверен на 100%, хотите ли вы присвоить значения только своему df_c срезу или полностью распространить его до исходного df . Чтобы избежать этого, когда вы впервые назначаете df_c убедитесь, что вы сообщите pandas, что это его собственный фрейм данных (а не срез), используя

df_c = df.loc[df.encountry == country, :].copy()

Это исправит вашу ошибку. Я приведу краткий пример, чтобы объяснить вышесказанное, поскольку я заметил, что многие пользователи путаются с пандами в этом аспекте.

Пример с выдуманными данными

>>> import pandas as pd
>>> df = pd.DataFrame({'A':[1,2,3,4,5], 'B':list('QQQCC')})
>>> df
   A  B
0  1  Q
1  2  Q
2  3  Q
3  4  C
4  5  C
>>> df.loc[df['B'] == 'Q', 'new_col'] = 'hello'
>>> df
   A  B new_col
0  1  Q   hello
1  2  Q   hello
2  3  Q   hello
3  4  C     NaN
4  5  C     NaN

Итак, все вышесказанное работает так, как мы и ожидали! Теперь давайте попробуем пример, который отражает то, что вы пытались сделать со своими данными.

>>> df = pd.DataFrame({'A':[1,2,3,4,5], 'B':list('QQQCC')})
>>> df_q = df.loc[df['B'] == 'Q']
>>> df_q
   A  B
0  1  Q
1  2  Q
2  3  Q
>>> df_q.loc[df['A'] < 3, 'new_col'] = 'hello'
/Users/riddellcd/anaconda/lib/python3.6/site-packages/pandas/core/indexing.py:337: 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

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  self.obj[key] = _infer_fill_value(value)

>>> df_q
   A  B new_col
0  1  Q   hello
1  2  Q   hello
2  3  Q     NaN

Похоже, мы получили ту же ошибку! Но это изменило df_q как мы и ожидали! Это потому, что df_q - это часть df поэтому, даже если мы используем .loc [] df_q pandas предупреждает нас, что он не будет распространять изменения вверх в df . Чтобы избежать этого, нам нужно быть более явным и сказать, что df_q - это собственный фрейм данных, отдельный от df , явным образом объявив это так.

Давайте начнем с df_q но .copy() этот раз используем

>>> df_q = df.loc[df['B'] == 'Q'].copy()
>>> df_q
   A  B
0  1  Q
1  2  Q
2  3  Q

Lets try to reassign our value now!
>>> df_q.loc[df['A'] < 3, 'new_col'] = 'hello'
>>> df_q
   A  B new_col
0  1  Q   hello
1  2  Q   hello
2  3  Q     NaN

Это работает без ошибок, потому что мы сказали пандам, что df_q отделен от df

Если вы действительно хотите, чтобы эти изменения в df_c распространялись до df другой пункт и вы ответите, если хотите.

@CRiddler Отлично, спасибо !
Как вы упомянули, цепочка .loc никогда не возвращала неожиданных результатов. Насколько я понимаю, .copy() гарантирует Pandas, что мы рассматриваем выбранный df_sliced_once как отдельный объект и не собираемся изменять исходный полный df . Пожалуйста, поправьте, если я что-л. Перепутала.

документация находится здесь http://pandas.pydata.org/pandas-docs/stable/indexing.html#returning -a-view-versus-a-copy, а у @CRiddler есть хорошее объяснение. вы вообще НЕ должны использовать inplace .

Если вы действительно хотите, чтобы эти изменения в df_c распространялись до df другая точка, и вы ответите, если хотите.

@CRiddler Спасибо, ваш ответ лучше, чем ответы в Stack Overflow, не могли бы вы добавить, если хотите перейти к исходному фрейму данных или указать, как это делается?

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

Исходные данные:

>>>import pandas as pd
>>> df = pd.DataFrame({'A':[1,2,3,4,5], 'B':list('QQQCC')})
>>> df
   A  B
0  1  Q
1  2  Q
2  3  Q
3  4  C
4  5  C

Помните, что создание временного фрейма данных НЕ приведет к распространению изменений
Как показано в предыдущем примере, это вносит изменения только в df_q и вызывает предупреждение pandas (здесь не копируется / вставляется). И НЕ распространяет никаких изменений на df

>>> df_q = df.loc[df["B"] == "Q"]
>>> df_q.loc[df["A"] < 3, "new_column"] = "hello"

# df remains unchanged because we only made changes to `df_q`
>>> df
   A  B
0  1  Q
1  2  Q
2  3  Q
3  4  C
4  5  C

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

Однако, если мы немного изменим наше мышление и будем работать с масками вместо полноценных подмножеств, мы сможем достичь желаемого результата. Хотя это не обязательно «распространение» изменений в исходный фрейм данных из подмножества, мы гарантируем, что любые изменения, которые мы действительно вносим, ​​произойдут в исходном фрейме данных df . Для этого мы сначала создаем маски, а затем применяем их, когда хотим внести изменения в это подмножество df

>>> q_mask = df["B"] == "Q"
>>> a_mask = df["A"] < 3

# Combine masks (in this case we used "&") to achieve what a nested subset would look like
#  In the same step we add in our item assignment. Instructing pandas to create a new column in `df` and assign
#  the value "hello" to the rows in `df` where `q_mask` & `a_mask` overlap.
>>> df.loc[q_mask & a_mask, "new_col"] = "hello"

# Successful "propagation" of new values to the original dataframe
>>> df
   A  B new_col
0  1  Q   hello
1  2  Q   hello
2  3  Q     NaN
3  4  C     NaN
4  5  C     NaN

Наконец, если мы когда-нибудь захотим увидеть, как будет выглядеть df_q, мы всегда можем подмножество его из исходного фрейма данных, используя наш q_mask

>>> df.loc[q_mask, :]
   A  B new_col
0  1  Q   hello
1  2  Q   hello
2  3  Q     NaN

Хотя это не обязательно "распространение" изменений с df_q на df мы достигаем того же результата. Фактическое распространение должно быть выполнено явно и будет менее эффективным, чем простая работа с масками.

@CRiddler Спасибо, вы очень помогли

Была ли эта страница полезной?
0 / 5 - 0 рейтинги