Você está certo de que o cluster k-means não deve ser feito com dados de tipos mistos. Como o k-means é essencialmente um algoritmo de busca simples para encontrar uma partição que minimiza as distâncias euclidianas quadradas dentro do cluster entre as observações em cluster e o centróide do cluster, ela só deve ser usada com dados em que as distâncias euclidianas quadradas seriam significativas.
Quando seus dados consistem em variáveis de tipos mistos, você precisa usar a distância de Gower. O usuário do CV @ttnphns tem uma ótima visão geral da distância de Gower aqui . Em essência, você calcula uma matriz de distância para suas linhas para cada variável, por sua vez, usando um tipo de distância apropriado para esse tipo de variável (por exemplo, Euclidiano para dados contínuos, etc.); a distância final da linha a i ' é a média (possivelmente ponderada) das distâncias para cada variável. Uma coisa a ter em atenção é que a distância de Gower não é realmente uma métrica . No entanto, com dados mistos, a distância de Gower é em grande parte o único jogo na cidade. EuEu′
Nesse ponto, você pode usar qualquer método de cluster que possa operar sobre uma matriz de distância em vez de precisar da matriz de dados original. (Observe que o k-means precisa do último.) As opções mais populares são a partição em torno do medoids (PAM, que é essencialmente o mesmo que o k-means, mas usa a observação mais central em vez do centróide), várias abordagens hierárquicas de agrupamento (por exemplo, , mediana, ligação única e ligação completa; com cluster hierárquico, você precisará decidir onde ' cortar a árvore ' para obter as atribuições finais do cluster) e o DBSCAN, que permite formas de cluster muito mais flexíveis.
Aqui está uma R
demonstração simples (nb, na verdade, existem 3 clusters, mas os dados geralmente se parecem com 2 clusters):
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))
Podemos começar pesquisando diferentes números de clusters com o 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"
Esses resultados podem ser comparados aos resultados do armazenamento em cluster hierárquico:
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)
O método mediano sugere 2 (possivelmente 3) grupos, o único suporta apenas 2, mas o método completo pode sugerir 2, 3 ou 4 ao meu olho.
Finalmente, podemos tentar o DBSCAN. Isso requer a especificação de dois parâmetros: eps, a 'distância de alcançabilidade' (a que distância duas observações devem ser conectadas) e minPts (o número mínimo de pontos que precisam ser conectados um ao outro antes que você queira chamá-los de 'grupo'). Uma regra prática para minPts é usar uma a mais que o número de dimensões (no nosso caso 3 + 1 = 4), mas não é recomendável ter um número muito pequeno. O valor padrão para dbscan
é 5; nós vamos ficar com isso. Uma maneira de pensar sobre a distância de alcance é ver qual porcentagem das distâncias é menor que qualquer valor. Podemos fazer isso examinando a distribuição das distâncias:
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")
As próprias distâncias parecem agrupar-se em grupos visualmente discerníveis de 'mais perto' e 'mais longe'. Um valor de .3 parece distinguir mais claramente entre os dois grupos de distâncias. Para explorar a sensibilidade da saída a diferentes opções de eps, também podemos tentar .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
O uso eps=.3
fornece uma solução muito limpa, que (pelo menos qualitativamente) concorda com o que vimos nos outros métodos acima.
Como não existe um cluster 1-ness significativo , devemos ter o cuidado de tentar corresponder quais observações são chamadas 'cluster 1' de diferentes agrupamentos. Em vez disso, podemos formar tabelas e se a maioria das observações chamadas 'cluster 1' em um ajuste for chamada 'cluster 2' em outro, veríamos que os resultados ainda são substancialmente semelhantes. No nosso caso, os diferentes agrupamentos são geralmente muito estáveis e colocam as mesmas observações nos mesmos agrupamentos de cada vez; somente o cluster hierárquico de ligação completo difere:
# 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
Obviamente, não há garantia de que qualquer análise de cluster recupere os verdadeiros clusters latentes em seus dados. A ausência dos verdadeiros rótulos de cluster (que estariam disponíveis em, por exemplo, uma situação de regressão logística) significa que uma enorme quantidade de informações não está disponível. Mesmo com conjuntos de dados muito grandes, os clusters podem não estar suficientemente bem separados para serem perfeitamente recuperáveis. No nosso caso, como conhecemos a verdadeira associação de cluster, podemos compará-la com a saída para ver como ela se saiu. Como observei acima, na verdade existem 3 clusters latentes, mas os dados dão a aparência 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