Hai ragione sul fatto che il clustering k-mean non dovrebbe essere fatto con dati di tipi misti. Dato che k-mean è essenzialmente un semplice algoritmo di ricerca per trovare una partizione che minimizzi le distanze euclidee all'interno del cluster tra le osservazioni raggruppate e il centroide del cluster, dovrebbe essere usato solo con dati in cui le distanze euclidee al quadrato sarebbero significative.
Quando i tuoi dati sono costituiti da variabili di tipo misto, devi utilizzare la distanza di Gower. L'utente CV @ttnphns ha un'ottima panoramica della distanza di Gower qui . In sostanza, si calcola una matrice di distanza per le righe per ogni variabile a turno, utilizzando un tipo di distanza appropriato per quel tipo di variabile (ad esempio, Euclidean per dati continui, ecc.); la distanza finale della riga da a è la media (possibilmente ponderata) delle distanze per ciascuna variabile. Una cosa da tenere presente è che la distanza di Gower non è in realtà una metrica . Tuttavia, con dati contrastanti, la distanza di Gower è in gran parte l'unico gioco in città. ioio'
A questo punto, è possibile utilizzare qualsiasi metodo di clustering in grado di operare su una matrice di distanza anziché richiedere la matrice di dati originale. (Nota che k- mean ha bisogno di quest'ultimo.) Le scelte più popolari sono il partizionamento attorno ai medoidi (PAM, che è essenzialmente lo stesso di k- medie , ma utilizza l'osservazione più centrale anziché il centroide), vari approcci di raggruppamento gerarchico (ad es. , mediana, single-linkage e complete-linkage; con il clustering gerarchico dovrai decidere dove ' tagliare l'albero ' per ottenere le assegnazioni finali del cluster) e DBSCAN che consente forme di cluster molto più flessibili.
Ecco una semplice R
demo (nb, in realtà ci sono 3 cluster, ma i dati sembrano principalmente 2 cluster appropriati):
library(cluster) # we'll use these packages
library(fpc)
# here we're generating 45 data in 3 clusters:
set.seed(3296) # this makes the example exactly reproducible
n = 15
cont = c(rnorm(n, mean=0, sd=1),
rnorm(n, mean=1, sd=1),
rnorm(n, mean=2, sd=1) )
bin = c(rbinom(n, size=1, prob=.2),
rbinom(n, size=1, prob=.5),
rbinom(n, size=1, prob=.8) )
ord = c(rbinom(n, size=5, prob=.2),
rbinom(n, size=5, prob=.5),
rbinom(n, size=5, prob=.8) )
data = data.frame(cont=cont, bin=bin, ord=factor(ord, ordered=TRUE))
# this returns the distance matrix with Gower's distance:
g.dist = daisy(data, metric="gower", type=list(symm=2))
Possiamo iniziare cercando diversi numeri di cluster con PAM:
# we can start by searching over different numbers of clusters with PAM:
pc = pamk(g.dist, krange=1:5, criterion="asw")
pc[2:3]
# $nc
# [1] 2 # 2 clusters maximize the average silhouette width
#
# $crit
# [1] 0.0000000 0.6227580 0.5593053 0.5011497 0.4294626
pc = pc$pamobject; pc # this is the optimal PAM clustering
# Medoids:
# ID
# [1,] "29" "29"
# [2,] "33" "33"
# Clustering vector:
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
# 1 1 1 1 1 2 1 1 1 1 1 2 1 2 1 2 2 1 1 1 2 1 2 1 2 2
# 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
# 1 2 1 2 2 1 2 2 2 2 1 2 1 2 2 2 2 2 2
# Objective function:
# build swap
# 0.1500934 0.1461762
#
# Available components:
# [1] "medoids" "id.med" "clustering" "objective" "isolation"
# [6] "clusinfo" "silinfo" "diss" "call"
Tali risultati possono essere confrontati con i risultati del clustering gerarchico:
hc.m = hclust(g.dist, method="median")
hc.s = hclust(g.dist, method="single")
hc.c = hclust(g.dist, method="complete")
windows(height=3.5, width=9)
layout(matrix(1:3, nrow=1))
plot(hc.m)
plot(hc.s)
plot(hc.c)
Il metodo mediano suggerisce 2 (forse 3) cluster, il singolo supporta solo 2, ma il metodo completo potrebbe suggerire 2, 3 o 4 ai miei occhi.
Finalmente, possiamo provare DBSCAN. Ciò richiede di specificare due parametri: eps, la "distanza di raggiungibilità" (quanto due osservazioni ravvicinate devono essere collegate tra loro) e minPts (il numero minimo di punti che devono essere collegati tra loro prima di essere disposti a chiamarli un 'grappolo'). Una regola empirica per minPts è quella di utilizzare uno in più rispetto al numero di dimensioni (nel nostro caso 3 + 1 = 4), ma non è consigliabile avere un numero troppo piccolo. Il valore predefinito per dbscan
è 5; ci atteniamo a quello. Un modo di pensare alla distanza raggiungibile è vedere quale percentuale delle distanze è inferiore a qualsiasi valore dato. Possiamo farlo esaminando la distribuzione delle distanze:
windows()
layout(matrix(1:2, nrow=1))
plot(density(na.omit(g.dist[upper.tri(g.dist)])), main="kernel density")
plot(ecdf(g.dist[upper.tri(g.dist)]), main="ECDF")
Le distanze stesse sembrano raggrupparsi in gruppi visivamente distinguibili di "più vicino" e "più lontano". Un valore di .3 sembra distinguere in modo più netto tra i due gruppi di distanze. Per esplorare la sensibilità dell'output alle diverse scelte di eps, possiamo provare anche .2 e .4:
dbc3 = dbscan(g.dist, eps=.3, MinPts=5, method="dist"); dbc3
# dbscan Pts=45 MinPts=5 eps=0.3
# 1 2
# seed 22 23
# total 22 23
dbc2 = dbscan(g.dist, eps=.2, MinPts=5, method="dist"); dbc2
# dbscan Pts=45 MinPts=5 eps=0.2
# 1 2
# border 2 1
# seed 20 22
# total 22 23
dbc4 = dbscan(g.dist, eps=.4, MinPts=5, method="dist"); dbc4
# dbscan Pts=45 MinPts=5 eps=0.4
# 1
# seed 45
# total 45
L'utilizzo eps=.3
fornisce una soluzione molto pulita, che (almeno qualitativamente) concorda con ciò che abbiamo visto da altri metodi sopra.
Poiché non esiste un cluster 1 significativo , dovremmo fare attenzione a cercare di abbinare quali osservazioni sono chiamate "cluster 1" da cluster diversi. Invece, possiamo formare tabelle e se la maggior parte delle osservazioni chiamate "cluster 1" in un adattamento sono chiamate "cluster 2" in un altro, vedremmo che i risultati sono ancora sostanzialmente simili. Nel nostro caso, i diversi cluster sono per lo più molto stabili e mettono ogni volta le stesse osservazioni negli stessi cluster; differisce solo il clustering gerarchico di collegamento completo:
# comparing the clusterings
table(cutree(hc.m, k=2), cutree(hc.s, k=2))
# 1 2
# 1 22 0
# 2 0 23
table(cutree(hc.m, k=2), pc$clustering)
# 1 2
# 1 22 0
# 2 0 23
table(pc$clustering, dbc3$cluster)
# 1 2
# 1 22 0
# 2 0 23
table(cutree(hc.m, k=2), cutree(hc.c, k=2))
# 1 2
# 1 14 8
# 2 7 16
Naturalmente, non vi è alcuna garanzia che qualsiasi analisi dei cluster ripristinerà i veri cluster latenti nei dati. L'assenza delle vere etichette dei cluster (che sarebbero disponibili, per esempio, in una situazione di regressione logistica) significa che un'enorme quantità di informazioni non è disponibile. Anche con set di dati molto grandi, i cluster potrebbero non essere sufficientemente ben separati per essere perfettamente recuperabili. Nel nostro caso, poiché conosciamo la vera appartenenza al cluster, possiamo confrontarla con l'output per vedere come è andata bene. Come ho notato sopra, in realtà ci sono 3 cluster latenti, ma i dati danno invece l'aspetto di 2 cluster:
pc$clustering[1:15] # these were actually cluster 1 in the data generating process
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# 1 1 1 1 1 2 1 1 1 1 1 2 1 2 1
pc$clustering[16:30] # these were actually cluster 2 in the data generating process
# 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
# 2 2 1 1 1 2 1 2 1 2 2 1 2 1 2
pc$clustering[31:45] # these were actually cluster 3 in the data generating process
# 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
# 2 1 2 2 2 2 1 2 1 2 2 2 2 2 2