Ich liebe Pandas und benutze sie seit Jahren und bin mir ziemlich sicher, dass ich die Teilmenge von Datenrahmen und den angemessenen Umgang mit Ansichten und Kopien gut beherrsche (obwohl ich sicher viele Behauptungen verwende). Ich weiß auch, dass es Unmengen von Fragen zu SettingWithCopyWarning gegeben hat, z. Wie mit SettingWithCopyWarning in Pandas umgehen? und einige gute aktuelle Anleitungen zum Umwickeln des Kopfes, wenn es passiert, z.B. Grundlegendes zu SettingWithCopyWarning in Pandas .
Aber ich weiß auch, dass bestimmte Dinge wie das Zitat aus diese Antwort nicht mehr in den neuesten Dokumenten (0.22.0
) enthalten sind und dass viele Dinge im Laufe der Jahre veraltet sind (was zu einigen unangemessenen alten SO führt Antworten), und dass sich die Dinge weiterhin ändern .
Kürzlich, nachdem ich Pandas beigebracht habe, Neulinge mit sehr grundlegenden allgemeinen Python-Kenntnissen über Dinge wie das Vermeiden von verketteter Indizierung (und die Verwendung von .iloc
/.loc
) zu vervollständigen, habe ich immer noch Probleme, allgemeine Faustregeln anzugeben, um zu wissen, wann es wichtig ist um auf die SettingWithCopyWarning
zu achten (zB wenn es sicher ist, sie zu ignorieren).
Ich persönlich habe festgestellt, dass das spezifische Muster, einen Datenrahmen nach einer bestimmten Regel zu unterteilen (z. B. durch Schneiden oder Boolesche Operationen) und dann diese Untergruppe zu ändern (unabhängig vom ursprünglichen Datenrahmen), eine weitaus häufigere Operation ist als die Dokumente schlagen vor. In dieser Situation möchten wir die Kopie nicht das Original ändern und die Warnung ist für Neulinge verwirrend/beängstigend.
Ich weiß, dass es nicht trivial ist, im Voraus zu wissen, wann eine Ansicht gegen eine Kopie zurückgegeben wird, z.
Welche Regeln verwenden Pandas, um eine Ansicht gegen eine Kopie zu generieren?
Überprüfen, ob der Datenrahmen in Pandas kopiert oder angezeigt wird
Stattdessen suche ich nach der Antwort auf eine allgemeinere (anfängerfreundliche) Frage: Wann wirkt sich die Ausführung einer Operation für einen untergeordneten Datenrahmen auf den ursprünglichen Datenrahmen aus, von dem er erstellt wurde, und wann sind sie unabhängig? .
Ich habe unten einige Fälle erstellt, die ich für sinnvoll halte, aber ich bin mir nicht sicher, ob ein "gotcha" fehlt oder ob es eine einfachere Möglichkeit gibt, dies zu überprüfen. Ich hatte gehofft, jemand könnte bestätigen, dass meine Intuitionen zu den folgenden Anwendungsfällen korrekt sind, da sie sich auf meine obige Frage beziehen.
import pandas as pd
df1 = pd.DataFrame({'A':[2,4,6,8,10],'B':[1,3,5,7,9],'C':[10,20,30,40,50]})
1) Warnung: Nein
Original geändert: Nein
# df1 will be unaffected because we use .copy() method explicitly
df2 = df1.copy()
#
# Reference: docs
df2.iloc[0,1] = 100
2) Warnung: Ja (ich habe nicht wirklich verstanden, warum)
Original geändert: Nein
# df1 will be unaffected because .query() always returns a copy
#
# Reference:
# https://stackoverflow.com/a/23296545/8022335
df2 = df1.query('A < 10')
df2.iloc[0,1] = 100
3) Warnung: Ja
Original geändert: Nein
# df1 will be unaffected because boolean indexing with .loc
# always returns a copy
#
# Reference:
# https://stackoverflow.com/a/17961468/8022335
df2 = df1.loc[df1['A'] < 10,:]
df2.iloc[0,1] = 100
4) Warnung: Nein
Original geändert: Nein
# df1 will be unaffected because list indexing with .loc (or .iloc)
# always returns a copy
#
# Reference:
# Same as 4)
df2 = df1.loc[[0,3,4],:]
df2.iloc[0,1] = 100
5) Warnung: Nein
Original geändert: Ja (für Neulinge verwirrend, aber sinnvoll)
# df1 will be affected because scalar/slice indexing with .iloc/.loc
# always references the original dataframe, but may sometimes
# provide a view and sometimes provide a copy
#
# Reference: docs
df2 = df1.loc[:10,:]
df2.iloc[0,1] = 100
tl; dr Wenn Sie einen neuen Datenrahmen aus dem Original erstellen, ändern Sie den neuen Datenrahmen:
Ändert das Original, wenn Skalar/Slice-Indizierung mit .loc/.iloc zum Erstellen des neuen Datenrahmens verwendet wird .
Wird nicht das Original ändern, wenn boolesche Indizierung mit .loc, .query()
oder .copy()
zum Erstellen des neuen Datenrahmens verwendet wird
Dies ist ein etwas verwirrender und sogar frustrierender Teil von Pandas. In der Regel sollten Sie sich jedoch keine Sorgen darüber machen, wenn Sie einige einfache Workflowregeln befolgen. Beachten Sie insbesondere, dass es hier nur zwei allgemeine Fälle gibt, in denen Sie zwei Datenframes haben, wobei einer der beiden eine Untermenge ist.
Dies ist ein Fall, in dem die Zen-of-Python-Regel "explizit ist besser als implizit" eine großartige Richtlinie ist.
df2
sollten sich nicht auf df1
auswirken.Das ist natürlich trivial. Sie möchten zwei völlig unabhängige Datenrahmen, so dass Sie explizit eine Kopie erstellen:
df2 = df1.copy()
Danach betrifft alles, was Sie mit df2
tun, nur df2
und nicht df1
und umgekehrt.
df2
sollten sich auch auf df1
auswirkenIn diesem Fall glaube ich nicht, dass es einen generellen Weg gibt, um das Problem zu lösen, denn es hängt davon ab, was Sie genau tun. Es gibt jedoch einige Standardansätze, die ziemlich unkompliziert sind und keine Unklarheiten über ihre Funktionsweise haben sollten.
Methode 1: Kopieren Sie df1 nach df2 und aktualisieren Sie df1 anschließend mit df2
In diesem Fall können Sie grundsätzlich eine 1: 1-Konvertierung der obigen Beispiele durchführen. Hier ist Beispiel # 2:
df2 = df1.copy()
df2 = df1.query('A < 10')
df2.iloc[0,1] = 100
df1 = df2.append(df1).reset_index().drop_duplicates(subset='index').drop(columns='index')
Leider ist das erneute Zusammenführen über append
etwas verbos. Sie können dies mit den folgenden Schritten sauberer ausführen, obwohl es den Nebeneffekt hat, Ganzzahlen in Gleitkommazahlen zu konvertieren.
df1.update(df2) # note that this is an inplace operation
Methode 2: Verwenden Sie eine Maske (erstellen Sie df2
nicht)
Ich denke, der beste allgemeine Ansatz hier ist, df2
überhaupt nicht zu erstellen, sondern es muss eine maskierte Version von df1
sein. Leider können Sie den obigen Code leider nicht direkt übersetzen, da er loc
und iloc
gemischt hat, was für dieses Beispiel in Ordnung ist, für die tatsächliche Verwendung jedoch wahrscheinlich unrealistisch ist.
Der Vorteil ist, dass Sie sehr einfachen und lesbaren Code schreiben können. Hier ist eine alternative Version von Beispiel # 2 oben, bei der df2
eigentlich nur eine maskierte Version von df1
ist. Aber anstatt über iloc
zu wechseln, werde ich mich ändern, wenn Spalte "C" == 10.
df2_mask = df1['A'] < 10
df1.loc[ df2_mask & (df1['C'] == 10), 'B'] = 100
Wenn Sie nun df1
oder df1[df2_mask]
drucken, wird für die erste Zeile jedes Datenrahmens die Spalte "B" = 100 angezeigt. Offensichtlich ist das hier nicht sehr überraschend, aber das ist der inhärente Vorteil, "explizit ist besser als implizit" zu folgen.
Ich habe den gleichen Zweifel, ich habe diese Antwort in der Vergangenheit erfolglos gesucht. Jetzt bestätige ich nur, dass sich das Original nicht ändert, und verwende diesen Code, um Warnungen zu entfernen:
import pandas as pd
pd.options.mode.chained_assignment = None # default='warn'
Sie müssen nur .iloc[0,1]
durch .iat[0,1]
ersetzen.
Allgemeiner ausgedrückt, wenn Sie nur ein Element ändern möchten, verwenden Sie die .iat
- oder .at
-Methode. Wenn Sie mehrere Elemente auf einmal ändern, sollten Sie stattdessen .loc
- oder .iloc
-Methoden verwenden.
Auf diese Weise würden Pandas keine Warnung auslösen.