W3docs

Matplotlib Subplots — Vollständige Anleitung

Erstelle ein- und mehrpanelige Abbildungen mit Matplotlib Subplots: plt.subplots(), GridSpec, gemeinsame Achsen, Größen und Speicherbeispiele.

Subplots ermöglichen es dir, mehrere Diagramme in einer einzigen Matplotlib-Abbildung zu platzieren. Anstatt für jedes Diagramm separate Fenster zu erstellen, ordnest du sie in einem Raster an — nebeneinander, übereinander oder in einem individuellen Layout — damit der Leser Daten auf einen Blick vergleichen kann. Dieses Kapitel behandelt alles von einer einfachen zweipaneligen Abbildung bis hin zu flexiblen Rasterlayouts mit gemeinsamen Achsen und zellenübergreifenden Spans.

Bevor du beginnst, installiere Matplotlib, falls noch nicht geschehen:

pip install matplotlib numpy

Wenn du neu in der Bibliothek bist, lies zuerst die Kapitel Matplotlib Introduction und Getting Started.

Der schnellste Weg: plt.subplots()

plt.subplots(nrows, ncols) ist der standardmäßige Einstiegspunkt. Er gibt ein Figure- und ein array von Axes-Objekten zurück.

import matplotlib.pyplot as plt
import numpy as np

fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(10, 4))

x = np.linspace(0, 2 * np.pi, 200)

axes[0].plot(x, np.sin(x), color='steelblue')
axes[0].set_title('Sine')
axes[0].set_xlabel('x')
axes[0].set_ylabel('sin(x)')

axes[1].plot(x, np.cos(x), color='darkorange')
axes[1].set_title('Cosine')
axes[1].set_xlabel('x')
axes[1].set_ylabel('cos(x)')

plt.tight_layout()
plt.show()

Wichtige Punkte:

  • figsize=(width, height) legt die Abbildungsgröße in Zoll fest. Der Standardwert ist (6.4, 4.8), der für mehrere Panels oft zu klein ist.
  • plt.tight_layout() passt den Abstand automatisch an, sodass sich Titel und Beschriftungen nicht überlappen. Rufe es direkt vor show() oder savefig() auf.
  • Wenn ncols=1 und nrows=1 (der Standard), ist axes ein einzelnes Axes-Objekt und kein array.

Erstellen eines 2×2-Rasters

Übergib sowohl nrows als auch ncols, um ein zweidimensionales NumPy-array von Axes zu erhalten. Indiziere es mit [row, col], wobei beide Indizes nullbasiert sind.

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 2 * np.pi, 200)

fig, ax = plt.subplots(nrows=2, ncols=2, figsize=(10, 8))

# Top-left
ax[0, 0].plot(x, np.sin(x), color='steelblue')
ax[0, 0].set_title('sin(x)')

# Top-right
ax[0, 1].plot(x, np.cos(x), color='darkorange')
ax[0, 1].set_title('cos(x)')

# Bottom-left  — clamp tan to avoid ±∞ spikes
y_tan = np.clip(np.tan(x), -10, 10)
ax[1, 0].plot(x, y_tan, color='seagreen')
ax[1, 0].set_title('tan(x) [clipped]')

# Bottom-right
ax[1, 1].plot(x, np.exp(x / np.pi), color='tomato')
ax[1, 1].set_title('exp(x/π)')

plt.suptitle('Trigonometric & Exponential Functions', fontsize=14, y=1.02)
plt.tight_layout()
plt.show()

plt.suptitle() fügt einen übergeordneten Abbildungstitel über allen Subplots hinzu. Der Versatz y=1.02 schiebt ihn von den Paneltiteln der obersten Zeile weg.

Über Achsen iterieren

Bei einem großen Raster ist es einfacher, über das geglättete Achsen-array zu iterieren, als jede Zelle manuell zu indizieren:

import matplotlib.pyplot as plt
import numpy as np

functions = [np.sin, np.cos, np.tan, np.arctan]
names     = ['sin', 'cos', 'tan', 'arctan']
colors    = ['steelblue', 'darkorange', 'seagreen', 'tomato']

x = np.linspace(-np.pi, np.pi, 300)

fig, axes = plt.subplots(2, 2, figsize=(10, 8))

for ax, fn, name, color in zip(axes.flat, functions, names, colors):
    y = fn(x)
    y = np.clip(y, -5, 5)          # keep tan from dominating the scale
    ax.plot(x, y, color=color)
    ax.set_title(name)
    ax.axhline(0, color='black', linewidth=0.6, linestyle='--')
    ax.axvline(0, color='black', linewidth=0.6, linestyle='--')

plt.tight_layout()
plt.show()

axes.flat gibt einen eindimensionalen Iterator zurück, unabhängig von der Rasterform — dieselbe Schleife funktioniert für 2×2, 3×3 oder jedes andere Layout.

