SettingWithCopyWarning
ist eine Warnung, die von Pandas ausgelöst werden kann, wenn wir eine Zuweisung an ein DataFrame durchführen. Dies kann passieren, wenn wir verkettete Zuweisungen verwenden oder wenn wir ein DataFrame verwenden, das aus einem Slice erstellt wurde. Es ist eine häufige Fehlerquelle in Pandas-Code, mit der wir alle schon konfrontiert wurden. Es kann schwierig sein zu debuggen, weil die Warnung in Code auftreten kann, der so aussieht, als ob er einwandfrei funktionieren sollte.
Das Verständnis der SettingWithCopyWarning
ist wichtig, da sie auf potenzielle Probleme bei der Datenmanipulation hinweist. Diese Warnung legt nahe, dass Ihr Code die Daten möglicherweise nicht wie beabsichtigt verändert, was zu unbeabsichtigten Konsequenzen und undurchsichtigen Fehlern führen kann, die schwer nachzuvollziehen sind.
In diesem Artikel werden wir die SettingWithCopyWarning
in Pandas untersuchen und wie man sie vermeiden kann. Wir werden auch über die Zukunft von Pandas und wie die Option copy_on_write
die Arbeit mit DataFrames verändern wird, diskutieren.
DataFrame-Ansichten und Kopien
Wenn wir einen Slice eines DataFrames auswählen und ihn einer Variablen zuweisen, können wir entweder eine Ansicht oder eine frische DataFrame-Kopie erhalten.
Bei einer Ansicht wird der Speicher zwischen beiden DataFrames geteilt. Das bedeutet, dass das Ändern eines Werts aus einer Zelle, die in beiden DataFrames vorhanden ist, beide ändert.
Bei einer Kopie wird neuer Speicher zugewiesen, und es wird ein unabhängiges DataFrame mit denselben Werten wie das Original erstellt. In diesem Fall sind beide DataFrames eigenständige Entitäten, sodass das Ändern eines Werts in einem von ihnen den anderen nicht beeinflusst.
Pandas versucht, das Erstellen einer Kopie zu vermeiden, wenn möglich, um die Leistung zu optimieren. Es ist jedoch unmöglich im Voraus vorherzusagen, ob wir eine Ansicht oder eine Kopie erhalten werden. Die SettingWithCopyWarning
wird immer dann ausgelöst, wenn wir einen Wert einem DataFrame zuweisen, bei dem unklar ist, ob es sich um eine Kopie oder eine Ansicht von einem anderen DataFrame handelt.
Das Verständnis der SettingWithCopyWarning
mit echten Daten
Wir werden den Kaggle-Datensatz dieser Immobiliendaten London 2024 verwenden, um zu lernen, wie die SettingWithCopyWarning
auftritt und wie man sie behebt.
Dieser Datensatz enthält aktuelle Immobiliendaten aus London. Hier ist eine Übersicht über die in dem Datensatz vorhandenen Spalten:
addedOn
: Das Datum, an dem das Inserat hinzugefügt wurde.title
: Der Titel des Inserats.descriptionHtml
: Eine HTML-Beschreibung des Inserats.propertyType
: Der Typ der Immobilie. Der Wert wird auf"Not Specified"
gesetzt, wenn der Typ nicht angegeben wurde.sizeSqFeetMax
: Die maximale Größe in Quadratfuß.bedrooms
: Die Anzahl der Schlafzimmer.listingUpdatedReason
: Grund für die Aktualisierung des Inserats (z.B. neues Inserat, Preissenkung).price
: Der Preis des Inserats in Pfund.
Beispiel mit einer expliziten temporären Variable
Angenommen, uns wurde mitgeteilt, dass die Eigenschaften mit einem nicht spezifizierten Eigenschaftstyp Häuser sind. Wir möchten daher alle Zeilen mit propertyType
gleich "Not Specified"
auf "House"
aktualisieren. Eine Möglichkeit hierfür besteht darin, die Zeilen mit einem nicht spezifizierten Eigenschaftstyp in eine temporäre DataFrame-Variable zu filtern und die Werte der propertyType
-Spalte wie folgt zu aktualisieren:
import pandas as pd dataset_name = "realestate_data_london_2024_nov.csv" df = pd.read_csv(dataset_name) # Erhalten aller Zeilen mit nicht spezifiziertem Eigenschaftstyp no_property_type = df[df["propertyType"] == "Not Specified"] # Aktualisieren des Eigenschaftstyps auf „House“ in diesen Zeilen no_property_type["propertyType"] = "House"
Die Ausführung dieses Codes führt dazu, dass Pandas die SettingWithCopyWarning
produziert:
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: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy no_property_type["propertyType"] = "House"
Der Grund dafür ist, dass Pandas nicht wissen kann, ob das no_property_type
DataFrame eine Ansicht oder eine Kopie von df
ist.
Dies ist ein Problem, da das Verhalten des folgenden Codes je nachdem, ob es sich um eine Ansicht oder eine Kopie handelt, sehr unterschiedlich sein kann.
In diesem Beispiel ist unser Ziel, das ursprüngliche DataFrame zu ändern. Dies wird nur geschehen, wenn no_property_type
eine Ansicht ist. Wenn der Rest unseres Codes davon ausgeht, dass df
geändert wurde, kann das falsch sein, denn es gibt keine Möglichkeit zu garantieren, dass dies der Fall ist. Aufgrund dieses unsicheren Verhaltens wirft Pandas die Warnung aus, um uns über diese Tatsache zu informieren.
Auch wenn unser Code korrekt ausgeführt wird, weil wir eine Ansicht erhalten haben, könnten wir bei nachfolgenden Ausführungen eine Kopie erhalten, und der Code wird nicht wie beabsichtigt funktionieren. Daher ist es wichtig, diese Warnung nicht zu ignorieren und sicherzustellen, dass unser Code immer das tut, was wir wollen.
Beispiel mit einer versteckten temporären Variable
Im vorherigen Beispiel ist klar, dass eine temporäre Variable verwendet wird, weil wir einen Teil des DataFrame explizit einer Variable mit dem Namen no_property_type
zuweisen.
Manchmal ist dies jedoch nicht so explizit. Das häufigste Beispiel, bei dem die Warnung SettingWithCopyWarning
auftritt, ist bei verketteten Indizierungen. Angenommen, wir ersetzen die letzten beiden Zeilen durch eine einzige Zeile:
df[df["propertyType"] == "Not Specified"]["propertyType"] = "House"
Auf den ersten Blick scheint es nicht, als würde eine temporäre Variable erstellt. Das Ausführen führt jedoch ebenfalls zu einer SettingWithCopyWarning
.
Der Code wird folgendermaßen ausgeführt:
df[df["propertyType"] == "Not Specified"]
wird ausgewertet und vorübergehend im Speicher gespeichert.- Der Index
["propertyType"]
dieses temporären Speicherorts wird abgerufen.
Indizes werden nacheinander ausgewertet, daher führt verkettete Indizierung ebenfalls zu der Warnung, weil wir nicht wissen, ob die Zwischenergebnisse Ansichten oder Kopien sind. Der obige Code entspricht im Wesentlichen dem Folgenden:
tmp = df[df["propertyType"] == "Not Specified"] tmp["propertyType"] = "House"
Dieses Beispiel wird oft als verkettete Indizierung bezeichnet, weil wir indizierte Zugriffe mit []
verkettet haben. Zuerst greifen wir auf [df["propertyType"] == "Not Specified"]
zu und dann auf ["propertyType"]
.
Wie man die SettingWithCopyWarning
löst
Lassen Sie uns lernen, wie wir unseren Code schreiben können, damit es keine Mehrdeutigkeiten gibt und die SettingWithCopyWarning
nicht ausgelöst wird. Wir haben gelernt, dass die Warnung aus einer Mehrdeutigkeit darüber entsteht, ob ein DataFrame eine Ansicht oder eine Kopie eines anderen DataFrames ist.
Der Weg, um dies zu beheben, besteht darin, sicherzustellen, dass jedes DataFrame, das wir erstellen, eine Kopie ist, wenn wir möchten, dass es eine Kopie ist, oder eine Ansicht, wenn wir möchten, dass es eine Ansicht ist.
Das ursprüngliche DataFrame sicher ändern mit loc
Lassen Sie uns den Code aus dem obigen Beispiel korrigieren, in dem wir das ursprüngliche DataFrame ändern möchten. Um die Verwendung einer temporären Variablen zu vermeiden, verwenden Sie die Eigenschaft des Indexers loc
.
df.loc[df["propertyType"] == "Not Specified", "propertyType"] = "House"
Mit diesem Code arbeiten wir direkt am ursprünglichen df
DataFrame über die Eigenschaft des Indexers loc
, daher gibt es keine Notwendigkeit für Zwischenvariablen. Dies ist das, was wir tun müssen, wenn wir das ursprüngliche DataFrame direkt ändern möchten.
Dies mag auf den ersten Blick wie verkettetes Indexing aussehen, weil es immer noch Parameter gibt, aber das ist es nicht. Was jedes Indexing definiert, sind die eckigen Klammern []
.
Beachten Sie, dass die Verwendung von loc
nur sicher ist, wenn wir direkt einen Wert zuweisen, wie wir es oben getan haben. Wenn wir stattdessen eine temporäre Variable verwenden, geraten wir erneut in dasselbe Problem. Hier sind zwei Beispiele für Code, die das Problem nicht beheben:
- Verwendung von
loc
mit einer temporären Variablen:
# Die Verwendung von loc zusammen mit einer temporären Variablen behebt das Problem nicht no_property_type = df.loc[df["propertyType"] == "Not Specified"] no_property_type["propertyType"] = "House"
- Verwendung von
loc
zusammen mit einem Index (gleichbedeutend mit verschachteltem Indexing):
# Die Verwendung von loc zusammen mit einem Index entspricht verschachteltem Indexing df.loc[df["propertyType"] == "Not Specified"]["propertyType"] = "House"
Beide Beispiele neigen dazu, Menschen zu verwirren, weil es ein häufiges Missverständnis ist, dass solange ein loc
vorhanden ist, die ursprünglichen Daten geändert werden. Das ist falsch. Der einzige Weg, um sicherzustellen, dass der Wert dem ursprünglichen DataFrame zugewiesen wird, besteht darin, ihn direkt mit einem einzigen loc
ohne separate Indizierung zuzuweisen.
Sicherer Umgang mit einer Kopie des ursprünglichen DataFrames mit copy()
Wenn wir sicherstellen möchten, dass wir mit einer Kopie des DataFrame arbeiten, sollten wir dieMethode .copy()
verwenden.
Angenommen, wir sollen den Quadratmeterpreis der Immobilien analysieren. Wir möchten die Originaldaten nicht verändern. Das Ziel ist es, einen neuen DataFrame mit den Analyseergebnissen zu erstellen, um ihn an ein anderes Team zu senden.
Der erste Schritt besteht darin, einige Zeilen zu filtern und die Daten zu bereinigen. Konkret müssen wir:
- Entfernen Sie die Zeilen, in denen
sizeSqFeetMax
nicht definiert ist. - Entfernen Sie die Zeilen, in denen der
price
"POA"
(Preis auf Anfrage) ist. - Konvertieren Sie die Preise in numerische Werte (im Originaldatensatz sind die Preise Zeichenfolgen im folgenden Format:
"£25,000,000"
)
Die obigen Schritte können mit folgendem Code ausgeführt werden:
# 1. Filtern Sie alle Eigenschaften ohne Größe oder Preis heraus properties_with_size_and_price = df[df["sizeSqFeetMax"].notna() & (df["price"] != "POA")] # 2. Entfernen Sie die £ und , Zeichen aus den Preis-Spalten properties_with_size_and_price["price"] = properties_with_size_and_price["price"].str.replace("£", "", regex=False).str.replace(",", "", regex=False) # 3. Konvertieren Sie die Preis-Spalte in numerische Werte properties_with_size_and_price["price"] = pd.to_numeric(properties_with_size_and_price["price"])
Um den Preis pro Quadratfuß zu berechnen, erstellen wir eine neue Spalte, deren Werte das Ergebnis der Division der Spalte price
durch die Spalte sizeSqFeetMax
sind:
properties_with_size_and_price["pricePerSqFt"] = properties_with_size_and_price["price"] / properties_with_size_and_price["sizeSqFeetMax"]
Wenn wir diesen Code ausführen, erhalten wir erneut die SettingWithCopyWarning
. Dies sollte keine Überraschung sein, da wir explizit eine temporäre DataFrame-Variable properties_with_size_and_price
erstellt und modifiziert haben.
Da wir an einer Kopie der Daten arbeiten möchten und nicht am Original-DataFrame, können wir das Problem beheben, indem wir sicherstellen, dass properties_with_size_and_price
eine frische DataFrame-Kopie ist und kein Ansichtsobjekt, indem wir die Methode .copy()
in der ersten Zeile verwenden:
properties_with_size_and_price = df[df["sizeSqFeetMax"].notna() & (df["price"] != "POA")].copy()
Das sichere Hinzufügen neuer Spalten
Das Erstellen neuer Spalten verhält sich genauso wie das Zuweisen von Werten. Immer wenn unklar ist, ob wir mit einer Kopie oder einer Ansicht arbeiten, wird pandas eine SettingWithCopyWarning
ausgeben.
Wenn wir mit einer Kopie der Daten arbeiten möchten, sollten wir sie explizit mit der Methode .copy()
kopieren. Dann sind wir frei, eine neue Spalte auf die gewünschte Weise zuzuweisen. Das haben wir getan, als wir die Spalte pricePerSqFt
im vorherigen Beispiel erstellt haben.
Andererseits, wenn wir das Original-DataFrame ändern möchten, gibt es zwei Fälle zu beachten.
- Wenn die neue Spalte alle Zeilen umfasst, können wir das Original-DataFrame direkt ändern. Dies wird keine Warnung verursachen, da wir keine Teilmengen der Zeilen auswählen. Zum Beispiel könnten wir eine
note
-Spalte für jede Zeile hinzufügen, in der der Haustyp fehlt:
df["notes"] = df["propertyType"].apply(lambda house_type: "Missing house type" if house_type == "Not Specified" else "")
- Wenn die neue Spalte nur Werte für eine Teilmenge der Zeilen definiert, können wir die Eigenschaft
loc
des Indexers verwenden. Zum Beispiel:
df.loc[df["propertyType"] == "Not Specified", "notes"] = "Missing house type"
Beachten Sie, dass in diesem Fall der Wert in den Spalten, die nicht ausgewählt wurden, nicht definiert ist. Der erste Ansatz ist daher bevorzugt, da er es uns ermöglicht, für jede Zeile einen Wert anzugeben.
SettingWithCopyWarning
Fehler in Pandas 3.0
Derzeit ist SettingWithCopyWarning
nur eine Warnung, kein Fehler. Unser Code wird weiterhin ausgeführt, und Pandas informiert uns lediglich, vorsichtig zu sein.
Gemäß der offiziellen Pandas-Dokumentation, wird SettingWithCopyWarning
ab Version 3.0 nicht mehr verwendet und durch einen tatsächlichen Fehler standardmäßig ersetzt, um strengere Code-Standards durchzusetzen.
Um sicherzustellen, dass unser Code kompatibel mit zukünftigen Versionen von Pandas bleibt, wird empfohlen, ihn bereits jetzt so zu aktualisieren, dass ein Fehler anstelle einer Warnung ausgelöst wird.
Dies erfolgt durch das Festlegen der folgenden Option nach dem Importieren von pandas:
import pandas as pd pd.options.mode.copy_on_write = True
Das Hinzufügen dieses Codes zu vorhandenem Code stellt sicher, dass wir mit jeder mehrdeutigen Zuweisung in unserem Code umgehen und sicherstellen, dass der Code immer noch funktioniert, wenn wir auf pandas 3.0 aktualisieren.
Fazit
Der SettingWithCopyWarning
tritt immer dann auf, wenn unser Code mehrdeutig macht, ob der Wert, den wir ändern, eine Ansicht oder eine Kopie ist. Wir können dies beheben, indem wir immer explizit angeben, was wir wollen:
- Wenn wir mit einer Kopie arbeiten wollen, sollten wir sie explizit mit der Methode
copy()
kopieren. - Wenn wir das originale DataFrame ändern wollen, sollten wir das Indexer-Eigenschaft
loc
verwenden und den Wert direkt zuweisen, wenn wir auf die Daten zugreifen, ohne Zwischenvariablen zu verwenden.
Obwohl es sich nicht um einen Fehler handelt, sollten wir diese Warnung nicht ignorieren, da sie zu unerwarteten Ergebnissen führen kann. Darüber hinaus wird es ab Pandas 3.0 standardmäßig zu einem Fehler, daher sollten wir unseren Code zukunftssicher machen, indem wir Copy-on-Write in unserem aktuellen Code aktivieren, indem wir pd.options.mode.copy_on_write = True
verwenden. Dies wird sicherstellen, dass der Code auch in zukünftigen Versionen von Pandas funktionsfähig bleibt.
Source:
https://www.datacamp.com/tutorial/settingwithcopywarning-pandas