Pandas: .loc [...] = valor retorna SettingWithCopyWarning

Criado em 8 set. 2017  ·  8Comentários  ·  Fonte: pandas-dev/pandas

Amostra de Código

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

Descrição do Problema

Este código no Pandas 20.3 lança SettingWithCopyWarning e sugere

"Tente usar .loc[row_indexer,col_indexer] = value invés".

Já estou fazendo isso, parece que tem um bichinho. Eu uso o Jupyter.
Obrigado! :)

Resultado de pd.show_versions()


commit: Nenhum
python: 3.6.1.final.0
python-bits: 64
SO: Windows
Versão do sistema operacional: 8.1
máquina: AMD64
processador: Intel64 Family 6 Model 61 Stepping 4, GenuineIntel
byteorder: pouco
LC_ALL: Nenhum
LANG: Nenhum
LOCALE: Nenhum. Nenhum

pandas: 0,20,1
pytest: 3.0.7
pip: 9.0.1
ferramentas de configuração: 35.0.2
Cython: 0.25.2
numpy: 1.12.1
scipy: 0.19.0
xarray: Nenhum
IPython: 5.3.0
esfinge: 1.5.6
patsy: 0.4.1
dateutil: 2.6.0
pytz: 2017.2
blosc: nenhum
gargalo: 1.2.1
tabelas: 3.2.2
numexpr: 2.6.2
pena: nenhuma
matplotlib: 2.0.2
openpyxl: Nenhum
xlrd: 1.0.0
xlwt: 1.2.0
xlsxwriter: 0.9.6
lxml: 3.7.3
bs4: 4.6.0
html5lib: 0,999
sqlalchemy: 1.1.9
pymysql: Nenhum
psycopg2: Nenhum
jinja2: 2.9.6
s3fs: nenhum
pandas_gbq: Nenhum
pandas_datareader: Nenhum

Indexing Usage Question

Comentários muito úteis

O problema aqui é que você está dividindo o dataframe primeiro com .loc na linha 4. A tentativa de atribuir valores a essa fatia.

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

O Pandas não tem 100% de certeza se deseja atribuir valores apenas à sua fatia df_c ou que ela se propague de volta até a df . Para evitar isso, quando você atribuir df_c certifique-se de dizer aos pandas que é seu próprio quadro de dados (e não uma fatia) usando

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

Fazer isso corrigirá seu erro. Vou acrescentar um breve exemplo para ajudar a explicar o acima, pois percebi que muitos usuários ficam confusos com os pandas nesse aspecto.

Exemplo com dados inventados

>>> 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

Portanto, o procedimento acima funciona como esperamos! Agora vamos tentar um exemplo que reflete o que você tentou fazer com seus dados.

>>> 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

Parece que encontramos o mesmo erro! Mas mudou df_q como esperávamos! Isso ocorre porque df_q é uma fatia de df , então, embora estejamos usando .loc [] df_q pandas está nos avisando que não irá propagar as mudanças para cima para df . Para evitar isso, precisamos ser mais explícitos e dizer que df_q é seu próprio dataframe, separado de df por declarar explicitamente assim.

Vamos começar de volta de df_q mas use .copy() desta vez.

>>> 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

Isso funciona sem erros porque dissemos aos pandas que df_q é diferente de df

Se você de fato deseja que essas mudanças em df_c se propaguem até df é um outro ponto e responderá se você quiser.

Todos 8 comentários

@NadiaRom Você pode fornecer um exemplo completo? É difícil dizer com certeza, mas suspeito que df veio de uma operação que pode ser uma visualização ou cópia. Por exemplo:

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

Portanto, estamos atualizando df1 corretamente. A ambigüidade é se df também será atualizado ou não. Acho que algo semelhante está acontecendo com você, mas sem um exemplo reproduzível é difícil dizer com certeza.

@TomAugspurger Aqui está o código, em geral, eu nunca atribuo valores a pandas sem .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

Obrigado pela resposta rápida!

O problema aqui é que você está dividindo o dataframe primeiro com .loc na linha 4. A tentativa de atribuir valores a essa fatia.

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

