Clustering Gerarchico
Impara il clustering gerarchico in Python con scikit-learn e SciPy: metodi di linkage, dendrogrammi e confronto con K-Means.
Il clustering gerarchico è un algoritmo di machine learning non supervisionato che raggruppa i punti dati in un albero di cluster annidati, senza richiedere di specificare il numero di cluster in anticipo. Questa pagina spiega come funziona l'algoritmo, la differenza tra approcci agglomerativi e divisivi, come scegliere un metodo di linkage e come implementare e visualizzare il clustering gerarchico in Python usando sia scikit-learn che SciPy.
Cos'è il Clustering Gerarchico?
Il clustering gerarchico costruisce una gerarchia di cluster unendo o dividendo iterativamente gruppi di punti dati in base a una misura di distanza (o similarità). Il risultato è rappresentato come un dendrogramma — un diagramma ad albero in cui l'asse y mostra la distanza a cui i cluster vengono uniti e le foglie rappresentano i singoli punti dati.
A differenza di K-Means, il clustering gerarchico non richiede di scegliere il numero di cluster prima di eseguire l'algoritmo. Si esegue l'algoritmo una volta e poi si "taglia" il dendrogramma a una soglia di distanza scelta per produrre qualsiasi numero di cluster.
Quando usare il clustering gerarchico
Usa il clustering gerarchico quando:
- Non conosci in anticipo il numero di cluster e vuoi esplorare diverse opzioni da una singola esecuzione.
- Il tuo dataset ha dimensioni piccole o medie (migliaia di righe, non milioni) — il costo di memoria O(n²) dell'algoritmo lo rende impraticabile per dataset molto grandi.
- Hai bisogno di relazioni tra cluster interpretabili (il dendrogramma mostra chiaramente la cronologia delle fusioni).
- I tuoi cluster potrebbero non essere sferici — i metodi gerarchici con linkage completo o medio gestiscono forme non globulari meglio di K-Means.
Clustering Agglomerativo vs Divisivo
Esistono due strategie principali:
| Strategia | Direzione | Come funziona |
|---|---|---|
| Agglomerativo (dal basso verso l'alto) | Inizia con ogni punto come proprio cluster, poi unisce ripetutamente i due cluster più vicini. | Il più comune; usato da AgglomerativeClustering di scikit-learn e da linkage di SciPy. |
| Divisivo (dall'alto verso il basso) | Inizia con tutti i punti in un unico cluster, poi divide ricorsivamente il cluster più grande. | Raramente usato in pratica; computazionalmente costoso. |
Questa pagina si concentra sul clustering agglomerativo, che è ciò che la maggior parte dei professionisti intende quando parla di "clustering gerarchico."
Come Funziona l'Algoritmo
Il clustering agglomerativo segue questi passaggi:
- Tratta ognuno degli n punti dati come proprio cluster (n cluster in totale).
- Calcola la matrice delle distanze — le distanze a coppie tra tutti i cluster.
- Unisce i due cluster con la distanza minore in un nuovo cluster.
- Aggiorna la matrice delle distanze per riflettere il nuovo cluster.
- Ripete i passi 3–4 fino a quando rimane un solo cluster.
L'output finale è un dendrogramma che codifica tutti gli n-1 passaggi di fusione.
Metodi di Linkage
Il metodo di linkage controlla come viene calcolata la distanza tra due cluster a ogni passaggio di fusione. La scelta influisce significativamente sulla forma e sulla qualità dei cluster.
| Metodo | Distanza tra i cluster A e B | Ideale per |
|---|---|---|
| Ward | Aumento della varianza totale intra-cluster dopo la fusione | Cluster compatti di dimensioni approssimativamente uguali (scelta predefinita) |
| Complete | Distanza massima tra qualsiasi punto in A e qualsiasi punto in B | Cluster compatti; evita il chaining |
| Average | Distanza media tra tutte le coppie (uno da A, uno da B) | Compromesso equilibrato tra single e complete |
| Single | Distanza minima tra qualsiasi punto in A e qualsiasi punto in B | Rilevamento di cluster allungati o di forma irregolare; soggetto al "chaining" |
Il linkage Ward è il punto di partenza più diffuso. Se i tuoi cluster sono allungati o non convessi, prova il linkage average o single.
Normalizzazione delle Feature Prima del Clustering
Poiché il clustering gerarchico si basa su calcoli di distanza, le feature con intervalli numerici elevati dominano la matrice delle distanze e distorcono i risultati. Normalizza sempre le tue feature prima.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)StandardScaler centra ogni feature con media 0 e varianza unitaria. Consulta il capitolo sulla normalizzazione per alternative come MinMaxScaler e RobustScaler.
Implementare il Clustering Gerarchico con scikit-learn
La classe AgglomerativeClustering in scikit-learn adatta il modello e assegna le etichette dei cluster in un unico passaggio. Non produce un dendrogramma — usa SciPy (mostrato nella sezione successiva) per quello.
Passaggio 1 — Generare e normalizzare i dati
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
# 150 points in 3 natural clusters
X, y_true = make_blobs(n_samples=150, centers=3, cluster_std=0.6, random_state=42)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)make_blobs crea un dataset sintetico riproducibile, quindi questo esempio può essere eseguito senza alcun file CSV.
Passaggio 2 — Adattare il clustering agglomerativo
from sklearn.cluster import AgglomerativeClustering
hc = AgglomerativeClustering(n_clusters=3, linkage='ward')
labels = hc.fit_predict(X_scaled)
print(labels[:10])
# e.g. [2 2 0 1 0 1 2 2 0 1]fit_predict adatta il modello e restituisce l'etichetta del cluster (0, 1 o 2) per ogni punto dati in una singola chiamata.
Passaggio 3 — Visualizzare i cluster
import matplotlib.pyplot as plt
colors = ['red', 'blue', 'green']
for cluster_id in range(3):
mask = labels == cluster_id
plt.scatter(X_scaled[mask, 0], X_scaled[mask, 1],
s=60, label=f'Cluster {cluster_id + 1}')
plt.title('Agglomerative Clustering (Ward linkage, k=3)')
plt.xlabel('Feature 1 (scaled)')
plt.ylabel('Feature 2 (scaled)')
plt.legend()
plt.tight_layout()
plt.show()Dendrogrammi con SciPy
Il dendrogramma è l'output più distintivo del clustering gerarchico. Ti permette di scegliere visivamente il numero di cluster tagliando l'albero a diverse altezze. Il modulo scipy.cluster.hierarchy di SciPy fornisce sia linkage (per costruire l'albero) che dendrogram (per tracciarlo).
Costruire e tracciare un dendrogramma
from scipy.cluster.hierarchy import dendrogram, linkage
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
# Use a smaller dataset so the dendrogram is readable
X, _ = make_blobs(n_samples=30, centers=3, cluster_std=0.6, random_state=42)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# Build the linkage matrix
Z = linkage(X_scaled, method='ward')
# Plot
plt.figure(figsize=(12, 5))
dendrogram(Z, leaf_rotation=90, leaf_font_size=8)
plt.title('Dendrogram (Ward linkage)')
plt.xlabel('Sample index')
plt.ylabel('Merge distance')
plt.tight_layout()
plt.show()Leggere il dendrogramma
- Le foglie (in basso) rappresentano i singoli punti dati.
- Le linee orizzontali rappresentano le fusioni. L'altezza di una linea equivale alla distanza tra i due cluster che vengono uniti.
- Tagliare l'albero a una determinata altezza fornisce il numero di cluster al di sotto di quel taglio.
Per scegliere k, cerca la linea verticale più alta che non viene attraversata da una linea orizzontale — quel gap suggerisce il numero più naturale di cluster.
Tagliare il dendrogramma per assegnare le etichette
Dopo aver costruito la matrice di linkage Z, usa fcluster per tagliare l'albero al numero desiderato di cluster:
from scipy.cluster.hierarchy import linkage, fcluster
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
import numpy as np
X, _ = make_blobs(n_samples=150, centers=3, cluster_std=0.6, random_state=42)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
Z = linkage(X_scaled, method='ward')
# Cut the tree to get exactly 3 clusters
labels = fcluster(Z, t=3, criterion='maxclust')
print('Unique cluster IDs:', np.unique(labels)) # [1 2 3] (1-indexed)
print('Sizes:', np.bincount(labels[labels > 0])) # [50 50 50]Nota che le etichette di fcluster sono indicizzate da 1 (partono da 1), a differenza delle etichette indicizzate da 0 di scikit-learn.
Confronto dei Metodi di Linkage
Il metodo di linkage modifica significativamente le forme e le dimensioni dei cluster. Ecco come confrontare tutti e quattro i metodi sullo stesso dataset:
from sklearn.cluster import AgglomerativeClustering
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
import numpy as np
X, _ = make_blobs(n_samples=150, centers=3, cluster_std=0.6, random_state=42)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
methods = ['ward', 'complete', 'average', 'single']
fig, axes = plt.subplots(1, 4, figsize=(16, 4))
for ax, method in zip(axes, methods):
hc = AgglomerativeClustering(n_clusters=3, linkage=method)
labels = hc.fit_predict(X_scaled)
ax.scatter(X_scaled[:, 0], X_scaled[:, 1], c=labels, cmap='Set1', s=30)
ax.set_title(f'{method.capitalize()} linkage')
ax.set_xticks([])
ax.set_yticks([])
plt.suptitle('Linkage method comparison (k=3)', y=1.02)
plt.tight_layout()
plt.show()Clustering Gerarchico vs K-Means
| Caratteristica | Clustering gerarchico | K-Means |
|---|---|---|
| Numero di cluster | Si sceglie dopo l'esecuzione (ispezionando il dendrogramma) | Deve essere specificato prima dell'esecuzione |
| Scalabilità | Spazio O(n²) — fatica oltre ~10 000 righe | O(n·k·i) — scala a milioni |
| Determinismo | Completamente deterministico | Dipende dall'inizializzazione casuale |
| Forme dei cluster | Flessibile (specialmente con linkage single/average) | Assume cluster sferici |
| Interpretabilità | Il dendrogramma mostra la cronologia completa delle fusioni | I centroidi sono facilmente interpretabili |
Usa il clustering gerarchico per l'analisi esplorativa su dataset più piccoli; passa a K-Means (o DBSCAN) quando hai milioni di righe o conosci già il numero di cluster.
Errori Comuni
Dimenticare di normalizzare le feature. Senza la normalizzazione delle feature, una feature misurata in migliaia (ad es. il reddito) domina una misurata in cifre singole (ad es. la valutazione per età), producendo cluster fuorvianti.
Usare il linkage Ward con distanze non euclidee. Il linkage Ward è valido solo con la distanza euclidea. Per altre metriche di distanza (ad es. coseno, Manhattan), usa il linkage complete o average e passa metric= esplicitamente.
Interpretare dendrogrammi su dataset molto grandi. Un dendrogramma con 10 000 foglie è illeggibile. Usa p= e truncate_mode='lastp' in dendrogram() di SciPy per mostrare solo le ultime p fusioni.
Trattare gli ID dei cluster come stabili. Gli ID dei cluster (0, 1, 2) sono etichette arbitrarie. Quando si confrontano due esecuzioni, abbina i cluster per contenuto, non per numero.
Conclusione
Il clustering gerarchico è un algoritmo flessibile e interpretabile che non richiede di scegliere k in anticipo. Si costruisce il dendrogramma completo una volta sola e poi lo si taglia a qualsiasi livello. Il linkage Ward con la pre-elaborazione StandardScaler è il punto di partenza più sicuro. Per dataset molto grandi, preferisci K-Means per le prestazioni.
Capitoli correlati:
- Normalizzazione — perché e come standardizzare le feature prima del clustering
- K-Means — l'alternativa di clustering piatto per dataset di grandi dimensioni
- Tutorial SciPy — ulteriori informazioni sulla libreria di calcolo scientifico SciPy
- Scatter Plot — visualizzazione di dati multidimensionali con Matplotlib