5. Erstellung von Graphiken mittels Matplotlib, Seaborn und Plotly#

Matplotlib ist eine mächtige Python-Bibliothek, welche viele Funktionen für die graphische Darstellung in Form von Plots und Diagrammen bereitstellt. Wir werden in diesem Abschnitt die grundlegende Funktionsweise kennenlernen. Sehr viele neuere Graphik-Pakete bauen auf diesem Basispaket auf und erleichtern die Erstellung von Graphiken oder erweitern den Funktionsumfang. So können mit Plotly, welches Bestandteil des Matplotlib-Pakets ist, interaktive Graphiken erstellt werden und mit Seaborn insbesondere klassiche statistische Plots sehr komfortabel erstellt werden.

5.1. Visualisierungen mit Matplotlib#

Matplotlib bietet zwei verschiedene Arbeitsweisen, um Diagramme zu erstellen: das state-based Interface über pyplot und das object-oriented Interface mit Figure und Axes.

Das state-based Interface (plt) funktioniert so, dass Matplotlib im Hintergrund immer ein „aktuelles“ Diagramm verwaltet. Befehle wie plt.plot() oder plt.title() wirken automatisch auf dieses aktive Diagramm. Das ist sehr praktisch für schnelle Visualisierungen, weil man mit wenigen Befehlen sofort ein Ergebnis erhält. Allerdings wird es schnell unübersichtlich, sobald man mehrere Diagramme oder komplexere Layouts darstellen möchte, da man nicht immer genau im Blick hat, auf welches Objekt sich der jeweilige Befehl gerade bezieht.

Das object-oriented Interface arbeitet hingegen explizit mit Objekten. Man erstellt eine Figur (Figure) und darin ein oder mehrere Achsen (Axes). Auf diese Objekte ruft man dann Methoden wie ax.plot() oder ax.set_title() auf. Der große Vorteil liegt darin, dass man immer genau weiß, welches Element man verändert, und dass sich die Struktur bei mehreren Subplots oder komplexen Darstellungen viel besser kontrollieren lässt. Der Nachteil ist lediglich, dass der Code etwas ausführlicher wirkt.

Zusammengefasst kann man sagen: Für schnelle und einfache Plots ist das state-based Interface völlig ausreichend. Für größere Projekte, Publikationen oder Layouts mit mehreren Teilplots empfiehlt sich das object-oriented Interface, da sie deutlich sauberer und flexibler ist.

Ähnlich wie bei NumPy müssen wir Matplotlib und Seaborn erst installieren. Dies erledigt man in seiner Umgebung mit folgendem Befehl:

conda install -c conda-forge matplotlib
pip install matplotlib

Anschließend können wir Matplotlib, besser gesagt das Submodul matplotlib.pyplot in unser Programm einbinden:

import matplotlib.pyplot as plt

5.1.1. State-based Interface#

Der wichtigste Befehl ist plt.plot(). Wir geben plt.plot? ein um zu erfahren wie dieser funktioniert. In der einfachsten Form sind die Argumente dieser Funktion 2 Vektoren mit x- bzw. y-Koordinaten einer Punktwolke. Diese Vektoren können Listen, oder Numpy-Arrays sein. Die Sinusfunktion können wir wie folgt plotten:

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 2*np.pi, 11)   # Erzeuge Gitter [0, 0.2*pi, 0.4*pi, ..., 2*pi])
y = np.sin(x)                     # Berechne zugehörige Funktionswerte

plt.plot(x,y)                     # Erzeuge Plot
plt.show()                        # Zeige den Plot
_images/c639205202cdb52b3c5ae942f14a8740d524ca17024893bb18b3f57177e1a951.png

Machen wir unseren Plot noch etwas schöner:

import numpy as np

x = np.linspace(0,4*np.pi, 1001) # Erzeuge äquidistantes Gitter für das Intervall [0, 4*pi]
y = np.sin(x)                    # Berechne zugehörige Funktionswerte für sinus
z = np.cos(x)                    # und cosinus

plt.figure(figsize=(10,5))       # Größe einstellen

plt.plot(x,y,label="$\sin(x)$")  # Erzeuge Plot für Sinus
plt.plot(x,z,label="$\cos(x)$")  # Erzeuge Plot für Cosinus

plt.title("Sinus und Cosinus")   # Titel des Plots
plt.grid()                       # Gitterlinien einschalten

plt.xlabel('x')                  # Bezeichner an x-Achse
plt.ylabel('f(x)')               # Bezeichner an y-Achse (in Latex-Code)

plt.legend()                     # Legende
plt.show()                       # Zeige den Plot
_images/939dec550a5129fc0f55621629897cb2d480f5f21caf266c9d03afe5d7f9b0f0.png

Im Prinzip hat Matplotlib unsere Punktwolke mit Koordinaten aus x und y gezeichnet, und die Punkte mit Linen verbunden. Es gibt aber noch einige andere Linientypen:

x = np.linspace(0,2,21)

y1= x-0.5*x**2
y2= 2*x-x**2
y3= 3*x-1.5*x**2 

plt.figure(figsize=(10,5))       # Größe einstellen

# Zeichne rote (r) Kreise (o) mit Verbindungslinien (-)
plt.plot(x, y1, 'ro-', linewidth=0.2, label='f1')  

# Zeichne blaue (b) Quadrate (s)
plt.plot(x, y2, 'bs', label='f1')   

# Zeichne cyane (c) Dreiecke (^) mit gestrichelten Verbindungslinien (--)
plt.plot(x, y3, 'c^--', markersize=10, label='f1')

plt.xlabel('x')
plt.ylabel('f(x)')
plt.grid()
plt.legend()
plt.show()
_images/de7b51d61d6303d9958934c0ff896b5499e0c76ee35e67aee863f898467e1fd9.png

Im Hilfetext plt.plot? sind noch weitere Linien- und Markertypen erklärt. Vordefinierte Farben im Submodul matplotlib.colors. Es gibt verschiedene Farbpaletten. Die Grundfarben sind:

import matplotlib.colors as mcolors
mcolors.BASE_COLORS
{'b': (0, 0, 1),
 'g': (0, 0.5, 0),
 'r': (1, 0, 0),
 'c': (0, 0.75, 0.75),
 'm': (0.75, 0, 0.75),
 'y': (0.75, 0.75, 0),
 'k': (0, 0, 0),
 'w': (1, 1, 1)}

Es gibt weiterhin die Farbpalette Tableau:

mcolors.TABLEAU_COLORS
{'tab:blue': '#1f77b4',
 'tab:orange': '#ff7f0e',
 'tab:green': '#2ca02c',
 'tab:red': '#d62728',
 'tab:purple': '#9467bd',
 'tab:brown': '#8c564b',
 'tab:pink': '#e377c2',
 'tab:gray': '#7f7f7f',
 'tab:olive': '#bcbd22',
 'tab:cyan': '#17becf'}

Und außerdem noch CSS-Farben:

