7 hilfreiche Pandas-Funktionen, die den Tag retten können!

Die Python-Bibliothek „Pandas“ bietet eine Vielzahl unterschiedlicher Funktionen. Wir bei eoda nutzen Pandas gerade bei der Datenaufbereitung und den ersten explorativen Analysen, um ein Gefühl für die Datensätze zu gewinnen und die nächsten Schritten zu planen.

Mit diesem Beitrag gibt unser Data-Science-Team einen Blick hinter die Kulissen und zeigt Einsteigern und Fortgeschrittenen ausgewählte Pandas-Funktionen, die sich gerade am Anfang von Analyseprojekten als hilfreich erwiesen haben.

Viele der Funktionen tauchen in klassischen Einführungen nicht auf – sie sind Nischenfunktionen, die aber lästige Probleme mit einer Zeile Code und zusätzlichen Parametern lösen. Ein Grund mehr sie zu teilen!

Vorbereitung

Gute Vorbereitung ist das halbe Leben: Wir starten also mit dem Import der Bibliotheken und den beiden Datensätzen.

Gute Vorbereitung ist das halbe Leben: Wir starten also mit dem Import der Bibliotheken und den beiden Datensätzen.
Gute Vorbereitung ist das halbe Leben: Wir starten also mit dem Import der Bibliotheken und den beiden Datensätzen.

Gute Vorbereitung ist das halbe Leben: Wir starten also mit dem Import der Bibliotheken und den beiden Datensätzen.

# Vorbereitung

# Import der Bibliotheken
import pandas as pd
import numpy as np


# Ein simpler Beispieldatensatz
simple_df = pd.DataFrame(
    {"a": [[0, 1, 2], "foo", [100], [3, 4]], "c": [["a", "b", "c"], np.nan, [9], ["d", "e"]], "size": range(4), "some_missing":["first", np.NAN, np.NAN, "last"]},
    index=["row_0", "row_1", "row_2", "row_3"],
)
# Der bekannte Tips-Datensatz (Trinkgelder)
tips_df = pd.read_csv(
    "https://raw.githubusercontent.com/mwaskom/seaborn-data/799924f46906146ad36b8b1c27d83e51dd8b411a/tips.csv", sep=","
)

1. explode

Das simple_df Dataframe ist noch nicht im sogenannten tidy Format, d.h. Zeilen der Spalten a und c umfassen Listen und somit mehr als eine individuelle Beobachtung.

Diese verschachtelten Beobachtungen sollen in separate Zeilen ausgedehnt werden.

# unbearbeiteter Dataframe:
simple_df
a c size some_missing
row_0 [0, 1, 2] [a, b, c] 0 first
row_1 foo NaN 1 NaN
row_2 100] [9] 2 NaN
row_3 [3, 4] [d, e] 3 last

Lösung

Die Pandas Funktion explode nimmt eine oder mehrere Spalten als Input und transformiert Listen zu skalaren Werten. Dabei wird das Dataframe ‚länger‘, die Einträge der anderen Spalten wie hier z.B. size werden für jeden Listeneintrag der Spalte a dupliziert.

Da wir hier zunächst nur die Spalte a auswählen, sind die Listenelemente in Spalte c weiterhin vorhanden.

simple_df.explode(["a"])Code-Sprache: CSS (css)
a c size some_missing
row_0 0 [a, b, c] 0 first
row_0 1 [a, b, c] 0 first
row_0 2 [a, b, c] 0 first
row_1 foo NaN 1 NaN
row_2 100 [9] 2 NaN
row_3 3 [d, e] 3 last
row_3 4 [d, e] 3 last

Im Falle von mehreren Spalten als Input müssen die Anzahl an Listenelementen in jeder Zeile übereinstimmen! Hier enthalten row_0 und row_3 für die Spalten a und c jeweils die gleiche Anzahl an Elementen.

Durch explode() werden nun die korrespondierenden Paare gebildet und nicht jede mögliche Kombination an Werten:

# mehrere Spalten
simple_df.explode(["a", "c"])Code-Sprache: Perl (perl)
a c size some_missing
row_0 0 a 0 first
row_0 1 b 0 first
row_0 2 c 0 first
row_1 foo NaN 1 NaN
row_2 100 9 2 NaN
row_3 3 d 3 last
row_3 4 e 3 last

2. sample

Um sich einen schnellen Überblick über einen Datensatz zu verschaffen, wird häufig der Befehl head() verwendet. Sind einzelne Spalten jedoch nach Werten sortiert, ist diese Ausgabe wenig informativ.

Dies ist der Fall für die Spalten smokerday und time im tips Datensatz:

