使用层次聚类算法对wine数据集进行聚类分析
1.作者介绍
彭彦珍,女,西安工程大学电子信息学院,2024级研究生
研究方向:模式识别与人工智能技术
电子邮件:3614635922@qq.com
王海博, 男 , 西安工程大学电子信息学院, 2024级研究生, 张宏伟人工智能课题组
研究方向:模式识别与人工智能
电子邮件:1137460680@qq.com
2.层次聚类算法介绍
层次聚类是一种通过逐步合并或分裂数据对象来构建层次化聚类结构的经典算法。核心目标是生成一个树状结构,直观展示数据点之间的相似性关系。
层次聚类分为两类:凝聚型层次聚类和分裂型层次聚类
凝聚型层次聚类是一种自底向上的方法:每个数据点初始为一个簇,逐步合并最近的簇,直到所有数据点聚为一类。。
分裂型层次聚类是一种自顶向下的方法:所有数据点初始为一类,逐步递归分割为更小的簇,直到每个点单独成簇。
3.数据集介绍
Wine数据来自意大利同一产区的三种不同葡萄品种酿造的葡萄酒,数据集包含178个样本,每个样本精确测量了13种关键化学属性,涵盖酒精含量、苹果酸及灰分等指标,并标注了对应的葡萄品种类别。该数据集由 UCI 机器学习库(UCI Machine Learning Repository)收录,后被整合到 scikit-learn 中。
UCI Wine数据集具有13维特征,既支持监督学习中的品种分类模型构建,也适用于无监督学习的聚类分析。无监督学习的核心在于无需依赖人工标注的标签数据,直接从数据中挖掘潜在的结构或模式。
数据集下载链接: https://archive.ics.uci.edu/dataset/109/wine
4.代码实现
4.1代码分析
(1) 导入必要的库,使用load_wine方法从sklearn.datasets中加载Wine数据集,再使用 StandardScaler对数据进行标准化,使用了sklearn的凝聚型层次聚类(Agglomerative Clustering)算法实现聚类。以及进行葡萄酒数据集的加载和预处理工作。
// 导入必要库
# 导入必要库
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_wine
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import AgglomerativeClustering
from sklearn.metrics import adjusted_rand_score
from scipy.cluster.hierarchy import dendrogram, linkage
from sklearn.decomposition import PCA
# 设置中文字体(任选其一)
plt.rcParams['font.sans-serif'] = ['SimHei']
# plt.rcParams['font.sans-serif'] = ['Kaiti']
# plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
# 解决负号显示问题
plt.rcParams['axes.unicode_minus'] = False
# 加载数据集
wine = load_wine()
X = wine.data
y = wine.target
feature_names = wine.feature_names
target_names = wine.target_names
# 数据预处理:标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
(2) 层次聚类树状图代码,在层次聚类中对 Wine数据集(13维特征) 进行PCA降维,保留关键特征,使聚类更聚焦于主要数据结构。
// 层次聚类树状图
# 层次聚类树状图
plt.figure(figsize=(16, 8))
linked = linkage(X_scaled,
method='ward',
metric='euclidean')
# 绘制树状图
dend = dendrogram(
linked,
orientation='top',
labels=np.arange(len(X)),
distance_sort='descending',
show_leaf_counts=True,
leaf_rotation=90,
leaf_font_size=8,
color_threshold=15,
above_threshold_color='grey'
)
plt.title('层次聚类树状图 (Wine Dataset)', fontsize=14, pad=20)
plt.xlabel('样本索引(按聚类关系重新排列)', fontsize=12)
plt.ylabel('合并距离(Ward方法)', fontsize=12)
plt.axhline(y=15, color='r', linestyle='--', linewidth=1, label='建议分割阈值')
plt.legend()
plt.grid(axis='y', linestyle='--', alpha=0.5)
plt.tight_layout()
plt.show()
# 执行层次聚类(n_clusters=3)
cluster_model = AgglomerativeClustering(
n_clusters=3,
affinity='euclidean',
linkage='ward'
)
cluster_labels = cluster_model.fit_predict(X_scaled)
# 评估指标
ari = adjusted_rand_score(y, cluster_labels)
print(f"调整兰德指数(ARI): {ari:.3f}")
# 使用PCA降维到2D
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)
plt.figure(figsize=(14, 6))
(3) 为方便查看分类结果,使用散点图可视化聚类结果,显示不同聚类的样本。
代码中通过双面板可视化对比葡萄酒数据集的真实类别与层次聚类结果。
// 散点图1:真实类别分布
# 散点图1:真实类别分布
plt.subplot(1, 2, 1)
sc1 = plt.scatter(X_pca[:, 0], X_pca[:, 1],
c=y,
cmap='tab10',
edgecolor='k',
s=60)
plt.title('真实类别分布(PCA降维)', fontsize=12)
plt.xlabel('主成分1({:.2f}%方差)'.format(pca.explained_variance_ratio_[0]*100), fontsize=10)
plt.ylabel('主成分2({:.2f}%方差)'.format(pca.explained_variance_ratio_[1]*100), fontsize=10)
plt.grid(alpha=0.3)
cbar1 = plt.colorbar(sc1, ticks=[0, 1, 2])
cbar1.set_label('真实类别', rotation=270, labelpad=15)
cbar1.set_ticklabels(target_names)
# 散点图2:聚类结果分布
plt.subplot(1, 2, 2)
sc2 = plt.scatter(X_pca[:, 0], X_pca[:, 1],
c=cluster_labels,
cmap='tab10',
edgecolor='k',
s=60)
plt.title('层次聚类结果(ARI={:.3f})'.format(ari), fontsize=12) # 标题显示ARI值
plt.xlabel('主成分1({:.2f}%方差)'.format(pca.explained_variance_ratio_[0]*100), fontsize=10)
plt.ylabel('主成分2({:.2f}%方差)'.format(pca.explained_variance_ratio_[1]*100), fontsize=10)
plt.grid(alpha=0.3)
cbar2 = plt.colorbar(sc2, ticks=[0, 1, 2])
cbar2.set_label('预测簇', rotation=270, labelpad=15)
cbar2.set_ticklabels(['簇 0', '簇 1', '簇 2'])
(4) 这段代码通过绘制每个聚类的95%置信椭圆(基于协方差矩阵的特征值),展示数据分布的离散程度和方向。
//
# 添加数据分布椭圆(显示95%置信区间)
from matplotlib.patches import Ellipse
def plot_ellipse(ax, points, color, alpha=0.2):
cov = np.cov(points, rowvar=False)
lambda_, v = np.linalg.eigh(cov)
lambda_ = np.sqrt(lambda_)
ell = Ellipse(xy=np.mean(points, axis=0),
width=lambda_[0]*2*2,
height=lambda_[1]*2*2,
angle=np.degrees(np.arctan2(v[1,0], v[0,0])),
color=color,
alpha=alpha)
ax.add_artist(ell)
# 为每个簇绘制椭圆
for i in range(3):
cluster_points = X_pca[cluster_labels == i]
plot_ellipse(plt.gca(), cluster_points, sc2.cmap(sc2.norm(i)))
plt.tight_layout()
plt.show()
# 输出聚类统计信息
print("\n聚类分布统计:")
for cluster in np.unique(cluster_labels):
original_classes = y[cluster_labels == cluster]
unique, counts = np.unique(original_classes, return_counts=True)
print(f"簇 {cluster}({len(original_classes)}个样本):")
for cls, cnt in zip(unique, counts):
print(f" - 包含 {target_names[cls]} 类样本:{cnt}个")
print("─"*40)
4.2结果分析
如图是聚类分布统计。三个簇分别有不同的样本数量和类别分布。簇0有58个样本,全部属于class_1,说明是完全纯净的簇,表明 class_1 的数据在特征空间中明显区别于其他类别,聚类算法能完美分离。簇1有56个样本,其中8个class_1和48个class_2,说明这个簇主要是class_2,但有部分混入的class_1。簇2有64个样本,59个class_0和5个class_1,同样大部分属于一个类别,但有少量其他类别的样本。
调整兰德指数(Adjusted Rand Index, ARI)是一种用于评估聚类结果与真实标签之间相似性的统计指标。ARI 的取值范围为[-1,1],值越大表明和真实结果越吻合,聚类效果更好。在[-1,1]范围内,0.79属于优秀水平(通常认为ARI>0.6即为良好,>0.8为极佳)。
4.3问题及解决方法
中文字符显示问题:代码中的中文标签(如“样本索引”)触发了matplotlib的字体缺失警告。当前matplotlib配置的字体库不包含所需的中文字符。这些属于警告性质,RuntimeWarning不会阻止程序运行,但会影响可视化效果(中文显示为方框或空白)。