O Pandas não tem 100% de certeza se deseja atribuir valores apenas à sua fatia df_c ou que ela se propague de volta até a df . Para evitar isso, quando você atribuir df_c certifique-se de dizer aos pandas que é seu próprio quadro de dados (e não uma fatia) usando

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

Fazer isso corrigirá seu erro. Vou acrescentar um breve exemplo para ajudar a explicar o acima, pois percebi que muitos usuários ficam confusos com os pandas nesse aspecto.

Exemplo com dados inventados

>>> 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

Portanto, o procedimento acima funciona como esperamos! Agora vamos tentar um exemplo que reflete o que você tentou fazer com seus dados.

>>> 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

Parece que encontramos o mesmo erro! Mas mudou df_q como esperávamos! Isso ocorre porque df_q é uma fatia de df , então, embora estejamos usando .loc [] df_q pandas está nos avisando que não irá propagar as mudanças para cima para df . Para evitar isso, precisamos ser mais explícitos e dizer que df_q é seu próprio dataframe, separado de df por declarar explicitamente assim.

Vamos começar de volta de df_q mas use .copy() desta vez.

>>> 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

Isso funciona sem erros porque dissemos aos pandas que df_q é diferente de df

Se você de fato deseja que essas mudanças em df_c se propaguem até df é um outro ponto e responderá se você quiser.

@CRiddler Ótimo, obrigado !
Como você mencionou, .loc cadeia nunca retornou resultados inesperados. Pelo que entendi, .copy() garante aos Pandas que trataremos os df_sliced_once selecionados como objetos separados e não pretendemos alterar totalmente df iniciais. Por favor, corrija se eu confundi smth.

a documentação está aqui http://pandas.pydata.org/pandas-docs/stable/indexing.html#returning -a-view-versus-a-copy e @CRiddler tem uma bela explicação. em geral, você NÃO deve usar inplace .

Se você de fato deseja que essas mudanças em df_c se propaguem até df é outro ponto inteiramente e responderá se você quiser.

@CRiddler Obrigado, sua resposta é melhor do que as que você poderia adicionar no Stack Overflow quando quiser propagar para o dataframe inicial ou dar uma indicação de como isso é feito?

@persep Em geral, não gosto de transformar problemas em threads de stackoverflow para obter ajuda, mas parece que esse problema recebeu um pouco de atenção desde a última postagem, então irei em frente e postarei meu método de lidar com esse tipo de problema em pandas. Eu normalmente faço isso não subdividindo o dataframe em variáveis ​​separadas, mas, em vez disso, transformo as máscaras em variáveis ​​- em seguida, combino as máscaras conforme necessário e defino os valores com base nessas máscaras para garantir que as mudanças aconteçam no dataframe original, e não em alguma cópia flutuante .

Dados originais:

>>>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

Lembre-se de que a criação de um dataframe temporário NÃO propagará as alterações
Conforme mostrado no exemplo anterior, isso faz alterações em apenas df_q e gera um aviso de pandas (não copiado / colado aqui). E NÃO propaga nenhuma mudança para 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

Até onde sei, não há como usar o mesmo código acima e forçar a propagação das alterações de volta ao dataframe original.

No entanto, se mudarmos um pouco nosso pensamento e trabalharmos com máscaras em vez de subconjuntos completos, poderemos alcançar o resultado desejado. Embora isso não esteja necessariamente "propagando" alterações para o dataframe original de um subconjunto, estamos garantindo que todas as alterações que fizermos aconteçam no dataframe original df . Para fazer isso, primeiro criamos as máscaras e, em seguida, as aplicamos quando queremos fazer uma alteração nesse subconjunto de 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

Por último, se quisermos ver como df_q ficaria, sempre podemos subconjuí-lo do dataframe original usando nosso q_mask

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

Embora isso não esteja necessariamente "propagando" as mudanças de df_q para df obtemos o mesmo resultado. A propagação real precisaria ser feita explicitamente e seria menos eficiente do que apenas trabalhar com máscaras.

@CRiddler Obrigado, você foi muito útil

Esta página foi útil?
0 / 5 - 0 avaliações