Erweiterte Pandas-Muster
Bild vom Autor

# Einführung

Die meisten Datenwissenschaftler lernen Pandas indem Sie Tutorials lesen und Muster kopieren, die funktionieren.

Für den Anfang ist das in Ordnung, aber es führt oft dazu, dass sich Anfänger schlechte Gewohnheiten aneignen. Die Verwendung von iterrows() Schleifen, Zwischenvariablenzuweisungen und Wiederholungen merge() Aufrufe sind einige Beispiele für Code, der technisch korrekt, aber langsamer als nötig und schwieriger zu lesen ist, als er sein sollte.

Die folgenden Muster sind keine Randfälle. Sie decken die häufigsten täglichen Vorgänge in der Datenwissenschaft ab, z. B. Filtern, Transformieren, Verknüpfen, Gruppieren und Berechnen von bedingten Spalten.

In jedem von ihnen gibt es einen gemeinsamen Ansatz und einen besseren Ansatz, und der Unterschied besteht typischerweise eher im Bewusstsein als in der Komplexität.

Diese sechs haben den größten Einfluss: Methodenverkettung, die pipe() Muster, effiziente Verknüpfungen und Zusammenführungen, Groupby-Optimierungen, vektorisierte bedingte Logik und Leistungsprobleme.

Erweiterte Pandas-Muster

# Methodenverkettung

Zwischenvariablen können dafür sorgen, dass sich der Code besser organisiert anfühlt, verursachen aber oft nur Rauschen. Methodenverkettung ermöglicht es Ihnen, eine Folge von Transformationen als einen einzelnen Ausdruck zu schreiben, der sich natürlich liest und die Benennung von Objekten vermeidet, die keine eindeutigen Bezeichner benötigen.

Stattdessen:

df1 = df(df('standing') == 'lively')
df2 = df1.dropna(subset=('income'))
df3 = df2.assign(revenue_k=df2('income') / 1000)
outcome = df3.sort_values('revenue_k', ascending=False)

Du schreibst das:

outcome = (
    df
    .question("standing == 'lively'")
    .dropna(subset=('income'))
    .assign(revenue_k=lambda x: x('income') / 1000)
    .sort_values('revenue_k', ascending=False)
)

Das Lambda drin assign() ist hier wichtig.

Beim Verketten wird der aktuelle Standing angezeigt DataFrame nicht namentlich zugänglich; Sie müssen ein Lambda verwenden, um darauf zu verweisen. Die häufigste Ursache für Kettenbrüche ist das Vergessen, was typischerweise zu einem führt NameError oder ein veralteter Verweis auf eine Variable, die zuvor im Skript definiert wurde.

Ein weiterer wissenswerter Fehler ist die Verwendung von inplace=True in einer Kette. Methoden mit inplace=True zurückkehren Nonewodurch die Kette sofort unterbrochen wird. Direktoperationen sollten beim Schreiben von verkettetem Code vermieden werden, da sie keinen Speichervorteil bieten und es schwieriger machen, dem Code zu folgen.

# Das Pipe()-Muster

Wenn eine Ihrer Transformationen so komplex ist, dass sie eine eigene Funktion verdient, verwenden Sie Rohr() ermöglicht es Ihnen, es innerhalb der Kette zu halten.

pipe() passiert die DataFrame als erstes Argument für jedes Callable:

def normalize_columns(df, cols):
    df(cols) = (df(cols) - df(cols).imply()) / df(cols).std()
    return df

outcome = (
    df
    .question("standing == 'lively'")
    .pipe(normalize_columns, cols=('income', 'periods'))
    .sort_values('income', ascending=False)
)

Dadurch bleibt die komplexe Transformationslogik innerhalb einer benannten, testbaren Funktion, während die Kette erhalten bleibt. Jede Pipe-Funktion kann einzeln getestet werden, was eine Herausforderung darstellt, wenn die Logik in einer umfangreichen Kette verborgen ist.

Der praktische Wert von pipe() geht über das Erscheinungsbild hinaus. Aufteilen einer Verarbeitungspipeline in gekennzeichnete Funktionen und Verknüpfen dieser mit pipe() Ermöglicht die Selbstdokumentation des Codes. Jeder, der die Sequenz liest, kann jeden Schritt anhand des Funktionsnamens verstehen, ohne die Implementierung analysieren zu müssen.

Außerdem ist es einfacher, Schritte während des Debuggens auszutauschen oder zu überspringen, wenn Sie einen auskommentieren pipe() Anruf, der Relaxation der Kette läuft trotzdem reibungslos.

