3D Graphiken#
In den bisherigen Kapiteln haben Sie gelernt, wie man zweidimensionale Graphiken mit \(\texttt{plt.plot}\) erzeugt – also Kurven, bei denen zwei Vektoren die \(x\)- und \(y\)-Werte der Datenpunkte angeben. Solche Darstellungen eignen sich um Zusammenhänge zwischen zwei Größen zu visualisieren. Doch in vielen numerischen Anwendungen interessiert man sich für Funktionen, die nicht nur von einer, sondern von zwei Variablen abhängen – etwa Temperaturverteilungen auf einer Fläche, Höhenprofile eines Geländes oder Fehlerfunktionen in mehreren Dimensionen.
Für solche Szenarien benötigen wir eine Möglichkeit, Funktionen in drei Dimensionen zu visualisieren. Dabei wird üblicherweise ein Gitter von \((x,y)\)-Koordinaten erzeugt, auf dem eine dritte Größe \(z=f(x,y)\) als Höhe dargestellt wird. Diese Art der Darstellung nennt man eine Oberfläche oder im Englischen surface.
Oberflächen erzeugen#
Eine besonders einfache und intuitive Möglichkeit, eine Oberfläche zu erzeugen, bietet die Funktion \(\texttt{plt.plot_surface(X,Y,Z)}\). Dabei sind \(X\) und \(Y\) Vektoren, die die Koordinatenpunkte im \(x\)-\(y\)-Raum definieren, und \(Z\) ist eine Matrix, die die zugehörigen Höhenwerte (also die \(z\)-Koordinaten) an diesen Punkten angibt. Die Werte \(Z_{i,j}\) in \(Z\) entsprechen dabei den Höhen an den jeweiligen Positionen \((X_i,Y_j)\).
Ein Beispiel ist die Funktion \(f(x,y)=\sin(x) \cdot \cos(y)\), die eine wellenartige Oberfläche erzeugt. Sie können diese Funktion auf einem Gitter auswerten und die resultierende Matrix mit \(\texttt{plt.plot_surface}\) darstellen:
import numpy as np
import matplotlib.pyplot as plt
# Wertebereiche für x und y festlegen
x = np.linspace(- np.pi, 2 * np.pi, 100)
y = np.linspace(- np.pi, 2 * np.pi, 100)
# Ein Gitter aus x- und y-Werten erzeugen
X, Y = np.meshgrid(x, y)
# Z-Werte berechnen: Z = sin(x) * cos(y)
Z = np.sin(X) * np.cos(Y)
# Neue Abbildung und 3D-Achsen erzeugen
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# - 111 bedeutet: 1 Zeile, 1 Spalte, 1. (und einzige) Position
# - projection='3d' aktiviert die 3D-Darstellung
# Oberfläche plotten
ax.plot_surface(X, Y, Z)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z = sin(x) * cos(y)")
plt.show()
Folgendes müssen Sie beim Erstellen von Oberflächenplots beachten:
Die Vektoren \(x\) und \(y\) müssen mit \(\texttt{np.meshgrid}\) zu einem Gitter \((X, Y)\) kombiniert werden.
Die Matrix \(Z\) hat die gleiche Form wie \(X\) und \(Y\) und enthält die Höhenwerte.
Der Befehl \(\texttt{plot_surface(X, Y, Z)}\) wird für die Darstellung verwendet.
Vor dem Plotten muss eine neue Figur mit 3D-Achsen erstellt werden, dabei erzeugt \(\texttt{fig = plt.figure()}\) eine neue leere Grafik und \(\texttt{fig.add_subplot(111, projection='3d')}\) fügt ein 3D-Achsensystem hinzu.
Um 3D-Oberflächenplots besser zu verstehen, verwenden wir in diesem Abschnitt ein Beispiel mit (leider fiktiven) Messdaten, der die wöchentlichen Umsätze (in Euro) des Café Soleil, aufgeteilt nach Wochentagen und Tageszeiten, enthält.
Das Café Soleil hat an jedem Tag fünf feste Zeitfenster: 8:00 – 10:00 Uhr, 10:00 – 12:00 Uhr, 12:00 – 14:00 Uhr, 14:00 – 15:30 Uhr und 15:30 – 17:30 Uhr. Am Samstag und Sonntag bleibt das Café geschlossen. Die Umsatzdaten des Café Soleil für die einzelnen Tage und Zeitfenster lassen sich wie folgt zusammenfassen:
Tag |
8:00 - 10:00 |
10:00 - 12:00 |
12:00 - 14:00 |
14:00 - 15:30 |
15:30 - 17:30 |
|---|---|---|---|---|---|
Montag |
120 |
180 |
250 |
200 |
160 |
Dienstag |
100 |
150 |
300 |
230 |
180 |
Mittwoch |
90 |
170 |
280 |
220 |
190 |
Donnerstag |
130 |
160 |
270 |
210 |
170 |
Freitag |
140 |
190 |
310 |
260 |
0 |
Um diese Daten in Python zu analysieren und zu visualisieren, speichern wir sie in einem NumPy Array namens \(\texttt{revenue}\). Der zugehörige Code lautet:
revenue = np.array([
# 8-10 10-12 12-14 14-15:30 15:30-17:30
[120, 180, 250, 200, 160], # Montag
[100, 150, 300, 230, 180], # Dienstag
[ 90, 170, 280, 220, 190], # Mittwoch
[130, 160, 270, 210, 170], # Donnerstag
[140, 190, 310, 260, 0], # Freitag
])
Aufgabe 1.1
Erstellen Sie ein 3D-Surface-Plot, der die Umsätze des Café Soleil in Abhängigkeit der Wochentage (Zeilen) und der verschiedenen Zeitfenster (Spalten) visualisiert.
# Achsen vorbereiten
days = np.arange(revenue.shape[0])
times = np.arange(revenue.shape[1])
Days, Times = np.meshgrid(times, days)
Hinweis
Um eine 3D-Oberfläche zu erzeugen, benötigen Sie zunächst ein Gitter aus Koordinatenpunkten. Verwenden Sie \(\texttt{np.meshgrid}\), um aus einem Tages- und Zeitvektor ein zweidimensionales Gitter zu erstellen. Die Umsatzmatrix \(\texttt{revenue}\) wird dabei als Höhenmatrix \(Z\) interpretiert. Erzeugen Sie anschließend mit \(\texttt{plt.plot_surface}\) die 3D Oberfläche.
Lösung
# Achsen vorbereiten
days = np.arange(revenue.shape[0])
times = np.arange(revenue.shape[1])
Days, Times = np.meshgrid(times, days)
# Surface Plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(Days, Times, revenue)
ax.set_xlabel("Tageszeit")
ax.set_ylabel("Wochentag")
ax.set_zlabel("Einnahmen")
plt.show()
Wenn Sie bei einem Plot statt numerischer Werte aussagekräftige Beschriftungen wie zum Beipsiel Wochentage oder Uhrzeiten verwenden möchten, können Sie die Achsenticks und die zugehörigen Labels manuell setzen.
Dazu stehen die Methoden \(\texttt{set_xticks()}\) und \(\texttt{set_xticklabels()}\), und analog \(\texttt{set_yticks()}\) und \(\texttt{set_yticklabels()}\), zur Verfügung:
ax.set_xticks([0, 1, 2, 3]) # Positionen auf der x-Achse
ax.set_xticklabels(["Beschriftung 1",
"Beschriftung 2",
"Beschriftung 3",
"Beschriftung 4"]) # Beschriftung dieser Positionen
Diese Befehle geben an:
wo sich die Ticks befinden sollen,
wie diese beschriftet werden.
Das ist besonders hilfreich bei 3D-Oberflächenplots, wenn Sie die Achsen nicht nur mit Zahlen, sondern mit realitätsnahen Kategorien beschriften möchten.
Aufgabe 1.2
Passen Sie den Plot aus Aufgabe 1.1 so an, dass an den Achsen nicht nur Zahlen, sondern die tatsächlichen Bezeichnungen für Wochentage und Tageszeiten erscheinen.
# Ihr Code
Hinweis
Verwenden Sie \(\texttt{set_xticklabels()}\) und \(\texttt{set_yticklabels()}\), um die Ticks passend zu beschriften.
Lösung
# Wochentage und Zeitfenster
wochentage = ["Mo", "Di", "Mi", "Do", "Fr"]
zeitfenster = ["8–10", "10–12", "12–14", "14–15:30", "15:30–17:30"]
# Gitter erzeugen
x = np.arange(revenue.shape[1]) # Zeitfenster
y = np.arange(revenue.shape[0]) # Wochentage
X, Y = np.meshgrid(x, y)
# Surface Plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, revenue)
# Achsenbeschriftung anpassen
ax.set_xticks(x)
ax.set_xticklabels(zeitfenster)
ax.set_yticks(y)
ax.set_yticklabels(wochentage)
ax.set_xlabel("Tageszeit")
ax.set_ylabel("Wochentag")
ax.set_zlabel("Einnahmen (EUR)")
plt.show()
Auch bei 3D-Oberflächenplots lassen sich zahlreiche Anpassungen vornehmen. Dazu gehören unter anderem die Farbgebung, Transparenz oder die Dichte des Gitters. Die folgende Tabelle fasst die wichtigsten Parameter zusammen, mit denen sich das Aussehen von Oberflächenplots über \(\texttt{plt.plot_surface()}\) gezielt steuern lässt:
Eigenschaft |
Beschreibung |
Beispiel |
|---|---|---|
\(\texttt{cmap}\) |
Farbschema der Oberfläche |
\(\texttt{cmap='viridis'}\) |
\(\texttt{rstride}\) / \(\texttt{cstride}\) |
Schrittweite in Zeilen- bzw. Spaltenrichtung (Rasterdichte) |
\(\texttt{rstride=2, cstride=2}\) |
\(\texttt{linewidth}\) |
Dicke der Gitterlinien |
\(\texttt{linewidth=0.5}\) |
\(\texttt{antialiased}\) |
Kantenglättung für weichere Übergänge |
\(\texttt{antialiased=True}\) |
\(\texttt{alpha}\) |
Transparenz der Oberfläche (zwischen 0 und 1) |
\(\texttt{alpha=0.8}\) |
x = np.linspace(- np.pi, 2 * np.pi, 100)
y = np.linspace(- np.pi, 2 * np.pi, 100)
X, Y = np.meshgrid(x, y)
Z = np.sin(X) * np.cos(Y)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# Eigenschaften des 3D Plots anpassen
ax.plot_surface(X, Y, Z, cmap="coolwarm", alpha=0.9, linewidth=0, antialiased=True)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z = sin(x) * cos(y)")
plt.show()
Aufgabe 1.3
Nehmen Sie folgende Anpassungen für den bestehenden 3D-Surface-Plot vor:
Farbverlauf |
\(\texttt{"plasma"}\) |
Transparenz |
\(0.8\) |
Gitterliniendicke |
\(0.5\) |
Kantenglättung |
\(\texttt{True}\) |
wochentage = ["Mo", "Di", "Mi", "Do", "Fr"]
zeitfenster = ["8–10", "10–12", "12–14", "14–15:30", "15:30–17:30"]
x = np.arange(revenue.shape[1]) # Zeitfenster
y = np.arange(revenue.shape[0]) # Wochentage
X, Y = np.meshgrid(x, y)
# Surface Plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, revenue)
# Achsenbeschriftungen setzen
ax.set_xticks(x)
ax.set_xticklabels(zeitfenster)
ax.set_yticks(y)
ax.set_yticklabels(wochentage)
ax.set_xlabel("Tageszeit")
ax.set_ylabel("Wochentag")
ax.set_zlabel("Einnahmen (EUR)")
plt.show()
Hinweis
Nutzen Sie Parameter \(\texttt{cmap}\), \(\texttt{alpha}\), \(\texttt{linewidth}\) und \(\texttt{antialiased}\) innerhalb von \(\texttt{plt.plot_surface()}\).
Lösung
wochentage = ["Mo", "Di", "Mi", "Do", "Fr"]
zeitfenster = ["8–10", "10–12", "12–14", "14–15:30", "15:30–17:30"]
# Gitter erzeugen
x = np.arange(revenue.shape[1]) # Zeitfenster
y = np.arange(revenue.shape[0]) # Wochentage
X, Y = np.meshgrid(x, y)
# Surface Plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, revenue,
cmap="plasma", # Farbverlauf anpassen
alpha=0.8, # Transparenz einstellen
linewidth=0.5, # Gitterlinien dünner zeichnen
antialiased=True) # Kantenglättung aktivieren
# Achsenbeschriftungen setzen
ax.set_xticks(x)
ax.set_xticklabels(zeitfenster)
ax.set_yticks(y)
ax.set_yticklabels(wochentage)
ax.set_xlabel("Tageszeit")
ax.set_ylabel("Wochentag")
ax.set_zlabel("Einnahmen (EUR)")
plt.show()
Neben \(\texttt{plt.plot_surface}\) bietet Matplotlib noch weitere Möglichkeiten, 3D-Oberflächen visuell darzustellen. Eine davon ist \(\texttt{plt.plot_wireframe}\), welche nur die Gitterlinien ohne Flächenfüllung zeichnet. Die Darstellung ist nützlich, wenn man die Struktur betonen möchte. Die Funktionen lässt sich analog zu \(\texttt{plt.plot_surface}\) verwenden.
Aufgabe 1.4
Erstellen Sie ein 3D-Wireframe-Plot, der die Umsätze des Café Soleil visualisiert.
# Ihr Code
Hinweis
Nutzen Sie \(\texttt{plot_wireframe}\) statt \(\texttt{plot_surface}\).
Lösung
wochentage = ["Mo", "Di", "Mi", "Do", "Fr"]
zeitfenster = ["8–10", "10–12", "12–14", "14–15:30", "15:30–17:30"]
# Gitter erzeugen
x = np.arange(revenue.shape[1]) # Zeitfenster
y = np.arange(revenue.shape[0]) # Wochentage
X, Y = np.meshgrid(x, y)
# Surface Plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_wireframe(X, Y, revenue)
# Achsenbeschriftungen setzen
ax.set_xticks(x)
ax.set_xticklabels(zeitfenster)
ax.set_yticks(y)
ax.set_yticklabels(wochentage)
ax.set_xlabel("Tageszeit")
ax.set_ylabel("Wochentag")
ax.set_zlabel("Einnahmen (EUR)")
plt.show()
In 3D-Plots kann man die Perspektive ändern, um die räumliche Struktur der dargestellten Oberfläche gut zu erkennen. Mit der Methode \(\texttt{ax.view_init(elev, azim)}\) wird der Blickwinkel auf die Grafik gezielt eingestellt:
\(\texttt{elev}\) (engl. elevation) bestimmt die Höhe des Blicks über der \(x\)-\(y\)-Ebene (vertikaler Winkel).
\(\texttt{azim}\) (azimut) bestimmt die Drehung um die \(z\)-Achse (horizontaler Winkel).
x = np.linspace(- np.pi, 2 * np.pi, 100)
y = np.linspace(- np.pi, 2 * np.pi, 100)
X, Y = np.meshgrid(x, y)
Z = np.sin(X) * np.cos(Y)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, Z)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z = sin(x) * cos(y)")
# Perspektive anpassen
ax.view_init(elev=90, azim=50)
plt.show()
Aufgabe 1.5
Erweitern Sie den Plot so, dass die Perspektive mit einer Elevation von 30° und einem Azimut von 45° eingestellt wird.
x = np.arange(revenue.shape[1]) # Zeitfenster
y = np.arange(revenue.shape[0]) # Wochentage
X, Y = np.meshgrid(x, y)
# Surface Plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, revenue)
ax.set_xticks(x)
ax.set_xticklabels(zeitfenster)
ax.set_yticks(y)
ax.set_yticklabels(wochentage)
ax.set_xlabel("Tageszeit")
ax.set_ylabel("Wochentag")
ax.set_zlabel("Einnahmen (EUR)")
plt.show()
Hinweis
Nutzen Sie \(\texttt{ax.view_init(elev, azim)}\).
Lösung
# Gitter erzeugen
x = np.arange(revenue.shape[1]) # Zeitfenster
y = np.arange(revenue.shape[0]) # Wochentage
X, Y = np.meshgrid(x, y)
# Surface Plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, revenue)
# Achsenbeschriftungen setzen
ax.set_xticks(x)
ax.set_xticklabels(zeitfenster)
ax.set_yticks(y)
ax.set_yticklabels(wochentage)
ax.set_xlabel("Tageszeit")
ax.set_ylabel("Wochentag")
ax.set_zlabel("Einnahmen (EUR)")
# Perspektive anpassen
ax.view_init(elev=30, azim=45)
plt.show()