tips_df.head()Code-Sprache: Python (python)
total_bill tip sex smoker day time size
0 16.99 1.01 Female No Sun Dinner 2
1 10.34 1.66 Male No Sun Dinner 3
2 21.01 3.50 Male No Sun Dinner 3
3 23.68 3.31 Male No Sun Dinner 2
4 24.59 3.61 Female No Sun Dinner 4

Lösung

Mit der sample Funktion können wir stattdessen zufällige Zeilen auswählen, um einen besseren Überblick über sortierte Spalten zu erhalten. Der Parameter n gibt uns Kontrolle über die Anzahl der ausgegebenen Zeilen, zur Reproduzierbarkeit des Zufallsprozesses können wir zudem einen random_state setzen:

tips_df.sample(n=7, random_state=42)Code-Sprache: Python (python)
total_bill tip sex smoker day time size
24 19.82 3.18 Male No Sat Dinner 2
6 8.77 2.00 Male No Sun Dinner 2
153 24.55 2.00 Male No Sun Dinner 4
211 25.89 5.16 Male Yes Sat Dinner 4
198 13.00 2.00 Female Yes Thur Lunch 2
176 17.89 2.00 Male Yes Sun Dinner 2
192 28.44 2.56 Male Yes Thur Lunch 2

Anstatt die Anzahl der Zeilen vorzugeben, können wir auch einen Prozentsatz aller Beobachtungen des Datensatzes auswählen. Hier setzen wir das Argument frac=0.02, um 2% aller Zeilen zufällig auszuwählen:

tips_df.sample(frac=0.02, random_state=42)Code-Sprache: Python (python)
total_bill tip sex smoker day time size
24 19.82 3.18 Male No Sat Dinner 2
6 8.77 2.00 Male No Sun Dinner 2
153 24.55 2.00 Male No Sun Dinner 4
211 25.89 5.16 Male Yes Sat Dinner 4
198 13.00 2.00 Female Yes Thur Lunch 2

3. merge-indicator

Eine häufige Aufgabe von Pandas besteht darin, mehrere Datensätze über eine Schlüsselspalte zusammenzufügen. Oftmals wollen wir dabei wissen, ob die beiden Datensätze 1:1 zueinander passen oder ob Werte in der Schlüsselspalte keine Entsprechung im anderen Datensatz haben.

Zunächst führen wir einen full join bzw. outer join durch. Dabei bleiben alle Einträge beider Datensätze erhalten und der Datensatz kann sowohl ‚länger‘ als auch ‚breiter‘ werden.

Lösung

Hierfür lässt sich merge mit dem zusätzlichen indicator Argument nutzen.

Der Parameter on bestimmt die Schlüsselspalte, über welche die Datensätze verbunden werden:

# ohne merge-indicator
tips_df.head(n=5).merge(simple_df, on="size", how="outer")Code-Sprache: Python (python)
total_bill tip sex smoker day time size a c some_missing
0 16.99 1.01 Female No Sun Dinner 2 [100] [9] NaN
1 23.68 3.31 Male No Sun Dinner 2 [100] [9] NaN
2 10.34 1.66 Male No Sun Dinner 3 [3, 4] [d, e] last
3 21.01 3.50 Male No Sun Dinner 3 [3, 4] [d, e] last
4 24.59 3.61 Female No Sun Dinner 4 NaN NaN NaN
5 NaN NaN NaN NaN NaN NaN 0 [0, 1, 2] [a, b, c] first
6 NaN NaN NaN NaN NaN NaN 1 foo NaN NaN

Bei genauer Betrachtung des Dataframes sehen wir, dass nur die size-Werte 2 und 3 in beiden Dataframes vorhanden sind. Der merge-indicator zeigt uns diese Information direkt in einer zusätzlichen Spalte _merge an:

merged_df = tips_df.head(n=5).merge(simple_df, on="size", how="outer", indicator=True)
merged_dfCode-Sprache: Python (python)
total_bill tip sex smoker day time size a c some_missing _merge
0 16.99 1.01 Female No Sun Dinner 2 [100] [9] NaN both
1 23.68 3.31 Male No Sun Dinner 2 [100] [9] NaN both
2 10.34 1.66 Male No Sun Dinner 3 [3, 4] [d, e] last both
3 21.01 3.50 Male No Sun Dinner 3 [3, 4] [d, e] last both
4 24.59 3.61 Female No Sun Dinner 4 NaN NaN NaN left_only
5 NaN NaN NaN NaN NaN NaN 0 [0, 1, 2] [a, b, c] first right_only
6 NaN NaN NaN NaN NaN NaN 1 foo NaN NaN right_only

Hiervon ausgehend können wir nun die Anzahl der im linken oder rechten Dataframe fehlenden Werte schnell berechnen:

merged_df["_merge"].value_counts()Code-Sprache: Python (python)
both          4
right_only    2
left_only     1
Name: _merge, dtype: int64Code-Sprache: Python (python)

