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_df
Code-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 |
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 smoker
, day
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_df
Code-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: int64
Code-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: float64
Code-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: float64
Code-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: float64
Code-Sprache: Python (python)
Tipp: shift()
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_df
Code-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: object
Code-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: object
Code-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.
Blog:
Shiny for Python: Posit (ehemals RStudio) öffnet Shiny für Python-Community
Posit ist die erste Adresse für die R-Community. Längst stellt sich RStudio aber breiter auf und adressiert insbesondere auch Python-User. Deutlich wird dies in zwei bemerkenswerten Schritten: Der Bekanntgabe eines neuen Unternehmensnamens und der Veröffentlichung von Shiny for Python.
Software:
Das Potenzial von Python & R voll ausschöpfen – mit YUNA
YUNA automatisiert Data-Science! In einer zentralen Plattform konzipieren, erstellen und optimieren Sie digitale Services. Mit YUNA verbinden Sie Datentöpfe, verwalten Skripte, automatisieren ganze Geschäftsprozesse und beantworten Fragen, an die Sie vorher nicht gedacht haben!
Blog:
Ansible: Infrastruktur als Code
Was Ansible von anderen Tools abhebt ist das Konzept von Idempotenz. Es werden nur Aktionen durchgeführt, wenn sie notwendig sind, was bedeutet, dass selbst wenn ein Vorgang mehrfach wiederholt wird – z. B. bei der Wiederherstellung nach einem Ausfall – das System immer in denselben Zustand versetzt.
Ihr Partner:
Wir realisieren Ihre Datenprojekte
Konzeption, Datenmanagement, Modellbildung, Produktivsetzung: Wir realisieren Ihre Datenprojekte von der Idee bis zur Implementierung der Lösungen in Ihre Unternehmensprozesse.