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.

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!

Inhalte

Vorbereitung

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=","
)Code-Sprache: Python (python)

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_dfCode-Sprache: Python (python)

acsizesome_missing
row_0[0, 1, 2][a, b, c]0first
row_1fooNaN1NaN
row_2100]
[9]2NaN
row_3[3, 4]
[d, e]3last

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)
acsizesome_missing
row_00[a, b, c]0first
row_01[a, b, c]0first
row_02[a, b, c]0first
row_1fooNaN1NaN
row_2100[9]2NaN
row_33[d, e]3last
row_34[d, e]3last

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)
acsizesome_missing
row_00a0first
row_01b0first
row_02c0first
row_1fooNaN1NaN
row_210092NaN
row_33d3last
row_34e3last

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_billtipsexsmokerdaytimesize
016.991.01FemaleNoSunDinner2
110.341.66MaleNoSunDinner3
221.013.50MaleNoSunDinner3
323.683.31MaleNoSunDinner2
424.593.61FemaleNoSunDinner4

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_billtipsexsmokerdaytimesize
2419.823.18MaleNoSatDinner2
68.772.00MaleNoSunDinner2
15324.552.00MaleNoSunDinner4
21125.895.16MaleYesSatDinner4
19813.002.00FemaleYesThurLunch2
17617.892.00MaleYesSunDinner2
19228.442.56MaleYesThurLunch2

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_billtipsexsmokerdaytimesize
2419.823.18MaleNoSatDinner2
68.772.00MaleNoSunDinner2
15324.552.00MaleNoSunDinner4
21125.895.16MaleYesSatDinner4
19813.002.00FemaleYesThurLunch2

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_billtipsexsmokerdaytimesizeacsome_missing
016.991.01FemaleNoSunDinner2[100][9]NaN
123.683.31MaleNoSunDinner2[100][9]NaN
210.341.66MaleNoSunDinner3[3, 4][d, e]last
321.013.50MaleNoSunDinner3[3, 4][d, e]last
424.593.61FemaleNoSunDinner4NaNNaNNaN
5NaNNaNNaNNaNNaNNaN0[0, 1, 2][a, b, c]first
6NaNNaNNaNNaNNaNNaN1fooNaNNaN

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_billtipsexsmokerdaytimesizeacsome_missing_merge
016.991.01FemaleNoSunDinner2[100][9]NaNboth
123.683.31MaleNoSunDinner2[100][9]NaNboth
210.341.66MaleNoSunDinner3[3, 4][d, e]lastboth
321.013.50MaleNoSunDinner3[3, 4][d, e]lastboth
424.593.61FemaleNoSunDinner4NaNNaNNaNleft_only
5NaNNaNNaNNaNNaNNaN0[0, 1, 2][a, b, c]firstright_only
6NaNNaNNaNNaNNaNNaN1fooNaNNaNright_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 dayDinnerLunch
Fri127
Sat870
Sun760
Thur161

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_billtipsexsmokerdaytimesize
016.991.01FemaleNoSunDinner2
110.341.66MaleNoSunDinner3
221.013.50MaleNoSunDinner3
323.683.31MaleNoSunDinner2
424.593.61FemaleNoSunDinner4

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)
acsizesome_missing
row_0[0, 1, 2][a, b, c]0first
row_1fooNaN1NaN
row_2[100][9]2NaN
row_3[3, 4][d, e]3last

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)
acsizesome_missing
row_0[0, 1, 2][a, b, c]0first
row_1foo[a, b, c]1first
row_2[100][9]2first
row_3[3, 4][d, e]3last

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)
tipsdaily_mean_tipday
632.642.99Sat
643.762.99Sat
553.513.26Sun
1111.002.99Sat
2252.502.73Fri
921.002.73Fri
763.082.99Sat
1815.653.26Sun
1883.503.26Sun
1803.683.26Sun

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.