Corso
k-means è una popolare tecnica di machine learning non supervisionato che consente di identificare cluster (gruppi di punti dati simili) all’interno dei dati. In questo tutorial imparerai il clustering k-means in R usando tidymodels, ggplot2 e ggmap. Tratteremo:
- come funziona l’algoritmo di clustering k-means
- come visualizzare i dati per capire se sono un buon candidato per il clustering
- un caso di studio di training e tuning di un modello k-means usando un dataset di recensioni Airbnb
Prima di andare più a fondo, ti consigliamo di ripassare le basi di R, come vettori e data frame. Se invece sei pronto per partire, salta pure e iniziamo!
Panoramica del clustering k-means
I modelli di clustering mirano a raggruppare i dati in “cluster” o gruppi distinti. Questo può essere usato come analisi a sé stante, oppure come caratteristica in un algoritmo di apprendimento supervisionato.

Nella parte sinistra del diagramma sopra, vediamo 2 insiemi distinti di punti non etichettati e colorati come punti dati simili. Adattare un modello k-means a questi dati (lato destro) può rivelare 2 gruppi distinti (mostrati sia con cerchi distinti sia con colori).
In due dimensioni, per gli umani è facile separare questi cluster, ma con più dimensioni serve un modello.
L’algoritmo k-means
Immagina di voler creare diverse macedonie di frutta, ciascuna composta da frutti simili.
- Decidi il numero di macedonie che vuoi preparare (k). Per esempio, decidi di farne 3.
- Scegli casualmente 3 frutti dal tavolo come “rappresentanti” di ciascuna macedonia.
- Ora, giri intorno al tavolo e assegni ogni frutto alla macedonia il cui rappresentante gli somiglia di più. Per esempio, le mele potrebbero andare con le pere, le arance con i mandarini e le banane con i platani.
- Una volta assegnati tutti i frutti, trovi il nuovo “centro” di ogni macedonia calcolando le caratteristiche medie dei frutti in ciascun gruppo (ad esempio dimensione, colore, gusto). Questi nuovi centri diventano i rappresentanti aggiornati.
- Ripeti i passaggi 3 e 4 più volte, assegnando i frutti al rappresentante più simile e aggiornando ogni volta il rappresentante finché i raggruppamenti non cambiano più.

Più in generale:
- Scegli il numero di cluster, k.
- Seleziona casualmente k punti (cluster di dimensione 1).
- Calcola la distanza tra ogni punto e il centroide e assegna ciascun punto dati al cluster più vicino.
- Calcola il centroide (posizione media) per ciascun cluster.
- Continua a ripetere i passaggi 3–4 finché i cluster non cambiano oppure finché non si raggiunge il numero massimo di iterazioni.
Poiché k-means sceglie casualmente i centri dei cluster, l’algoritmo va eseguito un certo numero di volte, stabilito dall’utente. In questo modo si raggiunge la soluzione migliore, minimizzando una misura della qualità del modello. Questa misura è chiamata somma totale dei quadrati entro i cluster (WCSS), cioè la somma delle distanze tra i punti dati e il centroide corrispondente per ciascun cluster. In effetti, più è piccola la nostra misura di qualità del modello, più ci avviciniamo al modello “vincente”.
Dataset degli annunci Airbnb
Tutto il contenuto di questo tutorial ruoterà attorno agli annunci di affitti Airbnb a Città del Capo, disponibili su DataLab. I dati contengono diversi tipi di informazioni, come host, prezzo, numero di recensioni, coordinate e così via.
Anche se il dataset sembra fornire informazioni dettagliate sugli affitti per le vacanze, ci sono comunque alcune domande chiare a cui si può rispondere. Per esempio, immaginiamo che Airbnb chieda ai suoi data scientist di indagare sulla segmentazione degli annunci all’interno della piattaforma a Città del Capo.
Ogni città è diversa dalle altre e ha esigenze differenti a seconda della cultura delle persone che vi vivono. L’identificazione dei cluster in questa città può essere utile per estrarre nuove intuizioni, in grado di aumentare la soddisfazione dei clienti attuali con strategie di fidelizzazione adeguate, evitando l’abbandono. Allo stesso tempo, è anche fondamentale attrarre nuove persone con promozioni appropriate.
Visualizzare i dati Airbnb
Prima di applicare k-means, vogliamo indagare meglio la relazione tra le variabili dando un’occhiata alla matrice di correlazione. Per comodità di visualizzazione, arrotondiamo le cifre a due decimali.
library(dplyr)
airbnb |>
select(price, minimum_nights, number_of_reviews) |>
cor(use = "pairwise.complete.obs") |>
round(2)

Dall’output, sembra esserci una leggera relazione negativa tra prezzo e numero di recensioni: più alto è il prezzo, minore è il numero di recensioni. Lo stesso vale per notti minime e numero di recensioni. Poiché le notti minime non hanno un grande impatto sul prezzo, vogliamo approfondire come prezzo e numero di recensioni siano correlati:
library(ggplot2)
ggplot(data, aes(number_of_reviews, price, color = room_type, shape = room_type)) +
geom_point(alpha = 0.25) +
xlab("Number of reviews") +
ylab("Price")

Lo scatterplot mostra che il costo degli alloggi, in particolare delle case intere, è più alto quando c’è un numero ridotto di recensioni e sembra diminuire man mano che il numero di recensioni cresce.
Normalizzazione
Prima di adattare il modello, c’è un ulteriore passaggio da fare. k-means è sensibile alle variabili con unità non confrontabili, il che può portare a risultati fuorvianti. In questo esempio, il numero di recensioni è nell’ordine delle decine o centinaia, mentre il prezzo è nell’ordine delle decine di migliaia. Senza alcuna elaborazione dei dati, le differenze di prezzo sembrerebbero maggiori di quelle nelle recensioni, ma vogliamo che queste variabili siano trattate in modo equo.
Per evitare questo problema, è necessario trasformare le variabili su scale simili. In questo modo possono essere confrontate correttamente usando la metrica di distanza.
Esistono diversi metodi per affrontare la questione. Il più noto e usato è la standardizzazione, che consiste nel sottrarre il valore medio dalla caratteristica e poi dividerlo per la sua deviazione standard. Questa tecnica consente di ottenere caratteristiche con media 0 e deviazione 1.
Puoi scalare le variabili con la funzione scale(). Poiché questa restituisce una matrice, il codice è più pulito usando uno stile base-R piuttosto che tidyverse.
airbnb[, c("price", "number_of_reviews")] = scale(airbnb[, c("price", "number_of_reviews")])
Adattare e valutare il modello
Possiamo finalmente identificare i cluster degli annunci con k-means. Per iniziare, proviamo a eseguire k-means impostando 3 cluster e nstart uguale a 20. Questo ultimo parametro serve a eseguire k-means con 20 diverse assegnazioni iniziali casuali e poi R sceglierà automaticamente il miglior risultato in termini di somma totale dei quadrati entro i cluster. Impostiamo anche un seed per replicare gli stessi risultati a ogni esecuzione del codice.
# Get the two columns of interest
airbnb_2cols <- data[, c("price", "number_of_reviews")]
set.seed(123)
km.out <- kmeans(airbnb_2cols, centers = 3, nstart = 20)
km.out
Output:
K-means clustering with 3 clusters of sizes 785, 37, 16069
Cluster means:
price number_of_reviews
1 14264.102 5.9401274
2 83051.541 0.6756757
3 1589.879 18.2649200
Clustering vector:
[1] 3 3 3 3 3 3 3 3 1 3 3 3 3 3 3 3 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
[37] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
[73] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
[109] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3
[145] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 1 3 3
...
[16777] 3 3 3 3 3 3 3 3 3 3 1 3 3 1 3 1 3 3 3 3 3 1 3 3 3 3 3 3 3 3 1 3 3 3 3 3
[16813] 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 3
[16849] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 3 3 3 3 3 3 3 3 3 3 3
[16885] 3 3 3 3 3 3 3
Within cluster sum of squares by cluster:
[1] 41529148852 49002793251 33286433394
(between_SS / total_SS = 74.5 %)
Available components:
[1] "cluster" "centers" "totss" "withinss" "tot.withinss"
[6] "betweenss" "size" "iter" "ifault"
Dall’output possiamo osservare che sono stati trovati tre cluster diversi con dimensioni 785, 37 e 16069. Per ciascun cluster vengono calcolate le distanze al quadrato tra le osservazioni e i centroidi. Quindi, ogni osservazione verrà assegnata a uno dei tre cluster.
Anche se sembra un buon risultato, il modo migliore per trovare il modello ottimale è provare modelli diversi con un numero di cluster diverso. Dobbiamo quindi partire da un modello con un singolo cluster, poi provare con due cluster e così via. Tutta questa procedura va tracciata con una rappresentazione grafica, chiamata scree plot, in cui il numero di cluster è sull’asse x, mentre la WCSS è sull’asse y.
In questo case study, costruiamo 10 modelli k-means, ciascuno con un numero diverso di cluster, fino a un massimo di 10. Inoltre, useremo solo una parte del dataset, includendo quindi solo il prezzo e il numero di recensioni. Per tracciare lo scree plot, dobbiamo salvare la somma totale dei quadrati entro i cluster di tutti i modelli nella variabile wss.
# Decide how many clusters to look at
n_clusters <- 10
# Initialize total within sum of squares error: wss
wss <- numeric(n_clusters)
set.seed(123)
# Look over 1 to n possible clusters
for (i in 1:n) {
# Fit the model: km.out
km.out <- kmeans(airbnb_2cols, centers = i, nstart = 20)
# Save the within cluster sum of squares
wss[i] <- km.out$tot.withinss
}
# Produce a scree plot
wss_df <- tibble(clusters = 1:n, wss = wss)
scree_plot <- ggplot(wss_df, aes(x = clusters, y = wss, group = 1)) +
geom_point(size = 4)+
geom_line() +
scale_x_continuous(breaks = c(2, 4, 6, 8, 10)) +
xlab('Number of clusters')
scree_plot

