Data Science, Machine Learning und KI
Kontakt

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:

  1. via pyplot
  2. 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:

  1. Erstellung eines figure-Objekts inkl. axes-Objekten
  2. Bearbeiten der axes-Objekte
  3. 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:

leere-abbildung

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)

2x2-layout

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

linien-grafik

An dieser Stelle sei kurz angemerkt, dass Matplotlib im Hintergrund zwei Vorgänge durchgeführt hat:

  1. Die Skala wurde automatisch an die Daten angepasst.
  2. 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

scatter

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:

histogramm

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.

DataFrames Beispiel

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:

  1. Daten für die jeweiligen Spalten
  2. 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() und df.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 mit loc 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:

  1. Wie sind die Daten strukturiert?
  2. Was sind besondere Merkmale?
  3. 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 Blog Beitrag.

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:

  1. Terminal/Bash-Konsole öffnen
  2. mit cd in das Verzeichnis des Python-Skriptes wechseln
  3. 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:

dashboard plots

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.

dashboard widgets

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:

dashboard demo gif

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:

  1. Vorbereitung der Daten und Erstellung von Dictionaries
  2. Festlegung der Visualisierung und zugehörigen Skalen
  3. Hinzufügen der passenden Widgets
  4. 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:

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:

  1. Wie sind die Daten strukturiert?
  2. Was sind besondere Merkmale?
  3. 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 Blog Beitrag.

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:

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:

  1. Terminal/Bash-Konsole öffnen
  2. mit cd in das Verzeichnis des Python-Skriptes wechseln
  3. 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:

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:

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:

dashboard plots

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.

dashboard widgets

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:

dashboard demo gif

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:

  1. Vorbereitung der Daten und Erstellung von Dictionaries
  2. Festlegung der Visualisierung und zugehörigen Skalen
  3. Hinzufügen der passenden Widgets
  4. 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: