Pandas: .loc [...] = Wert gibt SettingWithCopyWarning zurück

Erstellt am 8. Sept. 2017  ·  8Kommentare  ·  Quelle: pandas-dev/pandas

Codebeispiel

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

Problembeschreibung

Dieser Code in Pandas 20.3 löst SettingWithCopyWarning aus und schlägt vor

"Versuchen Sie stattdessen .loc[row_indexer,col_indexer] = value verwenden".

Ich mache das schon, es sieht so aus, als gäbe es einen kleinen Fehler. Ich benutze Jupyter.
Vielen Dank! :) :)

Ausgabe von pd.show_versions()


Festschreiben: Keine
Python: 3.6.1.final.0
Python-Bits: 64
Betriebssystem: Windows
Betriebssystemversion: 8.1
Maschine: AMD64
Prozessor: Intel64 Family 6 Model 61 Stepping 4, GenuineIntel
Byteorder: wenig
LC_ALL: Keine
LANG: Keine
LOCALE: Keine

Pandas: 0,20,1
Pytest: 3.0.7
pip: 9.0.1
setuptools: 35.0.2
Cython: 0,25,2
Anzahl: 1.12.1
scipy: 0.19.0
xarray: Keine
IPython: 5.3.0
Sphinx: 1.5.6
Patsy: 0.4.1
Datum: 2.6.0
pytz: 2017.2
blosc: Keine
Engpass: 1.2.1
Tabellen: 3.2.2
numexpr: 2.6.2
Feder: Keine
matplotlib: 2.0.2
openpyxl: Keine
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: Keine
psycopg2: Keine
jinja2: 2.9.6
s3fs: Keine
pandas_gbq: Keine
pandas_datareader: Keine

Indexing Usage Question

Hilfreichster Kommentar

Das Problem hierbei ist, dass Sie Ihren Datenrahmen zuerst mit .loc in Zeile 4 aufteilen. Der Versuch, diesem Slice Werte zuzuweisen.

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

Pandas ist sich nicht 100% sicher, ob Sie nur Ihrem df_c Slice Werte zuweisen oder es bis zum ursprünglichen df möchten. Um dies zu vermeiden, wenn Sie df_c zum ersten Mal zuweisen, stellen Sie sicher, dass Sie Pandas mitteilen, dass es sich um einen eigenen Datenrahmen (und nicht um ein Slice) handelt

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

Dadurch wird Ihr Fehler behoben. Ich werde ein kurzes Beispiel anführen, um das oben Gesagte zu erklären, da ich festgestellt habe, dass viele Benutzer in diesem Aspekt von Pandas verwirrt sind.

Beispiel mit erfundenen Daten

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

Das oben genannte funktioniert also wie erwartet! Versuchen wir nun ein Beispiel, das widerspiegelt, was Sie mit Ihren Daten versucht haben.

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

Sieht so aus, als hätten wir den gleichen Fehler gemacht! Aber es hat df_q geändert, wie wir erwartet hatten! Dies liegt daran, dass df_q ein Stück von df , obwohl wir .loc [] df_q Pandas verwenden, warnt uns, dass es die Änderungen nicht weitergeben wird bis df . Um dies zu vermeiden, müssen wir expliziter sein und sagen, dass df_q ein eigener Datenrahmen ist, der von df indem wir dies explizit deklarieren.

Beginnen wir wieder bei df_q aber verwenden Sie diesmal .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

Dies funktioniert ohne Fehler, da wir Pandas mitgeteilt haben, dass df_q von df

Wenn Sie tatsächlich möchten, dass diese Änderungen an df_c bis zu df weitergegeben werden, ist dies ein weiterer Punkt und wird beantwortet, wenn Sie möchten.

Alle 8 Kommentare

@NadiaRom Können Sie ein vollständiges Beispiel liefern? Es ist schwer zu sagen, aber ich vermute, dass df von einer Operation stammt, die eine Ansicht oder eine Kopie sein kann. Zum Beispiel:

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

Wir aktualisieren also df1 korrekt. Die Unklarheit besteht darin, ob df ebenfalls aktualisiert wird oder nicht. Ich denke, Ihnen passiert etwas Ähnliches, aber ohne ein reproduzierbares Beispiel ist es schwer zu sagen.

@ TomAugspurger Hier ist der Code, im Allgemeinen weise ich Pandas ohne .loc niemals Werte zu

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

Vielen Dank für die schnelle Antwort!

Das Problem hierbei ist, dass Sie Ihren Datenrahmen zuerst mit .loc in Zeile 4 aufteilen. Der Versuch, diesem Slice Werte zuzuweisen.

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

Pandas ist sich nicht 100% sicher, ob Sie nur Ihrem df_c Slice Werte zuweisen oder es bis zum ursprünglichen df möchten. Um dies zu vermeiden, wenn Sie df_c zum ersten Mal zuweisen, stellen Sie sicher, dass Sie Pandas mitteilen, dass es sich um einen eigenen Datenrahmen (und nicht um ein Slice) handelt

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

