Pandas: .loc [...] = valeur renvoie SettingWithCopyWarning

Créé le 8 sept. 2017  ·  8Commentaires  ·  Source: pandas-dev/pandas

Exemple de code

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

Description du problème

Ce code dans Pandas 20.3 lance SettingWithCopyWarning et suggère de

"Essayez d'utiliser .loc[row_indexer,col_indexer] = value place".

Je le fais déjà, on dirait qu'il y a un petit bug. J'utilise Jupyter.
Je vous remercie! :)

Sortie de pd.show_versions()


commit: aucun
python: 3.6.1.final.0
bits python: 64
OS: Windows
Version du système d'exploitation: 8.1
machine: AMD64
Processeur: Intel64 Family 6 Model 61 Stepping 4, Genuine Intel
byteorder: petit
LC_ALL: Aucun
LANG: Aucun
LOCALE: Aucun. Aucun

pandas: 0.20.1
pytest: 3.0.7
pip: 9.0.1
setuptools: 35.0.2
Cython: 0,25,2
numpy: 1.12.1
scipy: 0.19.0
xarray: Aucun
IPython: 5.3.0
sphinx: 1,5,6
patsy: 0,4,1
dateutil: 2.6.0
pytz: 2017.2
blosc: Aucun
goulot d'étranglement: 1.2.1
tableaux: 3.2.2
numexpr: 2.6.2
plume: Aucune
matplotlib: 2.0.2
openpyxl: Aucun
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: Aucun
psycopg2: Aucun
jinja2: 2.9.6
s3fs: Aucun
pandas_gbq: Aucun
pandas_datareader: Aucun

Indexing Usage Question

Commentaire le plus utile

Le problème ici est que vous découpez d'abord votre dataframe avec .loc à la ligne 4. La tentative d'attribution de valeurs à cette tranche.

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

