Datenvisualisierung mit Python und Bokeh — Baugenehmigungen in Kiel

In diesem Tutorial möchte ich zeigen, wie in Python mit der Bokeh-Bibliothek Daten visualisiert werden können. Der Datensatz liegt als CSV-Datei vor, die mit requests aus dem Internet geladen wird. Als Ergebnis erhält man eine HTML-Datei. Bei den Daten handelt es sich um Baugenehmigungen in Kiel (1988-2023). Die CSV-Daten werden mit Pandas gelesen, um anschließend mithilfe von Bokeh als Liniendiagramm dargestellt zu werden.

Es beginnt mit den Import-Anweisungen:

from io import StringIO

from bokeh.plotting import ColumnDataSource, figure, save, show

import pandas as pd

import requests
from requests.exceptions import (
    ConnectionError,
    RequestException,
    Timeout,
    TooManyRedirects,
)

Neben dem Import von bokeh, pandas und requests, kommt noch StringIO hinzu. Diese Bibliothek ermöglicht es, aus dem heruntergeladenen Datensatz ein Datei-ähnliches Objekt zu erstellen, das im Arbeitsspeicher verbleibt. Dadurch wird keine CSV-Datei im Dateisystem gespeichert.

Der nachfolgende Code lässt sich im Grunde in drei Abschnitte teilen:

  1. Der Datensatz (CSV-Datei) wird mit requests heruntergeladen.
  2. Unter Verwendung von Pandas wird ein DataFrame erstellt.
  3. Zum Schluß werden die Daten mit Bokeh visualisiert.

Datensatz mit requests laden

Das Herunterladen der Daten bzw. der CSV-Datei wird für diese Aufgabe eine Funktion verwendet, die einige Ausnahmebehandlungen enthält, um etwaige Fehler abzufangen.

URL = (
    "https://www.kiel.de/opendata/kiel_infrastruktur_bauen_wohnen_baugenehmigungen.csv"
)

def fetch_csv_data():
    """Fetch CSV data from given URL

    Returns:
        str -- CSV data
    """
    try:
        response = requests.get(URL)

        # Falls der Status-Code nicht 200 ist,
        # wird eine HTTPError-Ausnahme erzeugt.
        response.raise_for_status()

        return response.text

    except ConnectionError as e:
        print(f"Connection error: {e}")
    except Timeout as e:
        print(f"Request timed out: {e}")
    except TooManyRedirects as e:
        print(f"Too many redirects: {e}")
    except RequestException as e:
        print(f"An error occurred: {e}")

csv_data = fetch_csv_data()

DataFrame erstellen

Aus den heruntergeladenen Daten wird dann ein Pandas-DataFrame erzeugt, das später Bokeh als Datenquelle dient. Hier kommt die bereits erwähnte Bibliothek StringIO zum Einsatz.

csv_content = StringIO(csv_data)
df = pd.read_csv(csv_content, sep=";")

Daten visualisieren

Da wir nun eine Datenquelle (Pandas-DataFrame) haben, die von Bokeh für die Visualisierung genutzt werden kann, können wir das Liniendiagramm erstellen. In diesem Beispiel wird uns das Ergebnis mit show(plot) im Browser angezeigt und zusätzlich aufgrund der Zeile save(plot) gespeichert.

plot = figure(
    title="Baugenehmigungen in Kiel (1988 - 2023)",
    width=1000,
    x_axis_label="Jahr",
    y_axis_label="Baugenehmigungen",
)

datenquelle = ColumnDataSource(df)
plot.line(source=datenquelle, x="Jahr", y="Wohnungen gesamt")

show(plot)
save(plot)

Zum Schluß dieses Artikels zeige ich noch den kompletten Code:

#!/usr/bin/env python3

from io import StringIO

from bokeh.plotting import ColumnDataSource, figure, save, show

import pandas as pd

import requests
from requests.exceptions import (
    ConnectionError,
    RequestException,
    Timeout,
    TooManyRedirects,
)

URL = (
    "https://www.kiel.de/opendata/kiel_infrastruktur_bauen_wohnen_baugenehmigungen.csv"
)


def fetch_csv_data():
    """Fetch CSV data from given URL

    Returns:
        str -- CSV data
    """
    try:
        response = requests.get(URL)
        
        # Falls der Status-Code nicht 200 ist,
        # wird eine HTTPError-Ausnahme erzeugt.
        response.raise_for_status()

        return response.text

    except ConnectionError as e:
        print(f"Connection error: {e}")
    except Timeout as e:
        print(f"Request timed out: {e}")
    except TooManyRedirects as e:
        print(f"Too many redirects: {e}")
    except RequestException as e:
        print(f"An error occurred: {e}")


csv_data = fetch_csv_data()

csv_content = StringIO(csv_data)
df = pd.read_csv(csv_content, sep=";")

plot = figure(
    title="Baugenehmigungen in Kiel (1988 - 2023)",
    width=1000,
    x_axis_label="Jahr",
    y_axis_label="Baugenehmigungen",
)

datenquelle = ColumnDataSource(df)
plot.line(source=datenquelle, x="Jahr", y="Wohnungen gesamt")

show(plot)
save(plot)

Weiterführende Links