# Effiziente Verknüpfungen und Zusammenführungen

Eine der am häufigsten missbrauchten Funktionen bei Pandas ist verschmelzen(). Die beiden Fehler, die wir am häufigsten sehen, sind Many-to-Many-Joins und stille Zeileninflation.

Wenn beide Datenrahmen doppelte Werte im Verbindungsschlüssel haben, merge() führt ein kartesisches Produkt dieser Zeilen durch. Wenn der Verknüpfungsschlüssel beispielsweise auf mindestens einer Seite nicht eindeutig ist, kann die Verknüpfung einer 500 Zeilen umfassenden „Benutzer“-Tabelle mit einer „Ereignis“-Tabelle zu Millionen von Zeilen führen.

Dies führt nicht zu einem Fehler. es erzeugt einfach ein DataFrame Das scheint richtig zu sein, ist aber größer als erwartet, bis Sie seine Type untersuchen.

Die Lösung ist die validate Parameter:

df.merge(different, on='user_id', validate="many_to_one")

Dies wirft eine MergeError sofort, wenn die Viele-zu-Eins-Annahme verletzt wird. Verwenden Sie „one_to_one“, „one_to_many“ oder „many_to_one“, je nachdem, was Sie von der Verknüpfung erwarten.

Der indicator=True Der Parameter ist gleichermaßen nützlich für das Debuggen:

outcome = df.merge(different, on='user_id', how='left', indicator=True)
outcome('_merge').value_counts()

Dieser Parameter fügt a hinzu _merge Spalte, die angibt, ob jede Zeile von „left_only“, „right_only“ oder „each“ stammt. Dies ist die schnellste Möglichkeit, Zeilen zu erkennen, die nicht zusammengefügt werden konnten, obwohl Sie erwartet hatten, dass sie übereinstimmen.

In Fällen, in denen beide Datenrahmen einen Index teilen, verbinden() ist schneller als merge() da es direkt am Index arbeitet, anstatt eine bestimmte Spalte zu durchsuchen.

# Groupby-Optimierungen

Bei Verwendung von a GroupByeine wenig genutzte Methode ist rework(). Der Unterschied zwischen agg() Und rework() Es kommt darauf an, welche Type Sie zurückhaben möchten.

Der agg()-Methode gibt eine Zeile professional Gruppe zurück. Auf der anderen Seite, rework() Gibt die gleiche Type wie das Unique zurück DataFramewobei jede Zeile mit dem aggregierten Wert ihrer Gruppe gefüllt ist. Dies macht es excellent zum Hinzufügen von Statistiken auf Gruppenebene als neue Spalten, ohne dass eine anschließende Zusammenführung erforderlich ist. Es ist auch schneller als der manuelle Aggregations- und Zusammenführungsansatz, da Pandas nicht nachträglich zwei Datenrahmen ausrichten muss:

df('avg_revenue_by_segment') = df.groupby('section')('income').rework('imply')

Dadurch wird der durchschnittliche Umsatz für jedes Section direkt zu jeder Zeile hinzugefügt. Das gleiche Ergebnis mit agg() würde die Berechnung des Mittelwerts und die anschließende Zusammenführung des Segmentschlüssels erfordern, wobei zwei Schritte anstelle von einem erforderlich wären.

Verwenden Sie für kategoriale Groupby-Spalten immer noticed=True:

df.groupby('section', noticed=True)('income').sum()

Ohne dieses Argument berechnet pandas Ergebnisse für jede im dtype der Spalte definierte Kategorie, einschließlich Kombinationen, die nicht in den tatsächlichen Daten erscheinen. Bei großen Datenrahmen mit vielen Kategorien führt dies zu leeren Gruppen und unnötigen Berechnungen.

# Vektorisierte bedingte Logik

Benutzen apply() mit einem Lambda-Funktion für jede Zeile ist die am wenigsten effiziente Methode zur Berechnung bedingter Werte. Es vermeidet die C-Degree-Operationen, die Pandas beschleunigen, indem eine Python-Funktion für jede Zeile unabhängig ausgeführt wird.

Für binäre Bedingungen gilt: NumPy‚S np.the place() ist der direkte Ersatz:

df('label') = np.the place(df('income') > 1000, 'excessive', 'low')

Für mehrere Bedingungen, np.choose() geht sauber damit um:

circumstances = (
    df('income') > 10000,
    df('income') > 1000,
    df('income') > 100,
)
selections = ('enterprise', 'mid-market', 'small')
df('section') = np.choose(circumstances, selections, default="micro")