Osservando lo scree plot, notiamo come la somma totale dei quadrati entro i cluster diminuisca all’aumentare del numero di cluster. Il criterio per scegliere il numero di cluster consiste nel trovare un “gomito”, cioè un punto in cui la WCSS diminuisce molto più lentamente dopo aver aggiunto un altro cluster. In questo caso non è così evidente, quindi aggiungeremo linee orizzontali per farci un’idea migliore:
scree_plot +
geom_hline(
yintercept = wss,
linetype = 'dashed',
col = c(rep('#000000',4),'#FF0000', rep('#000000', 5))
)

Se riguardi il grafico, la decisione sembra molto più chiara di prima, non credi? Da questa visualizzazione, possiamo dire che la scelta migliore è impostare il numero di cluster uguale a 5. Dopo k=5, i miglioramenti dei modelli sembrano ridursi nettamente.
# Select number of clusters
k <- 5
set.seed(123)
# Build model with k clusters: km.out
km.out <- kmeans(airbnb_2cols, centers = k, nstart = 20)
Possiamo provare di nuovo a visualizzare lo scatterplot tra prezzo e numero di recensioni. Coloriamo anche i punti in base all’id del cluster:
data$cluster_id <- factor(km.out$cluster)
ggplot(data, aes(number_of_reviews, price, color = cluster_id)) +
geom_point(alpha = 0.25) +
xlab("Number of reviews") +
ylab("Price")

Possiamo osservare che:
- Ci sono alcuni annunci con prezzi alti e poche recensioni, colorati in verde e fucsia
- Abbiamo anche un gruppo di punti con prezzi e recensioni entrambi bassi, segnati in rosso
- Dopodiché, il prezzo sembra stabilizzarsi non appena il numero di recensioni aumenta.
Quando fallisce k-means?
Attenzione: k-means è semplice e facile da applicare, ma non è sempre la scelta migliore per segmentare i dati in gruppi, perché può fallire. Si assume che i cluster siano sferici e quindi funziona bene in questi casi, mentre gruppi con dimensioni e densità diverse tendono a non essere catturati bene da questo algoritmo.
Quando queste condizioni non sono rispettate, è preferibile adottare approcci alternativi, come DBSCAN e BIRCH. Clustering in Machine Learning: 5 Essential Clustering Algorithms offre un’ottima panoramica degli approcci al clustering se vuoi approfondire.
Portalo al livello successivo
Possiamo concludere che k-means rimane uno degli algoritmi di clustering più usati per identificare sottogruppi distinti, anche se non è sempre perfetto in tutte le situazioni. Ora hai le conoscenze per applicarlo con R in altri casi di studio
Se vuoi approfondire ulteriormente questo metodo, dai un’occhiata al corso Unsupervised Learning in R. Ci sono anche Cluster Analysis in R e An Introduction to Hierarchical Clustering in Python per avere una panoramica completa degli approcci di clustering disponibili, utili quando k-means non basta a fornire insight significativi dai tuoi dati. Se vuoi anche esplorare i modelli supervisionati con R, questo corso è consigliato!


