La visualizzazione in Python è frammentata. Non c’è una singola libreria canonica come pandas è canonico per i DataFrame o scikit-learn è canonico per il machine learning classico. Ci sono tre librerie che la maggior parte della gente finisce per usare, ognuna costruita per un pubblico diverso, e la cosa produttiva non è scegliere una e fare finta che le altre non esistano: è sapere quale tirare fuori a seconda di chi guarderà il grafico.
In questa lezione: matplotlib, seaborn e plotly. In cosa è bravo ognuno, perché i loro default sono fatti come sono, e lo stesso grafico scritto tre volte così da poter vedere i trade-off uno accanto all’altro.
matplotlib: la fondazione
matplotlib esiste dal 2003. John Hunter l’ha scritto come un port dell’interfaccia di plotting di MATLAB così che i neuroscienziati potessero tenere la loro memoria muscolare mentre si spostavano a Python. Quella storia è visibile praticamente in ogni scelta API che la libreria fa: l’interfaccia globale plt.plot, plt.title, plt.show è quella di MATLAB. I default sono da pubblicazione scientifica dei primi anni 2000: sfondo bianco, linea blu, etichette degli assi sans-serif, un’estetica leggermente robusta che non è invecchiata particolarmente bene.
Ma matplotlib è la fondazione. Seaborn è costruito sopra. Il df.plot() di pandas la chiama. La maggior parte delle altre librerie di plotting Python o renderizza tramite matplotlib o ne segue le convenzioni. Conoscere matplotlib al livello di “so leggere il codice e modificarlo” è non negoziabile, anche se per lo più usi librerie di livello più alto.
Il pattern da usare, ogni volta, è l’interfaccia object-oriented:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
y = np.sin(x)
fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(x, y, label="sin(x)", color="steelblue", linewidth=2)
ax.set_xlabel("x")
ax.set_ylabel("sin(x)")
ax.set_title("A sine wave")
ax.legend()
ax.grid(alpha=0.3)
fig.tight_layout()
fig.savefig("sine.png", dpi=150)
plt.show()
fig è l’intera figura (la tela). ax sono gli axes (la regione di plotting effettiva al suo interno). Ogni personalizzazione passa per ax.qualcosa. Questa è l’API che dovresti sempre usare. L’interfaccia globale stile MATLAB (plt.plot(x, y); plt.title("...")) funziona, ma si appoggia a stato nascosto, si rompe in modi sottili dentro notebook e pipeline, ed è impossibile da comporre in figure multi-pannello in modo pulito. Usa fig, ax = plt.subplots().
Per pannelli multipli, chiedi una griglia in anticipo:
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
axes[0, 0].plot(x, np.sin(x))
axes[0, 1].plot(x, np.cos(x))
axes[1, 0].plot(x, np.tan(x))
axes[1, 1].plot(x, x ** 2)
fig.tight_layout()
Quando tirare fuori matplotlib direttamente: quando ti serve controllo a livello di pixel. Annotazioni a coordinate specifiche. Figure multi-pannello con assi condivisi. Legende custom. Qualunque cosa destinata a un report PDF o a un paper. Matplotlib 3.x, che è quello su cui sarai nel 2026, ha default decenti se imposti lo stile: plt.style.use("seaborn-v0_8-whitegrid") è una prima riga comune che subito fa sembrare le cose meno 2003.
seaborn: matplotlib per la grafica statistica
Seaborn è uno strato sottile sopra matplotlib scritto da Michael Waskom. Fa due cose: spedisce default sensati così che i grafici sembrino moderni out-of-the-box, e ha funzioni di livello più alto per i tipi di plot che vuoi davvero quando stai facendo analisi dati.
Il modello mentale è: seaborn sa dei DataFrame. Gli passi un DataFrame e gli dici quali colonne mappare su x, y, colore e shape. Fa l’aggregazione e il layout. Sotto, sta chiamando matplotlib, il che significa che tutto ciò che hai imparato su fig, ax si applica ancora; puoi tirare fuori gli axes matplotlib da qualunque chiamata seaborn e personalizzare.
I plot che userai il 90% delle volte:
import seaborn as sns
import pandas as pd
# Load tips dataset bundled with seaborn for examples
tips = sns.load_dataset("tips")
# Scatter with color encoding
sns.scatterplot(data=tips, x="total_bill", y="tip", hue="time", style="smoker")
# Boxplot grouped by category
sns.boxplot(data=tips, x="day", y="total_bill", hue="sex")
# Heatmap (great for correlation matrices)
corr = tips.select_dtypes("number").corr()
sns.heatmap(corr, annot=True, cmap="coolwarm", center=0)
# Pairplot — every numeric column against every other
sns.pairplot(tips, hue="time")
I due parametri che spuntano dappertutto sono hue= (encoding di colore: passa qualunque colonna, categoriale o continua) e style= (forma del marker, solo per categoriale). Insieme ti permettono di codificare tre o quattro variabili contemporaneamente su un plot 2-D senza scrivere codice di aggregazione tu stesso.
Le funzioni relplot, catplot e displot di seaborn (le funzioni “figure-level”) sono la versione che gestisce il faceting, splittare un plot in una griglia in base a un’altra variabile:
sns.relplot(
data=tips,
x="total_bill", y="tip",
hue="day", col="time", row="sex",
kind="scatter",
)
Sono sei pannelli, codificati a colore per giorno, senza loop manuali. Quando stai esplorando un dataset e vuoi una lettura rapida di “come cambia X attraverso questi gruppi”, seaborn figure-level è lo strumento più veloce in Python.
Quando tirare fuori seaborn: qualunque lavoro analitico dove il pubblico sei tu o un collega. Notebook di EDA. Report statistici. Qualunque cosa dove vuoi che sembri ragionevole senza negoziare con matplotlib sulle dimensioni dei font.
plotly: plot interattivi che funzionano sul web
matplotlib e seaborn producono immagini statiche: PNG, PDF, SVG. Plotly produce grafici interattivi. Passi sopra un punto e un tooltip mostra il valore. Trascini una regione per zoomare. Attivi e disattivi i trace cliccando sulla legenda. L’output è HTML+JavaScript e renderizza inline in Jupyter, nella vista notebook di VS Code, in un’app Streamlit o Dash, o in qualunque pagina web.
La libreria Python di plotly ha due strati. Quello di alto livello, plotly.express, è quello che userai per l’80% dei grafici:
import plotly.express as px
fig = px.scatter(
tips,
x="total_bill", y="tip",
color="time", symbol="smoker",
size="size", hover_data=["day"],
title="Tips vs total bill",
)
fig.show()
Quello è l’equivalente plotly dello scatterplot seaborn di prima: stessi dati, API simile, ma l’output è interattivo. Passi sopra un punto e vedi tutte le colonne in hover_data. Il parametro color= prende una colonna direttamente (categoriale o continua) e plotly sceglie la colormap. size= mappa una colonna numerica alla dimensione del marker.
Gli altri tipi di plot in plotly.express: px.line, px.bar, px.box, px.histogram, px.violin, px.heatmap, px.choropleth (geografico), px.scatter_3d. Per la maggior parte dei casi d’uso analitici non ti serve nulla sotto questo strato.
Lo strato di basso livello è plotly.graph_objects, dove costruisci figure componendo trace:
import plotly.graph_objects as go
fig = go.Figure()
fig.add_trace(go.Scatter(
x=tips["total_bill"], y=tips["tip"],
mode="markers",
marker=dict(color="steelblue", size=8),
name="tips",
))
fig.update_layout(title="Tips", xaxis_title="Total bill", yaxis_title="Tip")
fig.show()
Scendi a graph_objects quando ti serve una forma di grafico che express non ha, o quando stai componendo molteplici tipi di trace in una figura (un candlestick con medie mobili sovrapposte, per esempio), o quando stai embeddando plotly dentro una callback Dash che aggiorna pezzi della figura.
Quando tirare fuori plotly: qualunque cosa interattiva. Dashboard (Streamlit e Dash renderizzano entrambi plotly nativamente). Report che vivono sul web invece che in un PDF. Notebook dove vuoi che il lettore possa esplorare. Serie temporali con migliaia di punti dove lo zoom è la feature vera. Plotly è alla versione 5.x nel 2026, maturo e stabile; l’API non cambia molto da anni.
Lo stesso grafico, tre volte
Così da poter vedere la differenza, ecco uno scatter di total_bill contro tip, codificato a colore per momento della giornata, scritto in tre modi:
# matplotlib: explicit, low-level
fig, ax = plt.subplots(figsize=(8, 5))
for time, group in tips.groupby("time"):
ax.scatter(group["total_bill"], group["tip"], label=time, alpha=0.7)
ax.set_xlabel("Total bill")
ax.set_ylabel("Tip")
ax.legend(title="Time")
ax.set_title("Tips vs bill")
# seaborn: one line, looks right by default
sns.scatterplot(data=tips, x="total_bill", y="tip", hue="time")
# plotly: one line, plus interactivity
px.scatter(tips, x="total_bill", y="tip", color="time", title="Tips vs bill").show()
Tutti e tre renderizzano gli stessi dati. La versione matplotlib ti dà controllo totale e ti costringe a scrivere la logica della legenda da te. La versione seaborn è il percorso più breve verso un grafico statico pubblicabile. La versione plotly è il percorso più breve verso un grafico che puoi mettere su una pagina web.
Salvare le figure, DPI, e qualche nota pratica
Per matplotlib e seaborn (che è matplotlib sotto), funziona lo stesso savefig:
fig.savefig("output.png", dpi=300, bbox_inches="tight")
fig.savefig("output.pdf", bbox_inches="tight") # vector, scales perfectly
fig.savefig("output.svg", bbox_inches="tight")
dpi=300 è il default giusto per qualunque cosa vada in un report a stampa o in una slide deck. bbox_inches="tight" ritaglia lo spazio bianco intorno alla figura, cosa che il default non fa. Per il PDF, il dpi non conta: i formati vettoriali sono indipendenti dalla risoluzione.
Per plotly: fig.write_html("output.html") per la versione interattiva, o fig.write_image("output.png", scale=2) per quella statica (questo richiede il pacchetto kaleido; installalo con uv add kaleido).
La dimensione della figura in matplotlib è in pollici (figsize=(8, 5) è largo 8 pollici, alto 5). Per un notebook Jupyter, 8x5 o 10x6 si legge bene. Per una dashboard Streamlit, il sizing di default di plotly di solito funziona e basta.
Qualche cosa che morde la gente
Tre tranelli che vale la pena segnalare perché costano a chiunque un’ora a un certo punto.
plt.show() si comporta diversamente in script e notebook. In un notebook Jupyter con %matplotlib inline (il default), le figure renderizzano automaticamente quando una cella finisce; plt.show() è un no-op. In uno script .py lanciato da terminale, plt.show() è quello che blocca e apre la finestra. In un contesto non interattivo (un job CI, un server) non vuoi una finestra del tutto: imposta il backend a Agg prima di importare pyplot, matplotlib.use("Agg"), e chiama solo fig.savefig su disco.
Gli assi datetime hanno bisogno di formattazione esplicita in matplotlib. Quando plotti una Series di pandas con un DatetimeIndex, matplotlib fa la cosa giusta circa l’80% delle volte. L’altro 20% ottieni etichette di data sovrapposte che hanno bisogno di fig.autofmt_xdate() o di un mdates.DateFormatter custom. Seaborn e plotly tendono a gestire questo in modo più grazioso out-of-the-box.
Il fig.show() di plotly non sempre mostra in ogni ambiente. In un notebook renderizza inline. In un REPL Python normale prova ad aprire una tab del browser. Dentro Streamlit non chiami fig.show() proprio: chiami st.plotly_chart(fig). Dentro Dash, va in un dcc.Graph(figure=fig). Conoscere il target di rendering prima di scrivere il codice ti risparmia una sessione di debugging confusa.
Sceglierne uno
L’albero decisionale, da un capo all’altro:
- Grafico statico per un paper, una slide o un report PDF? Matplotlib (o seaborn per partire più veloce, poi scendere a matplotlib per le rifiniture).
- Grafico analitico veloce in un notebook? Seaborn. Una riga, sembra giusto, vai oltre.
- Grafico interattivo per una web app o per HTML condivisibile? Plotly Express.
- Dashboard? Plotly dentro Streamlit o Dash.
Finirai con tutti e tre installati in qualunque progetto dati serio. Non c’è vergogna nel mischiarli: un notebook che costruisce un’analisi con seaborn e finisce con un grafico plotly rifinito per il riassunto esecutivo è un manufatto perfettamente normale nel 2026.
Una nota su cosa ho deliberatamente lasciato fuori: Altair (dichiarativo, basato su Vega-Lite, delizioso per la grafica statistica ma con community più piccola), Bokeh (libreria interattiva più vecchia che plotly ha per lo più soppiantato per i nuovi progetti), e HoloViews (wrapper di alto livello che compila o a Bokeh o a matplotlib). Sono tutte scelte valide (Altair in particolare ha un seguito devoto), ma se stai partendo da zero nel 2026 il trio matplotlib/seaborn/plotly copre più del 95% di quello che ti servirà e matcha ciò che i tuoi colleghi già sanno.
La prossima lezione, SciPy, è l’ultimo pezzo della fondazione numerica prima di muoversi nel Modulo 9 e iniziare ad applicare questo stack a lavoro vero di ML e analytics.
Riferimento: documentazione matplotlib, documentazione seaborn, documentazione Plotly Python, recuperate il 2026-05-01.