Nachdem wir in dem vorherigen Artikel eine Einführung in Pandas gegeben haben und somit nun Daten auswählen sowie manipulieren können, soll sich in diesem Artikel alles um die Visualisierung von Daten drehen. Bekanntlicherweise lassen sich mit der passenden Grafik Daten häufig noch besser verstehen und ermöglichen eine andere Art der Interpretation, unabhängig von Mittelwerten und anderen Kennzahlen.
Welche Bibliothek zu Datenvisualisierung in Python?
In dem Bibliotheksdschungel von Python wimmelt es nur so von Bibliotheken, die sich zur Visualisierung eignen. Die Bandbreite reicht von einem einfachen Streudiagramm über die Darstellung der Struktur eines neuronalen Netzes bis zu 3D-Visualisierungen. Die älteste und ausgereifteste Datenvisualisierungs Bibliothek im Python Ökosystem ist Matplotlib. Mit dem Start der Entwicklung im Jahr 2003 hat sie sich kontinuierlich weiterentwickelt und bildet auch die Basis für die Bibliothek seaborn. Sie bietet eine große Fülle an Einstellungs- bzw. Customizing Möglichkeiten und orientiert sich dabei an MATLAB. Der Namenszusammenhang ist offensichtlich ;-).
Start mit Matplotlib
Grafiken mit Matplotlib zu erstellen kann auf zwei Arten erfolgen:
- via
pyplot
- Objekt-orientierter Ansatz
An dieser Stelle sei angemerkt, dass es sich bei pyplot
um eine Teilbibliothek von matplotlib handelt. Der erste Ansatz ist der meist verbereitete, da dieser einen einfachen Einstieg in Matplotlib ermöglicht. Hingegen bietet sich der zweite für wirklich sehr detailreiche Grafiken an, die viele Anpassungen erfordern, an. In diesem Blog-Beitrag werden die Grafiken ausschließlich mit pyplot
erstellen. Die Erstellung orientiert sich dabei an folgender Vorgehensweise:
- Erstellung eines
figure
-Objekts inkl.axes
-Objekten - Bearbeiten der
axes
-Objekte - Ausgabe des Objekts
Folglich bearbeiten wir in Python somit ein Objekt direkt, anstatt wie bei der „Gramma of Graphics“ Schicht für Schicht auf eine Grafik zu legen.
Initialisierung einer Grafik
Der Import von matplotlib typerweise wie folgt:
# Import Matplotlib
import matplotlib.pyplot as plt
Nun gilt es ein figure
-Objekt anzulegen. An dieser Stelle sollte man sich zudem überlegen, wie viele Darstellungen man in seinem Objekt integrieren möchte, dies erleichtert den späteren Arbeitsfluss. Im folgenden Codebeispiel wird eine figure
mit einer Spalte sowie einer Zeile erstellt.
# Initilaisierung einer Figure
fig, ax = plt.subplots(nrows = 1, ncols = 1)
# Ausgabe der Figure
fig
Die Methode figure
liefert zwei Argumente zurück, es handelt sich dabei um die eigentlich figure
sowie um ein Axes
-Objekt. Dies hängt mit dem objektorientierten Ansatz von Matplotlib zusammen. Umfasst eine figure
mehrere Teildarstellungen wie z.B. Histogramme von unterschiedlichen Daten, so kann man die Teildarstellungen einzeln über das Axes
-Objekt bearbeiten und das figure
-Objekt fasst alle Teildarstellungen zusammen. Die figure
umfasst somit die graphische Darstellung und das Axes
-Objekt die einzelnen Teildarstellungen. Lässt man sich die erstellte Variable fig
ausgeben, so erhält man folgende Darstellung:
Unsere Grafik enthält also bis jetzt nur ein Koordinatensystem. Um den objektorientieren Ansatz noch einmal zu verdeutlichen erstellen wir eine figure
mit insgesamt zwei Zeilen und Splaten.
# Erstellen einer Figure mit 4 separaten Grafiken
fig, ax = plt.subplots(nrows = 2, ncols = 2)
Mittels Indexselektion kann man nun die einzelnen Teildarstellungen aus dem Grid auswählen. Somit sollte der objektorientierte Ansatz klar und deutlich geworden sein.
# Auswahl der einzelnen Teildarstellungen
ax[0,0]
ax[0,1]
ax[1,0]
ax[1,1]
Bei der Initialisierung einer Grafik sind natürlich Anpassungen im Hinblick auf die Größe der Grafik denkbar, dies lässt sich wie folgt umsetzen:
# Festlegung der figure Größe
fig, ax = plt.subplots(nrows = 2, ncols = 2, figsize = (10, 12))
Die Grafikgröße wird als Tuple (Breite, Höhe) in Inches angegeben.
Darstellung von Daten
Nachdem wir dargestellt haben wie man eine Grafik in Matplotlib initialisieren kann, soll es nun darum gehen, diese mit Daten zu befüllen. Für den Einstieg nehmen wir die erste figure
aus dem Blog-Beitrag. Zur Darstellung von Daten greifen wir auf das Axes
-Objekt zu und wählen zu Beginn die Funktion plot
. Eine Vielzahl von weiteren Funktionen kann unter diesem Link gefunden werden.
# Erstellen der Grafik
fig, ax = plt.subplots(nrows = 1, ncols = 1)
# Erstellen von Beispieldaten
sample_data = np.random.randint(low = 0, high = 10, size = 10)
# Darstellung der Beispieldaten
ax.plot(sample_data)
# Ausgabe der Figure
fig
An dieser Stelle sei kurz angemerkt, dass Matplotlib im Hintergrund zwei Vorgänge durchgeführt hat:
- Die Skala wurde automatisch an die Daten angepasst.
- Die Darstellung der Daten erfolgte über den Index, d. h. der eigentliche Wert ist auf der y-Achse.
Ist man der Auffassung, dass z.B. ein Streudiagramm besser geeignet wäre, so lässt sich die Grafik einfach ändern. Anstelle von plot
schreibt man scatter
. Der Codes zeigt, dass in diesem Fall nicht die Daten automatisch über den Index dargestellt wurden, sondern explizit x
und y
als Argumente übergeben müssen.
# Erstellen der Grafik
fig, ax = plt.subplots(nrows = 1, ncols = 1)
# Erstellen von Beispieldaten
sample_data = np.random.randint(low = 0, high = 10, size = 10)
# Darstellung der Beispieldaten
ax.scatter(x = range(0, len(sample_data)), y=sample_data)
# Ausgabe der Figure
fig
Alle typischen Datenvisualisierungen lassen sich so mit Matplotlib umsetzen.
Beschriftungen und Speichern von Grafiken
Die Darstellung der Daten ist die eine Sache, mit einer passenden Überschrift werden sie dann doch verständlicher.
Als Beispiel wollen wir ein Histogramm mit den Beispieldaten beschriften. Mithilfe der Funktionen set_
kann die Beschriftung einfach vorgenommen werden.
fig, ax = plt.subplots(nrows=1, ncols=1)
sample_data = np.random.randint(low = 0, high = 10, size = 10)
ax.hist(sample_data,width = 0.4)
# Hinzufügen der Beschriftungen
ax.set_xlabel('Beispieldaten')
ax.set_ylabel('Häufigkeiten')
ax.set_title('Histogramm mit Matplotlib')
# Ausgabe der Figure
fig
Wir erhalten folgende Grafik:
Möchte man gerne seine Grafik speichern, so kann man über die Funktion fig.savefig('Pfad der Grafik')
dies vornehmen.
Resümee und Ausblick
Dieser Blog Beitrag sollte einen ersten Einstieg in die Python Visualisierungsbibliothek Matplotlib geben und grundlegende Konzepte vermitteln. Die Anpassbarkeit der Grafiken geht weit über die Funktionen, die wir vorgestellt haben, hinaus:
- Anpassung der Farben, Farbverläufe,…
- individuelle Axenbeschriftung/-skalierung
- Annotieren von Grafiken mit Text Boxen
- Änderung der Schriftart
- …
Allgemein ist zu berücksichtigen, die passenden Daten für die Grafiken auszuwählen, wie man das mit Pandas umsetzen kann, lässt sich hier nachlesen. Im nächsten Beitrag in dieser Serie wird es dann um Scikit-Learn gehen, der Einsteiger Machine Learning Bibliothek in Python.
Nachdem mein Kollege Marvin in seinem Artikel die Bibliothek NumPy vorgestellt hat, wird sich dieser STATWORX Blog Beitrag rund um die Bibliothek Pandas drehen. Pandas fußt zu einem großen Teil auf NumPy, bietet allerdings gerade für einen Einsteiger in den Data Science Bereich eine einfache Möglichkeit, Daten in Python einzulesen sowie zu manipulieren. Wer die Funktionsweise von NumPy verstanden hat, wird mit Pandas auch keine Probleme haben.
Übersicht der Datenstrukturen in Pandas
Bevor wir auf die verschiedenen Möglichkeiten mit Pandas zu arbeiten eingehen, möchten wir kurz eine Einführung in die verschiedenen Strukturen, die unsere Daten in Pandas haben können, geben. Hierbei gibt es drei verschiedene Möglichkeiten:
Series
( 1-Dimensional)DataFrames
( 2-Dimensional)Panels
( 3-Dimensional)
Liegen beispielsweise Zeitreihendaten vor, so kann es ebenso vorteilhaft sein, nur eine Series zu verwenden. Mit DataFrames
haben wir in der Praxis am meisten zu tun, da sie einer typischen Tabelle bzw. n x m
Matrix entsprechen. DataFrames
vereinigen somit verschiedene Serien, sodass wir nicht nur einen Datenstrang repräsentieren können. Hierbei ist nun die Größe mutierbar, wobei die einzelnen Spalten logischerweise gleich langsein müssen. Mit DataFrames
werden wir in diesem Artikel primär arbeiten.
Panels
ergänzen DataFrames
wieder um eine weitere Dimension. Man kann sie sich somit als eine Sammlung von 2D-DataFrames vorstellen, wobei man sie aber in dieser Anordnung nicht wirklich visuell repräsentieren kann.
Installation und Erstellen eines DataFrames
Aber nun genug der Theorie! Pandas kann man wie jede andere Python Bibliothek über pip install pandas/ pip3 install pandas
bzw. conda install pandas
installieren. Der Import von Pandas erfolgt dann häufig mit der Abkürzung pd. Letztere ist sehr verbreitet und gibt jedem Data Scientist sofort die Information, dass in dem jeweiligen Skript mit Pandas gearbeitet wird.
import pandas as pd
Bevor wir darauf eingehen, wie man Datenformate mit Pandas einlesen kann, wollen wir kurz darstellen, wie man selbst einen DataFrame
in Pandas erstellen kann. Ein DataFrame
besteht aus zwei Bausteinen:
- Daten für die jeweiligen Spalten
- Spaltennamen
Die einfachste Möglichkeit zum Erstellen eines DataFrames
ist die Übergabe der Daten in Form eines arrays und die Vorgabe der Spaltennamen (columns) wie im folgenden Beispiel zu sehen.
import numpy as np
import pandas as pd
df = pd.DataFrame(data=np.random.randn(5, 3),
columns=['Spalte 1', 'Spalte 2', 'Spalte 3'])
Alternativ ist es ebenso möglich einen DataFrame
über ein Dictionary zu erstellen. In diesem Fall werden automatisch aus den Keys des Dictionaries, die Spaltennamen extrahiert:
df = pd.DataFrame(data={'Spalte 1': np.arange(start=10, stop=15),
'Spalte 2': np.arange(start=5, stop=10)})
Laden von Daten in Pandas
Da wir unsere Daten bekanntermaßen nicht alle manuell in Python eintragen wollen, existieren für die meisten populären Datenformate (*.csv, *.xlsx, *.dta, …) oder Datenbanken (SQL) bereits fertige Funktion in Pandas. Diese sind in den IO-Tools zusammengefasst. Einen Überblick findet ihr hier. Mit diesen Tools können nicht nur Daten eingelesen, sondern ebenfalls im passenden Format gespeichert werden. Die Funktionen sind vom Muster her alle sehr ähnlich und beginnen mit pd.read_...
, worauf die jeweilige Spezifikation folgt. Beispielsweise für Excel-Dateien pd.read_excel()
oder Stata pd.read_stata()
. Um die verschiedenen Funktionen und Arbeitsweise mit und an einem DataFrame
zu demonstrieren, nutzen wir den Titanic-Datensatz der Website Kaggle. Den Datensatz kann man auch über die Standford-Universität herunterladen. Nun laden wir einmal die Daten:
import pandas as pd
# Datensatz laden
df = pd.read_csv('.../titanic.csv')
Wie sind meine Daten aufgebaut?
Arbeitet man mit zunächst unbekannten Daten, kann man sich mit Pandas schnell einen Überblick über die Daten geben lassen, dabei greifen wir sowohl auf DataFrame
-Eigenschaften wie auch Funktionen zu. Als kleine Checkliste sollen folgende Befehle dienen:
# Datenüberblick
df.shape
df.columns
df.head()
df.tail()
df.describe()
Mit df.shape
fragen wir die Struktur des DataFrame
ab, d.h. wie viele Spalten und Zeilen der DataFrame
besitzt, in diesem Fall liegen 15 Spalten und 891 Zeilen bzw. Beobachtungen in dem Datensatz vor. Es ist also ein vergleichsweiser kleiner Datensatz. Mit df.columns
kann man sich nun die Spaltennamen ausgeben lassen. Jetzt wissen wir zwar wie die Spalten heißen, aber nicht wie die Daten konkret aussehen, das kann man einfach mit der head
oder tail
-Funktion ändern. Mit diesen Funktionen werden im Normalfall die ersten fünf oder letzten fünf Zeilen des Datensatzes zurückgegeben. Über den Funktionsparameter n können auch mehr Zeilen zurückgegeben werden. Wie so ein DataFrame
dann aussieht, seht ihr nun. Übersichtshalber sind hier nur die ersten fünf Spalten des Datensatz dargestellt, wenn ihr selbst die Funktion aufruft, werden deutlich mehr Spalte angezeigt.
Index | survived | pclass | sex | age | sibsp |
---|---|---|---|---|---|
0 | 0 | 3 | male | 22 | 1 |
1 | 1 | 1 | female | 38 | 1 |
2 | 1 | 3 | female | 26 | 0 |
3 | 1 | 1 | female | 35 | 1 |
4 | 0 | 3 | male | 35 | 0 |
Anhand dieser ersten Übersicht erkennt man, dass unterschiedliche Skalen in den Daten enthalten sind. Die letzte Funktion, die wir nun vorstellen möchten, um einen ersten Eindruck über Daten zu gewinnen, ist describe
. Mit dieser werden typische statistische Metriken wie der Durchschnitt und Median von denjenigen Spalten, die eine metrische Skala besitzen, zurückgegeben. Spalten wie z.B. die Spalte Geschlecht (sex) werden von dieser Funktion nicht berücksichtigt. Das Ergebnis gestaltet sich wie folgt:
survived | pclass | age | sibsp | parch | fare | |
---|---|---|---|---|---|---|
count | 891 | 891 | 714 | 891 | 891 | 891 |
mean | 0.383838 | 2.30864 | 29.6991 | 0.523008 | 0.381594 | 32.2042 |
std | 0.486592 | 0.836071 | 14.5265 | 1.10274 | 0.806057 | 49.6934 |
min | 0 | 1 | 0.42 | 0 | 0 | 0 |
25% | 0 | 2 | 20.125 | 0 | 0 | 7.9104 |
50% | 0 | 3 | 28 | 0 | 0 | 14.4542 |
75% | 1 | 3 | 38 | 1 | 0 | 31 |
max | 1 | 3 | 80 | 8 | 6 | 512.329 |
Neben den verschiedenen Metriken für die Spalten fällt doch deutlich auf, dass in der Spalte Alter (age) die Anzahl an Beobachtungen (count) von der Gesamtanzahl aller Beobachtungen abweicht. Um dieser Feststellung tiefer auf den Grund zu gehen, müssen wir uns die Spalte einmal genauer angucken.
Datenauswahl
Für die Auswahl von Spalten mit Pandas gibt es zwei Möglichkeiten:
# Spaltenauswahl
# 1. Moeglichkeit
df['age']
# 2. Moeglichkeit
df.age
Mit der ersten Möglichkeit kann man immer eine Spalte auswählen. Mit der zweiten Möglichkeit ist dies nicht immer garantiert, da Spaltennamen natürlich auch Leer- oder Sonderzeichen enthalten können. Führt man nun diesen Befehl aus, bekommt man eine Series
als Ergebnis zurück, also ein nx1
– Vektor. Dabei sieht man, dass einige Beobachtung fehlen und mit sogenannten NaN
Werte versehen sind. Wie man die NaNs
korrigieren kann, werden wir euch zum Ende des Artikels zeigen. Zunächst wollen wir noch darauf eingehen, wie man mehrere Spalten oder auch einen bestimmten Bereich eines DataFrame
auswählen kann.
# Auswahl von mehreren Spalten
df[['age', 'sex']]
# Auswahl eines Bereichs auf Indexbasis
df.iloc[:2, :3]
# Auswahl eines Bereichs mit z.B. Spaltennamen
df.loc[:3, ['class', 'age']]
Die Auswahl von mehreren Spalten ist ebenso einfach möglich, dabei ist es wichtig zu beachten, dass man eine Liste der auszuwählenden Spaltennamen erstellt bzw. übergibt. Bei der Auswahl eines Ausschnittes des DataFrames
bedient man sich der Funktionen iloc
oder loc
, erstere ist für eine indexbasierte Auswahl gedacht, d.h. man übergibt an die Funktion keine Spaltennamen, sondern die jeweilige Position der Spalte bzw. einen Bereich von Spalten bzw. Indizes. Bei der Funktion loc
kann man dann wiederrum mit Listen und einzelnen Spaltennamen arbeiten. In dem obigen Beispiel würden so zunächst alle Zeilen bis zum Index 2 und die ersten drei Spalten ausgewählt werden. Bei dem loc
-Beispiel werden hingegen Zeilen bis zum Index 3 in Verbindung mit den Spalten age
und class
ausgewählt. Mit diesen Funktionen schafft man einen Einstieg in die Auswahl von verschiedenen Datensätzen, die Beispiele kratzen nur an der Oberfläche der Möglichkeiten, wie man Daten auswählen kann.
Datenmanipulation
In der Praxis perfekt aufbereitete Daten vorzufinden, wäre eine super Sache, leider sieht die Realität dann doch etwas anders aus. Ein erstes Beispiel hatten wir bereits oben mit der Spalte age
. Um unsere Daten später für Auswertungen oder Visualisierungen zu nutzen, wollen wir nun eine Möglichkeit darstellen, die Werte zu korrigieren. Hierzu bietet sich die Verwendung der fillna
-Funktion an. Mit dieser werden die NaN
-Werte automatisch ausgewählt und mit Werten, die man vorher festlegt hat, aufgefüllt. In unserem Fall füllen wir als Wert einfach das Durchschnittsalter auf. Wir fügen somit dem Alter keine Ausreißer hinzu und der Durchschnitt verändert sich nicht.
# Auffüllen von NaN-Werten
df.age.fillna(value=df.age.mean())
Nach Aufruf der Funktionen müssen wir natürlich noch die neue Altersspalte in unseren Datensatz einfügen, da ansonsten die Transformation nicht gespeichert wird. Die Zuweisung funktioniert sehr einfach, indem wir den neuen Spaltennamen so übergeben, als ob wir eine existierende Spalte auswählen würden. Im Anschluss kann man dann die neuen Werte einfach aufrufen. Alternativ könnte man die bisherige Altersspalte überschreiben, dies würde aber dazu führen, dass die Rohdaten nicht mehr ersichtlich wären.
# Zuweisen von neuen Werte zu einer Spalte / Erstellen einer neuen Spalte
df['age_new']=df.age.fillna(value=df.age.mean())
Zusammenfassung
Am Ende des Artikels wollen wir kurz noch wichtige Informationen über den Einstieg mit Pandas zusammenfassen:
- Einlesen von Daten funktioniert einfach mit den Funktionen
pd.read_..
- Daten liegen in der Regel als zweidimensionaler
DataFrame
vor - Mit den Funktionen
df.shape
,df.head()
,df.tail()
unddf.describe()
erhält man einen ersten Überblick über die Daten - Spalten können sowohl einfach als auch mehrfach mit dem zugehörigen Namen ausgewählt werden
- Bereiche eines DataFrame werden entweder mit
iloc
oder mitloc
ausgewählt - Mit der Funktion
fillna
können fehlende Beobachtungen ersetzt werden
Diese Vorstellung stellt wie gesagt nur einen ersten Einstieg in die Bibliothek Pandas dar.
Als kleiner Ausblick in unseren nächsten Artikel in der Reihe Data Science mit Python werden wir uns dann mit der Visualisierungsbibliothek Matplotlib beschäftigen.
Ein wesentliches Problem von größeren und heterogenen Daten ist häufig ihre Interpretation. Als Data Scientist stellt man sich auch deshalb unter anderem folgende Fragen:
- Wie sind die Daten strukturiert?
- Was sind besondere Merkmale?
- Wie lassen sich die Daten graphisch aufbereiten?
Selbstverständlich lässt sich diese Liste noch um beliebige Fragestellungen erweitern. Als Hilfestellung zur Lösung der letzten Frage soll folgender Blog Artikel dienen. Anhand eines einfachen Datensatzes möchten wir euch zeigen, wie man mit einem überschaubaren Aufwand ein Dashboard mit der Python-Bibliothek Bokeh aufbauen kann. Dieses lässt sich dann nicht nur zur einfachen Visualisierung von Daten verwenden, sondern natürlich auch für einen Live Betrieb auf einer Website oder Ähnlichem.
Welche Voraussetzungen solltet ihr mitbringen?
Zur Umsetzung dieses kleinen Projektes solltet ihr eine aktuelle Version von Python 3 auf Eurem PC installiert haben. Falls nicht, ist es am einfachsten, Anaconda zu installieren – hiermit seid ihr bestens für dieses Projekt ausgestattet. Durch das Setup werden nicht nur Python, sondern auch viele weitere Bibliotheken wie Bokeh installiert. Zudem bietet es sich an, dass ihr bereits ein wenig Erfahrung in der grundsätzlichen Funktionsweise von Python sammeln konntet und vor der Benutzung der Kommandozeile/Terminal nicht zurückschreckt. Für die Erstellung des Dashboards solltet ihr zudem über einen passenden Texteditor/IDE wie z.B. Atom, PyCharm oder Jupyter verfügen. Ein Jupyter Notebook könnt ihr – sofern ihr Anaconda installiert habt – sehr einfach starten und müsst keine zusätzlichen Installationen vornehmen. Die Vorteile von Jupyter zeigt unser Data Scientist Marvin in einem Blogbeitrag.
Bokeh – Eine kurze Einführung in die Namensherkunft
Die ursprüngliche Bedeutung von Bokeh kommt aus der Fotografie und leitet sich von dem japanischen Wort boke ab. Es bedeutet so viel wie Unschärfe. Die Namenskomposition mit dem Wort boke und dem Buchstaben h ist auf Mike Johnston zurückzuführen, sie sollte die englische Aussprache vereinfachen.
Nun jedoch zurück zur eigentlichen Anwendung von Bokeh. Die Bibliothek ermöglicht es relativ einfach interaktive Grafiken in Anlehnung an D3.js zu erstellen, in denen z.B. per Mausklick Ausschnitte größer dargestellt und diese dann gespeichert werden können. Für Anwendung der Datenexploration ist diese Funktion eine gute Möglichkeit, sein Datenverständnis zu verbessern.
Unser Datensatz
Für das Dashboard wollen wir nicht fiktive Zahlen generieren und uns diese anzeigen lassen, sondern einen möglichst realen Datensatz verwenden. Hierzu begeben wir uns in die Gastronomie. Ein Kellner hat sich die Mühe gemacht, sein nach seinem Feierabend erhaltenes Trinkgeld und einige weitere Daten zu seinen Kunden zu notieren. Der Datensatz ist in der Grafikbibliothek Seaborn enthalten und lässt sich so einfach herunterladen.
import seaborn as sns
tips = sns.load_datset('tips')
Betrachten wir die ersten Zeilen des Datensatzes, so zeigt sich, dass der Kellner eine überschaubare Anzahl von verschiedenen Variablen erfasst hat. Ein Vorteil der Daten ist ihre unterschiedliche Struktur, so sind Variablen mit unterschiedlichen Skalen enthalten, welche wir für verschiedene Visualisierung in unserem Dashboard nutzen können.
total_bill | tip | sex | smoker | day | time | size |
---|---|---|---|---|---|---|
16.99 | 1.01 | Female | No | Sun | Dinner | 2 |
10.34 | 1.66 | Male | No | Sun | Dinner | 3 |
21.01 | 3.5 | Male | No | Sun | Dinner | 3 |
23.68 | 3.31 | Male | No | Sun | Dinner | 2 |
24.59 | 3.61 | Female | No | Sun | Dinner | 4 |
Möglichkeiten eines Bokeh Dashboards
Nachdem wir nun einen ersten Überblick über unsere Daten gewonnen haben, gilt es das Dashboard mit Bokeh aufzubauen. Grundsätzlich gibt es hierbei zwei Möglichkeiten:
- Erstellung eines HTML Dokuments inkl. aller Abbildungen
- Starten eines Bokeh Servers
Die erste Möglichkeit bietet den Vorteil, dass ein Dashboard sehr einfach in Form eines HTML Dokuments gespeichert werden kann, allerdings sind interaktive Gestaltungsmöglichkeiten nur beschränkt umsetzbar, sodass wir in diesem Blog Beitrag die zweite Möglichkeit genauer vorstellen. Das Starten des Bokeh Servers läuft folgendermaßen ab:
- Terminal/Bash-Konsole öffnen
- mit cd in das Verzeichnis des Python-Skriptes wechseln
- Dashboard mit
bokeh serve --show name-des-skriptes.py
starten
Der Befehl --show
ist nicht zwingend für das Dashboard erforderlich, bringt aber den Vorteil mit sich, dass das Dashboard direkt im Browser angzeigt wird.
Erstellung des Bokeh Dashboards
Kommen wir nun dazu, wie man das Dashboard aufbauen kann. Neben den verschiedenen Visualisierungen kann das Dashboard mit den Widgets wie ein Baukasten modular aufgebaut werden. Auf der Website von bokeh finden sich eine Vielzahl von unterschiedlichen Widgets, womit sich die Funktionen beliebig erweitern lassen. Ziel von unserem Dashboard sollen es sein, dass es folgende Eigenschaften erfüllt:
- Zwei unterschiedliche Visualisierungen
- Interaktionselemente zur Auswahl von Daten für unsere Darstellungen
Als Visualisierungen wollen wir zum einen ein Histogramm/Säulendiagramm und zum anderen einen Scatter-Plot in unser Dashboard aufnehmen. Hierzu importieren wir die Klasse figure
mit dieser können wir beide Visualisierung umsetzen. Hierzu drei Anmerkungen:
- Für Bokeh Grafiken ist es entscheidend, um welche Art von Skala es sich bei den Daten handelt. Wir haben in unserem Fall sowohl Daten mit einer Nominalskala, als auch Daten mit einer Verhältnisskala. Wir erstellen daher ein Histogramm sowie ein Säulendiagramm für die unterschiedlichen Fälle.
- Bevor wir ein Histogramm mit Bokeh darstellen können, müssen wir zunächst noch die Klassengrößen und die jeweilige Anzahl der Beobachtungen in den Klassen festlegen, da dies nicht direkt in Bokeh erfolgen kann, setzen wir diesen Schritt mit numpy und der Funktion
np.histogram()
um. - Zudem überführen wir unsere Daten in ein Dictionary, damit wir dieses später leicht ändern und das Dashboard interaktiv gestalten können.
Der folgende Python-Code zeigt, wie man das in Verbindung mit dem Bokeh Server und unserem Datensatz umsetzen kann.
import numpy as np
from seaborn import load_dataset
from bokeh.io import curdoc
from bokeh.layouts import row
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
# Festlegen des Dashboard Titles
curdoc().title = "Histogramm/Säulendiagramm"
# Datenset laden
tips = load_dataset("tips")
# VISUALISIERUNGEN
# Histogramm mit Numpy erstellen
top_hist, x_hist = np.histogram(tips.total_bill)
# Daten in Dict überführen
source_hist = ColumnDataSource(data=dict(x=x_hist[:-1], top=top_hist))
# Allgemeinen Plot erstellen
hist = figure(plot_height=400, plot_width=400,
title="Histogramm",
x_axis_label='total_bill',
y_axis_label='Absolute Häufigkeit')
# Darstellung des Säulendiagramms
hist.vbar(x='x', top='top', width=0.5, source=source_hist)
# Kategoriale Variablen
kat_data = tips.smoker.value_counts()
x_kat = list(kat_data.index)
top_kat = kat_data.values
# Daten in Dict überführen
source_kat = ColumnDataSource(data=dict(x=x_kat, top=top_kat))
# Allgemeinen Plot erstellen
bar = figure(x_range= x_kat, plot_height=400, plot_width=400,
title="Säulendiagramm",
x_axis_label='smoker',
y_axis_label='Absolute Häufigkeit')
# Darstellung des Säulendiagramm
bar.vbar(x='x', top='top', width=0.1, source=source_kat)
# Hinzufügen der beiden Visualisierungen in das Hauptdokument
curdoc().add_root(row(hist, bar))
Als nächstes fügen wir unseren Scatter-Plot zu unserem Dashboard hinzu, hierfür erstellen wir auch wieder eine figure mit einem zugehörigen Daten-Dictionary. Dieses können wir dann zu unserem Dashboard einfach hinzufügen.
# Hinzufügen des Scatter Plots
curdoc().add_root(row(hist, bar, scatter))
Der Übersicht halber stellen wir nicht mehr den gesamten Programmcode hier dar, sondern nur noch die wesentlichen Auszüge. Den gesamten Quellcode findet ihr in unserem unserem Git Repositroy. Für dieses Beispiel heißt die zugehörige Datei bokeh-hist-bar-scatter.py. Unser Dashboard sieht inzwischen folgendermaßen aus:
Bis jetzt hat unser Dashboard die erste Anforderung erfüllt, was jetzt noch fehlt, sind die Interaktionselemente auf Englisch: Widgets. Die Funktion der Widgets soll sein, die verschiedenen Variablen für die beiden Plots auszuwählen. Da unser Scatterplot sowohl eine x-, als auch eine y-Achse hat, verwenden wir zwei Select Widgets, um für beide Achsen Daten auszuwählen und beschränken uns auf die Variablen mit Verhältnisskala, also numerischer Natur.
Wie verleiht man dem Dashboard nun die notwendige Interaktion?
Ein wesentliches Manko unseres Dashboards bis jetzt ist es, dass es nur einen Datensatz darstellt, jedoch keine Aktualisierung vornimmt, wenn wir z.B. in den Widgets andere Variablen ausgewählt haben. Das lösen wir nun über die Funktion update_data und eine for-Schleife. Mit der Funktion verändern wir die Daten in unserem Histogramm/Säulendiagramm sowie den Scatter-Plot. Die aktuell gewählten Variablen in unseren Widgets erhalten wir, in dem wir auf das Attribut value zugreifen. Anschließend können wir das dict für unsere Daten aktualisieren. Für die Histogramme ist es entscheidend, ob eine kategoriale Variable vorliegt. Diese Fallunterscheidung decken wir mit der if-Bedinung ab, je nach Variable wird daher entweder das obere oder untere Diagramm aktualisiert. Mit der for-Schleife wird nun sobald eine Veränderung in einem unserer Widgets eintritt, die Funktion update_data ausgeführt.
def update_data(attrname, old, new):
"""Update der Daten sowie der Beschriftungen"""
# Scatter Diagramm
scatter.xaxis.axis_label = select_x.value
scatter.yaxis.axis_label = select_y.value
x = select_x.value
y = select_y.value
source_scatter.data = dict(x=tips[x], y= tips[y])
# Säulendiagramm
data_cat = tips[select_cat.value]
summary = data_cat.value_counts()
bar.x_range.factors = list(summary.index)
source_kat.data = dict(x=list(summary.index), top=summary.values)
bar.xaxis.axis_label = select_cat.value
# Historamm
data_hist = tips[select_hist.value]
top_hist_new, x_hist_new = np.histogram(data_hist)
source_hist.data = dict(x= x_hist_new[:-1], top=top_hist_new)
hist.xaxis.axis_label = select_hist.value
for w in [select_hist, select_cat, select_x, select_y]:
w.on_change('value', update_data)
Damit ist unser Dashboard nun fertig und hat die Zielsetzung erfüllt. Den Code für das fertige Dashboard findet ihr in der Datei bokeh-dashboard-final.py unter folgendem Link und so sieht es in Aktion aus:
Fazit
Zum Abschluss möchten wir noch ein kurzes Fazit zu unserem Dashboard ziehen. Es hat sich gezeigt, dass folgende Schritte für ein interaktives Bokeh-Dashboard notwendig sind:
- Vorbereitung der Daten und Erstellung von Dictionaries
- Festlegung der Visualisierung und zugehörigen Skalen
- Hinzufügen der passenden Widgets
- Definition einer oder mehrer Funkitonen zur Aktualisierung der Diagramme
Sobald dieses Grundgerüst steht, kann man sein Bokeh-Dashborad beliebig um Widgets und Darstellungen erweitern. Im Hinterkopf sollte man stets die objekteorientierte Arbeitsweise behalten und sich über die verschiedenen Klassen sowie Attribute der Objekte bewusst sein. Durch die Umsetzung in Python ist das Verarbeiten von Daten z.B. mit der Bibliothek pandas einfach möglich. Mit Bokeh spart ihr euch zudem den Aufwand, selber das Layout in HTML-Code festzulegen und auch die Interaktionen in JavaScript zu schreiben. Viel Spaß beim Erstellen eigener Dashboards!
Referenzen:
Neben dem Einstieg als Trainee oder Data Science Consultant bei STATWORX gibt es ebenso die Möglichkeit, ein Praktikum im Bereich Data Science zu absolvieren. Unsere aktuellen Stellenausschreibungen findet ihr übrigens hier.
Bewerbung bei STATWORX
Das Berufsbild des Data Scientists ist durch seine vielfältigen Aufgaben und die bunte Durchmischung der Kompetenzen vor allem in den letzten Jahren sehr attraktiv geworden. Dies spiegelt sich auch in den Suchanfragen bei Google zum Begriff Data Scientist. Da ich mich während meiner Bachelorarbeit bereits mit den Themen des maschinellen Lernens und der Datenanalyse ausführlich beschäftigt hatte, wollte ich sehr gerne mehr über diese Themen im Rahmen eines Praktikums lernen. Mit STATWORX habe ich das passende Unternehmen sehr schnell gefunden und nicht lange gezögert mich als Data Science Praktikant zu bewerben. Der gesamte Bewerbungsprozess von Telefoninterview über ein Vorstellungsgespräch lief innerhalb einer Woche wirklich sehr schnell ab. Wenn ihr gerne mehr über den Hintergrund von uns wissen wollt, so beschreibt Fabian Müller (Head of Data Science) in einem Blog-Beitrag, wie er von den Sozialwissenschaften zu STATWORX gekommen ist.
Die ersten Tage bei STATWORX
An meinem ersten Tag als Praktikant begrüßten mich die Mitarbeiter sehr herzlich. Nach einem kurzen Rundgang durch das Büro konnte ich direkt mit Fabian das neue MacBook Pro einrichten. Gleichzeitig gab es eine ausführliche Einführung in das gesamte Software-Ökosystem von STATWORX. Nachdem auch die Arbeitsstandards erklärt und Kommunikationskanäle eingerichtet waren, habe ich schon meine erste Aufgabe bekommen. Zunächst ein kurzer Ausflug in die Arbeitsweise von uns. Grundsätzlich teilen sich die Arbeitsfelder von STATWORX in drei Bereiche auf:
- Data Science Projekte
- Schulungen in Python/R
- Statistik-Beratung
Für die technische Implementierung der Projekte verwenden wir bei STATWORX ausschließlich OpenSource-Lösungen. Durch die florierende Community in diesem Bereich werden Pakte bzw. Bibliotheken für Python und R ständig weiterentwickelt sowie mit neuen Funktionen ausgestattet. Damit wir unsere Erfahrungen und den Status Quo stets an unsere Kunden weitergeben, ist es meine Aufgabe, eine Schulung im Bereich Deep Learning für R dahingehend zu aktualisieren und das Schulungskonzept zu erweitern.
Durch die schnelle Einrichtung des MacBooks und die Einführung konnte ich direkt nach einer Stunde die ersten Deep Learning Modelle in R entwickeln und trainieren. Im Laufe des Tages gab es immer wieder kleine Sitzungen mit meinen Kollegen, die mir ihre verschiedenen Projekte und aktuellen Herausforderungen vorgestellt sowie über ihre bisherigen Erfahrungen erzählt haben.
Zum Abschluss des ersten Tages haben wir noch das Data Science Meet-Up in Frankfurt besucht. Bei ein paar kühlen Drinks gab es Präsentationen zu verschiedenen AI Themen. Ein Vortrag von 904Labs aus Amsterdam hat die Relevanz von Suchfunktionen auf e-Commerce Website unterstrichen und ihre Lösung, wie sie die Suche mithilfe von AI verbessern, vorgestellt. Eine weitere Präsentation hatte eher einen geschichtswissenschaftlichen Charakter, sie beantwortete die Frage, wie sich die Menschheit im Laufe der Zeit Wissen angeeignet und untereinander ausgetauscht hat. So ging ein erster spannender Tag als Data Science Praktikant bei STATWORX zu Ende.
Wissenstransfer wird bei uns groß geschrieben!
Mein zweiter Tag begann mit einem Team Meeting des Data Science Teams. Hier haben alle Kollegen den Status aller Projekte und die nächsten Schritte besprochen. Die Bandbreite an verschiedenen Projekten und die jeweiligen Lösungsansätze haben mich beeindruckt und unterstreichen die Möglichkeiten während eines Praktikums bei STATWORX viel dazuzulernen. Durch das Projektgeschäft sind viele Kollegen nicht immer im Büro und so habe ich am Freitag noch einige neue Gesichter kennen lernen können. Nachmittags haben wir uns noch in einer kleineren Runde zusammengesetzt, um über verschiedene Möglichkeiten von Bootstrapping für Zeitreihen zu reden und hier mögliche Best Practices abzuleiten. Wie an jedem Freitag hat sich dann noch sowohl das Statistik Team als auch das Data Sience Team für einen Wissenstransfer zusammengesetzt. In dieser Sitzung hat Alexander Darrall aus dem Statistik-Team verschiedene parametrische sowie nicht-parametrische Testverfahren vorgestellt und diskutiert, welcher Anwendungsfall am besten für sie geeignet ist. Während seines Vortrags kam es zu interessanten Diskussionen innerhalb des gesamten Teams. Abends haben wir die Woche noch bei ein paar Burgern und kühlen Getränken ausklingen lassen.
Fazit
Nach meinen ersten Tagen bei STATWORX kann ich nur den positiven Eindruck aus meinem Bewerbungsgespräch bestätigen. Typische Praktikanten Aufgaben gibt es nicht. Man ist von Anfang an Teil des kompletten Data Science Teams und arbeitet ohne Einschränkungen an Projekten sowie Schulungen mit. Die Benefits für ein Praktikum bei STATWORX sind:
- ein super dynamisches Team
- flache Hierarchien
- spannende Projekte
- steile Lernkurve
- cooles Office
- Top Equipment
Referenzen: