聚类分析完整指南:k-均值和层次聚类(演算与程序) (一)


什么是聚类分析?


聚类分析是探索性数据分析的一种形式,在这种分析中,观测数据被分成具有共同特征的不同组。

聚类分析(也称为分类)的目的是构造群(或类或群),同时确保以下性质:在一个群中观测值必须尽可能相似,而属于不同群的观测值必须尽可能不同

主要有两种分类:

  1. K-means clustering
  2. Hierarchical clustering

第一种方法通常在预先确定类的数量时使用,而第二种方法通常用于未知数量的类,并帮助确定最佳数量。这两种方法在下面通过演算和R程序中的应用进行了说明。注意,对于层次聚类,本文只介绍了升序分类。

聚类算法利用距离将观测数据分成不同的组。因此,在深入介绍这两种分类方法之前,将介绍如何计算点之间距离的演算。

Application 1: Computing distances

存在一个数据集,包含点 a = ( 0 , 0 ) ′ , b = ( 1 , 0 ) ′ a = (0,0)', b=(1,0)' a=(0,0),b=(1,0) c = ( 5 , 5 ) ′ c=(5,5)' c=(5,5). 计算点间欧式距离矩阵(matrix of Euclidean distances)。

Solution

# We create the points in R
a <- c(0, 0)
b <- c(1, 0)
c <- c(5, 5)

X <- rbind(a, b, c) # a, b and c are combined per row
colnames(X) <- c("x", "y") # rename columns

X # display the points

OUTPUT:

##   x y
## a 0 0
## b 1 0
## c 5 5

根据勾股定理(Pythagorean formula),我们知道 ( x a , y a ) (x_a, y_a) (xa,ya) ( x b , y b ) (x_b, y_b) (xb,yb)之间的距离在 R 2 \mathbb{R}^2 R2中是 ( x a − x b ) 2 + ( y a − y b ) 2 \sqrt{(x_a - x_b)^2 + (y_a - y_b)^2} (xaxb)2+(yayb)2 . 此外, b = ( 1 , 0 ) ′ b=(1,0)' b=(1,0) c = ( 5 , 5 ) ′ c=(5,5)' c=(5,5) 的距离如下:
( x b − x c ) 2 + ( y b − y c ) 2 = ( 1 − 5 ) 2 + ( 0 − 5 ) 2 = 6.403124 \sqrt{(x_b - x_c)^2 + (y_b - y_c)^2} = \sqrt{(1-5)^2 + (0-5)^2}\\ = 6.403124 (xbxc)2+(ybyc)2 =(15)2+(05)2 =6.403124

在R中,我们可以用 dist() 函数来计算点之间的距离:

# The distance is found using the dist() function:
distance <- dist(X, method = "euclidean")
distance # display the distance matrix

OUTPUT:

##          a        b
## b 1.000000         
## c 7.071068 6.403124

需要注意的是,method = “euclidean” 不是强制性的,因为欧式方法是默认方法。

dist() 函数生成的距离矩阵给出了不同点之间的距离。点b和c之间的欧几里德距离是6.403124,这与我们上面通过勾股定理得到的结果相对应。

:如果两个变量的单位不同,在计算欧式距离时,一个变量的权重可能比另一个更大。在这种情况下,最好缩放数据。缩放数据允许获得独立于其单位的变量,这可以通过 scale() 函数来实现。

K-means clustering

该方法的目的是将n个观测值划分为k个聚类,其中每个观测值属于最接近平均值的聚类,作为聚类的原型。

K-means算法的思想很简单,简单来讲就是对于给定的样本集,按照样本之间的距离大小,将样本集划分为K个簇。让簇内的点尽量紧密的连在一起,而让簇间的距离尽量的大,两个对象之间的距离越近,相似性越高。聚类的结果就是使类内部的同质性高,而类之间的异质性高。- - - Kmeans聚类介绍及R语言实现(黑_太狼de数据)。

k-means算法就是将 n 个数据点进行聚类分析,得到 k 个聚类,使得每个数据点到聚类中心的距离最小。而实际上,这个问题往往是NP-hard的,以此有许多启发式的方法求解,从而避开局部最小值。值得注意的是,k-means算法往往容易和k-nearest neighbor classifier(k-NN)[下篇讲解] 算法混淆。后者是有监督学习的分类(回归)算法,主要是用来判定数据点属于哪个类别中心的。

Application 2: k-means clustering Data

下图的数据库包含了1979年26个欧洲国家不同行业的就业人口百分比。它包含10个变量:
聚类分析完整指南:k-均值和层次聚类(演算与程序) (一)_第1张图片

  • Country - the name of the country (identifier)
  • Agr - % of workforce employed in agriculture
  • Min - % in mining
  • Man - % in manufacturing
  • PS - % in power supplies industries
  • Con - % in construction
  • SI - % in service industries
  • Fin - % in finance
  • SPS - % in social and personal services
  • TC - % in transportation and communications
# Import data
Eurojobs <- read.csv(
  file = "data/Eurojobs.csv",
  sep = ",", dec = ".", header = TRUE
)
head(Eurojobs) # head() is used to display only the first 6 observations

OUTPUT:

##      Country  Agr Min  Man  PS  Con   SI Fin  SPS  TC
## 1    Belgium  3.3 0.9 27.6 0.9  8.2 19.1 6.2 26.6 7.2
## 2    Denmark  9.2 0.1 21.8 0.6  8.3 14.6 6.5 32.2 7.1
## 3     France 10.8 0.8 27.5 0.9  8.9 16.8 6.0 22.6 5.7
## 4 W. Germany  6.7 1.3 35.8 0.9  7.3 14.4 5.0 22.3 6.1
## 5    Ireland 23.2 1.0 20.7 1.3  7.5 16.8 2.8 20.8 6.1
## 6      Italy 15.9 0.6 27.6 0.5 10.0 18.1 1.6 20.1 5.7

可以看到,在第一个变量Country之前有一个编号。为了更清楚起见,用国家取代这个编号。为此,需要添加参数行名称在导入函数中为1读取.csv()指定第一列对应于行名称:

Eurojobs <- read.csv(
  file = "data/Eurojobs.csv",
  sep = ",", dec = ".", header = TRUE, row.names = 1
)
Eurojobs # displays dataset

OUTPUT:

##                 Agr Min  Man  PS  Con   SI  Fin  SPS  TC
## Belgium         3.3 0.9 27.6 0.9  8.2 19.1  6.2 26.6 7.2
## Denmark         9.2 0.1 21.8 0.6  8.3 14.6  6.5 32.2 7.1
## France         10.8 0.8 27.5 0.9  8.9 16.8  6.0 22.6 5.7
## W. Germany      6.7 1.3 35.8 0.9  7.3 14.4  5.0 22.3 6.1
## Ireland        23.2 1.0 20.7 1.3  7.5 16.8  2.8 20.8 6.1
## Italy          15.9 0.6 27.6 0.5 10.0 18.1  1.6 20.1 5.7
## Luxembourg      7.7 3.1 30.8 0.8  9.2 18.5  4.6 19.2 6.2
## Netherlands     6.3 0.1 22.5 1.0  9.9 18.0  6.8 28.5 6.8
## United Kingdom  2.7 1.4 30.2 1.4  6.9 16.9  5.7 28.3 6.4
## Austria        12.7 1.1 30.2 1.4  9.0 16.8  4.9 16.8 7.0
## Finland        13.0 0.4 25.9 1.3  7.4 14.7  5.5 24.3 7.6
## Greece         41.4 0.6 17.6 0.6  8.1 11.5  2.4 11.0 6.7
## Norway          9.0 0.5 22.4 0.8  8.6 16.9  4.7 27.6 9.4
## Portugal       27.8 0.3 24.5 0.6  8.4 13.3  2.7 16.7 5.7
## Spain          22.9 0.8 28.5 0.7 11.5  9.7  8.5 11.8 5.5
## Sweden          6.1 0.4 25.9 0.8  7.2 14.4  6.0 32.4 6.8
## Switzerland     7.7 0.2 37.8 0.8  9.5 17.5  5.3 15.4 5.7
## Turkey         66.8 0.7  7.9 0.1  2.8  5.2  1.1 11.9 3.2
## Bulgaria       23.6 1.9 32.3 0.6  7.9  8.0  0.7 18.2 6.7
## Czechoslovakia 16.5 2.9 35.5 1.2  8.7  9.2  0.9 17.9 7.0
## E. Germany      4.2 2.9 41.2 1.3  7.6 11.2  1.2 22.1 8.4
## Hungary        21.7 3.1 29.6 1.9  8.2  9.4  0.9 17.2 8.0
## Poland         31.1 2.5 25.7 0.9  8.4  7.5  0.9 16.1 6.9
## Rumania        34.7 2.1 30.1 0.6  8.7  5.9  1.3 11.7 5.0
## USSR           23.7 1.4 25.8 0.6  9.2  6.1  0.5 23.6 9.3
## Yugoslavia     48.7 1.5 16.8 1.1  4.9  6.4 11.3  5.3 4.0
dim(Eurojobs) # displays the number of rows and columns

OUTPUT:

## [1] 26  9

我们现在有了一个由26个观察值和9个连续定量变量组成的“干净”数据集,我们可以以此为基础进行分类。注意,在这种情况下,没有必要对数据进行标准化,因为它们都是用同一单位(百分比)表示的。否则,我们通过scale()函数标准化数据。

所谓的k-means集群是通过 kmeans() 函数完成的,参数中心对应于所需集群的数量。在下面的例子中,我们用2个类和3个类来应用分类。

kmeans() with 2 groups

model <- kmeans(Eurojobs, centers = 2)

# displays the class determined by
# the model for all observations:
print(model$cluster)

OUTPUT:

##        Belgium        Denmark         France     W. Germany        Ireland 
##              1              1              1              1              2 
##          Italy     Luxembourg    Netherlands United Kingdom        Austria 
##              1              1              1              1              1 
##        Finland         Greece         Norway       Portugal          Spain 
##              1              2              1              2              2 
##         Sweden    Switzerland         Turkey       Bulgaria Czechoslovakia 
##              1              1              2              2              1 
##     E. Germany        Hungary         Poland        Rumania           USSR 
##              1              2              2              2              2 
##     Yugoslavia 
##              2

参数centers=2用于设置预先确定的簇数。在此文章中,簇的数目是任意确定的。应该根据分析的上下文和目标,或者根据本文中介绍的方法来确定集群的数量.调用 print(model$cluster),此输出指定每个国家所属的组(即1或2)。

每个观测值的聚类可以作为列直接存储在数据集中:

Eurojobs_cluster <- data.frame(Eurojobs,
  cluster = as.factor(model$cluster)
)
head(Eurojobs_cluster)

OUTPUT:

##             Agr Min  Man  PS  Con   SI Fin  SPS  TC cluster
## Belgium     3.3 0.9 27.6 0.9  8.2 19.1 6.2 26.6 7.2       1
## Denmark     9.2 0.1 21.8 0.6  8.3 14.6 6.5 32.2 7.1       1
## France     10.8 0.8 27.5 0.9  8.9 16.8 6.0 22.6 5.7       1
## W. Germany  6.7 1.3 35.8 0.9  7.3 14.4 5.0 22.3 6.1       1
## Ireland    23.2 1.0 20.7 1.3  7.5 16.8 2.8 20.8 6.1       2
## Italy      15.9 0.6 27.6 0.5 10.0 18.1 1.6 20.1 5.7       1

Quality of a k-means partition

通过使用以下公式计算分区“解释”的TSS百分比:
BSS ⁡ TSS ⁡ × 100 % \dfrac{\operatorname{BSS}}{\operatorname{TSS}} \times 100\% TSSBSS×100%
其中BSS和TSS分别代表Stand for Between Sum of Squares 与 Total Sum of Squares。百分比越高,得分(以及质量)就越好,它意味着BSS很大和/或TSS很小。

# BSS and TSS are extracted from the model and stored
(BSS <- model$betweenss)

OUTPUT:

## [1] 4823.535
(TSS <- model$totss)

OUTPUT:

## [1] 9299.59
# We calculate the quality of the partition
BSS / TSS * 100

OUTPUT:

## [1] 51.86826

这个间隔比例是51.86826%,通常这个数字没有特定的含义。只有在和其他分区(具有相同数量的集群)的质量相比时,才能够反映出它的重要性。

nstart for several initial centers and better stability

k-means算法使用一组随机的初始点来达到最终的分类。由于初始中心是随机选择的,同一个命令kmeans(Eurojobs,centers=2)每次运行时可能会给出不同的结果,因此分区带来的结果会略有不同。kmeans()函数中的nstart参数允许使用不同的初始中心多次运行该算法,以便获得一个可能更好的分区:

model2 <- kmeans(Eurojobs, centers = 2, nstart = 10)
100 * model2$betweenss / model2$totss

OUTPUT:

## [1] 54.2503

根据最初的随机选择,这个新分区与第一个分区相比是否更好。在我们的例子中,当质量提高到54.2503%时,分区会更好。

关于k-means经常被引用的一个主要限制是结果的稳定性。由于初始中心是随机选择的,运行相同的命令可能会产生不同的结果。在kmeans()函数中添加nstart参数将限制此问题,因为它将生成多个不同的初始化,并采用最优化的初始化,从而提高分类的稳定性。

kmeans() with 3 groups

我们现在使用3个集群来执行k-均值分类并计算其质量:

model3 <- kmeans(Eurojobs, centers = 3)
BSS3 <- model3$betweenss
TSS3 <- model3$totss
BSS3 / TSS3 * 100

OUTPUT:

## [1] 74.59455

可以看出,分为三组可以获得更高的解释百分比和更高的质量。因此我们可以猜测:有了更多的类,分区就会更精细,BSS的贡献也会更高。另一方面,“模型”将更加复杂,需要更多的类。在k=n(每个观测值都是一个单例类)的极端情况下,会有BSS=TSS,但是分区也失去了意义。

Optimal number of clusters

为了找到k-means的最佳聚类数,建议根据以下条件选择:

  • 根据数据中有特定数量的组(较为主观).

  • 以下四种方法:

    1, 肘部法则(Elbow Method method), 使用簇内平方和。
    我们知道k-means是以最小化样本与质点平方误差作为目标函数,将每个簇的质点与簇内样本点的平方距离误差和称为畸变程度(distortions),那么,对于一个簇,它的畸变程度越低,代表簇内成员越紧密,畸变程度越高,代表簇内结构越松散。 畸变程度会随着类别的增加而降低,但对于有一定区分度的数据,在达到某个临界点时畸变程度会得到极大改善,之后缓慢下降,这个临界点就可以考虑为聚类性能较好的点。

    2, 平均轮廓法(Average silhouette method).
    s ( i ) = b ( i ) − a ( i ) m a x ( a ( i ) , b ( i ) , i f ∣ C i ∣ > 1 s(i) = \frac{b(i) - a(i)}{max(a(i),b(i)} , if |C_i| > 1 s(i)=max(a(i),b(i)b(i)a(i),ifCi>1
    a(i)是测量组内的相似度,b(i)是测量组间的相似度,s(i)范围从-1到1,值越大说明组内吻合越高,组间距离越远——也就是说,轮廓系数值越大,聚类效果越好.

    3, 缺口统计法(Gap statistic method).
    之前我们提到了通过找“肘点”来找到最佳聚类数,肘点的选择并不是那么清晰,因此 R. Tibshirani, G. Walther, and T. Hastie (Standford University, 2001)提出了Gap Statistic方法,定义的Gap值为:
    G a p ( k ) = 1 B ∑ b = 1 B l o g ( W k b ∗ ) − l o g ( W k ) Gap(k) = \frac{1}{B} \sum\limits_{b=1}^B log(W_{kb}^*) - log(W_k) Gap(k)=B1b=1Blog(Wkb)log(Wk)

    4, Nbclust包. 见RDocumentation.

下面我将用这四种方法用R来实现:

1, 肘部法则.

# load required packages
library(factoextra)
library(NbClust)

# Elbow method
fviz_nbclust(Eurojobs, kmeans, method = "wss") +
  geom_vline(xintercept = 4, linetype = 2) + # add line for better visualisation
  labs(subtitle = "Elbow method") # add subtitle

聚类分析完整指南:k-均值和层次聚类(演算与程序) (一)_第2张图片
图中膝部的位置通常被认为是适当数量的簇的一个指标,因为这意味着添加另一个簇并不能更好地改善分区。因此这个方法建议4个聚类。肘部法则有时候很难得到明确的结果。

2,平均轮廓法
平均轮廓法测量聚类的质量,并确定每个点在其簇中的位置。
聚类分析完整指南:k-均值和层次聚类(演算与程序) (一)_第3张图片
平均轮廓法建议2个簇。

3,缺口统计法
聚类分析完整指南:k-均值和层次聚类(演算与程序) (一)_第4张图片
最佳的聚类数是使差距统计最大化的聚类数。这种方法只建议1个聚类(因此这是一个无用的聚类)。

因此,这三种方法不一定会导致相同的结果。在这里,所有3种方法都建议不同数量的集群

4, NbClust()

nbclust_out <- NbClust(
  data = Eurojobs,
  distance = "euclidean",
  min.nc = 2, # minimum number of clusters
  max.nc = 5, # maximum number of clusters
  method = "kmeans"
) # one of: "ward.D", "ward.D2", "single", "complete", "average", "mcquitty", "median", "centroid", "kmeans"

聚类分析完整指南:k-均值和层次聚类(演算与程序) (一)_第5张图片
聚类分析完整指南:k-均值和层次聚类(演算与程序) (一)_第6张图片

OUTPUT:

*** : The Hubert index is a graphical method of determining the number of clusters.
                In the plot of Hubert index, we seek a significant knee that corresponds to a 
                significant increase of the value of the measure i.e the significant peak in Hubert
                index second differences plot. 
 
*** : The D index is a graphical method of determining the number of clusters. 
                In the plot of D index, we seek a significant knee (the significant peak in Dindex
                second differences plot) that corresponds to a significant increase of the value of
                the measure. 
 
******************************************************************* 
* Among all indices:                                                
* 6 proposed 2 as the best number of clusters 
* 15 proposed 3 as the best number of clusters 
* 2 proposed 5 as the best number of clusters 

                   ***** Conclusion *****                            
 
* According to the majority rule, the best number of clusters is  3 
 
 
******************************************************************* 

程序的建议是3个聚类。我们来进一步验证:

# create a dataframe of the optimal number of clusters
nbclust_plot <- data.frame(clusters = nbclust_out$Best.nc[1, ])
# select only indices which select between 2 and 5 clusters
nbclust_plot <- subset(nbclust_plot, clusters >= 2 & clusters <= 5)

# create plot
ggplot(nbclust_plot) +
  aes(x = clusters) +
  geom_histogram(bins = 30L, fill = "#0c4c8a") +
  labs(x = "Number of clusters", y = "Frequency among all indices", title = "Optimal number of clusters") +
  theme_minimal()

聚类分析完整指南:k-均值和层次聚类(演算与程序) (一)_第7张图片
在所有30个指标中,最佳聚类数为3个聚类。

Visualizations

通常还可以使用 fviz_cluster() 功能,主成分分析是用来表示二维平面中的变量。根据平均轮廓法的定义,我们将数据分为两个组。

library(factoextra)

km_res <- kmeans(Eurojobs, centers = 2, nstart = 20)
fviz_cluster(km_res, Eurojobs, ellipse.type = "norm")

聚类分析完整指南:k-均值和层次聚类(演算与程序) (一)_第8张图片
接下来,我们将一步一步来演算该算法。

验证与应用

我将对下图所示的点用k-means算法,k=2,以i=5和i=6为初始中心。计算刚刚找到的分区的质量,然后用R来验证答案。
假设变量具有相同的单位,因此不需要缩放数据。
聚类分析完整指南:k-均值和层次聚类(演算与程序) (一)_第9张图片

第一步:以下是6个点的坐标:
在这里插入图片描述
最初的中心是:第一个点: 5 (9 , 7); 第二个点: 6 (6 , 8)

第二步:用勾股定理逐点计算距离矩阵。a点和b点之间的距离是通过以下公式得出的:
( x a − x b ) 2 + ( y a − y b ) 2 \sqrt{(x_a - x_b)^2 + (y_a - y_b)^2} (xaxb)2+(yayb)2
由此可见,我们可以得到以下的距离矩阵(round(dist(X), 2)):

##       1     2     3     4     5
## 2  3.61                        
## 3  5.10  2.24                  
## 4  7.28  5.66  3.61            
## 5  4.47  5.39  7.62 10.82      
## 6  5.10  3.61  5.66  9.22  3.16

第三步。根据第二步计算的距离矩阵,我们可以将每个点放在最接近的组中,并计算出中心的坐标。

我们首先将每个点放在最接近的组中:

  • 点1比点6更接近点5,因为点1和点5之间的距离是4.47,而点1和点6之间的距离是5.10.
  • 点2比点5更接近点6,因为点2和点5之间的距离是5.39,而点2和点6之间的距离是3.61.
  • 由于点3和点5之间的距离为7.62,而点3和点6之间的距离为5.66,因此点3与点5的距离更接近点6.
  • 点4距离点6比点5更近,因为点4和点5之间的距离为10.82,而点4和点6之间的距离为9.22.

另外,计算每个点与点5和点6之间的距离就足够了。例如,当我们将每个点与初始中心(点5和6)进行比较时,不需要计算点1和点2之间的距离。

然后,我们通过取坐标x和y的平均值来计算两组中心的坐标:

  • 组1包括以(8,5)为中心的点5和点1 ( 8 = 9 + 7 2 8 = \frac{9+7}{2} 8=29+7 and 5 = 7 + 3 2 5 = \frac{7+3}{2} 5=27+3)
  • 组2包括以(3,4.5)为中心的点6、2、3和4 ( 3 = 6 + 4 + 2 + 0 4 3 = \frac{6+4+2+0}{4} 3=46+4+2+0 and 4.5 = 8 + 5 + 4 + 1 4 4.5 = \frac{8+5+4+1}{4} 4.5=48+5+4+1)

第4步。我们通过检查每个点是否在最近的簇(Cluster)中来确保分配是最优的。由于勾股定理,一个点和一个簇中心之间的距离再次被计算出来。因此,我们有:

未完待续

你可能感兴趣的:(算法)