mcolors.CSS4_COLORS
{'aliceblue': '#F0F8FF',
 'antiquewhite': '#FAEBD7',
 'aqua': '#00FFFF',
 'aquamarine': '#7FFFD4',
 'azure': '#F0FFFF',
 'beige': '#F5F5DC',
 'bisque': '#FFE4C4',
 'black': '#000000',
 'blanchedalmond': '#FFEBCD',
 'blue': '#0000FF',
 'blueviolet': '#8A2BE2',
 'brown': '#A52A2A',
 'burlywood': '#DEB887',
 'cadetblue': '#5F9EA0',
 'chartreuse': '#7FFF00',
 'chocolate': '#D2691E',
 'coral': '#FF7F50',
 'cornflowerblue': '#6495ED',
 'cornsilk': '#FFF8DC',
 'crimson': '#DC143C',
 'cyan': '#00FFFF',
 'darkblue': '#00008B',
 'darkcyan': '#008B8B',
 'darkgoldenrod': '#B8860B',
 'darkgray': '#A9A9A9',
 'darkgreen': '#006400',
 'darkgrey': '#A9A9A9',
 'darkkhaki': '#BDB76B',
 'darkmagenta': '#8B008B',
 'darkolivegreen': '#556B2F',
 'darkorange': '#FF8C00',
 'darkorchid': '#9932CC',
 'darkred': '#8B0000',
 'darksalmon': '#E9967A',
 'darkseagreen': '#8FBC8F',
 'darkslateblue': '#483D8B',
 'darkslategray': '#2F4F4F',
 'darkslategrey': '#2F4F4F',
 'darkturquoise': '#00CED1',
 'darkviolet': '#9400D3',
 'deeppink': '#FF1493',
 'deepskyblue': '#00BFFF',
 'dimgray': '#696969',
 'dimgrey': '#696969',
 'dodgerblue': '#1E90FF',
 'firebrick': '#B22222',
 'floralwhite': '#FFFAF0',
 'forestgreen': '#228B22',
 'fuchsia': '#FF00FF',
 'gainsboro': '#DCDCDC',
 'ghostwhite': '#F8F8FF',
 'gold': '#FFD700',
 'goldenrod': '#DAA520',
 'gray': '#808080',
 'green': '#008000',
 'greenyellow': '#ADFF2F',
 'grey': '#808080',
 'honeydew': '#F0FFF0',
 'hotpink': '#FF69B4',
 'indianred': '#CD5C5C',
 'indigo': '#4B0082',
 'ivory': '#FFFFF0',
 'khaki': '#F0E68C',
 'lavender': '#E6E6FA',
 'lavenderblush': '#FFF0F5',
 'lawngreen': '#7CFC00',
 'lemonchiffon': '#FFFACD',
 'lightblue': '#ADD8E6',
 'lightcoral': '#F08080',
 'lightcyan': '#E0FFFF',
 'lightgoldenrodyellow': '#FAFAD2',
 'lightgray': '#D3D3D3',
 'lightgreen': '#90EE90',
 'lightgrey': '#D3D3D3',
 'lightpink': '#FFB6C1',
 'lightsalmon': '#FFA07A',
 'lightseagreen': '#20B2AA',
 'lightskyblue': '#87CEFA',
 'lightslategray': '#778899',
 'lightslategrey': '#778899',
 'lightsteelblue': '#B0C4DE',
 'lightyellow': '#FFFFE0',
 'lime': '#00FF00',
 'limegreen': '#32CD32',
 'linen': '#FAF0E6',
 'magenta': '#FF00FF',
 'maroon': '#800000',
 'mediumaquamarine': '#66CDAA',
 'mediumblue': '#0000CD',
 'mediumorchid': '#BA55D3',
 'mediumpurple': '#9370DB',
 'mediumseagreen': '#3CB371',
 'mediumslateblue': '#7B68EE',
 'mediumspringgreen': '#00FA9A',
 'mediumturquoise': '#48D1CC',
 'mediumvioletred': '#C71585',
 'midnightblue': '#191970',
 'mintcream': '#F5FFFA',
 'mistyrose': '#FFE4E1',
 'moccasin': '#FFE4B5',
 'navajowhite': '#FFDEAD',
 'navy': '#000080',
 'oldlace': '#FDF5E6',
 'olive': '#808000',
 'olivedrab': '#6B8E23',
 'orange': '#FFA500',
 'orangered': '#FF4500',
 'orchid': '#DA70D6',
 'palegoldenrod': '#EEE8AA',
 'palegreen': '#98FB98',
 'paleturquoise': '#AFEEEE',
 'palevioletred': '#DB7093',
 'papayawhip': '#FFEFD5',
 'peachpuff': '#FFDAB9',
 'peru': '#CD853F',
 'pink': '#FFC0CB',
 'plum': '#DDA0DD',
 'powderblue': '#B0E0E6',
 'purple': '#800080',
 'rebeccapurple': '#663399',
 'red': '#FF0000',
 'rosybrown': '#BC8F8F',
 'royalblue': '#4169E1',
 'saddlebrown': '#8B4513',
 'salmon': '#FA8072',
 'sandybrown': '#F4A460',
 'seagreen': '#2E8B57',
 'seashell': '#FFF5EE',
 'sienna': '#A0522D',
 'silver': '#C0C0C0',
 'skyblue': '#87CEEB',
 'slateblue': '#6A5ACD',
 'slategray': '#708090',
 'slategrey': '#708090',
 'snow': '#FFFAFA',
 'springgreen': '#00FF7F',
 'steelblue': '#4682B4',
 'tan': '#D2B48C',
 'teal': '#008080',
 'thistle': '#D8BFD8',
 'tomato': '#FF6347',
 'turquoise': '#40E0D0',
 'violet': '#EE82EE',
 'wheat': '#F5DEB3',
 'white': '#FFFFFF',
 'whitesmoke': '#F5F5F5',
 'yellow': '#FFFF00',
 'yellowgreen': '#9ACD32'}

Durch Setzen des Parameters color im Plot-Befehl kann nun eine Farbe aus einer der oberen Farbpaletten gewählt werden:

x = np.linspace(0,1,10)

plt.plot(x,0.5*x*(1-x),'o-',color='g')         # Grundfarbe
plt.plot(x,x*(1-x),'d-',color='tab:olive')     # Tableau-Farbe
plt.plot(x,1.5*x*(1-x),'s-',color='firebrick') # CSS-Farbe

plt.show()
_images/a01922fd08bfc4f813dfe47b07cffaf9aae31e980af0a1276d654952ef77f964.png

Bei einigen Grafiken ist es hilfreich auf eine logarithmische Skalierung zu wechseln, wie beispielsweise bei der Fehleranalyse.

parties = ["CDU", "SPD", "FDP", "Grüne", "Linke", "AfD", "Sonstige"]
colors = ["black", "red", "yellow", "green", "violet", "blue", "gray"]
values = [25.3, 26.3, 13.9, 14.2, 6.2, 4.9]
values.append(100-sum(values))

Wir können anstelle des plot-Befehls auch stem verwenden um ein Liniendiagramm zu erstellen, scatter für ein Streudiagramm, bar für ein Balkendiagramm oder pie für ein Tortendiagramm. Im folgenden Beispiel werden die Unterschiede gezeigt:

plt.figure(figsize=(10,10))

plt.subplot(2,2,1)
plt.stem(parties, values)
plt.title("Plot")

plt.subplot(2,2,2)
plt.bar(parties, values, color=colors)
plt.title("Bar")

plt.subplot(2,2,3)
plt.scatter(parties, values, color=colors)
plt.title("Scatter")

plt.subplot(2,2,4)
plt.pie(values, labels=parties, explode=[0,0.2,0,0.2,0,0,0], colors=colors)
plt.title("Pie")

plt.show()
_images/8789d7d288751484c0cd26a6bad5d7d2bf9c634f913850c52b871fbd29edb31f.png

5.1.2. Object-oriented Interface#

Nach dem wir uns nun mit der ersten Ebene befasst haben, befassen wir uns jetzt mit der zweiten Ebene von matplotlib. Dazu benötigen wir oftmals den Befehle ax.plot().

import pandas as pd

Kurs_ALV = pd.read_csv("./../../Data/Allianz_Schlusskurse_2020_2025.csv")

Kurs_ALV=pd.read_csv("./../../Data/Allianz_Schlusskurse_2020_2025.csv", header=1) # To-Do: Anpassen; header = csv beginnt in Spalte eins nicht mit Kurs und Datum, sondern mit Ticker.

Kurs_ALV = Kurs_ALV.rename(columns={"Ticker": "Datum", "ALV.DE": "Schlusskurs"}) #Spaltenbeschriftungen umbennen
Kurs_ALV = Kurs_ALV.drop(0).reset_index(drop=True) #Erste Zeile rauswerfen --> drop(0); Mit .reset_index(drop=True) wird der Index neu durchnummeriert (0, 1, 2, …); Sonst bleibt der alte Index erhalten und wir beginnen mit Index 1

print(Kurs_ALV)

# Datumskonvertierung
Kurs_ALV["Datum"] = pd.to_datetime(Kurs_ALV["Datum"])

# Plot erstellen
plt.figure(figsize=(12,6))
plt.plot(Kurs_ALV["Datum"], Kurs_ALV["Schlusskurs"], label="Allianz Schlusskurs")

# Chart soll gleich an y-Achse starten
plt.xlim(Kurs_ALV["Datum"].min(), Kurs_ALV["Datum"].max())

plt.title("Allianz Schlusskurse (2020–2025)")
plt.xlabel("Datum")
plt.ylabel("Schlusskurs (€)")
plt.grid(True)

plt.show()
           Datum  Schlusskurs
0     2020-01-02   163.322479
1     2020-01-03   161.515991
2     2020-01-06   160.520554
3     2020-01-07   160.889252
4     2020-01-08   160.962997
...          ...          ...
1270  2024-12-19   284.622162
1271  2024-12-20   282.417297
1272  2024-12-23   282.033844
1273  2024-12-27   282.896606
1274  2024-12-30   283.663513

[1275 rows x 2 columns]
_images/10936b245472ada0fc857c9a03e6e9b0c143c4fefe8252506390c11127b6e680.png

Wir können auch Daten dem Diagramm hinzufügen.

concerns = pd.read_csv("./../../Data/Kurse_ASTBDR.csv", sep=";", decimal=",") # Decimal="," wegen europäischer Schreibweise -> TO-Do: Pfad anpassen
 
concerns["Datum"] = pd.to_datetime(concerns["Datum"], dayfirst=True) # dayfirst=True = Tag kommt vor dem Monat 

# Plot erstellen
fig, ax = plt.subplots(figsize=(12, 6))


# Einzelne Plots
ax.plot(concerns["Datum"], concerns["Allianz"], label="Allianz", color="r")
ax.plot(concerns["Datum"], concerns["Siemens"], label="Siemens", color="b")
ax.plot(concerns["Datum"], concerns["Telekom"], label="Telekom", color="g")
ax.plot(concerns["Datum"], concerns["BASF"], label="BASF", color="m")
ax.plot(concerns["Datum"], concerns["Deutsche Bank"], label="Deutsche Bank", color="c")
ax.plot(concerns["Datum"], concerns["RWE"], label="RWE", color="k")

plt.xlim(concerns["Datum"].min(), concerns["Datum"].max())

ax.set_xlabel("Datum")
ax.set_ylabel("Preis (€)")
ax.set_title("Aktienkurse Vergleich")

ax.legend()
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
/tmp/ipykernel_3832171/2366942567.py:3: UserWarning: Could not infer format, so each element will be parsed individually, falling back to `dateutil`. To ensure parsing is consistent and as-expected, please specify a format.
  concerns["Datum"] = pd.to_datetime(concerns["Datum"], dayfirst=True) # dayfirst=True = Tag kommt vor dem Monat
_images/b3ead7c7950b120de3656b866ea24148fda478c9ebc6eaf50d1601ccfb430c42.png

Nachfolgend sehen Sie, wie wir Unterdiagramme erstellen können. In den meisten Fällen benötigen wir für eine feinere Kontrolle das Package matplotlib.dates und verbinden das mit eine for-Schleife. Ein Beispiel dafür finden Sie hier.

import matplotlib.dates as mdates 

# Subplots 3x2
fig, ax = plt.subplots(3, 2, figsize=(12, 8), sharex=True)

ax[0, 0].plot(concerns["Datum"], concerns["Allianz"])
ax[0, 0].set_title("Allianz")

ax[0, 1].plot(concerns["Datum"], concerns["Siemens"])
ax[0, 1].set_title("Siemens")

ax[1, 0].plot(concerns["Datum"], concerns["Telekom"])
ax[1, 0].set_title("Telekom")

ax[1, 1].plot(concerns["Datum"], concerns["BASF"])
ax[1, 1].set_title("BASF")

ax[2, 0].plot(concerns["Datum"], concerns["Deutsche Bank"])
ax[2, 0].set_title("Deutsche Bank")

ax[2, 1].plot(concerns["Datum"], concerns["RWE"])
ax[2, 1].set_title("RWE")

plt.xlim(concerns["Datum"].min(), concerns["Datum"].max())

# Jahreszahlen
locator = mdates.YearLocator() # mdates.YearLocator() -> setzt die Tick-Positionen auf Jahresanfänge
formatter = mdates.DateFormatter("%Y") # formatiert die Labels als "2020", "2021", ...
for axes in ax.flat: # gehe über JEDES einzelne Unterdiagramm
    axes.xaxis.set_major_locator(locator) # sage der X-Achse: benutze Jahres-Ticks
    axes.xaxis.set_major_formatter(formatter) # beschrifte die Ticks nur mit der Jahreszahl
    axes.tick_params(labelbottom=True) # # zwinge, dass die Jahreszahlen auch angezeigt werden

plt.tight_layout()
plt.show()
_images/9e62817c7a5d0d2e0c6e967f90423c2513631fd45a155fe5eae05c218bca0bfb.png

Wir können auch zwei y-Achsen verwenden, wenn wir z.B. den Aktienkurs und das Handelsvolumen in einem Liniendiagramm darstellen wollen.

import numpy as np

alv_vol = pd.read_csv("./../../Data/Allianz_Vol_Price_2020.csv", sep=";", decimal=",") # Decimal="," wegen europäischer Schreibweise -> TO-Do: Pfad anpassen
print(alv_vol)
Schluss=alv_vol["Schluss"]
Volumen=alv_vol["Volumen"]

fig, ax1 = plt.subplots(figsize=(10,5))

# Datumkonvertierung
alv_vol["Datum"] = pd.to_datetime(alv_vol["Datum"], dayfirst=True, errors="coerce")

# Hier müssen jetzt die Zahlen im csv konvertiert werden
alv_vol["Schluss"] = pd.to_numeric(alv_vol["Schluss"], errors="coerce")
alv_vol["Volumen"] = pd.to_numeric(alv_vol["Volumen"], errors="coerce")

# linke y-Achse: Kurs
ax1.plot(alv_vol["Datum"], Schluss, color="b", label="Kurs")
ax1.set_xlabel("Datum")
ax1.set_ylabel("Kurs (€)", color="b")
ax1.tick_params(axis="y", labelcolor="b")

# rechte y-Achse: Volumen
ax2 = ax1.twinx()  # wir teilen ja für beide y-Achsen die x-Achse
ax2.bar(alv_vol["Datum"], Volumen, color="green", alpha=0.3, label="Volumen")
ax2.set_ylabel("Handelsvolumen", color="green")
ax2.tick_params(axis="y", labelcolor="green")

fig.suptitle("Kurschart und Handelsvolumen der Allianz-Aktie")

plt.show()
         Datum  Schluss  Volumen
0     10.09.20   183.16   717596
1     11.09.20   183.02   869256
2     14.09.20   182.58   579184
3     15.09.20   182.80   743918
4     16.09.20   181.80  1204644
...        ...      ...      ...
1270  03.09.25   353.00   495000
1271  04.09.25   354.10   341216
1272  05.09.25   351.40   398283
1273  08.09.25   352.70   274437
1274  09.09.25   353.30   269502

[1275 rows x 3 columns]
/tmp/ipykernel_3832171/2819810138.py:11: UserWarning: Could not infer format, so each element will be parsed individually, falling back to `dateutil`. To ensure parsing is consistent and as-expected, please specify a format.
  alv_vol["Datum"] = pd.to_datetime(alv_vol["Datum"], dayfirst=True, errors="coerce")
_images/d71ee3daed22c3521e9571a7dc0955f9ab20b727db9c303279d23717a14c1326.png

Eine angenehme Methode für Visualisierungen ist der Error-Bar, da dieser grafisch die Standardabweichung zu einem bestimmten (Zeit-)Punkt darstellt. Der nachfolgende Code mit ax.errorbar() demonstriert die einmal.

Für die Datumskonvertierung finden Sie unter den nachfolgenden Verlinkungen Hilfe 1, 2 und 3.

### Datum konvertieren
Kurs_ALV["Datum"] = pd.to_datetime(Kurs_ALV["Datum"], errors="coerce") # errors="coerce": ersetzt ungültige Werte durch NaT (bei Datumswerten) bzw. NaN (bei Zahlen); mithilfe von errors=coerce" gehen wir sicher, dass alle Werte in der Spalte in ein Datumformat konvertiert worden sind. Alternativ errors=ignore oder errors=raise

# Quartale bilden und aggregieren
Kurs_ALV["Quartal"] = Kurs_ALV["Datum"].dt.to_period("1Q") 
gruppen = Kurs_ALV.groupby("Quartal")["Schlusskurs"]
mittel = gruppen.mean()
std = gruppen.std()


## Erstellung error-bar
fig, ax = plt.subplots(figsize=(10,5)) # Diagramm vergrößern (Breite=10 Zoll, Höhe=5 Zoll)
ax.errorbar(
            mittel.index.astype(str), # Achtung, wir haben mit mittel.index keine Zahl o.ä. Matplotlib kann PeriodIndex nicht direkt verarbeiten, da wir Jahr+Quartal haben (Benutzerdefiniert). Wir müssen hier als den Index in einen String umwandeln.
            mittel.values, 
            yerr=std.values, 
            marker="o",
            linestyle="-",
            color="b",
            )

ax.set_title("Allianz – Schlusskurse je Quartal")
ax.set_ylabel("Kurs")
plt.tight_layout() # Passt das Layou des Diagramms automatisch an.
plt.xticks(rotation=45)
plt.show()
_images/4c6d32d66cfa3d7487d75bcc2db4405591b279217f97a1a79e8e37e94ad49d11.png

Als nächstes möchten wir einen Boxplot codieren.

Ein Boxplot ist ein Diagramm zur Darstellung der Verteilung von Daten. Er zeigt den Median, das untere (Q1) und obere Quartil (Q3), den Interquartilsabstand (IQR) sowie die Whiskers. Werte außerhalb dieses Bereichs werden als Ausreißer markiert. Mithilfe von ax.boxplot() können wir ganz einfach einen Boxplot erzeugen.

np.random.seed(0) # gleiche „zufällige“ Zahlen bei jedem Lauf
maenner = np.random.normal(loc=178, scale=7, size=100)  # Männer: Mittelwert 178 cm, Streuung 12
frauen  = np.random.normal(loc=165, scale=6, size=100)  # Frauen: Mittelwert 165 cm, Streuung 8

df = pd.DataFrame({
    "Männer": maenner,
    "Frauen": frauen
})

# Boxplot erstellen
fig, ax = plt.subplots(figsize=(8,6))
ax.boxplot([df["Männer"], df["Frauen"]],
           labels=["Männer", "Frauen"])

ax.set_title("Vergleich der Körpergrößen von Männern und Frauen")
ax.set_ylabel("Körpergröße (cm)")

plt.show()
_images/c89db65972f9ddf8afa4a7a0e96f02f9b282384fbfb9e4c184709a7a06787fbe.png

5.2. Visualisierungen mit Seaborn#

Fassen wir kurz zusammen, matplotlib.pyplot setzt seinen Schwerpunkt auf low-level plotting und volle Kontrolle, verbunden jedoch mit viel Handarbeit. Pandas hingegen ist besonders bequem, wenn man schnelle Diagramme aus DataFrames erzeugen will und bringt den Vorteil mit sich, dass wir Daten importieren können.

Seaborn legt den Fokus auf einen einfachen Syntax und anschaulichen Plot-Design. Mit diesem Package wollen wir uns nachfolgend intensiver befassen. Hier finden Sie weitere Informationen zu Seaborn.

Zu erst installieren wir das Package über unsere Konsole bzw. Terminal.

conda install -c conda-forge seaborn

In Python verwenden wir folgenden Code, um das Seaborn aktiv nutzen zu können.

import seaborn as sns

Übrigens die Abkürzung sns kommt von dem Fernsehcharakter Samuel Norman Seaborn aus der US-TV-Serie The West Wing.

5.2.1. Zähldiagramme#

Das nachstehende Codebeispiel demonstriert die Implementierung eines Zähldiagramms mithilfe von sns.countplot() zur Analyse kategorialer Merkmalsausprägungen.

import seaborn as sns

DAX_40=pd.read_csv("./../../Data/DAX40_Daten.csv", sep=";", header=1)
print(DAX_40["Industrie"])

sns.countplot(DAX_40["Industrie"])
plt.title("Anzahl der DAX-Konzerne je Industriezweig")

plt.show()
0                       Software & IT Services
1            Machinery, Equipment & Components
2                  Telecommunications Services
3                          Aerospace & Defense
4                                    Insurance
5                          Aerospace & Defense
6                             Renewable Energy
7                                    Insurance
8                             Banking Services
9                     Automobiles & Auto Parts
10             Healthcare Equipment & Supplies
11                    Automobiles & Auto Parts
12                    Automobiles & Auto Parts
13                             Pharmaceuticals
14    Investment Banking & Investment Services
15    Semiconductors & Semiconductor Equipment
16                Freight & Logistics Services
17                    Automobiles & Auto Parts
18                                   Chemicals
19                             Energy Supplier
20                            Banking Services
21                      Construction Materials
22                    Automobiles & Auto Parts
23                                   Insurance
24                          Textiles & Apparel
25                                   Chemicals
26                             Pharmaceuticals
27             Healthcare Providers & Services
28                             Energy Supplier
29                      Real Estate Operations
30                                   Chemicals
31                         Aerospace & Defense
32                    Automobiles & Auto Parts
33             Healthcare Providers & Services
34                                    Holdings
35                                   Chemicals
36                    Automobiles & Auto Parts
37            Biotechnology & Medical Research
38                                   Chemicals
39                          Textiles & Apparel
Name: Industrie, dtype: object
_images/ec03df7c7b86e567b600e514ab7aa0d91d99670e7d12af2ef7ba090b5bacb45a.png

5.2.2. Histograme#

Ein Histogramm mit überlagerter Dichtefunktion bietet eine anschauliche Möglichkeit, die Verteilung der Messwerte zu untersuchen. Dazu nutzen wir sns.histplot().

sns.histplot(alv_vol["Volumen"], bins=50) 
plt.xlabel("Handelsvolumen")
plt.ylabel("Häufigkeit")
plt.title("Histogramm des Handelsvolumens der Allianz-Aktie")

plt.show()
_images/a0b153df08ef52667e4f32d7899a23576ee3cd1e576530f6af4cd7651dbb6ddc.png

Wenn wir nur die Dichtefunktion uns ausgeben wollen, dann können wir das mithilfe von sns.displot ganz einfach erzeugen.

sns.displot(Kurs_ALV["Schlusskurs"], kind="kde", fill=True)
/HOME1/users/personal/dana/miniconda3/envs/finance/lib/python3.11/site-packages/seaborn/axisgrid.py:123: UserWarning: The figure layout has changed to tight
  self._figure.tight_layout(*args, **kwargs)
<seaborn.axisgrid.FacetGrid at 0x7fdcd46aa390>
_images/c6a6156b42936930c4f7928226d780bc4995c7bdb6ef99d90b62e8ba5b80a71a.png

Der nachfolgende Code zeigt uns, wie wir die Verteilungsfunktion ausgeben lassen.

sns.displot(Kurs_ALV["Schlusskurs"], kind="ecdf")
/HOME1/users/personal/dana/miniconda3/envs/finance/lib/python3.11/site-packages/seaborn/axisgrid.py:123: UserWarning: The figure layout has changed to tight
  self._figure.tight_layout(*args, **kwargs)
<seaborn.axisgrid.FacetGrid at 0x7fdc9265d650>
_images/144144a5a470cafa850b15af6d8d8339b4c1a5a34e09c77ea7f9fb4ff1ea6db3.png

5.2.3. Liniendiagramm#

Im nächsten Programm sehen wir, wie mit seaborn und sns.lineplot ein einfaches Liniendiagramm erstellt werden kann.

print(concerns)

plt.figure()
sns.lineplot(data=concerns, x="Datum", y="BASF", color="green")

plt.title("Aktienkurs BASF")
plt.xlabel("Datum")
plt.ylabel("Preis (€)")

plt.tight_layout()
plt.show()
          Datum  Allianz  Siemens  Telekom   BASF  Deutsche Bank    RWE
0    2020-09-10   183.16   117.62   15.355  54.51          7.995  32.16
1    2020-09-11   183.02   117.16   15.300  54.87          7.812  32.00
2    2020-09-14   182.58   116.84   15.245  55.23          7.901  31.49
3    2020-09-15   182.80   116.90   15.250  54.91          7.712  31.43
4    2020-09-16   181.80   118.68   15.230  55.06          7.866  31.56
...         ...      ...      ...      ...    ...            ...    ...
1270 2025-09-03   353.00   229.45   31.110  44.63         29.800  33.98
1271 2025-09-04   354.10   230.05   31.840  44.00         30.260  34.66
1272 2025-09-05   351.40   226.00   31.650  43.81         29.870  35.02
1273 2025-09-08   352.70   230.65   30.450  44.18         30.030  35.82
1274 2025-09-09   353.30   228.50   30.610  43.51         30.535  35.71

[1275 rows x 7 columns]
_images/84162c509632ddbc0f4147c46b3696cde86eacde686cd300d84361882ce41518.png

Seaborn basiert auf der Philosophie von tidy-Format, das heißt: Jede Beobachtung = 1 Zeile, jede Variable = 1 Spalte. Wir müssen somit unseren Dataframe in ein Long-Format bringen. Dafür verwenden wir den .melt() Befehl. Einige Informationen dazu finden Sie hier. Nach dem .melt() Befehl sieht unser Dataframe in etwa wie folgt aus:

Datum

Aktie

Preis

01.01.2020

Allianz

200

02.01.2020

Allianz

201

01.01.2020

Siemens

120

02.01.2020

Siemens

121

01.01.2020

Telekom

15

02.01.2020

Telekom

16

01.01.2025

RWE

35

Mithilfe von sns.relplot() geben wir uns Unterdiagramme aus.

cols = ["Allianz", "Siemens", "Telekom", "BASF", "Deutsche Bank", "RWE"]
concerns_long = concerns[["Datum"] + cols].melt(
    id_vars="Datum",   # Spalte, die fix bleibt
    var_name="Aktie",  # neue Spalte für die alten Spaltennamen
    value_name="Preis" # neue Spalte für die Werte
)

# 3x2-Unterdiagramme (Facets)
g = sns.relplot(
    data=concerns_long,
    x="Datum", y="Preis",
    col="Aktie", col_wrap=2,
    kind="line",
    facet_kws={"sharex": True, "sharey": False}   #eigene y-Achse pro Aktie
)

g.set_titles("{col_name}")

locator = mdates.YearLocator()
formatter = mdates.DateFormatter("%Y")
for ax in g.axes.flat:
    ax.xaxis.set_major_locator(locator)
    ax.xaxis.set_major_formatter(formatter)

for ax in g.axes.flat:
    ax.tick_params(labelbottom=True)
    ax.set_xlabel("")
    
plt.xlim(concerns["Datum"].min(), concerns["Datum"].max())

g.fig.suptitle("Aktienkurse")
plt.tight_layout()
plt.show()
/HOME1/users/personal/dana/miniconda3/envs/finance/lib/python3.11/site-packages/seaborn/axisgrid.py:123: UserWarning: The figure layout has changed to tight
  self._figure.tight_layout(*args, **kwargs)
/tmp/ipykernel_3832171/4153462330.py:32: UserWarning: The figure layout has changed to tight
  plt.tight_layout()
_images/60f499a778f11faad5cf92cefb4ae26f6058efb353e006d9fbe045557842063d.png

5.2.4. Balkendiagramme#

Der nachfolgende Code stellt dar, wie man mit seaborn ein Balkendiagramm erzeugt. Dazu müssen wir matplot.ticker verwenden, um die x-Achse in einer bestimmten Schrittweise darstellen zu können. Eine Übersicht zu den Befehlen finden Sie hier.

Mit dem sns.barplot() Kommando erstellen wir das Balkendiagramm.

import matplotlib.ticker as ticker

DAX_40 = pd.read_csv(
    "./../../Data/DAX40_Daten.csv",
    sep=";",
    header=1,
    decimal=",",       # Komma als Dezimaltrennzeichen
    thousands="."      # Punkt als Tausendertrennzeichen
)

# Kürzere Namen --> Kürzel definieren
Konzern = {
    "Allianz SE": "Allianz",
    "BASF SE": "BASF",
    "Siemens Aktiengesellschaft": "Siemens",
    "Deutsche Bank Aktiengesellschaft": "Deutsche Bank",
    "Airbus SE": "Airbus",
    "Rheinmetall Aktiengesellschaft": "Rheinmetall",
    "Siemens Energy AG": "Siemens Energy",
    "Münchener Rückversicherungs-Gesellschaft Aktiengesellschaft in München": "Munich Re",
    "Bayerische Motoren Werke Aktiengesellschaft": "BMW",
    "Deutsche Telekom AG": "Deutsche Telekom",
}

DAX_40["Konzern"] = DAX_40["Unternehmen"].replace(Konzern)

# Top 10 Börsenwerte anzeigen lassen
top10 = DAX_40.sort_values("Börsenwert (Mio EUR)", ascending=False).head(10)

print(top10[["Konzern", "Börsenwert (Mio EUR)"]])

# Plot
plt.figure(figsize=(12,8))
sns.barplot(
    data=top10,
    x="Börsenwert (Mio EUR)",
    y="Konzern",
    order=top10["Konzern"],
    palette="bright",
    hue="Konzern", 
    legend=False, 
    orient="h" # Horizontales Balkendiagramm
)


# Rahmen rechts entfernen
sns.despine(left=False, right=True, bottom=False)

plt.title("Top 10 DAX-Konzerne nach Börsenwert in Mio EUR", y=1.05) # Abstand zwischen obersten Balken und Titel 
plt.xlabel("")
plt.ylabel("")

# Achsenbegrenzung --> nehme den maximalen Börsenwert und gebe einen Puffer von 10% zum Ende hinzu
max_wert = top10["Börsenwert (Mio EUR)"].max()
plt.xlim(0, max_wert * 1.2)

# X-Achse in 25000er-Schrittenon
plt.gca().xaxis.set_major_locator(ticker.MultipleLocator(25000)) #plt.gca=plot get current axis --> Zugriff direkt auf x-Achse

plt.tight_layout()
plt.show()
            Konzern  Börsenwert (Mio EUR)
0            SAP SE              252460.2
1           Siemens              173740.9
2  Deutsche Telekom              140764.9
3            Airbus              132564.5
4           Allianz              130417.2
5       Rheinmetall               72432.3
6    Siemens Energy               67525.3
7         Munich Re               66037.6
8     Deutsche Bank               54117.2
9               BMW               51217.5
_images/7514582f34b76f3401b9b907ae73d278856e1ee46591b7d730c63b3136f16c54.png

5.2.5. Regression mit Seaborn#

Mithilfe des seaborn-Packages können ganz schnell und einfach Regressionsplots erstellt werden. Mit sns.regplot() zeichnen wir einen Scatterplot zweier Variablen und passt standardmäßig eine lineare Regressionsgerade mit Konfidenzintervall an die Daten an.

sns.regplot(data=concerns, x=concerns["Allianz"], y=concerns["Deutsche Bank"], marker="+")
<Axes: xlabel='Allianz', ylabel='Deutsche Bank'>
_images/fff1b02968c7a2adc127651738fd86358ea84decd681e1c71d1f9e38f53abcfd.png

Wenn wir in sns.regplot() den Parameter x_bins verwenden, wird ein Scatterplot erstellt und die vielen Punkte durch fünf Durchschnittspunkte ersetzt werden, sowie eine lineare Regressionsgerade mit Konfidenzintervall eingezeichnet. Diesen Analyse-Trick können wir u.a. verwenden, wenn die Rohdaten als Punktwolke schwer lesbar oder wenig aussagekräftig sind.

sns.regplot(data=concerns, x=concerns["Allianz"], y=concerns["Deutsche Bank"], x_bins=5) # Die x-Achse wird in 5 Intervalle aufgeteilt, und für jedes Intervall wird ein repräsentativer Punkt gezeichnet
<Axes: xlabel='Allianz', ylabel='Deutsche Bank'>
_images/fbf9cda213baec23fe0f49efb734f5b2d24dd0b5b0b7c4124555f2e79e0db71a.png

Mit sns.residplot() erstellt mit Seaborn einen Residuenplot: Zunächst wird eine lineare Regression zwischen den Werten der x- und y-Achse berechnet. Anschließend werden die Residuen (also die Abweichungen der beobachteten Werte von den durch die Regressionsgerade vorhergesagten Werten) gegen die x-Achse (in dem Fall Allianz-Werte) aufgetragen. Das Diagramm zeigt damit, ob die lineare Regression die Daten gut beschreibt oder ob systematische Muster in den Abweichungen erkennbar sind.

sns.residplot(data=concerns, x=concerns["Allianz"], y=concerns["Deutsche Bank"])
<Axes: xlabel='Allianz', ylabel='Deutsche Bank'>
_images/e9af8015bdd9ac20775d7fb5477c5516c9a57ba43713f269412c39e1e280ad88.png

Wir können auch polynomielle Regression anzeigen lassen, in dem wir in den Parameter order nutzen und mit x_estimator=np.mean steuern, welchen Wert seaborn berechnen soll. In dem nachfolgenden Code wäre dies die der Durchschnitt.

sns.regplot(data=concerns, x=concerns["Allianz"], y=concerns["Deutsche Bank"], x_estimator=np.mean, order=2)
<Axes: xlabel='Allianz', ylabel='Deutsche Bank'>
_images/c0f6db2e192ee789f6c1aaee66f50028232a1c14e38fb34f26292352c17b8ea8.png

5.2.6. Heatmaps mit Seaborn#

Eine Heatmap ist eine Form der Datenvisualisierung, die Informationen in einer Matrix darstellt und dabei Farben verwendet, um Werte zu kodieren. Jede Zelle der Matrix repräsentiert die Kombination zweier Merkmale, beispielsweise Monate und Wochentage, und die Farbe zeigt die Ausprägung oder Intensität des zugrunde liegenden Wertes an – etwa Häufigkeiten, Durchschnittswerte oder Korrelationen. Der zentrale Nutzen einer Heatmap liegt darin, Muster, Zusammenhänge und Ausreißer schnell erkennbar zu machen, ohne dass Zahlenkolonnen gelesen werden müssen. Besonders in großen Tabellen ermöglicht die farbliche Darstellung, Trends oder Konzentrationen visuell hervorzuheben, die in reinen Zahlen leicht übersehen würden. Heatmaps werden deshalb häufig in der explorativen Datenanalyse, bei Zeitreihen (z. B. Temperaturverläufe) oder in der Korrelationsanalyse eingesetzt, da sie komplexe Strukturen auf eine intuitive und anschauliche Weise vermitteln.

Seaborn ist in der Lage mit einfacher Codierung eine Heatmap zu erstellen. Dafür muss aber im Vorfeld mithilfe von pd.crosstab() ein Grid erstellt werden. Anschließend können wir mit sns.heatmap() uns die Heatmap ausgeben lassen.

temperature=pd.read_csv("./../../Data/bike_share.csv")
print(temperature)

pd.crosstab(temperature["mnth"], temperature["weekday"], values=temperature["temp"], aggfunc="mean")
sns.heatmap(pd.crosstab(temperature["mnth"], temperature["weekday"], values=temperature["temp"], aggfunc="mean"))
         dteday  season  yr  mnth  holiday  weekday  workingday  weathersit  \
0    2011-01-01       1   0     1        0        6           0           2   
1    2011-01-02       1   0     1        0        0           0           2   
2    2011-01-03       1   0     1        0        1           1           1   
3    2011-01-04       1   0     1        0        2           1           1   
4    2011-01-05       1   0     1        0        3           1           1   
..          ...     ...  ..   ...      ...      ...         ...         ...   
726  2012-12-27       1   1    12        0        4           1           2   
727  2012-12-28       1   1    12        0        5           1           2   
728  2012-12-29       1   1    12        0        6           0           2   
729  2012-12-30       1   1    12        0        0           0           1   
730  2012-12-31       1   1    12        0        1           1           2   

         temp     atemp       hum  windspeed  casual  registered  \
0    0.344167  0.363625  0.805833   0.160446     331         654   
1    0.363478  0.353739  0.696087   0.248539     131         670   
2    0.196364  0.189405  0.437273   0.248309     120        1229   
3    0.200000  0.212122  0.590435   0.160296     108        1454   
4    0.226957  0.229270  0.436957   0.186900      82        1518   
..        ...       ...       ...        ...     ...         ...   
726  0.254167  0.226642  0.652917   0.350133     247        1867   
727  0.253333  0.255046  0.590000   0.155471     644        2451   
728  0.253333  0.242400  0.752917   0.124383     159        1182   
729  0.255833  0.231700  0.483333   0.350754     364        1432   
730  0.215833  0.223487  0.577500   0.154846     439        2290   

     total_rentals  
0              985  
1              801  
2             1349  
3             1562  
4             1600  
..             ...  
726           2114  
727           3095  
728           1341  
729           1796  
730           2729  

[731 rows x 15 columns]
<Axes: xlabel='weekday', ylabel='mnth'>
_images/2e9d9adb6777ffd8d35115557af61e0f9e3f07f61c094f9ebec1656e706e6fe5.png

Indem die Heatmap angepasst wird, kann die Lesbarkeit verbessert und die Ableitung relevanter Erkenntnisse unterstützt werden. Dies zeigt das nachfolgende Codebeispiel.

temperature_crosstab = pd.crosstab(
    index=temperature["mnth"],         
    columns=temperature["weekday"],    
    values=temperature["temp"],        
    aggfunc="mean"           
)
sns.heatmap(temperature_crosstab, annot=True, cmap="YlGnBu", cbar=True, linewidths=.5)
<Axes: xlabel='weekday', ylabel='mnth'>
_images/9c450a255631cd8a4890a152c0e31247d1dff64743a83ca179a181e760808ff5.png

Wir können auch sehr einfach eine Korrelationsmatrix mithilfe von .corr() erstellen.

cols=["temp", "casual", "hum", "windspeed", "atemp"]
sns.heatmap(temperature[cols].corr(), cmap="YlGnBu" )
<Axes: >
_images/129f71cb1992ad1062fd94c8b9e1369d199b45cb0a7efa1e213814d9e3b02ee9.png

5.3. Visualisierungen mit Plotly#

Plotly ist eine moderne Visualisierungsbibliothek für Python, die sich besonders durch ihre Interaktivität und Flexibilität auszeichnet. Im Gegensatz zu klassischen Bibliotheken wie Matplotlib oder Seaborn, die überwiegend statische Diagramme erzeugen, bietet Plotly die Möglichkeit, interaktive Grafiken zu erstellen, die sich dynamisch anpassen lassen. Das bedeutet, dass Nutzer in Diagramme hineinzoomen, Datenpunkte mit der Maus hervorheben oder zusätzliche Informationen in sogenannten Tooltips anzeigen können. Dies ist vor allem bei größeren Datensätzen hilfreich, da komplexe Strukturen und Zusammenhänge leichter erkundet werden können, ohne das Diagramm jedes Mal neu generieren zu müssen.

Ein weiterer Vorteil liegt in der Vielseitigkeit: Plotly unterstützt eine große Bandbreite an Diagrammtypen – von klassischen Balken- und Liniendiagrammen über Heatmaps und 3D-Darstellungen bis hin zu spezialisierten Visualisierungen wie Sankey-Diagrammen oder Karten. Darüber hinaus ist die Bibliothek nahtlos mit pandas-DataFrames integrierbar, was die Arbeit mit realen Datenquellen erheblich vereinfacht.

Der Nutzen von Plotly zeigt sich vor allem in interaktiven Analysen und Präsentationen. Während statische Plots für Berichte oder Publikationen oft ausreichend sind, erlaubt Plotly eine explorative Datenanalyse direkt im Notebook oder im Webbrowser. Analysten, Forschende und Entscheidungsträger können so Muster, Ausreißer und Trends schneller erkennen, indem sie direkt mit den Visualisierungen interagieren. Zusätzlich lassen sich Visualisierungen mit Plotly relativ einfach in Dash-Apps einbetten – einem Framework, das auf Plotly aufbaut und die Erstellung kompletter, interaktiver Datenanwendungen ermöglicht.

Insgesamt bietet Plotly eine sehr anwenderfreundliche Kombination aus ansprechender Darstellung, Interaktivität und breitem Funktionsspektrum. Dadurch eignet es sich besonders für Szenarien, in denen Daten nicht nur visualisiert, sondern auch aktiv erkundet und vermittelt werden sollen – sei es in der wissenschaftlichen Analyse, im Business-Reporting oder in datengetriebenen Webanwendungen. Nähere Informationen zu Plotly finden Sie unter 1, 2 und 3.

Wir installieren Plotly in unserer Umgebung mit folgendem Befehl:

conda install -c conda-forge plotly
pip install plotly

Anschließend können wir Plotly in unser Programm einbinden:

import plotly.express as px

Im nachfolgenden Codesbeispiel sehen wir, dass interaktive Interface von Plotly.

### Verbindung herstellen zwischen Plotly und Jupyter Notebook
import plotly.io as pio
import plotly.express as px
import plotly.offline as py

#pio.renderers.default = "plotly_mimetype"
#pio.renderers.default = "browser"
pio.renderers.default = "notebook"  # oder "notebook_connected" für interaktive Version
import plotly.express as px

shares = ["BASF", "adidas", "Brenntag", "Fielmann", "Puma"]
dividend = [2.30, 3.07, 1.99, 1.40, 0.13]

fig=px.bar(x=shares, y=dividend, title="Dividenden deutscher Aktiengesellschaften in EUR")

fig.show()

Mit print(fig) können wir uns den Code anzeigen lassen.

print(fig)
Figure({
    'data': [{'hovertemplate': 'x=%{x}<br>y=%{y}<extra></extra>',
              'legendgroup': '',
              'marker': {'color': '#636efa', 'pattern': {'shape': ''}},
              'name': '',
              'orientation': 'v',
              'showlegend': False,
              'textposition': 'auto',
              'type': 'bar',
              'x': array(['BASF', 'adidas', 'Brenntag', 'Fielmann', 'Puma'], dtype=object),
              'xaxis': 'x',
              'y': {'bdata': 'ZmZmZmZmAkCPwvUoXI8IQNejcD0K1/8/ZmZmZmZm9j+kcD0K16PAPw==', 'dtype': 'f8'},
              'yaxis': 'y'}],
    'layout': {'barmode': 'relative',
               'legend': {'tracegroupgap': 0},
               'template': '...',
               'title': {'text': 'Dividenden deutscher Aktiengesellschaften in EUR'},
               'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title': {'text': 'x'}},
               'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'y'}}}
})

5.3.1. Zähldiagramm#

Auch, wenn es auf den aller ersten Blick iritierend ist, der Befehl px.histogram() gibt uns ein Zähldiagramm aus, wie der nachfolgende Code demonstriert.

print(DAX_40.head())
fig=px.histogram(data_frame=DAX_40, x=DAX_40["Industrie"], nbins=10, title="Anzahl der DAX-Konzerne je Industriezweig", color="Industrie")

# Legende soll horizontal unter dem Diagramm sein
fig.update_layout(
        width=1500,  # mehr Breite
        height=700,
        showlegend=False
)

fig.show()
                  Unternehmen                          Industrie    KGV  \
0                      SAP SE             Software & IT Services  88,20   
1  Siemens Aktiengesellschaft  Machinery, Equipment & Components  17,23   
2         Deutsche Telekom AG        Telecommunications Services  12,73   
3                   Airbus SE                Aerospace & Defense  28,89   
4                  Allianz SE                          Insurance  11,74   

     Kurs  Börsenwert (Mio EUR)           Konzern  
0  231.95              252460.2            SAP SE  
1  238.15              173740.9           Siemens  
2    6.60              140764.9  Deutsche Telekom  
3  179.50              132564.5            Airbus  
4   35.80              130417.2           Allianz  

5.3.2. Balkendiagramm#

Gerade beim Erstellen des Balkendiagrammes mit px.bar() sehen wir zum erstenmal deutlich den Vorteil von Plotly und dessen interaktiven Interface.

fig=px.bar(data_frame=DAX_40, 
           x=DAX_40["Industrie"], 
           y="Börsenwert (Mio EUR)", 
           color="Unternehmen", 
           hover_name="Börsenwert (Mio EUR)", 
           hover_data={"Börsenwert (Mio EUR)": ":,.0f"})

# Legende soll horizontal unter dem Diagramm sein
fig.update_layout(
        width=1500,  # mehr Breite
        height=700,
    legend=dict(
        orientation="h",
        yanchor="bottom",
        y=-2.0,
        xanchor="center",
        x=0.5
    ))

fig.show()

5.3.3. Streudiagramme#

Streudiagramme lassen sich mithilfe von px.scatter() erzeugen.

fig=px.scatter(data_frame=alv_vol, x=alv_vol["Volumen"], y=alv_vol["Schluss"])
fig.show()

5.3.4. Liniendiagramm#

Mithilfe von px.line können wir ein Liniendiagramm uns ausgeben lassen und durch das interaktive Interface ist es möglich den gesamten Kursverlauf abzulesen.

print(concerns)
fig=px.line(data_frame=concerns, x=concerns["Datum"], y=concerns["Deutsche Bank"])
fig.show()
          Datum  Allianz  Siemens  Telekom   BASF  Deutsche Bank    RWE
0    2020-09-10   183.16   117.62   15.355  54.51          7.995  32.16
1    2020-09-11   183.02   117.16   15.300  54.87          7.812  32.00
2    2020-09-14   182.58   116.84   15.245  55.23          7.901  31.49
3    2020-09-15   182.80   116.90   15.250  54.91          7.712  31.43
4    2020-09-16   181.80   118.68   15.230  55.06          7.866  31.56
...         ...      ...      ...      ...    ...            ...    ...
1270 2025-09-03   353.00   229.45   31.110  44.63         29.800  33.98
1271 2025-09-04   354.10   230.05   31.840  44.00         30.260  34.66
1272 2025-09-05   351.40   226.00   31.650  43.81         29.870  35.02
1273 2025-09-08   352.70   230.65   30.450  44.18         30.030  35.82
1274 2025-09-09   353.30   228.50   30.610  43.51         30.535  35.71

[1275 rows x 7 columns]

Bemerkung: Das arbeiten mit plotly wird erst richtig interessant, wenn wir dash installieren und kleine Apps schreiben. Dies möchten wir an dieser Stelle nicht weiter intensiver und verweisen für Interessierte auf diese Quelle.

Eine sehr “schöne” Funktion von plotly ist das Nutzen von time buttons.

print(Kurs_ALV.tail())
fig = px.line(Kurs_ALV, x="Datum", y="Schlusskurs", title="Kursentwicklung Allianz")

date_buttons = [
    dict(count=3, step="year",  stepmode="backward", label="3Y"),
    dict(count=1, step="year",  stepmode="backward", label="1Y"),
    dict(count=6, step="month", stepmode="backward", label="6M"),
    dict(count=14, step="day",  stepmode="backward", label="14D"),
    dict(step="all", label="ALL")
]

fig.update_layout(
    xaxis=dict(
        rangeselector=dict(buttons=date_buttons),
        rangeslider=dict(visible=True),   # Baut einen Rangeslider ein
        type="date"
    )
)

fig.show()
          Datum  Schlusskurs Quartal
1270 2024-12-19   284.622162  2024Q4
1271 2024-12-20   282.417297  2024Q4
1272 2024-12-23   282.033844  2024Q4
1273 2024-12-27   282.896606  2024Q4
1274 2024-12-30   283.663513  2024Q4

5.4. Zusammenfassung der Graphik-Pakete Matplotlib, Seaborn und Plotly#

import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np

np.random.seed(42) #initialisiere Zufallsgenerator für reproduzierbare "Zufallsdaten"

Erzeugen uns Daten mit Funktionswerten von sin(x) und cos(x), zufällig gestört mit etwas normalverteilten Rauschen

# Beispieldaten erzeugen
x = np.linspace(0, 10, 50)
y1 = np.sin(x) + np.random.normal(scale=0.1, size=len(x))
y2 = np.cos(x) + np.random.normal(scale=0.1, size=len(x))

df = pd.DataFrame({
    "x": np.concatenate([x, x]),
    "y": np.concatenate([y1, y2]),
    "Function": ["sin"]*len(x) + ["cos"]*len(x)
})

df
x y Function
0 0.000000 0.049671 sin
1 0.204082 0.188842 sin
2 0.408163 0.461693 sin
3 0.612245 0.727009 sin
4 0.816327 0.705219 sin
... ... ... ...
95 9.183673 -1.117426 cos
96 9.387755 -0.969703 cos
97 9.591837 -0.959973 cos
98 9.795918 -0.931403 cos
99 10.000000 -0.862530 cos

100 rows × 3 columns

type(df)
pandas.core.frame.DataFrame

df ist ein DataFrame im Tidy-Format, siehe beispielsweise tidy-format

# --- Matplotlib ---
plt.figure(figsize=(6,4))
for func in df["Function"].unique():
    subset = df[df["Function"] == func]
    plt.plot(subset["x"], subset["y"], label=func)
plt.title("Matplotlib")
plt.xlabel("x")
plt.ylabel("y")
plt.legend()
plt.show()
_images/11ade58a64fbfcf5c8bd9fee1f0923ae1c407937c9814520a05c31ce9e2195f6.png
# --- Seaborn ---
plt.figure(figsize=(6,4))
sns.lineplot(data=df, x="x", y="y", hue="Function")
plt.title("Seaborn")
plt.show()
_images/5d5c202fc24b0407d6663ab26098f5aad4d3cefb0448d6c264610f6445b4067d.png
# --- Plotly ---
fig = px.line(df, x="x", y="y", color="Function", title="Plotly (interaktiv)")
fig.show()
fig.write_image("plotly_bsp.png")

Die Interaktion ist insbesondere in Browsern sinnvoll. Fährt man nun mit dem Mauszeiger über die Graphen, erhält man Infos über die Datenwerte angezeigt oder kann in die Graphiken hinein- oder herauszoomen. Klinckt man in die Legende, können einzelne Funktionen ausgeschaltet werden.

Achtung: Plotly nicht für pdf-Version von jupyter-notebook verfügbar – daher wird hier die interaktive Graphik in eine Bilddatei umgewandlet und als Alternative hier eingebunden: