Wie SettingWithCopyWarning
gehe ich mit Pandas um?
Dieser Beitrag ist für Leser gedacht, die,
- Möchte verstehen, was diese Warnung bedeutet
- Ich möchte verschiedene Möglichkeiten zur Unterdrückung dieser Warnung verstehen
- Möchten Sie verstehen, wie Sie den Code verbessern und bewährte Methoden befolgen können, um diese Warnung in Zukunft zu vermeiden.
Installieren
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
Was ist der SettingWithCopyWarning
?
Um zu wissen, wie man mit dieser Warnung umgeht, ist es wichtig zu verstehen, was sie bedeutet und warum sie überhaupt ausgelöst wird.
Beim Filtern von DataFrames kann ein Frame geschnitten / indiziert werden, um entweder eine Ansicht oder eine Kopie zurückzugeben je nach internem Layout und verschiedenen Implementierungsdetails . Eine "Ansicht" ist, wie der Begriff andeutet, eine Ansicht in die Originaldaten, so dass das Ändern der Ansicht das ursprüngliche Objekt ändern kann. Andererseits ist eine "Kopie" eine Replikation von Daten aus dem Original, und das Ändern der Kopie hat keine Auswirkungen auf das Original.
Wie in anderen Antworten erwähnt, SettingWithCopyWarning
wurde das erstellt, um "verkettete Zuweisungs" -Operationen zu kennzeichnen. Betrachten Sie df
im obigen Setup. Angenommen, Sie möchten alle Werte in Spalte "B" auswählen, wobei die Werte in Spalte "A"> 5 sind. Mit Pandas können Sie dies auf verschiedene Arten tun, von denen einige korrekter sind als andere. Zum Beispiel,
df[df.A > 5]['B']
1 3
2 6
Name: B, dtype: int64
Und,
df.loc[df.A > 5, 'B']
1 3
2 6
Name: B, dtype: int64
Diese geben das gleiche Ergebnis zurück. Wenn Sie also nur diese Werte lesen, spielt dies keine Rolle. Also, was ist das Problem? Das Problem bei der verketteten Zuweisung besteht darin, dass es im Allgemeinen schwierig ist, vorherzusagen, ob eine Ansicht oder eine Kopie zurückgegeben wird. Dies wird daher größtenteils zu einem Problem, wenn Sie versuchen, Werte zurückzuweisen. Um auf dem vorherigen Beispiel aufzubauen, betrachten Sie, wie dieser Code vom Interpreter ausgeführt wird:
df.loc[df.A > 5, 'B'] = 4
# becomes
df.__setitem__((df.A > 5, 'B'), 4)
Mit einem einzigen __setitem__
Anruf an df
. OTOH, betrachten Sie diesen Code:
df[df.A > 5]['B'] = 4
# becomes
df.__getitem__(df.A > 5).__setitem__('B", 4)
Abhängig davon, ob __getitem__
eine Ansicht oder eine Kopie zurückgegeben wurde, __setitem__
funktioniert der Vorgang möglicherweise nicht .
Im Allgemeinen sollten Sie loc
für die beschriftungsbasierte Zuweisung und iloc
für die ganzzahlige / positionsbasierte Zuweisung verwenden, da die Spezifikation garantiert, dass sie immer mit dem Original arbeiten. Zum Festlegen einer einzelnen Zelle sollten Sie außerdem at
und verwendeniat
.
Weitere finden Sie in der Dokumentation .
Hinweis
Alle booleschen Indizierungsvorgänge, mit denen ausgeführt wird, loc
können auch ausgeführt werden iloc
. Der einzige Unterschied besteht darin, iloc
dass entweder Ganzzahlen / Positionen für den Index oder ein numpy-Array von Booleschen Werten und Ganzzahl- / Positionsindizes für die Spalten erwartet werden.
Zum Beispiel,
df.loc[df.A > 5, 'B'] = 4
Kann nas geschrieben werden
df.iloc[(df.A > 5).values, 1] = 4
Und,
df.loc[1, 'A'] = 100
Kann geschrieben werden als
df.iloc[1, 0] = 100
Und so weiter.
Sagen Sie mir einfach, wie ich die Warnung unterdrücken kann!
Betrachten Sie eine einfache Operation in der Spalte "A" von df
. Wenn Sie "A" auswählen und durch 2 teilen, wird die Warnung ausgelöst, aber der Vorgang funktioniert.
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
Es gibt verschiedene Möglichkeiten, diese Warnung direkt zum Schweigen zu bringen:
Mach ein deepcopy
df2 = df[['A']].copy(deep=True)
df2['A'] /= 2
Ändernpd.options.mode.chained_assignment
kann eingestellt werden None
, "warn"
oder "raise"
. "warn"
ist die Standardeinstellung. None
unterdrückt die Warnung vollständig und "raise"
wirft ein SettingWithCopyError
, wodurch verhindert wird , dass die Operation ausgeführt wird.
pd.options.mode.chained_assignment = None
df2['A'] /= 2
@Peter Cotton hat in den Kommentaren eine nette Möglichkeit gefunden, den Modus (geändert von diesem Kern ) mit einem Kontextmanager nicht aufdringlich zu ändern , um den Modus nur so lange einzustellen, wie es erforderlich ist, und ihn auf den zurückzusetzen Originalzustand, wenn fertig.
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
Die Verwendung ist wie folgt:
# some code here
with ChainedAssignent():
df2['A'] /= 2
# more code follows
Oder um die Ausnahme auszulösen
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
Das "XY-Problem": Was mache ich falsch?
In den meisten Fällen versuchen Benutzer, nach Möglichkeiten zu suchen, um diese Ausnahme zu unterdrücken, ohne vollständig zu verstehen, warum sie überhaupt ausgelöst wurde. Dies ist ein gutes Beispiel für ein XY-Problem , bei dem Benutzer versuchen, ein Problem "Y" zu lösen, das tatsächlich ein Symptom für ein tiefer verwurzeltes Problem "X" ist. Auf der Grundlage häufiger Probleme, auf die diese Warnung stößt, werden Fragen gestellt und anschließend Lösungen vorgestellt.
Frage 1
Ich habe einen 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
Ich möchte Werte in Spalte "A"> 5 bis 1000 zuweisen. Meine erwartete Ausgabe ist
A B C D E
0 5 0 3 3 7
1 1000 3 5 2 4
2 1000 6 8 8 1
Falscher Weg, dies zu tun:
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
Richtiger Weg mit loc
:
df.loc[df.A > 5, 'A'] = 1000
Frage 2 1
Ich versuche, den Wert in Zelle (1, 'D') auf 12345 zu setzen. Meine erwartete Ausgabe ist
A B C D E
0 5 0 3 3 7
1 9 3 5 12345 4
2 7 6 8 8 1
Ich habe verschiedene Möglichkeiten für den Zugriff auf diese Zelle ausprobiert, z
df['D'][1]
. Was ist der beste Weg, dies zu tun?
1. Diese Frage bezieht sich nicht speziell auf die Warnung, aber es ist gut zu verstehen, wie dieser bestimmte Vorgang korrekt ausgeführt wird, um Situationen zu vermeiden, in denen die Warnung möglicherweise in Zukunft auftreten könnte.
Sie können dazu eine der folgenden Methoden verwenden.
df.loc[1, 'D'] = 12345
df.iloc[1, 3] = 12345
df.at[1, 'D'] = 12345
df.iat[1, 3] = 12345
Frage 3
Ich versuche, Werte basierend auf einer bestimmten Bedingung zu unterteilen. Ich habe einen DataFrame
A B C D E
1 9 3 5 2 4
2 7 6 8 8 1
Ich möchte 123 Werte in "D" zuweisen, so dass "C" == 5. Ich habe es versucht
df2.loc[df2.C == 5, 'D'] = 123
Was scheint in Ordnung, aber ich bekomme immer noch die
SettingWithCopyWarning
! Wie behebe ich das?
Dies liegt wahrscheinlich wahrscheinlich an Code, der sich weiter oben in Ihrer Pipeline befindet. Hast du df2
aus etwas Größerem erschaffen , wie
df2 = df[df.A > 5]
? In diesem Fall gibt die boolesche Indizierung eine Ansicht zurück und df2
verweist auf das Original. Was Sie tun müssen, ist df2
einer Kopie zuzuweisen :
df2 = df[df.A > 5].copy()
# Or,
# df2 = df.loc[df.A > 5, :]
Frage 4
Ich versuche, die Spalte "C" an Ort und Stelle zu löschen
A B C D E
1 9 3 5 2 4
2 7 6 8 8 1
Aber mit
df2.drop('C', axis=1, inplace=True)
Würfe SettingWithCopyWarning
. Warum passiert dies?
Dies liegt daran, df2
dass eine Ansicht aus einer anderen Schnittoperation erstellt worden sein muss, z
df2 = df[df.A > 5]
Die Lösung besteht darin, entweder wie zuvor eine copy()
zu verwenden df
oder zu verwenden loc
.