domenica 8 gennaio 2023

Analisi dei gruppi (clustering gerarchico)

L'analisi dei gruppi si applica a dati multivariati ed è un metodo statistico di tassonomia numerica che riveste un ruolo importante nella analisi esplorativa dei dati.

L'obiettivo dell'analisi dei gruppi (cluster analysis o clustering) è concettualmente semplice: verificare la possibile esistenza, in un insieme di oggetti, di sottoinsiemi di oggetti particolarmente simili tra loro (gruppi/cluster).

Nonostante alla base dell'analisi dei gruppi vi sia un'idea semplice e logica, vi sono numerosi modi per realizzarla [1], qui ci occupiamo dell'implementazione del clustering gerarchico con metodi esclusivi nelle due versioni:
clustering agglomerativo o bottom-up (con il metodo di Ward e con AGglomerative NESting, AGNES);
clustering divisivo o top-down (con DIvisive ANAlysis, DIANA).

Come dati impieghiamo i valori di BMI (indice di massa corporea) rilevati a livello europeo alcuni anni fa e pubblicati dall'Istat [2].

Per proseguire è necessario:
→ effettuare il download del file di dati bmi.csv
→ salvare il file nella cartella C:\Rdati\

Per il file di dati trovate link e modalità di download alla pagina Dati, ma potete anche semplicemente copiare i dati riportati qui sotto aggiungendo un ↵ Invio al termine dell'ultima riga e salvarli in C:\Rdati\ in un file di testo denominato bmi.csv (assicuratevi che il file sia effettivamente salvato con l'estensione .csv).

Nazione;sottopeso;normale;sovrappeso;obeso
Austria;2.4;49.6;33.3;14.7
Belgio;2.7;48.0;35.3;14.0
Bulgaria;2.2;43.8;39.2;14.8
Cipro;3.9;47.8;33.8;14.5
Croazia;1.9;40.7;38.7;18.7
Danimarca;2.2;50.0;32.9;14.9
Estonia;2.2;43.9;33.5;20.4
Finlandia;1.2;44.1;36.4;18.3
Francia;3.2;49.6;31.9;15.3
Germania;1.8;46.1;35.2;16.9
Grecia;1.9;41.3;39.4;17.3
Irlanda;1.9;42.3;37.0;18.7
Lettonia;1.7;41.8;35.2;21.3
Lituania;1.9;42.5;38.3;17.3
Lussemburgo;2.8;49.3;32.4;15.6
Malta;2.0;37.0;35.0;26.0
Olanda;1.6;49.0;36.0;13.3
Polonia;2.4;42.9;37.5;17.2
Portogallo;1.8;44.6;36.9;16.6
Regno Unito;2.1;42.2;35.6;20.1
Repubblica Ceca;1.1;42.1;37.6;19.3
Romania;1.3;42.9;46.4;9.4
Slovacchia;2.1;43.6;38.0;16.3
Slovenia;1.6;41.8;37.4;19.2
Spagna;2.2;45.4;35.7;16.7
Svezia;1.8;48.3;35.9;14.0
Ungheria;2.9;41.9;34.0;21.2

Inoltre è necessario scaricare dal CRAN il pacchetto aggiuntivo cluster [3] e il pacchetto aggiuntivo dendextend [4]. 

Iniziamo con un esempio di custering gerarchico che impiega esclusivamente i valori di default. Copiate questo script, incollatelo nella Console di R e premete ↵ Invio:

# CLUSTERING GERARCHICO con i valori di default
#  
mydata <- read.table("c:/Rdati/bmi.csv", header=TRUE, sep=";", row.names="Nazione") # importa i dati
z <- scale(mydata) # calcola la deviata normale standardizzata dei dati
windows() # apre e inizializza una nuova finestra grafica
#
mat <- dist(z) # calcola la matrice delle distanze
clus <- hclust(mat) # effettua il clustering gerarchico
plot(clus) # traccia il dendrogramma
#  

Con la prima riga di codice i dati sono importati nell'oggetto mydata.

Con la funzione scale() della seconda riga per ciascun dato viene calcolata la deviata normale standardizzata z. In pratica questa funzione prima calcola per i dati di ciascuna colonna/variabile la media e la deviazione standard, poi calcola per ciascuno dato x la corrispondente deviata normale standardizzata z come

z = (x – media) / deviazione standard

I valori di z sono salvati nel nuovo oggetto qui denominato per comodità mnemonica zSe nella Console di R digitate z 

> z 

sono mostrati i dati standardizzati, in coda ai quali sono riportate  la media 

attr(,"scaled:center")
 sottopeso    normale sovrappeso      obeso 
  2.103704  44.537037  36.240741  17.111111 

e la deviazione standard

attr(,"scaled:scale")
 sottopeso    normale sovrappeso      obeso 
 0.6111033  3.3717318  2.8989732  3.2322097 

impiegate per effettuare la standardizzazione dei dati.

Dopo aver aperto e inizializzato una nuova finestra grafica con windows()si passa al clustering gerarchico, realizzato in modo molto semplice, con tre sole funzioni, che impiegano per i loro argomenti i valori di default:
la funzione dist() che, sui valori standardizzati z, calcola la matrice delle distanze;
→ la funzione hclust() che dalla matrice delle distanze genera il clustering gerarchico; 
→ la funzione plot() che dal clustering gerarchico traccia il dendrogramma.


I principali argomenti delle tre funzioni sono evidenziati negli script successivi e al bisogno sono illustrati nella documentazione che si può richiamare con help(nomedellafunzione).

In questo secondo esempio viene illustrato il metodo agglomerativo forse più estesamente impiegato nella pratica, e cioè il clustering gerarchico con il metodo di Ward.

Copiate questo script, incollatelo nella Console di R e premete ↵ Invio:

# CLUSTERING GERARCHICO con distanza euclidea e metodo di Ward
#  
mydata <- read.table("c:/Rdati/bmi.csv", header=TRUE, sep=";", row.names="Nazione") # importa i dati
z <- scale(mydata) # calcola la deviata normale standardizzata dei dati
windows() # apre e inizializza una nuova finestra grafica
#
mat <- dist(z, method="euclidean") # calcola la matrice delle distanze 
clus <- hclust(mat, method="ward.D2") # effettua il clustering gerarchico 
plot(clus, cex=0.8, hang=-1, main="Dendrogramma - metodo di Ward", xlab="BMI nei Paesi europei", sub="", ylab="Distanza nei valori di BMI dei cluster") # traccia il dendrogramma 
#
rect.hclust(clus, k=4, border=c("goldenrod", "red", "green", "blue")) # evidenzia 4 gruppi/cluster 
#  

Qui le funzioni necessarie diventano quattro:
→ la matrice delle distanze mat è calcolata con la funzione dist() sui dati standardizzati z impiegando l'argomento method="euclidean" che prevede di misurare la distanza tra due punti con il teorema di Pitagora e può assumere in alternativa uno dei seguenti valori: "euclidean", "maximum", "manhattan", "canberra", "binary", "minkowski";
→ il clustering gerarchico clus è effettuato con la funzione hclust() sulla matrice delle distanze mat impiegando l'argomento method=ward.D2 che specifica come costruire i cluster, e può assumere in alternativa uno dei seguenti valori: "ward.D", "ward.D2", "single", "complete", "average" (= UPGMA), "mcquitty" (= WPGMA), "median" (= WPGMC), "centroid" (= UPGMC), il valore di default per l'argomento method è "complete";
→ a partire dal clustering gerarchico contenuto nell'oggetto clus con la funzione plot() viene tracciato il dendrogramma. L'argomento hang=-1 fa si che rami del dendrogramma ed etichette risultino allineati in basso. Potete togliere questo argomento se preferite il posizionamento dei rami del dendrogramma e delle etichette previsto di default (vedere il dendrogramma precedente);
 con la funzione rect.hclust() e l'argomento k=4 viene stabilito il numero dei gruppi da evidenziare nel dendrogramma, mentre i riquadri che li delimitano sono tracciati con i colori definiti nell'argomento border=c(...).

Questo è il dendrogramma ottenuto con il metodo di Ward:


Ora vediamo come il clustering agglomerativo può essere realizzato anche con il metodo di AGglomerative NESting sostituendo la funzione hclust() dello script precedente con la funzione agnes().

Copiate questo nuovo script, incollatelo nella Console di R e premete ↵ Invio:

# CLUSTERING GERARCHICO agglomerativo (AGglomerative NESting)
#  
library(cluster) # carica il pacchetto
mydata <- read.table("c:/Rdati/bmi.csv", header=TRUE, sep=";", row.names="Nazione") # importa i dati
z <- scale(mydata) # calcola la deviata normale standardizzata dei dati
windows() # apre e inizializza una nuova finestra grafica
#
mat <- dist(z, method="euclidean") # calcola la matrice delle distanze
clus <- agnes(mat, method="ward") # effettua il clustering gerarchico agglomerativo (AGNES)
plot(clus, which.plots=2, cex=0.8, hang=-1, main="Dendrogramma - clustering con AGglomerative NESting", xlab="BMI nei Paesi europei", sub="", ylab="Distanza nei valori di BMI dei cluster") # traccia il dendrogramma
#
rect.hclust(clus, k=4, border=c("goldenrod","blue","green","red")) # evidenzia 4 gruppi/cluster 

Le differenze di codice necessarie per realizzare il clustering agglomerativo in questo script rispetto al precedente sono poche, ma ovviamente determinanti:
→ è richiesto il pacchetto cluster;
→ per il clustering gerarchico agglomerativo, viene impiegata la funzione agnes() che prevede per l'argomento method i valori "average", "single", "complete", "ward", "weighted", "gaverage";
→ nella funzione plot() l'argomento which.plot=2 serve per rappresentare solamente il dendrogramma, mentre l'argomento hang=-1 di nuovo allinea rami del dendrogramma ed etichette  in basso.


In effetti come vedete impiegando l'argomento method="ward" i cluster sono uguali a quelli ottenuti con il metodo di Ward dello script precedente.

Per realizzare il clustering divisivo (top-down) con la DIvisive ANAlysis impieghiamo quest'altro script, incollatelo nella Console di R e premete ↵ Invio:

# CLUSTERING GERARCHICO divisivo (DIvisive ANAlysis)
#  
library(cluster) # carica il pacchetto
mydata <- read.table("c:/Rdati/bmi.csv", header=TRUE, sep=";", row.names="Nazione") # importa i dati
z <- scale(mydata) # calcola la deviata normale standardizzata dei dati
windows() # apre e inizializza una nuova finestra grafica
#
mat <- dist(z, method="euclidean") # calcola la matrice delle distanze
clus <- diana(mat) # effettua il clustering gerarchico divisivo (DIANA)
plot(clus, which.plots=2, cex=0.8, hang=-1, main="Dendrogramma - clustering con DIvisive ANAlysis", xlab="BMI nei Paesi europei", sub="", ylab="Distanza nei valori di BMI dei cluster") # traccia il dendrogramma
#
rect.hclust(clus, k=4, border=c("goldenrod","blue","green","red")) # evidenzia 4 gruppi/cluster 

Nella documentazione della funzione diana() per il clustering con la DIvisive ANAlysis si legge che questo metodo "... is probably unique in computing a divisive hierarchy, whereas most other software for hierarchical clustering is agglomerative". 


La funzione prevede un unico algoritmo di clusterizzazione, per cui si riduce all'espressione diana(mat) mentre il resto del codice, a parte naturamente titolo ed etichette, resta identico a quello dello script precedente.

Per evidenziare le differenze tra i risultati ottenuti con il clustering divisivo e quelli ottenuti con il precedente clustering agglomerativo, impieghiamo la possibilità piuttosto interessante di effettuare il confronto tra due dendrogrammi, sia graficamente sia calcolando un indice numerico di corrispondenza.

Copiate questo script, incollatelo nella Console di R e premete ↵ Invio:

# CLUSTERING GERARCHICO confronto tra i dendrogrammi AGNES e DIANA
#  
library(dendextend) # carica il pacchetto
library(cluster) # carica il pacchetto
mydata <- read.table("c:/Rdati/bmi.csv", header=TRUE, sep=";", row.names="Nazione") # importa i dati
z <- scale(mydata) # calcola la deviata normale standardizzata dei dati
windows() # apre e inizializza una nuova finestra grafica
#
mat <- dist(z, method="euclidean") # calcola la matrice delle distanze
clusAgnes <- agnes(mat, method="ward") # effettua il clustering gerarchico agglomerativo (AGNES)
clusDiana <- diana(mat) # effettua il clustering gerarchico divisivo (DIANA)
dendAgnes <- as.dendrogram(clusAgnes) # predispone il dendrogramma
dendDiana <- as.dendrogram(clusDiana) # predispone il dendrogramma
tanglegram(dendAgnes, dendDiana, main_left="AGNES", main_right="DIANA") # traccia il grafico di confronto tra i due dendrogrammi
entanglement(dendAgnes, dendDiana) # calcola la diffferenza tra i due dendrogrammi
#

Dopo avere caricato anche il pacchetto dendextend, i passaggi prevedono di:
→ calcolare come sempre la matrice delle distanze con la funzione dist()
→ effettuare il clustering gerarchico con i due algoritmi che vogliamo confrontare, che sono rispettivamente agnes() per il clustering agglomerativo e diana() per il clustering divisivo;
con la funzione as.dendrogram() estrarre e salvare i due dendrogrammi che vogliamo confrontare;
→ impiegare la funzione tanglegram() per tracciare il grafico che confronta visivamente i due dendrogrammi;
→ impiegare la funzione entanglement() per calcolare l'indice numerico che misura la differenza tra due dendrogrammi.
 
Il grafico risultante


mette a confronto i due dendrogrammi.

L'indice numerico che misura la differenza tra i due dendrogrammi, che nella documentazione del pacchetto è denominato "entanglement", ha un valore compreso tra 0 e 1, dove:
 0 indica che non vi è alcuna differenza tra i due dendrogrammi;
 1 indica che i due dendrogrammi non hanno nulla in comune e sono totalmente differenti.

Nel nostro caso abbiamo

> entanglement(dendAgnes, dendDiana) # calcola la differenza tra i due dendrogrammi
[1] 0.09419058

quindi il valore ottenuto 0.09419058 conferma che i due dendrogrammi sono molto simili, anche se determinano un raggruppamento in cluster lievemente diverso.

Trovate il seguito e le strategie alternative di clustering e di analisi dei dati multivariati nei post:


----------

[1] Nonostante alla base dell'analisi dei gruppi vi sia un'idea semplice e logica, vi sono numerosi modi per realizzarla, infatti esistono:
→ metodi gerarchici, che danno luogo a una suddivisione ad albero (dendrogramma) in base alla distanza tra i singoli oggetti dell'insieme;
→ metodi non gerarchici, nei quali l'appartenenza di un oggetto (dell'insieme) ad uno specifico sottoinsieme/gruppo/cluster viene stabilita sulla base della sua distanza dal centro o dalla media dei dati o da un punto rappresentativo del cluster;
→ metodi bottom-up noti anche come metodi agglomerativi nei quali all'inizio del processo di classificazione ad ogni oggetto viene fatto corrispondere un cluster. In questo stadio gli oggetti sono considerati tutti dissimili tra di loro. Al passaggio successivo i due oggetti più simili sono raggruppati nello stesso cluster. Il numero dei cluster risulta quindi pari al numero di oggetti diminuito di uno. Il procedimento viene ripetuto ciclicamente, fino ad ottenere (all'ultimo passaggio) un unico cluster;
→ metodi top-down noti anche come metodi divisivi nei quali inizialmente tutti gli oggetti sono considerati come appartenenti ad un unico cluster, che viene via via suddiviso in cluster fino ad avere un numero di cluster uguale al numero degli oggetti;
→ metodi esclusivi, che prevedono che un oggetto possa appartenere esclusivamente a un cluster;
→ metodi non esclusivi (fuzzy) che prevedono che un oggetto possa appartenere, in modo quantitativamente diverso, a più di un cluster. 

[2] Vedere il post Indice di massa corporea (BMI).

[3] Trovate la documentazione nel manuale di riferimento del pacchetto Package 'cluster'. URL consultato il 05/01/2023.

[4] Trovate la documentazione nel manuale di riferimento del pacchetto Package ‘dendextend’. URL consultato il 05/01/2023.

Nessun commento:

Posta un commento