分析技术研习室

Logo

课题组每周研讨会

View the Project on GitHub XSLiuLab/Workshop

第十期:聚类

聚类分析的思想:对于有p个变量的数据集来说,每个观测值都是p维空间中的一个点,所以属于同一类的点在空间中的距离应该显著小于属于不同类的点之间的距离

聚类距离测度

1.欧氏(Euclidean)距离: \(d_{euc}(x, y)=\sqrt{\sum_{i=1}^{n}(x_i-y_i)^2}\) 2.曼哈顿(Manhattan)距离: \(d_{man}(x,y)=\sum_{i=1}^{n}|(x_i-y_i)|\) x,y都是n维的向量

还有一些不相似性度量也可以来表示距离:

3.Pearson线性相关距离:也就是1减去Pearson相关系数,Pearson相关是衡量两个变量的线性相关性的,对数据的假设:Pearson相关系数应用于连续变量,假定两组变量均为正态分布、存在线性关系且等方差 \(d_{cor}(x,y)=1-\frac{\sum_{i=1}^{n}(x_i-\bar{x})(y_i-\bar{y})}{\sqrt{\sum_{i=1}^{n}(x_i-\bar{x})^2\sum_{i=1}^{n}(y_i-\bar{y})^2}}\) 4.cosine相关距离:是Pearson相关的一个特例,就是将$\bar{x}$和$\bar{y}$用0代替 \(d_{cos}(x,y)=1-\frac{|\sum_{i=1}^nx_iy_i|}{\sqrt{\sum_{i=1}^nx_i^2\sum_{i=1}^ny_i^2}}\) 5.Spearman相关距离:spearman相关计算的是变量秩之间的相关性,也是1减去Spearman相关系数 \(d_{spear}(x,y)=1-\frac{\sum_{i=1}^{n}(x_i’-\bar{x’})(y_i’-\bar{y’})}{\sqrt{\sum_{i=1}^{n}(x_i’-\bar{x’})^2\sum_{i=1}^{n}(y_i’-\bar{y’})^2}}\) ​ $x_i’,y_i’$ 是$x_i,y_i$的秩

6.Kendall 相关距离:

Kendall相关方法是衡量变量秩的correspondence

对于大小是n的变量x和y,可能的匹配对数是$\frac{n(n-1)}{2}$ ;首先按照x对xy对进行排序,如果xy是相关的,x和y应该有一样的秩序;对于每个$y_i$计算大于$y_i$的y数量(concordant pairs (c))和小于$y_i$的y数量(discordant pairs (d)): \(d_{kend}(x,y)=1-\frac{n_c-n_d}{\frac{1}{2}n(n-1)}\) $n_c$是concordant pairs的总数量,$n_d$是discordant pairs的总数量

距离的选择

如果我们关注的是变量的变化趋势而不是变量的具体的值的时候,比如基因的表达,我们就可以使用基于相关性的距离测度,另外要注意的是pearson相关对outliers比较敏感,这个时候可以使用spearman相关

当我们关注的是变量的值的大小,可以使用欧氏距离来聚类

数据标准化

当变量是由不同的标度测量的时候,最好要对数据进行标准化使之可以进行比较;一般情况在下对变量进行缩放使之:标准差是1,均值是0;当变量的均值或者标准差相差较大的时候也可以对数据进行scale: \(\frac{x_i-center(x)}{scale(x)}\) center(x)可以是均值或者中位数;scale(x)可以是标准差,四分位间距,或者绝对中位差(median absolute deviation,MAD),R里面可以使用scale() 函数进行标准化

MAD的定义:数据点到中位数的绝对偏差的中位数

$MAD=median( X_i-median(X) )$

计算距离矩阵

使用的数据集为USArrests:

df <- USArrests
df_scaled <- scale(df)##标准化

计算距离的R函数有很多,如:

注意:这些计算距离的函数都是计算行之间的距离,所以如果我们要计算变量(列)之间的距离,先要将其转置

