it-swarm.com.de

Pandas: Wissen, wann sich eine Operation auf den ursprünglichen Datenrahmen auswirkt

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

30
ejolly

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.

Fall A: Änderungen an 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.

Fall B: Änderungen an df2 sollten sich auch auf df1 auswirken

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

2
JohnE

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'
0
romulomadu

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.

0
alububu