W3docs

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 numpy

Se 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 di show() o savefig().
  • Quando ncols=1 e nrows=1 (il valore predefinito), axes è un singolo oggetto Axes, 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:

Layoutfigsize 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 suptitle

In alternativa usa fig.subplots_adjust(top=0.90).

Riferimento rapido

OperazioneCodice
Griglia 1×2fig, (ax1, ax2) = plt.subplots(1, 2)
Griglia 2×2fig, ax = plt.subplots(2, 2)
Accedere a una cellaax[row, col]
Iterare su tutte le cellefor ax in axes.flat:
Condividere l'asse xplt.subplots(2, 1, sharex=True)
Condividere l'asse yplt.subplots(1, 2, sharey=True)
Array sempre 2-Dplt.subplots(..., squeeze=False)
Layout personalizzatogs = GridSpec(rows, cols); fig.add_subplot(gs[r, c])
Estendere su più colonnefig.add_subplot(gs[0, :])
Rapporti di altezzaGridSpec(2, 1, height_ratios=[3, 1])
Titolo della figuraplt.suptitle('Title')
Salvare la figuraplt.savefig('out.png', dpi=150, bbox_inches='tight')

Capitoli correlati

Was this page helpful?