Sous-graphiques Matplotlib — Guide complet
Créez des figures multi-panneaux avec Matplotlib : plt.subplots(), GridSpec, axes partagés, dimensionnement et exemples de sauvegarde.
Les sous-graphiques permettent de placer plusieurs graphiques dans une seule figure Matplotlib. Au lieu de créer des fenêtres séparées pour chaque tracé, on les dispose en grille — côte à côte, empilés ou dans une mise en page personnalisée — afin que le lecteur puisse comparer les données d'un coup d'œil. Ce chapitre couvre tout, d'une simple figure à deux panneaux aux mises en page de grilles flexibles avec axes partagés et cellules étendues.
Avant de commencer, installez Matplotlib si ce n'est pas encore fait :
pip install matplotlib numpySi vous découvrez la bibliothèque, lisez d'abord les chapitres Introduction à Matplotlib et Premiers pas.
La méthode la plus rapide : plt.subplots()
plt.subplots(nrows, ncols) est le point d'entrée standard. Elle retourne un objet Figure et un tableau d'objets Axes.
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()Points clés :
figsize=(width, height)définit la taille de la figure en pouces. La valeur par défaut est(6.4, 4.8), souvent trop petite pour plusieurs panneaux.plt.tight_layout()ajuste automatiquement l'espacement pour que les titres et les étiquettes ne se chevauchent pas. Appelez-la juste avantshow()ousavefig().- Quand
ncols=1etnrows=1(valeur par défaut),axesest un objetAxesunique, pas un tableau.
Créer une grille 2×2
Passez nrows et ncols pour obtenir un tableau NumPy 2D d'objets Axes. Indexez-le avec [row, col], les deux indices étant à base zéro.
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() ajoute un titre général à la figure au-dessus de tous les sous-graphiques. Le décalage y=1.02 permet d'éviter le chevauchement avec les titres des panneaux de la rangée supérieure.
Itérer sur les axes
Pour une grande grille, il est plus pratique de parcourir le tableau d'axes aplati que d'indexer chaque cellule manuellement :
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 renvoie un itérateur 1D quelle que soit la forme de la grille, de sorte que la même boucle fonctionne pour une disposition 2×2, 3×3 ou tout autre format.
Partager des axes
Lorsque des panneaux représentent la même quantité (même plage sur l'axe x ou même échelle sur l'axe y), liez leurs axes pour que le panoramique ou le zoom sur l'un mette tous les autres à jour :
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 supprime les étiquettes de l'axe x dupliquées des panneaux supérieurs et maintient tous les panneaux alignés. Utilisez sharey=True de la même façon lorsque l'échelle y doit correspondre entre les colonnes.
Déballage direct des axes
Lorsque la mise en page est petite et connue à l'avance, vous pouvez déballer directement le tableau retourné dans des variables nommées :
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()C'est plus lisible que axes[0], axes[1], axes[2] pour les mises en page courtes. Pour une grille 2D à deux lignes, imbriquez le déballage : (ax1, ax2), (ax3, ax4) = axes.
Mises en page personnalisées avec GridSpec
plt.subplots() ne crée que des grilles où chaque cellule a la même taille. Pour des mises en page inégales — un panneau de vue d'ensemble large en haut avec des panneaux de détail en dessous — utilisez 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()La syntaxe de découpage gs[row, col] reprend celle de NumPy. gs[0, :] s'étend sur les trois colonnes ; gs[1, 0] prend uniquement la première cellule de la ligne 1.
Contrôler la hauteur des lignes et la largeur des colonnes
GridSpec accepte height_ratios et width_ratios pour rendre certaines lignes ou colonnes plus grandes que d'autres :
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 et wspace contrôlent l'espace vertical et horizontal entre les sous-graphiques (en fraction de la hauteur/largeur moyenne d'un sous-graphique).
Définir une taille de figure commune
Le paramètre figsize désigne toujours la figure entière, et non les sous-graphiques individuels. Une bonne règle empirique :
| Mise en page | figsize recommandé |
|---|---|
| 1 ligne × 2 cols | (10, 4) |
| 1 ligne × 3 cols | (13, 4) |
| 2 lignes × 2 cols | (10, 8) |
| 3 lignes × 3 cols | (13, 11) |
Vous pouvez toujours ajuster en fonction de vos données ; ce sont des points de départ qui laissent de la place pour les titres et les étiquettes.
Ajouter des titres et des étiquettes
Chaque objet Axes possède son propre titre et ses propres étiquettes d'axe. La figure dans son ensemble peut avoir un titre général :
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()Enregistrer une figure multi-panneaux
Appelez plt.savefig() avant plt.show() (appeler show() en premier efface la figure) :
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' recadre les espaces blancs superflus autour de la figure — utile pour intégrer l'image dans un document ou une page web.
Pièges courants
Oublier tight_layout()
Sans tight_layout(), les titres des sous-graphiques et les étiquettes d'axe des panneaux adjacents se chevauchent souvent. Appelez-la toujours avant show() ou savefig().
Dimensions d'index incorrectes
plt.subplots(2, 3) retourne un tableau 2D ; plt.subplots(1, 3) retourne un tableau 1D. Tenter d'utiliser axes[0, 0] sur un tableau 1D lève une IndexError. Utilisez soit axes[0], soit passez squeeze=False pour toujours obtenir un tableau 2D :
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(...)Coupure du suptitle
plt.suptitle() peut être coupé par tight_layout(). Corrigez cela en passant un paramètre rect à tight_layout :
plt.tight_layout(rect=[0, 0, 1, 0.95]) # leave 5% at top for suptitleOu utilisez fig.subplots_adjust(top=0.90) à la place.
Référence rapide
| Tâche | Code |
|---|---|
| Grille 1×2 | fig, (ax1, ax2) = plt.subplots(1, 2) |
| Grille 2×2 | fig, ax = plt.subplots(2, 2) |
| Accéder à une cellule | ax[row, col] |
| Itérer sur toutes les cellules | for ax in axes.flat: |
| Partager l'axe x | plt.subplots(2, 1, sharex=True) |
| Partager l'axe y | plt.subplots(1, 2, sharey=True) |
| Toujours un tableau 2D | plt.subplots(..., squeeze=False) |
| Mise en page personnalisée | gs = GridSpec(rows, cols); fig.add_subplot(gs[r, c]) |
| Étendre sur les colonnes | fig.add_subplot(gs[0, :]) |
| Ratios de hauteur | GridSpec(2, 1, height_ratios=[3, 1]) |
| Titre de la figure | plt.suptitle('Title') |
| Enregistrer la figure | plt.savefig('out.png', dpi=150, bbox_inches='tight') |
Chapitres connexes
- Introduction à Matplotlib — présentation de la bibliothèque et installation
- Premiers pas avec Matplotlib — votre premier tracé
- Tracés en ligne Matplotlib — tracer des données continues
- Étiquettes Matplotlib — étiquettes d'axe, titres et annotations
- Grille Matplotlib — ajouter et styliser les lignes de grille
- Graphiques en barres Matplotlib — comparer des catégories
- Nuages de points Matplotlib — relations entre variables
- Histogrammes Matplotlib — distributions des données