dist.eucl <- dist(df_scaled,method = "euclidean")##欧氏距离
class(dist.eucl)#[1] "dist"
as.matrix(dist.eucl)[1:3,1:3]
#         Alabama   Alaska  Arizona
# Alabama 0.000000 2.703754 2.293520
# Alaska  2.703754 0.000000 2.700643
# Arizona 2.293520 2.700643 0.000000
library(factoextra)
dist_cor <- get_dist(df_scaled,method = "pearson")##相关性距离
as.matrix(dist_cor)[1:3,1:3]
#          Alabama    Alaska   Arizona
# Alabama 0.0000000 0.7138308 1.4465948
# Alaska  0.7138308 0.0000000 0.8307246
# Arizona 1.4465948 0.8307246 0.0000000

library(cluster)
data("flower")
head(flower,3)
#   V1 V2 V3 V4 V5 V6  V7 V8
# 1  0  1  1  4  3 15  25 15
# 2  1  0  0  2  1  3 150 50
# 3  0  1  0  3  3  1 150 50
str(flower)
# 'data.frame':	18 obs. of  8 variables:
# $ V1: Factor w/ 2 levels "0","1": 1 2 1 1 1 1 1 1 2 2 ...
# $ V2: Factor w/ 2 levels "0","1": 2 1 2 1 2 2 1 1 2 2 ...
# $ V3: Factor w/ 2 levels "0","1": 2 1 1 2 1 1 1 2 1 1 ...
# $ V4: Factor w/ 5 levels "1","2","3","4",..: 4 2 3 4 5 4 4 2 3 5 ...
# $ V5: Ord.factor w/ 3 levels "1"<"2"<"3": 3 1 3 2 2 3 3 2 1 2 ...
# $ V6: Ord.factor w/ 18 levels "1"<"2"<"3"<"4"<..: 15 3 1 16 2 12 13 7 4 14 ...
# $ V7: num  25 150 150 125 20 50 40 100 25 100 ...
# $ V8: num  15 50 50 50 15 40 20 15 15 60 ...
dd <- daisy(flower)##计算含有分类变量,定序变量的距离
as.matrix(dd)[1:3,1:3]
#         1         2         3
# 1 0.0000000 0.8875408 0.5272467
# 2 0.8875408 0.0000000 0.5147059
# 3 0.5272467 0.5147059 0.0000000

划分聚类(Partitioning clustering)

划分聚类需要我们指定类别的数量

最常用的有:

K均值聚类

k表示我们想要数据聚成的类数,最终的结果是实现高的类内相似性和低的类间相似性 \(W(C_k)=\sum_{xi\in C_k}(x_i-\mu_k)^2\) $x_i$是属于$C_k$类的数据点,$\mu_k$是$C_k$类的中心点,也就是属于$C_k$类的所有数据点的均值,所以$W(C_k)$表示了类内的variation,定义总的within-cluster variation为: \(tot.withiness=\sum^k_{k=1}W(C_k)=\sum^k_{k=1}\sum_{xi\in C_k}(x_i-\mu_k)^2\) 我们的目的就是使上式最小化

算法

R

kmeans(x, centers, iter.max = 10, nstart = 1)

如何选择最佳聚类数?

一个简单的方法就是尝试不同的聚类数目k,计算上面的total within sum of square;随着聚类数目的增加WSS的趋势一定会下降(最极端的情况就是每个点都是一个类),当k小于真实聚类数时WSS下降幅度会很大,而当k大于真实聚类数时,再增加k WSS的下降幅度会骤减,所以会存在一个拐点

library(factoextra)
fviz_nbclust(df_scaled,kmeans,method = "wss")+
  geom_vline(xintercept = 4,linetype=2)

image-20200720233852847

最佳聚类数为4:

set.seed(2020720)
km_res <- kmeans(df_scaled,4,nstart = 25)
print(km_res)

names(km_res)
# [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
# [6] "betweenss"    "size"         "iter"         "ifault"   

结果包括聚类的中心点,每个观测值所属的类,wss

对于高维的数据我们可以先降维再可视化聚类的结果:

fviz_cluster(km_res, data = df_scaled)

image-20200720235320015

K-Medoids

在k-medoids聚类中每个类由类内的某个点来代替,这些点就叫聚类中心(cluster medoids)

