哈喽,大家好~
今天我们来聊一个非常直观的无学习算法,层次聚类。
它不仅能给出聚类结果,还会完整保留样本从独立个体到逐步合并的过程。
层次聚类是什么
普通聚类更像直接告诉我们:“这些样本分成了4组。”
层次聚类则会继续解释:谁先和谁合并,哪些小组又组成了更大的组。
它主要有两种方式:
-
凝聚式聚类:每个样本最开始都是一类,然后不断合并,使用最广泛。 -
分裂式聚类:所有样本最开始属于一类,再逐层拆分。
我们这里使用凝聚式聚类。你可以把它想成公司组建团队:先让最相似的两个人组队,再让相近的小队组成部门,最后形成完整组织。
核心原理:两个簇到底有多近
算法每次都要找出距离最近的两个簇,但“簇之间的距离”有不同定义。
Single Linkage 取两个簇中最近样本的距离:
Complete Linkage 取最远样本的距离,而 Average Linkage 使用所有样本距离的平均值。
实际中常用的 Ward 方法稍有不同,它选择合并后类内平方误差增加最少的两个簇:
其中, 和 表示两个簇的样本数量, 和 是簇中心。
一句话概括:Ward 方法会尽量让同一类中的样本保持紧凑。
所有合并过程可以画成树状图。树枝连接得越低,表示两组样本越相似;在某个高度画一条横线,切出几根主树枝,就能得到几个类别。
Python完整案例
下面构造一份客户数据。横轴表示消费能力,纵轴表示活跃程度,同时加入不同方差、旋转结构和少量噪声,让数据更接近真实场景。
通过下面两幅图帮助大家理解~
-
层次聚类树状图:观察样本合并过程; -
聚类诊断图:同时展示聚类边界、簇中心和每类轮廓系数。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import AgglomerativeClustering
from sklearn.metrics import silhouette_samples, silhouette_score
from scipy.cluster.hierarchy import linkage, dendrogram
from scipy.spatial import ConvexHull
np.random.seed(42)
# 1. 生成不同密度的客户数据
X, _ = make_blobs(
n_samples=360,
centers=[(-4, -1), (-1, 3), (3, 2), (4, -2)],
cluster_std=[0.65, 1.05, 0.75, 1.20],
random_state=42
)
# 旋转和拉伸,构造非规则分布
transform = np.array([[1.0, 0.65], [-0.25, 1.15]])
X = X @ transform
# 添加少量噪声客户
noise = np.random.uniform(X.min(axis=0), X.max(axis=0), (20, 2))
X = np.vstack([X, noise])
X = StandardScaler().fit_transform(X)
# 2. Ward层次聚类
model = AgglomerativeClustering(n_clusters=4, linkage="ward")
labels = model.fit_predict(X)
# scipy linkage用于绘制树状图
Z = linkage(X, method="ward")
cut_height = (Z[-4, 2] + Z[-3, 2]) / 2
plt.style.use("dark_background")
colors = ["#00F5D4", "#F15BB5", "#FEE440", "#00BBF9"]
# 图1:截断树状图
plt.figure(figsize=(12, 6))
dendrogram(
Z,
truncate_mode="lastp",
p=28,
leaf_rotation=0,
leaf_font_size=10,
show_contracted=True,
color_threshold=cut_height
)
plt.axhline(cut_height, color="#FF4D6D", linestyle="--",
linewidth=2, label="4类切割线")
plt.title("Hierarchical Clustering Dendrogram", fontsize=16)
plt.xlabel("合并后的样本簇")
plt.ylabel("Ward合并距离")
plt.legend()
plt.tight_layout()
plt.show()
# 3. 计算轮廓系数
sample_scores = silhouette_samples(X, labels)
avg_score = silhouette_score(X, labels)
# 图2:聚类空间 + 轮廓系数联合诊断
fig, axes = plt.subplots(1, 2, figsize=(15, 6))
# 左图:聚类结果、中心与凸包边界
for k, color in enumerate(colors):
points = X[labels == k]
axes[0].scatter(
points[:, 0], points[:, 1],
s=38, color=color, alpha=0.82,
edgecolor="white", linewidth=0.25
)
center = points.mean(axis=0)
axes[0].scatter(
center[0], center[1],
marker="*", s=350, color="white",
edgecolor=color, linewidth=2.5
)
if len(points) >= 3:
hull = ConvexHull(points)
boundary = points[hull.vertices]
axes[0].fill(
boundary[:, 0], boundary[:, 1],
color=color, alpha=0.10
)
axes[0].plot(
np.r_[boundary[:, 0], boundary[0, 0]],
np.r_[boundary[:, 1], boundary[0, 1]],
color=color, linewidth=1.5
)
axes[0].set_title("客户聚类空间:颜色、中心与边界")
axes[0].set_xlabel("标准化消费能力")
axes[0].set_ylabel("标准化活跃程度")
axes[0].grid(alpha=0.15)
# 右图:每个簇的轮廓系数
y_lower = 10
for k, color in enumerate(colors):
values = np.sort(sample_scores[labels == k])
y_upper = y_lower + len(values)
axes[1].fill_betweenx(
np.arange(y_lower, y_upper),
0, values, color=color, alpha=0.9
)
axes[1].text(-0.08, (y_lower + y_upper) / 2, f"C{k}")
y_lower = y_upper + 10
axes[1].axvline(
avg_score, color="#FF4D6D", linestyle="--",
linewidth=2, label=f"平均值={avg_score:.3f}"
)
axes[1].axvline(0, color="white", linewidth=1)
axes[1].set_title("各簇轮廓系数诊断")
axes[1].set_xlabel("Silhouette Score")
axes[1].set_ylabel("簇内样本")
axes[1].legend()
plt.tight_layout()
plt.show()
print(f"整体轮廓系数:{avg_score:.3f}")
第一张树状图记录了整个合并过程。红色虚线相当于拿一把剪刀横着剪树,虚线下方形成的4根主要树枝,就对应4个客户群体。
虚线附近如果存在明显的高度空档,说明从4类继续合并到3类需要付出较大的类内误差,选择4类就比较合理。
第二张图是更完整的聚类诊断。左侧不同颜色代表不同客户群,白色星号是簇中心,半透明区域是每一类的空间边界。边界大量重叠时,说明类别不容易区分。
右侧是轮廓系数,其取值范围为 。越接近1,说明样本更适合当前类别;接近0表示它处在两个簇的边界;小于0则意味着它很可能被分错了。
因此,不要只看散点图“颜色分得好不好看”,还要结合树状图和轮廓系数判断结果是否可靠。
注意点
层次聚类对特征尺度非常敏感。收入以万元计算、访问次数以个位数计算时,收入会直接主导距离,所以代码中先使用了StandardScaler。
Ward适合数值型数据和相对紧凑的簇;如果数据是弯月形、链条形,可以尝试single或average。类别数量也不要凭感觉决定,可以同时比较树状图、业务需求和不同
值下的轮廓系数。
另外,层次聚类通常需要保存样本间距离,数据量很大时内存压力明显。它更适合中小规模数据的探索分析,而不是直接处理百万级样本。
总结
层次聚类的核心,就是不断合并距离最近的簇,并把整个过程保存成一棵树。它最大的价值不只是“完成分组”,而是让我们看见数据从局部到整体的组织关系。

