Subplot con Matplotlib — Guida Completa
Crea figure con più pannelli usando Matplotlib: plt.subplots(), GridSpec, assi condivisi, dimensioni e salvataggio con esempi.
I subplot consentono di inserire più grafici all'interno di una singola figura Matplotlib. Invece di creare finestre separate per ogni grafico, li si dispone in una griglia — affiancati, sovrapposti o in un layout personalizzato — così il lettore può confrontare i dati a colpo d'occhio. Questo capitolo tratta tutto, da una semplice figura a due pannelli a layout a griglia flessibili con assi condivisi e celle che si estendono su più colonne.
Prima di iniziare, installa Matplotlib se non lo hai già fatto:
pip install matplotlib numpySe sei nuovo alla libreria, leggi prima i capitoli Introduzione a Matplotlib e Per iniziare.
Il modo più rapido: plt.subplots()
plt.subplots(nrows, ncols) è il punto di ingresso standard. Restituisce un oggetto Figure e un array di oggetti 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()Punti chiave:
figsize=(width, height)imposta le dimensioni della figura in pollici. Il valore predefinito è(6.4, 4.8), che spesso è troppo piccolo per più pannelli.plt.tight_layout()regola automaticamente la spaziatura in modo che titoli ed etichette non si sovrappongano. Chiamarlo subito prima dishow()osavefig().- Quando
ncols=1enrows=1(il valore predefinito),axesè un singolo oggettoAxes, non un array.
Creare una griglia 2×2
Passa sia nrows che ncols per ottenere un array NumPy 2-D di Axes. Indicizzalo con [row, col], dove entrambi gli indici partono da zero.
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() aggiunge un titolo generale alla figura, sopra tutti i subplot. Il parametro y=1.02 lo sposta verso l'alto per evitare la sovrapposizione con i titoli dei pannelli della riga superiore.
Iterare sugli assi
Per una griglia estesa è più comodo scorrere l'array di assi appiattito che indicizzare ogni cella manualmente:
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 restituisce un iteratore 1-D indipendentemente dalla forma della griglia, quindi lo stesso ciclo funziona per una griglia 2×2, 3×3 o qualsiasi altro layout.
Condividere gli assi
Quando i pannelli rappresentano la stessa quantità (stesso intervallo x o stessa scala y), collega i loro assi in modo che il panoramico o lo zoom su uno aggiorni tutti:
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 rimuove le etichette duplicate dell'asse x dai pannelli superiori e mantiene tutti i pannelli allineati. Usa sharey=True in modo analogo quando la scala y deve coincidere tra le colonne.
Spacchettare gli assi direttamente
Quando il layout è piccolo e noto in anticipo, è possibile spacchettare l'array restituito direttamente in variabili con nome:
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()Questo approccio è più pulito di axes[0], axes[1], axes[2] per layout brevi. Per una griglia 2-D con due righe, annida lo spacchettamento: (ax1, ax2), (ax3, ax4) = axes.
Layout personalizzati con GridSpec
plt.subplots() crea solo griglie in cui ogni cella ha le stesse dimensioni. Per layout non uniformi — un ampio pannello panoramico in alto con pannelli di dettaglio in basso — usa 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 sintassi di slice gs[row, col] rispecchia l'indicizzazione di NumPy. gs[0, :] si estende su tutte e tre le colonne; gs[1, 0] occupa solo la prima cella della riga 1.
Controllare le altezze di righe e colonne
GridSpec accetta height_ratios e width_ratios per rendere alcune righe o colonne più grandi delle altre:
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 e wspace controllano lo spazio verticale e orizzontale tra i subplot (come frazione dell'altezza/larghezza media dei subplot).
Impostare le dimensioni comuni della figura
Il parametro figsize fa sempre riferimento all'intera figura, non ai singoli subplot. Una buona regola generale:
| Layout | figsize consigliato |
|---|---|
| 1 riga × 2 col | (10, 4) |
| 1 riga × 3 col | (13, 4) |
| 2 righe × 2 col | (10, 8) |
| 3 righe × 3 col | (13, 11) |
È sempre possibile regolare in base ai propri dati; questi sono punti di partenza che lasciano spazio a titoli ed etichette.
Aggiungere titoli ed etichette
Ogni oggetto Axes ha il proprio titolo e le proprie etichette degli assi. La figura nel suo insieme può avere un supertitolo:
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()Salvare una figura a più pannelli
Chiama plt.savefig() prima di plt.show() (chiamare show() prima cancella la figura):
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' ritaglia lo spazio bianco in eccesso attorno alla figura — utile quando si incorpora l'immagine in un documento o in una pagina web.
Errori comuni
Dimenticare tight_layout()
Senza tight_layout(), i titoli dei subplot e le etichette degli assi dei pannelli adiacenti spesso si sovrappongono. Chiamarlo sempre prima di show() o savefig().
Dimensioni degli indici errate
plt.subplots(2, 3) restituisce un array 2-D; plt.subplots(1, 3) restituisce un array 1-D. Tentare di usare axes[0, 0] su un array 1-D genera un IndexError. Usa axes[0], oppure passa squeeze=False per ottenere sempre un array 2-D:
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(...)Ritaglio del supertitolo
plt.suptitle() può essere ritagliato da tight_layout(). Per risolvere, passa un rect a tight_layout:
plt.tight_layout(rect=[0, 0, 1, 0.95]) # leave 5% at top for suptitleIn alternativa usa fig.subplots_adjust(top=0.90).
Riferimento rapido
| Operazione | Codice |
|---|---|
| Griglia 1×2 | fig, (ax1, ax2) = plt.subplots(1, 2) |
| Griglia 2×2 | fig, ax = plt.subplots(2, 2) |
| Accedere a una cella | ax[row, col] |
| Iterare su tutte le celle | for ax in axes.flat: |
| Condividere l'asse x | plt.subplots(2, 1, sharex=True) |
| Condividere l'asse y | plt.subplots(1, 2, sharey=True) |
| Array sempre 2-D | plt.subplots(..., squeeze=False) |
| Layout personalizzato | gs = GridSpec(rows, cols); fig.add_subplot(gs[r, c]) |
| Estendere su più colonne | fig.add_subplot(gs[0, :]) |
| Rapporti di altezza | GridSpec(2, 1, height_ratios=[3, 1]) |
| Titolo della figura | plt.suptitle('Title') |
| Salvare la figura | plt.savefig('out.png', dpi=150, bbox_inches='tight') |
Capitoli correlati
- Introduzione a Matplotlib — panoramica della libreria e installazione
- Matplotlib: Per iniziare — il tuo primo grafico
- Grafici a linee con Matplotlib — visualizzare dati continui
- Etichette con Matplotlib — etichette degli assi, titoli e annotazioni
- Griglia con Matplotlib — aggiungere e personalizzare le linee della griglia
- Grafici a barre con Matplotlib — confrontare categorie
- Grafici a dispersione con Matplotlib — relazioni tra variabili
- Istogrammi con Matplotlib — distribuzioni dei dati