在 K-means 算法中,我们每次选簇的平均值作为新的中心,迭代直到簇中对象分布不再变化。因此一个具有很大极端值的对象会扭曲数据分布,造成算法对极端值敏感; K-Medoids算法不选用平均值而是用中心点作为参照点

最常用的k-medoids聚类方法是PAM算法(Partitioning Around Medoids)

PAM 算法

R

cluster::pam(x, k, metric = "euclidean", stand = FALSE)

首先需要估计最佳聚类数,可以使用平均轮廓法(average silhouette method),平均轮廓值越高说明聚类质量越好

可以使用factoextra 包中的fviz_nbclust函数来计算:

fviz_nbclust(df_scaled,pam,method = "silhouette")+
  theme_classic()

image-20200721222804298

最佳聚类数为2:

pam_res <- pam(df_scaled,2)
names(pam_res)
# [1] "medoids"    "id.med"     "clustering" "objective"  "isolation"  "clusinfo"  
# [7] "silinfo"    "diss"       "call"       "data" 

继续使用fviz_cluster进行PCA降维后可视化聚类的效果:

fviz_cluster(pam_res, data = df_scaled)

image-20200721223407152

CLARA聚类

CLARA (Clustering Large Applications)是k-medoids聚类的延伸,用来处理比较大的数据集

算法

R

##generate data
set.seed(2020721)
df <- rbind(cbind(rnorm(200,0,8), rnorm(200,0,8)),
            cbind(rnorm(300,50,8), rnorm(300,50,8)))
colnames(df) <- c("x","y")
rownames(df) <- paste("S",1:nrow(df),sep = "")
cluster::clara(x, k, metric = "euclidean", stand = FALSE, samples = 5, pamLike = FALSE)

首先使用silhouette方法来估计最佳聚类数:

fviz_nbclust(df, clara, method = "silhouette")+
  theme_classic()

image-20200721233550297

最佳聚类数为2:

clara_res <- clara(df,2,samples = 50,pamLike = T)
print(clara_res)
names(clara_res)
# [1] "sample"     "medoids"    "i.med"      "clustering" "objective"  "clusinfo"  
# [7] "diss"       "call"       "silinfo"    "data" 
fviz_cluster(clara_res,data = df)

image-20200721234056740

层次聚类(Hierarchical clustering)

层次聚类和划分聚类一个显著不同就是层次聚类不需要预先规定聚类数目

image-20200722083259840

凝聚聚类

连接函数获取由函数dist()返回的距离信息,并根据对象的相似性将对象对分组;重复此过程,直到原始数据集中的所有对象在层次树中链接在一起为止

res_hc <- stats::hclust(d = dist.eucl, method = "ward.D2")

主要使用的连接函数(也就是类间距离)有:

plot(res_hc)
fviz_dend(res_hc, cex = 0.5)

image-20200722133701585

连接两个对象的竖线的高度衡量了这两个对象的距离,越长距离越大,这个高度也叫这两个对象的共同距离cophenetic distance

两个点的共同距离是这两个点第一次被聚在一起时的节点的高度,截取一小部分说明:

image-20200725182546137

我们可以看聚类树的共同距离和原始的距离矩阵的相似性来衡量聚类的好坏:

res_coph <- cophenetic(res_hc)
cor(dist_eucl, res_coph)#[1] 0.6975266

res_hc2 <- hclust(dist_eucl, method = "average") ##类平均法
cor(dist_eucl, cophenetic(res_hc2))#[1] 0.7180382

可以使用cutree函数来分割树进行聚类结果的展示:

cluster <- cutree(res_hc, k = 4)
table(cluster)
# cluster
# 1  2  3  4 
# 7 12 19 12 

factoextra::fviz_dend(res_hc, k = 4,rect = TRUE)##可视化

fviz_cluster(list(data = df_scaled, cluster = cluster))##先PCA再展现聚类结果

image-20200722133714891

image-20200722133728396

另外cluster包也可以很方便的进行凝聚或者分裂聚类:

library("cluster")
# Agglomerative 
res.agnes <- agnes(x = USArrests, # data matrix
                   stand = TRUE, # Standardize the data
                   metric = "euclidean", # metric for distance matrix method = "ward" # Linkage method
)
# Divisive
res.diana <- diana(x = USArrests, # data matrix
                   stand = TRUE, # standardize the data
                   metric = "euclidean" # metric for distance matrix 
)