Achsen gemeinsam nutzen

Wenn Panels dieselbe Größe darstellen (gleicher x-Bereich oder gleiche y-Skala), verknüpfe ihre Achsen, sodass Schwenken oder Zoomen in einem Panel alle anderen aktualisiert:

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 4 * np.pi, 400)

# sharex=True links horizontal axes; sharey=True links vertical axes
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 6), sharex=True)

ax1.plot(x, np.sin(x), color='steelblue')
ax1.set_ylabel('sin(x)')
ax1.set_title('Shared x-axis example')

ax2.plot(x, np.sin(2 * x), color='darkorange')
ax2.set_ylabel('sin(2x)')
ax2.set_xlabel('x (radians)')

# Hide redundant x-tick labels on the top panel
ax1.tick_params(labelbottom=False)

plt.tight_layout()
plt.show()

sharex=True entfernt die doppelten x-Achsenbeschriftungen aus den oberen Panels und hält alle Panels ausgerichtet. Verwende sharey=True entsprechend, wenn die y-Skala spaltenübergreifend übereinstimmen muss.

Achsen direkt entpacken

Wenn das Layout klein und im Voraus bekannt ist, kannst du das zurückgegebene array direkt in benannte Variablen entpacken:

import matplotlib.pyplot as plt
import numpy as np

# 1 row, 3 columns → unpack into three named axes
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(13, 4))

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

ax1.plot(x, x ** 0.5,  color='steelblue',  label='√x')
ax1.set_title('Square Root')
ax1.legend()

ax2.plot(x, np.log1p(x), color='darkorange', label='ln(1+x)')
ax2.set_title('Natural Log')
ax2.legend()

ax3.plot(x, x,           color='seagreen',  label='x')
ax3.set_title('Linear')
ax3.legend()

plt.tight_layout()
plt.show()

Dies ist sauberer als axes[0], axes[1], axes[2] für kurze Layouts. Für ein 2-D-Raster mit zwei Zeilen verschachtle das Entpacken: (ax1, ax2), (ax3, ax4) = axes.

Individuelle Layouts mit GridSpec

plt.subplots() erstellt nur Raster, bei denen jede Zelle gleich groß ist. Für ungleiche Layouts — ein breites Übersichtspanel oben mit Detailpanels darunter — verwende matplotlib.gridspec.GridSpec:

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np

x = np.linspace(0, 2 * np.pi, 300)

fig = plt.figure(figsize=(10, 8))
gs  = gridspec.GridSpec(nrows=2, ncols=3, figure=fig)

# Top row: one wide panel spanning all three columns
ax_top = fig.add_subplot(gs[0, :])
ax_top.plot(x, np.sin(x), color='steelblue', linewidth=2)
ax_top.set_title('Overview — sin(x)')

# Bottom row: three equal panels
ax_bl = fig.add_subplot(gs[1, 0])
ax_bl.plot(x, np.sin(x),     color='steelblue')
ax_bl.set_title('sin(x)')

ax_bm = fig.add_subplot(gs[1, 1])
ax_bm.plot(x, np.cos(x),     color='darkorange')
ax_bm.set_title('cos(x)')

ax_br = fig.add_subplot(gs[1, 2])
ax_br.plot(x, np.sin(x) * np.cos(x), color='seagreen')
ax_br.set_title('sin(x)·cos(x)')

plt.tight_layout()
plt.show()

Die gs[row, col]-Slice-Syntax spiegelt NumPy-Slicing wider. gs[0, :] erstreckt sich über alle drei Spalten; gs[1, 0] nimmt nur die erste Zelle in Zeile 1.

Zeilen- und Spaltenhöhen steuern

GridSpec akzeptiert height_ratios und width_ratios, um einige Zeilen oder Spalten größer als andere zu machen:

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np

fig = plt.figure(figsize=(9, 7))
gs  = gridspec.GridSpec(
    2, 2,
    height_ratios=[3, 1],   # top row is 3× taller than bottom row
    width_ratios=[2, 1],    # left column is 2× wider than right column
    hspace=0.4,
    wspace=0.3,
)

x = np.linspace(0, 4 * np.pi, 400)

ax1 = fig.add_subplot(gs[0, 0])
ax1.plot(x, np.sin(x), color='steelblue')
ax1.set_title('Main (tall + wide)')

ax2 = fig.add_subplot(gs[0, 1])
ax2.plot(x, np.cos(x), color='darkorange')
ax2.set_title('Side (tall + narrow)')

ax3 = fig.add_subplot(gs[1, 0])
ax3.plot(x, np.sin(x) ** 2, color='seagreen')
ax3.set_title('Bottom-left (short + wide)')

ax4 = fig.add_subplot(gs[1, 1])
ax4.plot(x, np.cos(x) ** 2, color='tomato')
ax4.set_title('Bottom-right (short + narrow)')

plt.suptitle('Proportional Grid Layout', fontsize=13)
plt.show()

hspace und wspace steuern den vertikalen und horizontalen Abstand zwischen Subplots (als Bruchteil der durchschnittlichen Subplot-Höhe/-Breite).

Eine gemeinsame Abbildungsgröße festlegen

Der Parameter figsize bezieht sich immer auf die gesamte Abbildung, nicht auf einzelne Subplots. Eine gute Faustregel:

LayoutEmpfohlene figsize
1 Zeile × 2 Spalten(10, 4)
1 Zeile × 3 Spalten(13, 4)
2 Zeilen × 2 Spalten(10, 8)
3 Zeilen × 3 Spalten(13, 11)

Du kannst die Werte je nach deinen Daten anpassen; dies sind Ausgangspunkte, die Platz für Titel und Beschriftungen lassen.

Titel und Beschriftungen hinzufügen

Jedes Axes-Objekt hat seinen eigenen Titel und eigene Achsenbeschriftungen. Die gesamte Abbildung kann einen übergeordneten Titel haben:

import matplotlib.pyplot as plt
import numpy as np

fig, axes = plt.subplots(1, 2, figsize=(10, 4))
x = np.linspace(0, 5, 100)

for ax, power, color in zip(axes, [2, 3], ['steelblue', 'darkorange']):
    ax.plot(x, x ** power, color=color)
    ax.set_title(f'$x^{power}$')      # LaTeX in title
    ax.set_xlabel('x')
    ax.set_ylabel(f'$x^{power}$')
    ax.grid(True, linestyle='--', alpha=0.5)

plt.suptitle('Power Functions', fontsize=14)
plt.tight_layout()
plt.show()

Eine mehrteilige Abbildung speichern

Rufe plt.savefig() vor plt.show() auf (wenn show() zuerst aufgerufen wird, wird die Abbildung gelöscht):

import matplotlib.pyplot as plt
import numpy as np

fig, axes = plt.subplots(1, 3, figsize=(13, 4))
x = np.linspace(0, 2 * np.pi, 300)

axes[0].plot(x, np.sin(x),        color='steelblue')
axes[0].set_title('sin(x)')

axes[1].plot(x, np.cos(x),        color='darkorange')
axes[1].set_title('cos(x)')

axes[2].plot(x, np.sin(x) + np.cos(x), color='seagreen')
axes[2].set_title('sin(x) + cos(x)')

plt.tight_layout()
plt.savefig('trig_panels.png', dpi=150, bbox_inches='tight')
plt.show()

bbox_inches='tight' schneidet überschüssigen Leerraum um die Abbildung herum ab — nützlich beim Einbetten des Bildes in ein Dokument oder eine Webseite.

Häufige Fallstricke

tight_layout() vergessen

Ohne tight_layout() überlappen sich Subplot-Titel und Achsenbeschriftungen benachbarter Panels oft. Rufe es immer vor show() oder savefig() auf.

Falsche Indexdimensionen

plt.subplots(2, 3) gibt ein 2-D-array zurück; plt.subplots(1, 3) gibt ein 1-D-array zurück. Der Versuch, axes[0, 0] auf einem 1-D-array zu verwenden, löst einen IndexError aus. Verwende entweder axes[0] oder übergib squeeze=False, um immer ein 2-D-array zu erhalten:

fig, axes = plt.subplots(1, 3, squeeze=False)
# axes is now shape (1, 3) — always index with [row, col]
axes[0, 0].plot(...)
axes[0, 1].plot(...)
axes[0, 2].plot(...)

Suptitle wird abgeschnitten

plt.suptitle() kann durch tight_layout() abgeschnitten werden. Behebe dies, indem du ein rect an tight_layout übergibst:

plt.tight_layout(rect=[0, 0, 1, 0.95])   # leave 5% at top for suptitle

Oder verwende stattdessen fig.subplots_adjust(top=0.90).

Kurzübersicht

AufgabeCode
1×2-Rasterfig, (ax1, ax2) = plt.subplots(1, 2)
2×2-Rasterfig, ax = plt.subplots(2, 2)
Auf Zelle zugreifenax[row, col]
Alle Zellen durchlaufenfor ax in axes.flat:
x-Achse teilenplt.subplots(2, 1, sharex=True)
y-Achse teilenplt.subplots(1, 2, sharey=True)
Immer 2-D-arrayplt.subplots(..., squeeze=False)
Individuelles Layoutgs = GridSpec(rows, cols); fig.add_subplot(gs[r, c])
Spalten überspannenfig.add_subplot(gs[0, :])
HöhenverhältnisseGridSpec(2, 1, height_ratios=[3, 1])
Abbildungstitelplt.suptitle('Title')
Abbildung speichernplt.savefig('out.png', dpi=150, bbox_inches='tight')

Verwandte Kapitel

Was this page helpful?