一、层次聚类的概念
层次聚类算法(Hierarchical Clustering)将数据集划分为一层一层的簇(clusters),后面一层生成的簇是基于前面一层的结果。比较常用的方法是凝聚层次聚类(Agglomerative)简称AGNES算法。
1.AGNES聚类思路
1)最开始将每一个样本都视为一个簇,
2)每次按一定的准则将距离最近的两个簇合并生成一个新的簇
3)如此往复,直至最终所有的对象都属于一个簇。
例如:20国信息水平(见上一篇博客)聚类效果如下:
层次聚类是一种聚合算法
聚类的关键:在于定义簇间距离(当然也得知道样本间距离),进而不断合并簇
2.样本间距离与簇间距离
1) 样本间距离
记为第i个样本对应的向量,
为第j个样本对应的向量。则两个样本间距离定义如下:
(1)明氏距离
(2)马氏距离
其中,为两个向量构造出的协方差矩阵,
,
注意,仅当协方差矩阵可逆时,则马氏距离才存在。
(3)兰氏距离
.
此处仅适用于一切的情况。
(3)夹角余弦
即表示两个样本i与j间距离。
(5)相关系数
.
即表示两个样本i与j间距离。
2)簇间距离
下面介绍几种簇间距离来度量簇与簇之间的差异。距离越大,说明簇与簇之间的差异越大。
簇间距离的定义要使用上述样本间距离(记作),下面介绍比较常用的四种。
1) 簇间最短距离
两个簇间最短距离为其中一个簇中所有成员到另一簇中的所有成员间距离的最小值。具体如下:
其中,为两个不同的簇,
为两个样本I与j之间的距离(上面定义的样本间距离).
2) 簇间最长距离
两个簇间最长距离为其中一个簇中所有成员到另一簇中的所有成员间距离的最大值。
3) 簇中心距离
设的中心分别是
,则两个簇之间的距离定义为它们中心间样本距离:
4) 簇平均距离
一个簇的重心,虽然有很好的代表性,但未能充分利用各成员的信息,于是提出了利用两簇成员中两两之间的距离的平均值作为类与类之间的距离,即:
.
*注:上述所有距离均可选用聚类中的数学指标中的样本相似度指标。
二、AGNES算法流程
步骤1:计算簇间距离度量 ,以及样本点间距离;
步骤2:令每个点为一个簇;
步骤3:计算任意两簇间的距离;
步骤4:合并距离最小的两个簇;
步骤5:重复步骤3-4,直到簇数达到预设的某个值k(或簇间距离达到某个阈值)为止。
三、AGNES算法的Python实现
方法1:用scipy.cluster.hierarchy的linkage函数实现簇的合并,并用dendrogram画聚类图,最后再使用fcluster函数决定最后聚类的结果(直到簇数达到预设的某个值k,或簇间距离达到某个阈值)。
Z=scipy.cluster.hierarchy linkage(X, method='ward', metric='euclidean')
p = dendrogram(Z, 0)
labels =fcluster(Z, t=5, criterion='distance')
其中, method参数可以为:
’single’,’complete’,’average’,’centroid’,以及’ward’中的一种。分别对应:簇间最小距离、簇间最大距离、簇间平均距离、簇中心距离以及’ward’(使合并后的两个簇内样本点与合并后的中心点的对应方差最小的一种簇合并方式)等。metric为样本间距离度量,默认为’euclidean’. 即:欧几里得距离。
而fcluster的参数 criterion 取值为:'distance'(用阈值决定聚类),或'maxclust'(用最大簇数决定聚类),对应的临界距离或聚类的数量则由参数 t 所记录。fcluster 函数的结果labels为一个一维数组,记录每个样本的类别信息。
方法2:用sklearn.cluster.中的AgglomerativeClustering函数
Clustering=sklearn.cluster.AgglomerativeClustering(n_clusters=2, affinity=’euclidean’,linkage=’ward’).fit(X) # X为待分类样本数据
labels = Clustering.fit_predict(data_M) # labels即为聚类结果(分类标签)
其中,n_clusters为聚类的个数;
Affinity为样本间距离度量,默认为’euclidean’.
Linkage为簇间距离的方法,可选的值包括:“ward”, “complete”, “average”等。
例1用层次聚类法对sklearn.datas.make_blobs生成的数据聚类。
#方法1:使用用scipy实现聚类
#步骤1:生成数据
#程序如下:
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
centers = [[1, 1], [-1, -1], [1, -1]] #设置生成数据的样本中心
X, labels_true = make_blobs(n_samples=750, centers=centers, cluster_std=0.4, random_state=0)
plt.figure(figsize=(10, 8))
plt.scatter(X[:, 0], X[:, 1], c='b')
plt.show()
#步骤2:调用函数实现层次聚类
#SciPy 里面进行层次聚类,直接调用 linkage 函数。
#程序如下:
from scipy.cluster.hierarchy import linkage
Z = linkage(X, method='ward', metric='euclidean')
#print(Z.shape)
#print(Z[: 5])
#步骤3:画出树形图
#SciPy 中给出了根据层次聚类的结果 Z 绘制树形图的函数dendrogram
#程序如下:
from scipy.cluster.hierarchy import dendrogram
plt.figure(figsize=(10, 8))
dendrogram(Z,truncate_mode='lastp',p=20,show_leaf_counts=False, leaf_rotation=90, leaf_font_size=15,
show_contracted=True)
plt.show()
#步骤4:获取聚类结果
#② 是指定阈值d,得到在该距离以下的未合并的所有 cluster 作为聚类结果;
#② 是指定 cluster 的数量 k,函数会返回最后的 k 个 cluster 作为聚类结果。
#程序如下:
from scipy.cluster.hierarchy import fcluster
# 根据临界距离返回聚类结果
d = 15
labels_1 = fcluster(Z, t=d, criterion='distance')
print(labels_1[: 100]) # 打印聚类结果
print(len(set(labels_1))) # 看看在该临界距离下有几个 cluster
# 根据聚类数目返回聚类结果
k = 3
labels_2 = fcluster(Z, t=k, criterion='maxclust')
print(labels_2[: 100])
list(labels_1) == list(labels_2) # 看看两种不同维度下得到的聚类结果是否一致
# 聚类的结果可视化,相同的类的样本点用同一种颜色表示
plt.figure(figsize=(10, 8))
plt.scatter(X[:, 0], X[:, 1], c=labels_2, cmap='prism')
plt.show()
使用不同簇间距离效果
from time import time
import numpy as np
from sklearn.datasets import make_blobs
from scipy.cluster.hierarchy import linkage, fcluster
from sklearn.metrics.cluster import adjusted_mutual_info_score
import matplotlib.pyplot as plt
# 生成样本点
centers = [[1, 1], [-1, -1], [1, -1]]#设置生成数据的样本中心
X, labels = make_blobs(n_samples=750, centers=centers,
cluster_std=0.4, random_state=0)
# 可视化聚类结果
def plot_clustering(X, labels, title=None):
plt.scatter(X[:, 0], X[:, 1], c=labels, cmap='prism')
if title is not None:
plt.title(title, size=17)
plt.axis('off')
plt.tight_layout()
# 进行 Agglomerative 层次聚类
linkage_method_list = ['single', 'complete', 'average', 'ward']
plt.figure(figsize=(10, 8))
ncols, nrows = 2, int(np.ceil(len(linkage_method_list) / 2))
plt.subplots(nrows=nrows, ncols=ncols)
for i, linkage_method in enumerate(linkage_method_list):
print('method %s:' % linkage_method)
start_time = time()
Z = linkage(X, method=linkage_method)
labels_pred = fcluster(Z, t=3, criterion='maxclust')
print('Adjust mutual information: %.3f' % adjusted_mutual_info_score(labels, labels_pred))
print('time used: %.3f seconds' % (time() - start_time))
plt.subplot(nrows, ncols, i + 1)
plot_clustering(X, labels_pred, '%s linkage' % linkage_method)
plt.show()
除了用上面模块实现聚类,还可以使用使用sklearn.cluster的AgglomerativeClustering函数实现聚类,程序如下
from sklearn.datasets import make_blobs
from sklearn.cluster import AgglomerativeClustering
import matplotlib.pyplot as plt
# 生成样本点
centers = [[1, 1], [-1, -1], [1, -1]]
X, labels = make_blobs(n_samples=750, centers=centers,
cluster_std=0.4, random_state=0)
clustering = AgglomerativeClustering(n_clusters=3, linkage='ward').fit(X)
plt.figure(figsize=(10, 8))
plt.scatter(X[:, 0], X[:, 1], c=clustering.labels_, cmap='prism')
plt.show()
例2 用层次聚类法对世界20个国家和地区进行聚类,具体数据如下:
country | call | movecall | fee | computer | mips | net | |
1 | 美 国 | 631.60 | 161.90 | 0.36 | 403.00 | 26073.00 | 35.34 |
2 | 日 本 | 498.40 | 143.20 | 3.57 | 176.00 | 10223.00 | 6.26 |
3 | 德 国 | 557.60 | 70.60 | 2.18 | 199.00 | 11571.00 | 9.48 |
4 | 瑞 典 | 684.10 | 281.80 | 1.40 | 286.00 | 16660.00 | 29.39 |
5 | 瑞 士 | 644.00 | 93.50 | 1.98 | 234.00 | 13621.00 | 22.68 |
6 | 丹 麦 | 620.30 | 248.60 | 2.56 | 296.00 | 17210.00 | 21.84 |
7 | 新加坡 | 498.40 | 147.50 | 2.50 | 284.00 | 13578.00 | 13.49 |
8 | 中国台湾 | 469.40 | 56.10 | 3.68 | 119.00 | 6911.00 | 1.72 |
9 | 韩 国 | 434.50 | 73.00 | 3.36 | 99.00 | 5795.00 | 1.68 |
10 | 巴 西 | 81.90 | 16.30 | 3.02 | 19.00 | 876.00 | 0.52 |
11 | 智 利 | 138.60 | 8.20 | 1.40 | 31.00 | 1411.00 | 1.28 |
12 | 墨西哥 | 92.20 | 9.80 | 2.61 | 31.00 | 1751.00 | 0.35 |
13 | 俄罗斯 | 174.90 | 5.00 | 5.12 | 24.00 | 1101.00 | 0.48 |
14 | 波 兰 | 169.00 | 6.50 | 3.68 | 40.00 | 1796.00 | 1.45 |
15 | 匈牙利 | 262.20 | 49.40 | 2.66 | 68.00 | 3067.00 | 3.09 |
16 | 马来西亚 | 195.50 | 88.40 | 4.19 | 53.00 | 2734.00 | 1.25 |
17 | 泰 国 | 78.60 | 27.80 | 4.95 | 22.00 | 1662.00 | 0.11 |
18 | 印 度 | 13.60 | 0.30 | 6.28 | 2.00 | 101.00 | 0.01 |
19 | 法 国 | 559.10 | 42.90 | 1.27 | 201.00 | 11702.00 | 4.76 |
20 | 英 国 | 521.10 | 122.50 | 0.98 | 248.00 | 14461.00 | 11.91 |
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
X= np.loadtxt("E:\\python教材\\信息基础设施数据.txt")#把信息水平数据存在本地位置
#计算簇间距离
from scipy.cluster.hierarchy import linkage
Z = linkage(X, method='ward', metric='euclidean')
#画层次结构图
from scipy.cluster.hierarchy import dendrogram
plt.figure(figsize=(10, 8))
dendrogram(Z,truncate_mode='lastp',p=20,show_leaf_counts=False, leaf_rotation=90, leaf_font_size=15,
show_contracted=True)
plt.show()
#使用两种方法聚类,并现实聚类结果
##方法1指定阈值d
from scipy.cluster.hierarchy import fcluster
# 根据临界距离返回聚类结果
d = 10000
labels_1 = fcluster(Z, t=d, criterion='distance')
print(labels_1[: 100]) # 打印聚类结果
print(len(set(labels_1))) # 看看在该临界距离下有几个 cluster
##方法2:根据聚类数目返回聚类结果
k = 3
labels_2 = fcluster(Z, t=k, criterion='maxclust')
print(labels_2[: 100])
list(labels_1) == list(labels_2) # 看看两种不同维度下得到的聚类结果是否一致
练习:请根据2007年河北省社会经济发展指标,对河北省11个市进行聚类分析。
数据选取2007年河北省社会经济发展指标因子8项,组成一个原始数据矩阵X,作为分析的依据。所选取的指标有:乡村劳动力资源(x1)、农用机械总动力(x2)、农村用电量(x3)、农村社会总阿产值(x4)、自来水受益村(x5)、通电话村(x6)、有效灌溉面积(x7)、农村运输业固定资产(x8)。具体数据如下:
城市 | 乡村劳动力资源(人) | 农用机械 总动力 (千瓦) | 农村用电量(万千瓦小时) | 农村社会总产值 (万元) | 自来受 益村 (个) | 通电村数 (个) | 有效灌溉面积(公顷) | 农村运输业固定资产(万元) |
石家庄 | 3849814 | 19001804 | 624877 | 37574543 | 3980 | 4418 | 495628 | 525146 |
唐山 | 3175708 | 9317581 | 1169791 | 48736085 | 3785 | 5020 | 499068 | 547760 |
秦皇岛 | 1201891 | 2782741 | 113192 | 6792279 | 847 | 2262 | 124364 | 138657 |
邯郸 | 3691615 | 12068651 | 448244 | 36969131 | 4590 | 5347 | 535707 | 435227 |
邢台 | 2992865 | 8278320 | 241519 | 21763646 | 4954 | 5172 | 553587 | 379118 |
保定 | 5446331 | 10247700 | 321625 | 28093014 | 4116 | 6210 | 655113 | 431368 |
张家口 | 1945917 | 2415071 | 68599 | 6484145 | 2863 | 4067 | 249021 | 159708 |
承德 | 1743264 | 2114710 | 121829 | 9254893 | 1620 | 2552 | 138738 | 182419 |
沧州 | 3212666 | 10811991 | 537227 | 34369166 | 5679 | 5741 | 561042 | 332882 |
廊坊 | 1706083 | 6467986 | 386814 | 23224066 | 3164 | 3222 | 283788 | 280718 |
衡水 | 1895102 | 7838729 | 267634 | 15258973 | 4933 | 4982 | 473719 | 138165 |
参考书目:《数学建模常用算法及Python实现》陈星,吴松林