Dadurch wird Ihr Fehler behoben. Ich werde ein kurzes Beispiel anführen, um das oben Gesagte zu erklären, da ich festgestellt habe, dass viele Benutzer in diesem Aspekt von Pandas verwirrt sind.

Beispiel mit erfundenen Daten

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

Das oben genannte funktioniert also wie erwartet! Versuchen wir nun ein Beispiel, das widerspiegelt, was Sie mit Ihren Daten versucht haben.

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

Sieht so aus, als hätten wir den gleichen Fehler gemacht! Aber es hat df_q geändert, wie wir erwartet hatten! Dies liegt daran, dass df_q ein Stück von df , obwohl wir .loc [] df_q Pandas verwenden, warnt uns, dass es die Änderungen nicht weitergeben wird bis df . Um dies zu vermeiden, müssen wir expliziter sein und sagen, dass df_q ein eigener Datenrahmen ist, der von df indem wir dies explizit deklarieren.

Beginnen wir wieder bei df_q aber verwenden Sie diesmal .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

Dies funktioniert ohne Fehler, da wir Pandas mitgeteilt haben, dass df_q von df

Wenn Sie tatsächlich möchten, dass diese Änderungen an df_c bis zu df weitergegeben werden, ist dies ein weiterer Punkt und wird beantwortet, wenn Sie möchten.

@ CRiddler Großartig, danke !
Wie Sie bereits erwähnt haben, hat verkettetes .loc nie zu unerwarteten Ergebnissen geführt. Soweit ich weiß, stellt .copy() Pandas sicher, dass wir ausgewählte df_sliced_once als separates Objekt behandeln und nicht beabsichtigen, die anfänglichen vollen df zu ändern. Bitte korrigieren Sie, wenn ich etw verwechselt habe.

Die Dokumentation finden Sie hier http://pandas.pydata.org/pandas-docs/stable/indexing.html#returning -a-view-versus-a-copy und @CRiddler hat eine nette Erklärung. Sie sollten inplace im Allgemeinen NICHT verwenden.

Wenn Sie tatsächlich möchten, dass diese Änderungen an df_c bis zu df weitergegeben werden, ist dies ein weiterer Punkt und wird beantwortet, wenn Sie möchten.

@CRiddler Vielen Dank, dass Ihre Antwort besser ist als die in Stack Overflow. Können Sie sie hinzufügen, wenn Sie sie an den ursprünglichen Datenrahmen weitergeben oder einen Hinweis darauf geben möchten, wie sie ausgeführt wird?

@persep Im Allgemeinen mag ich es nicht, Probleme in Stackoverflow-Threads umzuwandeln, um Hilfe zu erhalten, aber es scheint, dass dieses Problem seit dem letzten Posting einiges an Aufmerksamkeit erhalten hat, also werde ich meine Methode zur Behebung dieser Art von Problem in veröffentlichen Pandas. Normalerweise mache ich das, indem ich den Datenrahmen nicht in separate Variablen unterteile, sondern Masken in Variablen umwandle. Kombiniere dann Masken nach Bedarf und setze Werte basierend auf diesen Masken, um sicherzustellen, dass die Änderungen im ursprünglichen Datenrahmen und nicht in einer herumschwebenden Kopie stattfinden .

Originale Daten:

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

Denken Sie daran, dass beim Erstellen eines temporären Datenrahmens KEINE Änderungen weitergegeben werden
Wie im vorherigen Beispiel gezeigt, werden nur Änderungen an df_q und eine Pandas-Warnung ausgelöst (hier nicht kopiert / eingefügt). AND verbreitet KEINE Änderungen an 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

Meines Wissens gibt es keine Möglichkeit, denselben Code wie oben zu verwenden und Änderungen zu erzwingen, um zum ursprünglichen Datenrahmen zurückzukehren.

Wenn wir jedoch unser Denken ein wenig ändern und mit Masken anstelle von vollständigen Teilmengen arbeiten, können wir das gewünschte Ergebnis erzielen. Dies "überträgt" zwar nicht unbedingt Änderungen am ursprünglichen Datenrahmen aus einer Teilmenge, stellen jedoch sicher, dass alle Änderungen, die wir am ursprünglichen Datenrahmen vornehmen, df . Dazu erstellen wir zuerst Masken und wenden sie dann an, wenn wir eine Änderung an dieser Teilmenge von df vornehmen möchten

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

Wenn wir jemals sehen wollten, wie df_q aussehen würde, können wir es jederzeit mit unserem q_mask vom ursprünglichen Datenrahmen unterteilen

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

Obwohl dies nicht unbedingt Änderungen von df_q auf df "propagiert", erzielen wir das gleiche Ergebnis. Die tatsächliche Weitergabe müsste explizit erfolgen und wäre weniger effizient als nur die Arbeit mit Masken.

@CRiddler Danke, du warst sehr hilfreich

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen