Matplotlib-Tutorial: Konturflächen und -linien

multiple plots WEBOFF


Eine Konturlinie oder Isolinie einer Funktion aus zwei Variablen ist eine Kurve entlang des konstanten Wertes der Funktion. Es ist ein Querschnitt des dreidimensionalen Graphen der Funktion f(x,y) parallel zur x,y-Ebene.

Konturlinien werden bspw. in der Geographie oder Meteorologie benutzt. In der Kartographie verbindet eine Konturlinie die Punkte gleicher Höhe über einem bestimmten Level, wie z.B. der mittlere Meeresspiegel

Allgemeiner können wir also sagen, dass eine Konturlinie einer Funktion mit zwei Variablen eine Kurve ist, die Punkte mit gleichen Werten verbindet.



Erstellen eines Maschengitters (meshgrid)



Ein Maschengitter (Meshgrid) ist rechteckiges Gitter (Datengitter), was aus zwei eindimensionalen Arrays erzeugt wird, d.h. den x-Werten und den y-Werten. Im weiteren Verlauf dieses Kapitels werden wir nochmals auf die Funktion meshgrid und zu ihren Alternativen zurück kommen.

subplot layout WEBOFF

import numpy as np
xlist = np.linspace(-3.0, 3.0, 3)
ylist = np.linspace(-3.0, 3.0, 4)
X, Y = np.meshgrid(xlist, ylist)
print(xlist)
print(ylist)
print(X)
print(Y)
[-3.  0.  3.]
[-3. -1.  1.  3.]
[[-3.  0.  3.]
 [-3.  0.  3.]
 [-3.  0.  3.]
 [-3.  0.  3.]]
[[-3. -3. -3.]
 [-1. -1. -1.]
 [ 1.  1.  1.]
 [ 3.  3.  3.]]



Berechnung der Werte



Nun berechnen wir die Funktionswerte zu den Wertepaaren des Maschengitters:

Calculation of the Contour Values WEBOFF

import numpy as np
xlist = np.linspace(-3.0, 3.0, 3)
ylist = np.linspace(-3.0, 3.0, 4)
X, Y = np.meshgrid(xlist, ylist)
Z = np.sqrt(X**2 + Y**2)
print(Z)
[[4.24264069 3.         4.24264069]
 [3.16227766 1.         3.16227766]
 [3.16227766 1.         3.16227766]
 [4.24264069 3.         4.24264069]]

Aus den Daten erzeugen wir nun den Konturplot:

import matplotlib.pyplot as plt
plt.figure()
cp = plt.contour(X, Y, Z)
plt.clabel(cp, inline=True, 
          fontsize=10)
plt.title('Konturplot')
plt.xlabel('x (cm)')
plt.ylabel('y (cm)')
plt.show()
<Figure size 640x480 with 1 Axes>

Unser Konturplot sieht sehr kantig aus, weil unser Maschengitter nur aus 12 Punkten besteht. Im folgenden verfeinern wir unser Maschengitter:

import numpy as np
import matplotlib.pyplot as plt
xlist = np.linspace(-3.0, 3.0, 100)
ylist = np.linspace(-3.0, 3.0, 100)
X, Y = np.meshgrid(xlist, ylist)
Z = np.sqrt(X**2 + Y**2)
plt.figure()
cp = plt.contour(X, Y, Z)
plt.clabel(cp, inline=True, 
          fontsize=10)
plt.title('Konturplot')
plt.xlabel('x (cm)')
plt.ylabel('y (cm)')
plt.show()



Anpassen der Farben und des Linien-Stils

Bisher hatten wir den Linienstil automatisch von Matplotlib bestimmen lassen, ebenso wir die Einfärbung. Mit den Parametern linestyles und colors können wir diese individuell einstellen.

import matplotlib.pyplot as plt
plt.figure()
cp = plt.contour(X, Y, Z, colors='black', linestyles='dashed')
plt.clabel(cp, inline=True, 
          fontsize=10)
plt.title('Konturplot')
plt.xlabel('x (cm)')
plt.ylabel('y (cm)')
plt.show()



Gefüllte Konturen

Wir können auch den Zwischenraum zwischen den Konturlinien einfärben:

import numpy as np
import matplotlib.pyplot as plt
xlist = np.linspace(-3.0, 3.0, 100)
ylist = np.linspace(-3.0, 3.0, 100)
X, Y = np.meshgrid(xlist, ylist)
Z = np.sqrt(X**2 + Y**2)
plt.figure()
cp = plt.contourf(X, Y, Z)
plt.colorbar(cp)
plt.title('Gefüllter Konturplot')
plt.xlabel('x (cm)')
plt.ylabel('y (cm)')
plt.show()



Individuelle Farben

Die Farben für die Flächen können wir natürlich auch selbst bestimmen, wie wir im folgenden Beispiel sehen:

import numpy as np
import matplotlib.pyplot as plt
xlist = np.linspace(-3.0, 3.0, 100)
ylist = np.linspace(-3.0, 3.0, 100)
X, Y = np.meshgrid(xlist, ylist)
Z = np.sqrt(X**2 + Y**2)
plt.figure()
contour = plt.contourf(X, Y, Z)
plt.clabel(contour, colors = 'k', fmt = '%2.1f', fontsize=12)
c = ('#ff0000', '#ffff00', '#0000FF', '0.6', 'c', 'm')
contour_filled = plt.contourf(X, Y, Z, colors=c)
plt.colorbar(contour)
plt.title('Gefüllter Konturplot')
plt.xlabel('x (cm)')
plt.ylabel('y (cm)')
plt.show()



Schwellen

Die Schwellen für die Konturlinien und die Flächen werden automatisch durch contour und contourf gesetzt. Diese können auch manuell definiert werden, indem als viertes Argument eine Liste mit Levels übergeben wird. Konturlinien werden für jeden Wert in der Liste gezeichnet, wenn wir contour benutzen. Wenn contourf benutzt wird, so werden die Zwischenräume zwischen den Werten der Liste gefüllt.

import numpy as np
import matplotlib.pyplot as plt
xlist = np.linspace(-3.0, 3.0, 100)
ylist = np.linspace(-3.0, 3.0, 100)
X, Y = np.meshgrid(xlist, ylist)
Z = np.sqrt(X ** 2 + Y ** 2 )
plt.figure()
levels = [0.0, 0.2, 0.5, 0.9, 1.5, 2.5, 3.5]
contour = plt.contour(X, Y, Z, levels, colors='k')
plt.clabel(contour, colors = 'k', fmt = '%2.1f', fontsize=12)
contour_filled = plt.contourf(X, Y, Z, levels)
plt.colorbar(contour_filled)
plt.title('Plot mit individuellen Schwellen')
plt.xlabel('x (cm)')
plt.ylabel('y (cm)')
plt.show()



Andere Grids

Wir hatten bereits die Numpy-Funktion meshgrid kennengelernt. Numpy enthält aber noch zwei weitere wichtige Funktionen zur Erzeugung von Gitterähnlichen Strukturen:

Meshgrid genauer

Die Aufgabe von meshgrid besteht darin, wie wir gesehen hatten, aus zwei eindimensionalen Koordinatenvektoren eine zweidimensionale Koordinatenmatrix zu erzeugen. Im allgemeinen Fall kann man aus n eindimensionalen Array-ähnlichen Strukturen ein n-dimensionales Array zur vektorisierten Auswertung von n-dimensionalen Vektorfeldern über einem n-Gitter erzeugen.

Man könnte eine Gitterstruktur (englisch "grid") auch ohne meshgrid erzeugen. Im folgenden Beispiel erzeugen wir ein Gitter G mit den Werten 0, 1, 2 als x- und als y-Werte:

n = 3
X, Y = np.zeros((n, n), np.int8), np.zeros((n, n), np.int8)
for row in range(0, n):
    for col in range(0, n):
        X[row, col] = col
        Y[row, col] = row
        
print(X)
print(Y)
[[0 1 2]
 [0 1 2]
 [0 1 2]]
[[0 0 0]
 [1 1 1]
 [2 2 2]]

Das $(3, 3)$-Gitter entspricht den Paarungen der entsprechenden Komponenten aus den Arrays $X$ und $Y$, also $X[i, j]$ gepaart mit $Y[i, j]$ mit $0 <= i <= 2$ und $0 <= j <= 2$. Mit einem Plot können wir dieses Gitter sichtbar machen:

import matplotlib.pyplot as plt
import numpy as np
plt.plot(X, Y, marker='o', color='k', linestyle='none')
plt.xticks(range(-1, n+1))
plt.yticks(range(-1, n+1))
plt.show()

Dies war der umständliche direkte Weg und mit meshgrid geht es deutlich schneller und leichter:

import matplotlib.pyplot as plt
import numpy as np
n = 3
x_values = np.arange(0, n)
y_values = np.arange(0, n)
X, Y = np.meshgrid(x_values, y_values)
plt.plot(X, Y, marker='o', color='k', linestyle='none')
plt.xticks(range(-1, n+1))
plt.yticks(range(-1, n+1))
plt.show()

Im gewissen Sinne ist meshgrid überflüssig, da man das gleiche Resultat auch mittels Broadcasting erreichen kann:

import matplotlib.pyplot as plt
import numpy as np
n = 3
x_values = np.arange(0, n)
y_values = np.arange(0, n)
# meshgrid mit broadcasting:
X = np.ones((n, 1)) * x_values
Y = y_values.reshape((n, 1)) * np.ones((1, n))
plt.plot(X, Y, marker='o', color='k', linestyle='none')
plt.xticks(range(-1, n+1))
plt.yticks(range(-1, n+1))
plt.show()

Wir hatten mit meshgrid eine quadratische Gitterstruktur erzeugt. Selbstverständlich können wir auch beliebige rechteckige Strukturen erzeugen:

import matplotlib.pyplot as plt
import numpy as np
n, m = 9, 16
x_values = np.arange(0, n)
y_values = np.arange(0, m)
X, Y = np.meshgrid(x_values, y_values)
plt.plot(X, Y, marker='o', color='k', linestyle='none')
plt.xticks(range(-1, n+1))
plt.yticks(range(-1, m+1))
plt.show()

mgrid

mgrid benötigt keine Array-ähnlichen Eingabevektoren, sondern wird mit Indices indiziert. Deshalb verwenden wir hier auch eckige Klammern, da es sich nicht um einen Funktionsaufruf handelt. mgrid und meshgrid liefern prinzipiell das selbe Ergebnis, allerdings sind die Achsen vertauscht.

import numpy as np
n = 3
X_mgrid, Y_mgrid = np.mgrid[0:n, 0:n]
n = 3
X_meshgrid, Y_meshgrid = np.meshgrid(np.arange(0, n), 
                                     np.arange(0, n))
print(X_mgrid == Y_meshgrid)
print(Y_mgrid == X_meshgrid)
[[ True  True  True]
 [ True  True  True]
 [ True  True  True]]
[[ True  True  True]
 [ True  True  True]
 [ True  True  True]]

ogrid

Wir haben sowohl bei meshgrid als auch bei mgrid gesehen, dass sich die Werte der beiden erzeugten Matrizen jeweils zeilen- bzw. spaltenweise wiederholen. ogrid liefert nun jeweils nur einen Zeilen- und einen Spaltenvektor zurück. Dadurch erhalten wir eine speicherschonende Repräsentierung der Werte. Mittels Broadcasting können sich dann andere Funktionen, die diese Matrizen benötigen, sich diese implizit erzeugen.

import numpy as np
n = 5
X_ogrid, Y_ogrid = np.ogrid[0:n, 0:n]
print(X_ogrid)
print(Y_ogrid)
[[0]
 [1]
 [2]
 [3]
 [4]]
[[0 1 2 3 4]]
Z = np.sqrt(X**2 + Y**2)
plt.figure()
cp = plt.contour(X, Y, Z)

# progr4book
import numpy as np
import matplotlib.pyplot as plt
def sin2d(x, y):
    return np.sin(x**3) + np.cos(y**2)
X, Y = np.meshgrid(np.linspace(0, 5*np.pi, 200), 
                   np.linspace(0, 5*np.pi, 200))
Z = sin2d(X, Y) 
plt.imshow(Z, origin='lower')
plt.show()