Vous avez raison de dire que le clustering k-means ne doit pas être effectué avec des données de types mixtes. Étant donné que k-means est essentiellement un algorithme de recherche simple pour trouver une partition qui minimise les distances euclidiennes au sein d'un cluster entre les observations groupées et le centroïde du cluster, il ne doit être utilisé qu'avec des données où les distances euclidiennes au carré seraient significatives.
Lorsque vos données sont constituées de variables de types mixtes, vous devez utiliser la distance de Gower. Utilisateur CV @ttnphns a un bon aperçu de la distance de Gower ici . Essentiellement, vous calculez tour à tour une matrice de distance pour vos lignes pour chaque variable, en utilisant un type de distance qui convient à ce type de variable (par exemple, Euclidien pour les données continues, etc.); la distance finale de la ligne à est la moyenne (éventuellement pondérée) des distances pour chaque variable. Une chose à savoir est que la distance de Gower n'est pas réellement une métrique . Néanmoins, avec des données mitigées, la distance de Gower est largement le seul jeu en ville. i ′jeje′
À ce stade, vous pouvez utiliser n'importe quelle méthode de clustering qui peut fonctionner sur une matrice de distance au lieu d'avoir besoin de la matrice de données d'origine. (Notez que k-means a besoin de ce dernier.) Les choix les plus populaires sont le partitionnement autour des médoïdes (PAM, qui est essentiellement le même que k-means, mais utilise l'observation la plus centrale plutôt que le centroïde), diverses approches de regroupement hiérarchique (par exemple , médiane, liaison simple et liaison complète; avec le clustering hiérarchique, vous devrez décider où `` couper l'arborescence '' pour obtenir les affectations finales du cluster), et DBSCAN qui permet des formes de cluster beaucoup plus flexibles.
Voici une R
démo simple (nb, il y a en fait 3 clusters, mais les données ressemblent surtout à 2 clusters appropriés):
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))
Nous pouvons commencer par rechercher sur différents nombres de clusters avec 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"
Ces résultats peuvent être comparés aux résultats du clustering hiérarchique:
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)
La méthode médiane suggère 2 (peut-être 3) grappes, le single ne supporte que 2, mais la méthode complète pourrait suggérer 2, 3 ou 4 à mon œil.
Enfin, nous pouvons essayer DBSCAN. Cela nécessite de spécifier deux paramètres: eps, la `` distance d'accessibilité '' (à quelle distance deux observations doivent être reliées ensemble) et minPts (le nombre minimum de points qui doivent être connectés les uns aux autres avant de vouloir les appeler un 'grappe'). Une règle de base pour minPts est d'utiliser une de plus que le nombre de dimensions (dans notre cas 3 + 1 = 4), mais avoir un nombre trop petit n'est pas recommandé. La valeur par défaut pour dbscan
est 5; nous nous en tiendrons à cela. Une façon de penser à la distance d'accessibilité est de voir quel pourcentage des distances est inférieur à une valeur donnée. Nous pouvons le faire en examinant la distribution des distances:
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")
Les distances elles-mêmes semblent se regrouper en groupes visuellement perceptibles de «plus près» et «plus loin». Une valeur de 0,3 semble faire la distinction la plus nette entre les deux groupes de distances. Pour explorer la sensibilité de la sortie à différents choix d'eps, nous pouvons également essayer .2 et .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'utilisation eps=.3
donne une solution très propre, qui (au moins qualitativement) est en accord avec ce que nous avons vu des autres méthodes ci-dessus.
Puisqu'il n'y a pas de cluster 1 significatif , nous devons faire attention d'essayer de faire correspondre les observations qui sont appelées `` cluster 1 '' à partir de différents clusters. Au lieu de cela, nous pouvons former des tableaux et si la plupart des observations appelées «cluster 1» dans un ajustement sont appelées «cluster 2» dans un autre, nous verrions que les résultats sont toujours substantiellement similaires. Dans notre cas, les différents clusters sont pour la plupart très stables et mettent à chaque fois les mêmes observations dans les mêmes clusters; seul le clustering hiérarchique de liaison complet diffère:
# 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
Bien sûr, rien ne garantit qu'une analyse de cluster récupérera les véritables clusters latents dans vos données. L'absence des véritables étiquettes de cluster (qui seraient disponibles, par exemple, dans une situation de régression logistique) signifie qu'une énorme quantité d'informations n'est pas disponible. Même avec de très grands ensembles de données, les clusters peuvent ne pas être suffisamment bien séparés pour être parfaitement récupérables. Dans notre cas, puisque nous connaissons la véritable appartenance à un cluster, nous pouvons comparer cela à la sortie pour voir dans quelle mesure cela a fonctionné. Comme je l'ai noté ci-dessus, il y a en fait 3 clusters latents, mais les données donnent plutôt l'apparence de 2 clusters:
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