4. Heatmap-Tabelle

Explorative Datenanalysen mit Jupyterlab oder Jupyter Notebooks profitieren immer von Visualisierungen. Dabei kommt die bekannte plot() Funktion oftmals schnell an ihre Grenzen.

Lösung

Zusätzlich zu den gewöhnlichen Abbildungen wie Scatterplots, Lineplots, etc. kann Pandas auch direkt ein Dataframe als eingefärbte Heatmap darstellen. Dazu kann für ein beliebiges Dataframe einfach die background_gradient Methode aus dem style Namespace angehängt werden:

# Hinweis: Da Pandas hier auf die weitverbreitete matplotlib Grafik-Bibliothek zurückgreift, muss diese ebenfalls
# installiert, aber nicht importiert sein.
pd.crosstab(tips_df["day"], tips_df["time"]).style.background_gradient()Code-Sprache: Python (python)
time day Dinner Lunch
Fri 12 7
Sat 87 0
Sun 76 0
Thur 1 61

 

Die Farben der Heatmap lassen sich als Argument von background_gradient() ändern; gültige Namen sind hierfür alle verfügbaren matplotlib Farbpaletten. Eine Liste dieser kann nach Import von Matplotlib via plt.colormaps() ausgegeben werden.

pd.crosstab(tips_df["day"], tips_df["time"]).style.background_gradient("plasma_r")Code-Sprache: Python (python)
time day Dinner Lunch
Fri 12 7
Sat 87 0
Sun 76 0
Thur 1 61

5. shift

Bei der Arbeit mit Zeitreihendaten sind wir häufig an Berechnungen mit Bezug zu vorherigen oder nachfolgenden Zeilen interessiert. Am Beispiel des tips Datensatzes wollen wir die Differenz zwischen dem aktuellen und dem letzten Trinkgeld (der tip Spalte) berechnen:

tips_df.head()Code-Sprache: Python (python)
total_bill tip sex smoker day time size
0 16.99 1.01 Female No Sun Dinner 2
1 10.34 1.66 Male No Sun Dinner 3
2 21.01 3.50 Male No Sun Dinner 3
3 23.68 3.31 Male No Sun Dinner 2
4 24.59 3.61 Female No Sun Dinner 4

Lösung

Die shift Funktion erlaubt das Verschieben einer pandas.Series um eine oder mehrere Zeitschritte. Der periods Parameter bestimmt dabei das Ausmaß der Verschiebung:

tips_df["tip"].shift(periods=1).head()Code-Sprache: Python (python)
0     NaN
1    1.01
2    1.66
3    3.50
4    3.31
Name: tip, dtype: float64Code-Sprache: Python (python)

Der größte Nutzen von shift() offenbart sich beim Subtrahieren der verschobenen Spalte von der originalen Spalte. Dadurch ergeben sich wie gewünscht die Differenzen aufeinanderfolgender Trinkgelder. Es gilt zu beachten, dass die erste Zeile aufgrund der Verschiebung hierbei stets einen fehlenden Eintrag enthält:

tips_df["tip"] - tips_df["tip"].shift(1)Code-Sprache: Python (python)
0       NaN
1      0.65
2      1.84
3     -0.19
4      0.30
       ... 
239    1.25
240   -3.92
241    0.00
242   -0.25
243    1.25
Name: tip, Length: 244, dtype: float64Code-Sprache: Python (python)

Der periods Parameter kann auch negative Werte annehmen. In diesem Fall werden alle Werte ’nach oben‘ verschoben:

tips_df["tip"].shift(-2).head()Code-Sprache: Python (python)
0    3.50
1    3.31
2    3.61
3    4.71
4    2.00
Name: tip, dtype: float64Code-Sprache: Python (python)

Tippshift() lässt sich zudem nach einer groupby() Operation für jede Gruppe separat nutzen!

6. backfill & ffill

In Präsenz von fehlenden Werten ist die bequemste Lösung, einfach alle Zeilen mit mindestens einem fehlenden Wert zu löschen (dropna()) oder mit einem fixen Wert zu füllen (fillna()). Insbesondere bei kleinen Datensätzen können dabei im ersten Fall wichtige Beobachtungen anderer Variablen verloren gehen bzw. im zweiten Fall der Informationsgewinn deutlich reduziert werden.

Wir veranschaulichen das Problem an der some_missing Spalte des simple_df Datensatzes:

simple_dfCode-Sprache: Python (python)
a c size some_missing
row_0 [0, 1, 2] [a, b, c] 0 first
row_1 foo NaN 1 NaN
row_2 [100] [9] 2 NaN
row_3 [3, 4] [d, e] 3 last