Pandas n'est pas sûr à 100% si vous voulez attribuer des valeurs uniquement à votre tranche df_c , ou la faire propager jusqu'au df . Pour éviter cela lorsque vous affectez pour la première fois df_c assurez-vous de dire aux pandas qu'il s'agit de sa propre trame de données (et non d'une tranche) en utilisant

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

Cela corrigera votre erreur. Je vais vous donner un bref exemple pour vous aider à expliquer ce qui précède, car j'ai remarqué que de nombreux utilisateurs étaient confus par les pandas à cet égard.

Exemple avec des données constituées

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

Donc, ce qui précède fonctionne comme prévu! Essayons maintenant un exemple qui reflète ce que vous avez tenté de faire avec vos données.

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

On dirait que nous avons rencontré la même erreur! Mais cela a changé df_q comme prévu! C'est parce que df_q est une tranche de df donc, même si nous utilisons .loc [] df_q pandas nous avertit qu'il ne propagera pas les changements à df . Pour éviter cela, nous devons être plus explicites et dire que df_q est son propre dataframe, séparé de df en le déclarant explicitement.

Commençons à partir de df_q mais utilisez .copy() cette fois.

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

Cela fonctionne sans erreur car nous avons dit aux pandas que df_q est séparé de df

Si vous voulez en fait que ces modifications de df_c se propagent jusqu'à df c'est un tout autre point et vous répondra si vous le souhaitez.

Tous les 8 commentaires

@NadiaRom Pouvez-vous donner un exemple complet? C'est difficile à dire avec certitude, mais je soupçonne que df provient d'une opération qui peut être une vue ou une copie. Par exemple:

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

Nous mettons donc à jour correctement df1 . L'ambiguïté est de savoir si df sera également mis à jour. Je pense qu'une chose similaire vous arrive, mais sans un exemple reproductible, c'est difficile à dire avec certitude.

@TomAugspurger Voici le code, en général, je n'attribue jamais de valeurs aux pandas sans .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

Merci pour la réponse rapide!

Le problème ici est que vous découpez d'abord votre dataframe avec .loc à la ligne 4. La tentative d'attribution de valeurs à cette tranche.

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

Pandas n'est pas sûr à 100% si vous voulez attribuer des valeurs uniquement à votre tranche df_c , ou la faire propager jusqu'au df . Pour éviter cela lorsque vous affectez pour la première fois df_c assurez-vous de dire aux pandas qu'il s'agit de sa propre trame de données (et non d'une tranche) en utilisant

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

Cela corrigera votre erreur. Je vais vous donner un bref exemple pour vous aider à expliquer ce qui précède, car j'ai remarqué que de nombreux utilisateurs étaient confus par les pandas à cet égard.

Exemple avec des données constituées

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

Donc, ce qui précède fonctionne comme prévu! Essayons maintenant un exemple qui reflète ce que vous avez tenté de faire avec vos données.

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

On dirait que nous avons rencontré la même erreur! Mais cela a changé df_q comme prévu! C'est parce que df_q est une tranche de df donc, même si nous utilisons .loc [] df_q pandas nous avertit qu'il ne propagera pas les changements à df . Pour éviter cela, nous devons être plus explicites et dire que df_q est son propre dataframe, séparé de df en le déclarant explicitement.

Commençons à partir de df_q mais utilisez .copy() cette fois.

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

Cela fonctionne sans erreur car nous avons dit aux pandas que df_q est séparé de df

Si vous voulez en fait que ces modifications de df_c se propagent jusqu'à df c'est un tout autre point et vous répondra si vous le souhaitez.

@CRiddler Super, merci !
Comme vous l'avez mentionné, chained .loc n'a jamais renvoyé de résultats inattendus. Si je comprends bien, .copy() garantit aux Pandas que nous traitons les df_sliced_once comme des objets séparés et que nous n'avons pas l'intention de changer le df initial. Veuillez corriger si j'ai confondu smth.

la documentation est ici http://pandas.pydata.org/pandas-docs/stable/indexing.html#returning -a-view-versus-a-copy et @CRiddler a une belle expl. vous ne devriez en général PAS utiliser du tout inplace .

Si vous voulez en fait que ces modifications de df_c se propagent jusqu'à df c'est un tout autre point et vous répondra si vous le souhaitez.

@CRiddler Merci, votre réponse est meilleure que celles de Stack Overflow. Pourriez-vous ajouter lorsque vous souhaitez propager vers le dataframe initial ou donner une indication de la façon dont cela est fait?

@persep En général, je n'aime pas transformer les problèmes en threads stackoverflow pour obtenir de l'aide, mais il semble que ce problème ait attiré un peu d'attention depuis la dernière publication, je vais donc publier ma méthode pour résoudre ce type de problème pandas. Je fais généralement cela en ne sous-traitant pas la trame de données en variables séparées, mais je transforme plutôt les masques en variables - puis je combine les masques au besoin et je définis des valeurs en fonction de ces masques pour m'assurer que les modifications se produisent dans la trame de données d'origine, et non à une copie flottante. .

Données d'origine:

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

N'oubliez pas que la création d'un dataframe temporaire ne propagera PAS les modifications
Comme indiqué dans l'exemple précédent, cela ne modifie que df_q et déclenche un avertissement pandas (non copié / collé ici). ET ne propage PAS de modifications à 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

À ma connaissance, il n'y a aucun moyen d'utiliser le même code que ci-dessus et de forcer les modifications à se propager vers le dataframe d'origine.

Cependant, si nous changeons un peu notre façon de penser et que nous travaillons avec des masques au lieu de sous-ensembles complets, nous pouvons obtenir le résultat souhaité. Bien qu'il ne s'agisse pas nécessairement de "propager" les modifications à la trame de données d'origine à partir d'un sous-ensemble, nous nous assurons que toutes les modifications que nous apportons se produiront dans la trame df données d'origine 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

Enfin, si nous voulions voir à quoi ressemblerait df_q, nous pouvons toujours le sous-définir à partir du dataframe d'origine en utilisant notre q_mask

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

Bien que cela ne "propage" pas nécessairement les changements de df_q à df nous obtenons le même résultat. La propagation réelle devrait être faite explicitement et serait moins efficace que de simplement travailler avec des masques.

@CRiddler Merci, vous avez été très utile

Cette page vous a été utile?
0 / 5 - 0 notes