Der np.choose() Die Funktion wird mit vektorisierter Geschwindigkeit direkt auf eine if/elif/else-Struktur abgebildet, indem die Bedingungen der Reihe nach ausgewertet und die erste passende Choice zugewiesen werden. Dies ist normalerweise 50 bis 100 Mal schneller als ein Äquivalent apply() auf einem DataFrame mit einer Million Zeilen.

Beim numerischen Binning wird die bedingte Zuweisung vollständig durch ersetzt pd.reduce() (gleich breite Behälter) und pd.qcut() (quantilbasierte Bins), die automatisch eine kategoriale Spalte zurückgeben, ohne dass NumPy erforderlich ist. Pandas kümmert sich um alles, einschließlich der Beschriftung und Handhabung von Kantenwerten, wenn Sie ihm die Anzahl der Bins oder die Bin-Kanten übergeben.

# Leistungsfallen

Einige gängige Muster verlangsamen Pandas-Code mehr als alles andere.

Zum Beispiel, iterrows() iteriert über DataFrame Zeilen als (Index, Sequence) Paare. Es ist ein intuitiver, aber langsamer Ansatz. Für einen DataFrame Bei 100.000 Zeilen kann dieser Funktionsaufruf 100-mal langsamer sein als ein vektorisiertes Äquivalent.

Der Mangel an Effizienz ergibt sich aus dem Aufbau einer Gesamtheit Sequence Objekt für jede Zeile erstellen und Python-Code einzeln darauf ausführen. Wann immer Sie schreiben for _, row in df.iterrows()halte inne und überlege, ob np.the place(), np.choose()oder eine Groupby-Operation kann es ersetzen. Meistens kann einer von ihnen.

Benutzen apply(axis=1) ist schneller als iterrows() hat aber das gleiche Drawback: die Ausführung auf Python-Ebene für jede Zeile. Für jede Operation, die mit den integrierten Funktionen von NumPy oder Pandas dargestellt werden kann, ist die integrierte Methode immer schneller.

Objekt-Dtype-Spalten sind ebenfalls eine leicht zu übersehende Ursache für Langsamkeit. Wenn Pandas Zeichenfolgen als Objekt-D-Typ speichert, werden Vorgänge für diese Spalten in Python und nicht in C ausgeführt. Bei Spalten mit geringer Kardinalität, wie Statuscodes, Regionsnamen oder Kategorien, kann die Konvertierung in einen kategorialen D-Typ die Gruppierung erheblich beschleunigen value_counts().

df('standing') = df('standing').astype('class')

Vermeiden Sie schließlich verkettete Zuweisungen. Benutzen df(df('income') > 0)('label') = 'constructive' könnte die Initiale ändern DataFrameabhängig davon, ob Pandas hinter den Kulissen eine Kopie erstellt hat. Das Verhalten ist undefiniert. Nutzen .loc stattdessen neben einer booleschen Maske:

df.loc(df('income') > 0, 'label') = 'constructive'

Das ist eindeutig und wirft Nein auf SettingWithCopyWarning.

# Abschluss

Diese Muster unterscheiden Code, der funktioniert, von Code, der intestine funktioniert: effizient genug, um mit echten Daten ausgeführt zu werden, lesbar genug, um gewartet zu werden, und so strukturiert, dass das Testen einfach ist.

Methodenverkettung und pipe() adressieren die Lesbarkeit, während sich die Be a part of- und Groupby-Muster auf Korrektheit und Leistung konzentrieren. Vektorisierte Logik und die Adressgeschwindigkeit des Fallstrickabschnitts.

Erweiterte Pandas-Muster

Der meiste von uns überprüfte Pandas-Code weist mindestens zwei oder drei dieser Probleme auf. Sie sammeln sich nonetheless und leise an – eine langsame Schleife hier, eine nicht validierte Zusammenführung dort oder eine Objekt-Dtype-Spalte, die niemand bemerkt hat. Keines davon verursacht offensichtliche Fehler, weshalb sie bestehen bleiben. Es ist sinnvoll, damit anzufangen, sie einzeln zu reparieren.

Nate Rosidi ist Datenwissenschaftler und in der Produktstrategie tätig. Er ist außerdem außerordentlicher Professor für Analytik und Gründer von StrataScratch, einer Plattform, die Datenwissenschaftlern hilft, sich mit echten Interviewfragen von High-Unternehmen auf ihre Interviews vorzubereiten. Nate schreibt über die neuesten Tendencies auf dem Karrieremarkt, gibt Ratschläge zu Vorstellungsgesprächen, stellt Information-Science-Projekte vor und behandelt alles rund um SQL.



Von admin

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert