W3docs

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 numpy

Si 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 avant show() ou savefig().
  • Quand ncols=1 et nrows=1 (valeur par défaut), axes est un objet Axes unique, 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 pagefigsize 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 suptitle

Ou utilisez fig.subplots_adjust(top=0.90) à la place.

Référence rapide

TâcheCode
Grille 1×2fig, (ax1, ax2) = plt.subplots(1, 2)
Grille 2×2fig, ax = plt.subplots(2, 2)
Accéder à une celluleax[row, col]
Itérer sur toutes les cellulesfor ax in axes.flat:
Partager l'axe xplt.subplots(2, 1, sharex=True)
Partager l'axe yplt.subplots(1, 2, sharey=True)
Toujours un tableau 2Dplt.subplots(..., squeeze=False)
Mise en page personnaliséegs = GridSpec(rows, cols); fig.add_subplot(gs[r, c])
Étendre sur les colonnesfig.add_subplot(gs[0, :])
Ratios de hauteurGridSpec(2, 1, height_ratios=[3, 1])
Titre de la figureplt.suptitle('Title')
Enregistrer la figureplt.savefig('out.png', dpi=150, bbox_inches='tight')

Chapitres connexes

Was this page helpful?