如何在聚类中同时使用二进制变量和连续变量?

机器算法验证 r 聚类 二进制数据 k-均值 混合类型数据
2022-01-18 20:55:10

我需要在 k-means 中使用二进制变量(值 0 和 1)。但是 k-means 仅适用于连续变量。我知道有些人仍然在 k-means 中使用这些二进制变量,而忽略了 k-means 仅适用于连续变量的事实。这对我来说是不可接受的。

问题:

  1. 那么在 k-means/层次聚类中使用二进制变量的统计/数学正确方法是什么?
  2. 如何在 SAS/R 中实施解决方案?
2个回答

你是对的,不应该对混合类型的数据进行 k-means 聚类。由于 k-means 本质上是一种简单的搜索算法,用于找到最小化聚类观测值和聚类质心之间的聚类内平方欧几里德距离的分区,因此它只能用于平方欧几里德距离有意义的数据。

当您的数据由混合类型的变量组成时,您需要使用 Gower 距离。CV 用户@ttnphns在这里对高尔的距离有一个很好的概述本质上,您依次为每个变量的行计算距离矩阵,使用适合该类型变量的距离类型(例如,欧几里得用于连续数据等);行到的最终距离是每个变量的距离的(可能是加权的)平均值。需要注意的一件事是,高尔的距离实际上并不是一个度量尽管如此,由于数据参差不齐,高尔的距离在很大程度上是城里唯一的比赛。 ii

此时,您可以使用任何可以对距离矩阵进行操作的聚类方法,而不需要原始数据矩阵。(请注意,k-means 需要后者。)最流行的选择是围绕中心点进行分区(PAM,它与 k-means 基本相同,但使用最中心的观察而不是质心),各种层次聚类方法(例如,中值,单链接和完全链接;使用层次聚类,您需要决定在哪里“切割树”以获得最终的聚类分配),以及允许更灵活的聚类形状的 DBSCAN 。

这是一个简单的R演示(nb,实际上有 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(在您愿意称它们为 a 之前需要相互连接的最小点数) '簇')。minPts 的一个经验法则是使用比维数多一个(在我们的例子中为 3+1=4),但不建议使用太小的数字。的默认值为dbscan5;我们会坚持下去。考虑可达距离的一种方法是查看距离的百分比小于任何给定值。我们可以通过检查距离的分布来做到这一点:

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")

在此处输入图像描述

距离本身似乎聚集成视觉上可辨别的“更近”和“更远”组。0.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

Usingeps=.3确实提供了一个非常干净的解决方案,它(至少在质量上)与我们从上述其他方法中看到的一致。

由于没有有意义的集群 1-ness,我们应该小心尝试匹配来自不同集群的哪些观察被称为“集群 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 

查看 Finch 的这篇论文,http://www.jds-online.com/files/JDS-192.pdf它描述了为什么将连续方法应用于二进制数据可能会不准确地对数据进行聚类,更重要的是,在适当的距离函数中有哪些选择。它没有回答如何使用 k-means 进行聚类,而是回答如何使用非欧几里德度量和 Ward 等分层方法正确聚类二进制数据。