HOWTO · Matplotlib
Traccia due istogrammi insieme in Matplotlib
Questo articolo illustra come tracciare due istogrammi contemporaneamente in Matplotlib.
In questa pagina
Per tracciare insieme due istogrammi in Matplotlib, passa entrambi i dataset a plt.hist([a, b], bins=...) per ottenere barre affiancate, oppure chiama plt.hist() due volte sugli stessi assi per sovrapporli. Usa subplot separati se vuoi mostrare ogni distribuzione in un pannello distinto. In tutti i casi, usa gli stessi bordi dei bin, così le barre corrispondenti rappresentano gli stessi intervalli.
Gli esempi seguenti richiedono NumPy e Matplotlib e sono autonomi. Per visualizzare un grafico con plt.show() serve un ambiente grafico interattivo. In un ambiente non interattivo, salva la figura come descritto nell’ultima sezione.
Tracciare due istogrammi senza sovrapporre le barre
Passa un elenco contenente i due dataset a un’unica chiamata di plt.hist(). Con histtype="bar" e stacked=False, Matplotlib dispone le barre dei due dataset una accanto all’altra in ciascun bin. È utile per confrontare i conteggi intervallo per intervallo sugli stessi assi.
Al primo avvio, Matplotlib potrebbe segnalare l’inizializzazione della cache dei font. La configurazione del logging lascia visibili su standard output i messaggi diagnostici del gestore dei font, anziché inviarli a standard error; non li sopprime.
Questo esempio usa campioni generati e include una legenda:
import logging
import sys
# Keep first-run font-cache diagnostics visible on standard output.
font_logger = logging.getLogger("matplotlib.font_manager")
font_logger.addHandler(logging.StreamHandler(sys.stdout))
font_logger.propagate = False
import numpy as np
import matplotlib.pyplot as plt
rng = np.random.default_rng(42)
a = rng.normal(0, 3, 3000)
b = rng.normal(2, 4, 2000)
bins = np.linspace(min(a.min(), b.min()), max(a.max(), b.max()), 21)
plt.figure(figsize=(8, 4))
plt.hist([a, b], bins=bins, histtype="bar", stacked=False, label=["a", "b"])
plt.xlabel("Value")
plt.ylabel("Count")
plt.title("Two histograms with grouped bars")
plt.legend(loc="upper left")
plt.tight_layout()
plt.show()
Qui np.linspace() fornisce 21 bordi, che producono 20 bin sull’intervallo complessivo dei dati. Aumenta il numero di bordi per mostrare dettagli più fini; riducilo per mettere in risalto le tendenze generali. Puoi anche usare un intervallo fisso, ad esempio np.linspace(-10, 10, 21), ma le osservazioni esterne a tale intervallo saranno escluse dall’istogramma.
I campioni non devono seguire una distribuzione normale. Sostituisci a e b con array numerici monodimensionali contenenti le tue misurazioni, espresse in unità coerenti. Converti gli elenchi con np.asarray() prima di usare le chiamate .min() e .max() mostrate sopra. Prima di ricavare i bordi dei bin dai dati, rimuovi o gestisci i valori mancanti e non finiti. Entrambi gli array devono contenere osservazioni per poter calcolare minimo e massimo come nell’esempio. Se tutte le misurazioni hanno lo stesso valore, specifica bordi distinti e crescenti che includano quel valore, invece di ricavare bordi identici.
Tracciare due istogrammi con barre sovrapposte
Chiama plt.hist() due volte sugli stessi assi per sovrapporre le barre. Passa a entrambe le chiamate lo stesso array di bordi. Se imposti separatamente solo bins=20, ogni chiamata calcolerà i bordi in base all’intervallo del proprio dataset: gli intervalli potrebbero quindi differire anche se il numero di bin è uguale.
import logging
import sys
# Keep first-run font-cache diagnostics visible on standard output.
font_logger = logging.getLogger("matplotlib.font_manager")
font_logger.addHandler(logging.StreamHandler(sys.stdout))
font_logger.propagate = False
import numpy as np
import matplotlib.pyplot as plt
rng = np.random.default_rng(42)
a = rng.normal(0, 3, 1000)
b = rng.normal(2, 4, 900)
bins = np.linspace(min(a.min(), b.min()), max(a.max(), b.max()), 51)
plt.figure(figsize=(8, 4))
plt.hist(a, bins=bins, alpha=0.5, label="a", color="tab:blue")
plt.hist(b, bins=bins, alpha=0.5, label="b", color="tab:orange")
plt.xlabel("Value")
plt.ylabel("Count")
plt.title("Two histograms with overlapping bars")
plt.legend(loc="upper left")
plt.tight_layout()
plt.show()
Il parametro alpha controlla l’opacità: 0.0 è completamente trasparente, 1.0 è completamente opaco e 0.5 consente di vedere il contributo di entrambi i colori nelle zone in cui le barre si sovrappongono. Se ometti alpha, le barre sono normalmente opache: non diventano trasparenti. Quando le barre opache hanno lo stesso zorder, l’istogramma disegnato per secondo può coprire il primo nei punti in cui i rettangoli si sovrappongono, indipendentemente da quale dei due abbia il conteggio maggiore.
Gli stessi due campioni senza alpha mostrano perché la trasparenza è importante. L’istogramma arancione viene disegnato per secondo e copre quello blu fino alla propria altezza ovunque si sovrappongano; dove una barra arancione è più alta, la barra blu dietro di essa scompare completamente, anche quando rappresenta più osservazioni.
I conteggi dipendono anche dalla dimensione dei campioni. Se i gruppi contengono un numero diverso di osservazioni e vuoi confrontare la forma delle distribuzioni, imposta density=True in entrambe le chiamate e assegna all’asse verticale l’etichetta "Probability density". In questo modo, l’area totale di ciascun istogramma è pari a uno per le osservazioni incluse; le altezze delle barre non devono necessariamente sommare a uno.
Confrontare gli istogrammi in subplot affiancati
I pannelli separati evitano che un istogramma copra l’altro e rendono più semplice esaminare ciascuna distribuzione. Mantieni uguali i bin e le scale degli assi, così le differenze di scala tra i pannelli non alterano il confronto.
import logging
import sys
# Keep first-run font-cache diagnostics visible on standard output.
font_logger = logging.getLogger("matplotlib.font_manager")
font_logger.addHandler(logging.StreamHandler(sys.stdout))
font_logger.propagate = False
import numpy as np
import matplotlib.pyplot as plt
rng = np.random.default_rng(42)
data1 = rng.normal(0, 1, 1000)
data2 = rng.normal(1, 1, 1000)
bins = np.linspace(min(data1.min(), data2.min()), max(data1.max(), data2.max()), 31)
plt.figure(figsize=(9, 4))
left = plt.subplot(1, 2, 1)
plt.hist(data1, bins=bins, color="tab:blue")
plt.title("Dataset 1")
plt.xlabel("Value")
plt.ylabel("Count")
plt.subplot(1, 2, 2, sharex=left, sharey=left)
plt.hist(data2, bins=bins, color="tab:orange")
plt.title("Dataset 2")
plt.xlabel("Value")
plt.ylabel("Count")
plt.tight_layout()
plt.show()
In plt.subplot(1, 2, 1), i primi due argomenti definiscono una riga e due colonne; il terzo seleziona il pannello a sinistra. La posizione 2 seleziona quello a destra. Condividere entrambi gli assi mantiene allineate le scale, mentre plt.tight_layout() regola lo spazio attorno a titoli ed etichette. A differenza delle barre affiancate, questa disposizione usa due assi nella stessa figura.
Ricavare i bordi dei bin in modo sicuro quando un dataset può essere vuoto
Gli esempi precedenti ricavano i bordi condivisi dei bin dal minimo e dal massimo di entrambi i dataset. Tale derivazione fallisce quando un dataset è vuoto, perché un array vuoto non ha un minimo:
import numpy as np
import matplotlib.pyplot as plt
a = np.array([])
b = np.array([1.0, 2.0, 3.0])
bins = np.linspace(min(a.min(), b.min()), max(a.max(), b.max()), 21)
plt.hist([a, b], bins=bins)
Produzione:
Traceback (most recent call last):
...
ValueError: zero-size array to reduction operation minimum which has no identity
Filtra i dataset vuoti prima di ricavare i bordi. Rimuovi i valori non finiti nello stesso passaggio: un singolo NaN rende il minimo e il massimo NaN, così ogni bordo ricavato è NaN e l’istogramma non ha bin utilizzabili.
import numpy as np
a = np.array([])
b = np.array([1.0, 2.0, 3.0])
datasets = [d for d in (a, b) if d.size > 0]
bins = np.linspace(min(d.min() for d in datasets), max(d.max() for d in datasets), 21)
print("datasets kept:", len(datasets))
print("first and last bin edges:", bins[0], bins[-1])
Produzione:
datasets kept: 1
first and last bin edges: 1.0 3.0
Una volta filtrato il dataset vuoto, il dataset restante determina i bordi e può essere tracciato con le chiamate raggruppate o sovrapposte delle sezioni precedenti. Se datasets è vuoto dopo il filtraggio, non c’è nulla da tracciare; gestisci quel caso nel tuo codice invece di ricavare i bordi.
Personalizzare ed etichettare gli istogrammi
Assegna a ogni dataset un’etichetta descrittiva nella legenda e un colore distinto. Usa edgecolor per tracciare il contorno delle barre e linestyle per differenziarne ulteriormente il bordo. Linee orizzontali della griglia leggere aiutano a stimare le altezze senza prevalere sul confronto.
import logging
import sys
# Keep first-run font-cache diagnostics visible on standard output.
font_logger = logging.getLogger("matplotlib.font_manager")
font_logger.addHandler(logging.StreamHandler(sys.stdout))
font_logger.propagate = False
import numpy as np
import matplotlib.pyplot as plt
rng = np.random.default_rng(42)
data1 = rng.normal(0, 1, 1000)
data2 = rng.normal(1, 1, 1000)
bins = np.linspace(min(data1.min(), data2.min()), max(data1.max(), data2.max()), 31)
fig, ax = plt.subplots(figsize=(8, 4))
ax.hist(data1, bins=bins, color="tab:blue", edgecolor="black",
linestyle="--", alpha=0.5, label="Dataset 1")
ax.hist(data2, bins=bins, color="tab:orange", edgecolor="black",
linestyle=":", alpha=0.5, label="Dataset 2")
ax.set(title="Styled histogram comparison", xlabel="Value", ylabel="Count")
ax.set_axisbelow(True)
ax.grid(axis="y", alpha=0.3)
ax.legend()
fig.tight_layout()
plt.show()
Per mettere in risalto le forme senza barre riempite, usa histtype="step" e un valore adeguato di linewidth in ogni chiamata all’istogramma. Quando adatti gli esempi, indica le unità di misura nell’etichetta dell’asse orizzontale.
Per salvare un grafico, inserisci fig.savefig("histograms.png", dpi=150, bbox_inches="tight") prima di plt.show() nell’ultimo esempio. Negli esempi che usano pyplot, chiama plt.savefig() prima di mostrare la figura. Salvare prima di mostrarla evita di dipendere dal fatto che il backend di visualizzazione conservi o meno la figura in seguito; il salvataggio funziona anche senza una finestra grafica.
Per più di due dataset, estendi l’elenco passato a un’unica chiamata di plt.hist() e assegna un’etichetta a ciascun dataset, oppure aggiungi altre chiamate usando gli stessi bordi. Quando i gruppi aumentano, i contorni o i pannelli separati sono spesso più leggibili di molte aree trasparenti.