Вы правы, что кластеризация k-средних не должна выполняться с данными смешанных типов. Поскольку k-means - это по сути простой алгоритм поиска, позволяющий найти разбиение, которое минимизирует квадратные евклидовы расстояния внутри кластера между кластерными наблюдениями и центроидом кластера, его следует использовать только с данными, для которых квадратные евклидовы расстояния будут иметь смысл.
яя'
На этом этапе вы можете использовать любой метод кластеризации, который может работать с матрицей расстояний, вместо использования исходной матрицы данных. (Обратите внимание, что для k-средних требуется последнее.) Наиболее популярным выбором является разбиение вокруг медоидов (PAM, которое по сути то же самое, что и k-means, но использует наиболее центральное наблюдение, а не центроид), различные подходы иерархической кластеризации (например, , медиана, одиночная связь и полная связь; при иерархической кластеризации вам нужно будет решить, где « вырезать дерево » для получения окончательных назначений кластера), и DBSCAN, который обеспечивает гораздо более гибкие формы кластера.
Вот простая R
демонстрация (примечание, на самом деле есть 3 кластера, но данные выглядят в основном как 2 кластера):
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))
Мы можем начать с поиска по разному количеству кластеров с помощью 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"
Эти результаты можно сравнить с результатами иерархической кластеризации:
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)
Медианный метод предполагает 2 (возможно, 3) кластера, единственный поддерживает только 2, но полный метод может предложить 2, 3 или 4 на мой взгляд.
Наконец, мы можем попробовать DBSCAN. Для этого необходимо указать два параметра: eps, «расстояние достижимости» (насколько близко два наблюдения должны быть связаны друг с другом) и minPts (минимальное количество точек, которые необходимо соединить друг с другом, прежде чем вы захотите назвать их 'кластер'). Основное правило для minPts - использовать на единицу больше, чем количество измерений (в нашем случае 3 + 1 = 4), но слишком маленькое число не рекомендуется. Значение по умолчанию для dbscan
5; мы будем придерживаться этого. Один из способов думать о расстоянии достижимости - посмотреть, какой процент расстояний меньше любого заданного значения. Мы можем сделать это, изучив распределение расстояний:
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")
Сами расстояния, кажется, сгруппированы в визуально различимые группы «ближе» и «дальше». Значение .3 наиболее четко различает две группы расстояний. Чтобы изучить чувствительность вывода к различным вариантам eps, мы также можем попробовать .2 и .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
Использование eps=.3
действительно дает очень чистое решение, которое (по крайней мере, качественно) согласуется с тем, что мы видели из других методов выше.
Поскольку нет значимой кластерности 1 , мы должны быть осторожны, пытаясь сопоставить, какие наблюдения называются «кластером 1» из разных кластеров. Вместо этого мы можем формировать таблицы, и если большинство наблюдений, называемых «кластер 1» в одном подходе, называются «кластер 2» в другом, мы увидим, что результаты по-прежнему практически схожи. В нашем случае разные кластеры в основном очень стабильны и каждый раз помещают одни и те же наблюдения в одни и те же кластеры; отличается только полная иерархическая кластеризация связей:
# 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
Конечно, нет никакой гарантии, что любой кластерный анализ восстановит истинные скрытые кластеры в ваших данных. Отсутствие истинных меток кластера (которые будут доступны, скажем, в ситуации логистической регрессии) означает, что огромное количество информации недоступно. Даже с очень большими наборами данных кластеры могут быть недостаточно хорошо разделены, чтобы их можно было полностью восстановить. В нашем случае, поскольку мы знаем истинное членство в кластере, мы можем сравнить это с выходными данными, чтобы увидеть, насколько хорошо это было сделано. Как я отмечал выше, на самом деле существует 3 скрытых кластера, но вместо этого данные выглядят как 2 кластера:
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