Die Funktionen backfill und ffill erlauben das sequentielle Auffüllen fehlender Werte mittels benachbarter Werte derselben Spalte. Dies ist insbesondere bei der Arbeit mit Zeitreihendaten und bei kleineren Lücken im Datensatz hilfreich.

Zunächst füllen wir hier die some_missing Spalte mit backfill(), also mit dem nächsten vorhandenen Wert auf:

simple_df["some_missing"].backfill()Code-Sprache: Python (python)
row_0    first
row_1     last
row_2     last
row_3     last
Name: some_missing, dtype: objectCode-Sprache: Python (python)

Analog können wir mit ffill() (für ‚forward-fill‘) den letzten/vorherigen vorhandenen Wert zum Füllen wählen:

simple_df["some_missing"].ffill()Code-Sprache: Python (python)
row_0    first
row_1    first
row_2    first
row_3     last
Name: some_missing, dtype: objectCode-Sprache: Python (python)

Beide Funktionen sind auch direkt auf den gesamten Datensatz anwendbar. In diesem Fall wird ebenfalls die Zeile row_1 der Spalte c aufgefüllt:

simple_df.ffill()Code-Sprache: Python (python)
a c size some_missing
row_0 [0, 1, 2] [a, b, c] 0 first
row_1 foo [a, b, c] 1 first
row_2 [100] [9] 2 first
row_3 [3, 4] [d, e] 3 last

Tipp: Genau wie shift funktionieren auch backfill() und ffill() ebenfalls nach einer groupby() Operation für jede Gruppe separat!

7. transform (& groupby)

Wir wollen den tips Datensatz um eine Spalte ergänzen, welche für jede Beobachtung das mittlere Trinkgeld des jeweiligen Tages enthalten soll. Das Grundproblem besteht also darin, das Ergebnis einer groupby()-Aggregation direkt in den ursprünglichen Datensatz zurückzuspielen.

Gruppieren wir nach dem Tag, erhalten wir nach der Aggregation einen Output mit einer Zeile pro Tag/Gruppe:

tips_df.groupby(["day"])["tip"].mean()Code-Sprache: Python (python)
day
Fri     2.734737
Sat     2.993103
Sun     3.255132
Thur    2.771452
Name: tip, dtype: float64
Code-Sprache: Python (python)

Damit diese Informationen jedoch direkt im ursprünglichen Datensatz enthalten sind, müssten wir diesen Output wieder mit tips_df mergen. Pandas kann das besser!

Lösung

Die direkte Eingliederung in den Ursprungsdatensatz gelingt mittels der groupby() + transform() Kombination. Genau wie agg() nimmt transform() eine Funktion bzw. einen Alias für eine Funktion als String ein (hier „mean“).

Jedoch aggregiert transform() nicht die tip Spalte, sondern liefert als Window-Funktion wieder eine Spalte gleicher Länge als Output. Damit ist Gruppierung und Transformation binnen einer Zeile ohne einen merge-Prozess möglich:

tips_df["daily_mean_tip"] = tips_df.groupby(["day"]).tip.transform("mean").round(2)

tips_df[["tip", "daily_mean_tip", "day"]].sample(10, random_state=0)Code-Sprache: Python (python)
tips daily_mean_tip day
63 2.64 2.99 Sat
64 3.76 2.99 Sat
55 3.51 3.26 Sun
111 1.00 2.99 Sat
225 2.50 2.73 Fri
92 1.00 2.73 Fri
76 3.08 2.99 Sat
181 5.65 3.26 Sun
188 3.50 3.26 Sun
180 3.68 3.26 Sun

Fazit

Die vorgestellten Funktionen sind eine kleine, aber kuratierte Auswahl aus der Pandas-Bibliothek. Sie behandeln, wie eingangs besprochen, bestimmte Nischen bei der Datenanalyse und -aufbereitung.

Oft sind es kleine Dinge, die die tägliche Arbeit erleichtern.

Python, R & Shiny
Unsere Trainings ebnen Ihnen den Weg für Ihre nächsten Schritte. Machine Learning, Datenvisualisierung, Zeitreihenanalysen oder Shiny:
Finden Sie bei uns den richtigen Kurs für Ihre Anforderungen.

Veröffentlicht: 15. November 2022

AutorIn

Matthias Henneke

Matthias Henneke ist Data Scientist und kam nach Stationen in Wirtschaft und Wissenschaft zu eoda. Seine Hauptaufgaben beinhalten unter anderem die Entwicklung von kundenspezifischen Machine Learning Lösungen im Industriekontext. Im Rahmen von Trainings gibt er gerne praxisorientierte Data Science Skills weiter.

Starten Sie jetzt durch:
Wir freuen uns auf den Austausch mit Ihnen. 







    Nach oben scrollen