比较树状图

使用dendextend

首先创建两个不同的树状图:

dend1 <- stats::as.dendrogram(res_hc) 
dend2 <- stats::as.dendrogram(res_hc2)
dend_list <- dendextend::dendlist(dend1, dend2)
dendextend::tanglegram(dend1, dend2)

image-20200722135343171

比对的质量可以使用entanglement()函数来计算,这个值是0到1之间的,越小说明比对越好

dendextend::entanglement(dend1, dend2)
#[1] 0.8342094

也可以使用cor.dendlist()函数来计算两个树的相关性,有两种方法cophenetic和baker

# Cophenetic correlation matrix
dendextend::cor.dendlist(dend_list, method = "cophenetic")
#          [,1]     [,2]
# [1,] 1.000000 0.843143
# [2,] 0.843143 1.000000
# Baker correlation matrix
dendextend::cor.dendlist(dend_list, method = "baker")
#           [,1]      [,2]
# [1,] 1.0000000 0.8400675
# [2,] 0.8400675 1.0000000

选择最佳聚类数

  1. Elbow method(肘方法)

​ 将总的WSS看做是聚类数的函数,当增加聚类数不会大幅度降低WSS时会出现一个拐点,选择该点作为最佳聚类数

  1. Average silhouette method(平均轮廓法)

​ 该方法需要计算轮廓系数:

计算对象i到同类其他对象的平均距离$a_i$,$a_i$越小,说明样本i越应该被聚类到该类,将$a_i$称为样本i的簇内不相似度,类的所有对象的$a_i$均值称为该类的类不相似度;计算对象i到其他某类Cj 的所有对象的平均距离$b_{ij}$,称为样本i与簇Cj 的不相似度,对象i的类间不相似度:$bi =min({b_{i1}, b_{i2}, …, b_{ik}})$;根据类内不相似度和类间不相似度可以计算对象i的轮廓系数:

image-20200722142645591

所有样本的$s_i$的均值称为聚类结果的轮廓系数,是该聚类是否合理、有效的度量

和肘方法相似,计算不同聚类数目的轮廓系数,轮廓系数最大的聚类数为最佳聚类数

  1. Gap statistic method

一般选择B=500,结果就比较稳健

R

factoextra::fviz_nbclust(x, FUNcluster, method = c("silhouette", "wss", "gap_stat"))
# Elbow method
p1 <- factoextra::fviz_nbclust(df_scaled, kmeans, method = "wss") + 
  geom_vline(xintercept = 4, linetype = 2)+
  labs(subtitle = "Elbow method")
# Silhouette method
p2 <- factoextra::fviz_nbclust(df_scaled, kmeans, method = "silhouette")+ 
  labs(subtitle = "Silhouette method")
# Gap statistic
# nboot = 50 to keep the function speedy.
# recommended value: nboot= 500 for your analysis.
# Use verbose = FALSE to hide computing progression.
set.seed(123)
p3 <- factoextra::fviz_nbclust(df_scaled, kmeans, nstart = 25, method = "gap_stat", nboot = 50)+
  labs(subtitle = "Gap statistic method")
cowplot::plot_grid(p1,p2,p3)

image-20200722144751617

NbClust包的NbClust()函数可以提供30种指标来计算最佳聚类数

 NbClust(data = NULL, diss = NULL, 
         distance = "euclidean", min.nc = 2, max.nc = 15, method = NULL)
nb <- NbClust::NbClust(df_scaled, distance = "euclidean", min.nc = 2,
              max.nc = 10, method = "kmeans")
factoextra::fviz_nbclust(nb)

image-20200722145610875

还有一个增强版的hclust:fastcluster::hclust:更快,能处理更大的数据

image-20200722150942964

参考:

https://cran.r-project.org/web/packages/flashClust/index.html

https://www.biostars.org/p/85150/

https://www.cnblogs.com/lexus/archive/2012/12/13/2815769.html

Kassambara A. Practical guide to cluster analysis in R: Unsupervised machine learning[M]. Sthda, 2017.