聚类算法是一种无监督学习技术,用于将数据集中的相似对象分组到不同的类别(称为“簇”)中,而不需要预先定义的标签。其核心目标是:同一簇内的数据点尽可能相似(高内聚性),不同簇之间的数据点尽可能不同(高分离性)。聚类广泛应用于数据挖掘、模式识别、图像处理等领域,如客户细分、文档分类或异常检测。
聚类依赖于相似度度量(如欧氏距离)来评估数据点之间的接近程度。假设数据集包含 n n n 个点,每个点表示为向量 x i \mathbf{x}_i xi( i = 1 , 2 , … , n i=1,2,\dots,n i=1,2,…,n),算法通过优化目标函数来划分簇。例如,在 K-means 算法中,目标是最小化簇内平方和(WCSS):
∑ j = 1 k ∑ x ∈ C j ∣ x − μ j ∣ 2 \sum_{j=1}^{k} \sum_{\mathbf{x} \in C_j} |\mathbf{x} - \mu_j|^2 j=1∑kx∈Cj∑∣x−μj∣2
其中:
k k k 是簇的数量(需预先指定),
C j C_j Cj 是第 j j j 个簇,
μ j \mu_j μj 是簇 C j C_j Cj 的中心(即质心)。
数据划分与预处理
import pandas as pd #用于数据处理和分析,可处理表格数据。
import numpy as np #用于数值计算,提供了高效的数组操作。
import matplotlib.pyplot as plt #用于绘制各种类型的图表
import seaborn as sns #基于matplotlib的高级绘图库,能绘制更美观的统计图形。
import warnings
warnings.filterwarnings("ignore")
# 设置中文字体(解决中文显示问题)
plt.rcParams['font.sans-serif'] = ['SimHei'] # Windows系统常用黑体字体
plt.rcParams['axes.unicode_minus'] = False # 正常显示负号
data = pd.read_csv('heart.csv') #读取数据
#data = data.drop('student_id', axis=1) # 删除单列
data = pd.get_dummies(data, columns=['sex'])
data2 = pd.read_csv("heart.csv") # 重新读取数据,用来做列名对比
list_final = [] # 新建一个空列表,用于存放独热编码后新增的特征名
for i in data.columns:
if i not in data2.columns:
list_final.append(i) # 这里打印出来的就是独热编码后的特征名
for i in list_final:
data[i] = data[i].astype(int) # 这里的i就是独热编码后的特征名
#data['parental_education_level'].fillna('High School', inplace=True)
#data['is_pass'] = data['exam_score'].apply(lambda x: 1 if x >= 60 else 0)
from sklearn.model_selection import train_test_split
X = data.drop(['target'], axis=1)
y = data['target']
# 按照8:2划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
import numpy as np
import pandas as pd
from sklearn.cluster import KMeans, DBSCAN, AgglomerativeClustering
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import seaborn as sns
# 标准化数据(聚类前通常需要标准化)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# X_scaled
以下是三种常用的聚类效果评估指标,分别用于衡量聚类的质量和簇的分离与紧凑程度:
k
值作为最佳簇数量。k
值作为最佳簇数量。k
值作为最佳簇数量。算法原理
KMeans 是一种基于距离的聚类算法,需要预先指定聚类个数,即 k
。其核心步骤如下:
k
个样本点作为初始质心(簇中心)。确定簇数的方法:肘部法
k
值的方法。k
值下的簇内平方和(Within-Cluster Sum of Squares, WCSS),绘制 k
与 WCSS 的关系图。k
值,通常认为是最佳簇数。这是因为增加 k
值带来的收益(WCSS 减少)在该点后变得不显著。KMeans 算法的优缺点
优点
缺点
k
值:对簇数量 k
的选择敏感,不合适的 k
会导致聚类效果较差。import numpy as np
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.metrics import silhouette_score, calinski_harabasz_score, davies_bouldin_score
import matplotlib.pyplot as plt
import seaborn as sns
# 评估不同 k 值下的指标
k_range = range(2, 11) # 测试 k 从 2 到 10
inertia_values = []
silhouette_scores = []
ch_scores = []
db_scores = []
for k in k_range:
kmeans = KMeans(n_clusters=k, random_state=42)
kmeans_labels = kmeans.fit_predict(X_scaled)
inertia_values.append(kmeans.inertia_) # 惯性(肘部法则)
silhouette = silhouette_score(X_scaled, kmeans_labels) # 轮廓系数
silhouette_scores.append(silhouette)
ch = calinski_harabasz_score(X_scaled, kmeans_labels) # CH 指数
ch_scores.append(ch)
db = davies_bouldin_score(X_scaled, kmeans_labels) # DB 指数
db_scores.append(db)
print(f"k={k}, 惯性: {kmeans.inertia_:.2f}, 轮廓系数: {silhouette:.3f}, CH 指数: {ch:.2f}, DB 指数: {db:.3f}")
# 绘制评估指标图
plt.figure(figsize=(15, 10))
# 肘部法则图(Inertia)
plt.subplot(2, 2, 1)
plt.plot(k_range, inertia_values, marker='o')
plt.title('肘部法则确定最优聚类数 k(惯性,越小越好)')
plt.xlabel('聚类数 (k)')
plt.ylabel('惯性')
plt.grid(True)
# 轮廓系数图
plt.subplot(2, 2, 2)
plt.plot(k_range, silhouette_scores, marker='o', color='orange')
plt.title('轮廓系数确定最优聚类数 k(越大越好)')
plt.xlabel('聚类数 (k)')
plt.ylabel('轮廓系数')
plt.grid(True)
# CH 指数图
plt.subplot(2, 2, 3)
plt.plot(k_range, ch_scores, marker='o', color='green')
plt.title('Calinski-Harabasz 指数确定最优聚类数 k(越大越好)')
plt.xlabel('聚类数 (k)')
plt.ylabel('CH 指数')
plt.grid(True)
# DB 指数图
plt.subplot(2, 2, 4)
plt.plot(k_range, db_scores, marker='o', color='red')
plt.title('Davies-Bouldin 指数确定最优聚类数 k(越小越好)')
plt.xlabel('聚类数 (k)')
plt.ylabel('DB 指数')
plt.grid(True)
plt.tight_layout()
plt.show()
k=2, 惯性: 3631.89, 轮廓系数: 0.154, CH 指数: 50.56, DB 指数: 2.340
k=3, 惯性: 3134.93, 轮廓系数: 0.167, CH 指数: 52.97, DB 指数: 1.997
k=4, 惯性: 2936.98, 轮廓系数: 0.166, CH 指数: 44.29, DB 指数: 1.866
k=5, 惯性: 2804.62, 轮廓系数: 0.135, CH 指数: 38.18, DB 指数: 2.056
k=6, 惯性: 2713.23, 轮廓系数: 0.129, CH 指数: 33.47, DB 指数: 2.132
k=7, 惯性: 2570.17, 轮廓系数: 0.141, CH 指数: 32.09, DB 指数: 2.067
k=8, 惯性: 2459.28, 轮廓系数: 0.145, CH 指数: 30.55, DB 指数: 2.034
k=9, 惯性: 2393.94, 轮廓系数: 0.116, CH 指数: 28.37, DB 指数: 2.050
k=10, 惯性: 2348.98, 轮廓系数: 0.109, CH 指数: 26.24, DB 指数: 2.055
# 提示用户选择 k 值
selected_k = 3
# 使用选择的 k 值进行 KMeans 聚类
kmeans = KMeans(n_clusters=selected_k, random_state=42)
kmeans_labels = kmeans.fit_predict(X_scaled)
X['KMeans_Cluster'] = kmeans_labels
# 使用 PCA 降维到 2D 进行可视化
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)
# KMeans 聚类结果可视化
plt.figure(figsize=(6, 5))
sns.scatterplot(x=X_pca[:, 0], y=X_pca[:, 1], hue=kmeans_labels, palette='viridis')
plt.title(f'KMeans Clustering with k={selected_k} (PCA Visualization)')
plt.xlabel('PCA Component 1')
plt.ylabel('PCA Component 2')
plt.show()
# 打印 KMeans 聚类标签的前几行
print(f"KMeans Cluster labels (k={selected_k}) added to X:")
print(X[['KMeans_Cluster']].value_counts())
KMeans Cluster labels (k=3) added to X:
KMeans_Cluster
0 126
2 94
1 83
Name: count, dtype: int64
import numpy as np
import pandas as pd
from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.metrics import silhouette_score, calinski_harabasz_score, davies_bouldin_score
import matplotlib.pyplot as plt
import seaborn as sns
# 评估不同 eps 和 min_samples 下的指标
# eps这个参数表示邻域的半径,min_samples表示一个点被认为是核心点所需的最小样本数。
# min_samples这个参数表示一个核心点所需的最小样本数。
eps_range = np.arange(0.01, 0.1, 0.01) # 测试 eps 从 0.3 到 0.7
min_samples_range = range(2, 4) # 测试 min_samples 从 3 到 7
results = []
for eps in eps_range:
for min_samples in min_samples_range:
dbscan = DBSCAN(eps=eps, min_samples=min_samples)
dbscan_labels = dbscan.fit_predict(X_scaled)
# 计算簇的数量(排除噪声点 -1)
n_clusters = len(np.unique(dbscan_labels)) - (1 if -1 in dbscan_labels else 0)
# 计算噪声点数量
n_noise = list(dbscan_labels).count(-1)
# 只有当簇数量大于 1 且有有效簇时才计算评估指标
if n_clusters > 1:
# 排除噪声点后计算评估指标
mask = dbscan_labels != -1
if mask.sum() > 0: # 确保有非噪声点
silhouette = silhouette_score(X_scaled[mask], dbscan_labels[mask])
ch = calinski_harabasz_score(X_scaled[mask], dbscan_labels[mask])
db = davies_bouldin_score(X_scaled[mask], dbscan_labels[mask])
results.append({
'eps': eps,
'min_samples': min_samples,
'n_clusters': n_clusters,
'n_noise': n_noise,
'silhouette': silhouette,
'ch_score': ch,
'db_score': db
})
print(f"eps={eps:.1f}, min_samples={min_samples}, 簇数: {n_clusters}, 噪声点: {n_noise}, "
f"轮廓系数: {silhouette:.3f}, CH 指数: {ch:.2f}, DB 指数: {db:.3f}")
else:
print(f"eps={eps:.1f}, min_samples={min_samples}, 簇数: {n_clusters}, 噪声点: {n_noise}, 无法计算评估指标")
# 将结果转为 DataFrame 以便可视化和选择参数
results_df = pd.DataFrame(results)
print(results_df)
eps=0.0, min_samples=2, 簇数: 1, 噪声点: 301, 无法计算评估指标
eps=0.0, min_samples=3, 簇数: 0, 噪声点: 303, 无法计算评估指标
eps=0.0, min_samples=2, 簇数: 1, 噪声点: 301, 无法计算评估指标
eps=0.0, min_samples=3, 簇数: 0, 噪声点: 303, 无法计算评估指标
eps=0.0, min_samples=2, 簇数: 1, 噪声点: 301, 无法计算评估指标
eps=0.0, min_samples=3, 簇数: 0, 噪声点: 303, 无法计算评估指标
eps=0.0, min_samples=2, 簇数: 1, 噪声点: 301, 无法计算评估指标
eps=0.0, min_samples=3, 簇数: 0, 噪声点: 303, 无法计算评估指标
eps=0.1, min_samples=2, 簇数: 1, 噪声点: 301, 无法计算评估指标
eps=0.1, min_samples=3, 簇数: 0, 噪声点: 303, 无法计算评估指标
eps=0.1, min_samples=2, 簇数: 1, 噪声点: 301, 无法计算评估指标
eps=0.1, min_samples=3, 簇数: 0, 噪声点: 303, 无法计算评估指标
eps=0.1, min_samples=2, 簇数: 1, 噪声点: 301, 无法计算评估指标
eps=0.1, min_samples=3, 簇数: 0, 噪声点: 303, 无法计算评估指标
eps=0.1, min_samples=2, 簇数: 1, 噪声点: 301, 无法计算评估指标
eps=0.1, min_samples=3, 簇数: 0, 噪声点: 303, 无法计算评估指标
eps=0.1, min_samples=2, 簇数: 1, 噪声点: 301, 无法计算评估指标
eps=0.1, min_samples=3, 簇数: 0, 噪声点: 303, 无法计算评估指标
Empty DataFrame
Columns: []
Index: []
从结果来看 这个聚类是失败的 也可能是dbscan算法不太合适
Agglomerative Clustering 是一种自底向上的层次聚类方法,初始时每个样本是一个簇,然后逐步合并最相似的簇,直到达到指定的簇数量或满足停止条件。由于它需要指定簇数量(类似于 KMeans),我将通过测试不同的簇数量 n_clusters 来评估聚类效果,并使用轮廓系数(Silhouette Score)、CH 指数(Calinski-Harabasz Index)和 DB 指数(Davies-Bouldin Index)作为评估指标。
import numpy as np
import pandas as pd
from sklearn.cluster import AgglomerativeClustering
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.metrics import silhouette_score, calinski_harabasz_score, davies_bouldin_score
import matplotlib.pyplot as plt
import seaborn as sns
# 标准化数据
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 评估不同 n_clusters 下的指标
n_clusters_range = range(2, 11) # 测试簇数量从 2 到 10
silhouette_scores = []
ch_scores = []
db_scores = []
for n_clusters in n_clusters_range:
agglo = AgglomerativeClustering(n_clusters=n_clusters, linkage='ward') # 使用 Ward 准则合并簇
agglo_labels = agglo.fit_predict(X_scaled)
# 计算评估指标
silhouette = silhouette_score(X_scaled, agglo_labels)
ch = calinski_harabasz_score(X_scaled, agglo_labels)
db = davies_bouldin_score(X_scaled, agglo_labels)
silhouette_scores.append(silhouette)
ch_scores.append(ch)
db_scores.append(db)
print(f"n_clusters={n_clusters}, 轮廓系数: {silhouette:.3f}, CH 指数: {ch:.2f}, DB 指数: {db:.3f}")
# 绘制评估指标图
plt.figure(figsize=(15, 5))
# 轮廓系数图
plt.subplot(1, 3, 1)
plt.plot(n_clusters_range, silhouette_scores, marker='o')
plt.title('轮廓系数确定最优簇数(越大越好)')
plt.xlabel('簇数量 (n_clusters)')
plt.ylabel('轮廓系数')
plt.grid(True)
# CH 指数图
plt.subplot(1, 3, 2)
plt.plot(n_clusters_range, ch_scores, marker='o')
plt.title('Calinski-Harabasz 指数确定最优簇数(越大越好)')
plt.xlabel('簇数量 (n_clusters)')
plt.ylabel('CH 指数')
plt.grid(True)
# DB 指数图
plt.subplot(1, 3, 3)
plt.plot(n_clusters_range, db_scores, marker='o')
plt.title('Davies-Bouldin 指数确定最优簇数(越小越好)')
plt.xlabel('簇数量 (n_clusters)')
plt.ylabel('DB 指数')
plt.grid(True)
plt.tight_layout()
plt.show()
n_clusters=2, 轮廓系数: 0.209, CH 指数: 71.55, DB 指数: 1.822
n_clusters=3, 轮廓系数: 0.164, CH 指数: 55.56, DB 指数: 1.881
n_clusters=4, 轮廓系数: 0.159, CH 指数: 46.82, DB 指数: 2.296
n_clusters=5, 轮廓系数: 0.145, CH 指数: 40.52, DB 指数: 2.442
n_clusters=6, 轮廓系数: 0.154, CH 指数: 36.67, DB 指数: 2.330
n_clusters=7, 轮廓系数: 0.150, CH 指数: 33.34, DB 指数: 2.272
n_clusters=8, 轮廓系数: 0.131, CH 指数: 30.95, DB 指数: 2.275
n_clusters=9, 轮廓系数: 0.111, CH 指数: 29.20, DB 指数: 2.218
n_clusters=10, 轮廓系数: 0.119, CH 指数: 27.84, DB 指数: 2.164
# 提示用户选择 n_clusters 值(这里可以根据图表选择最佳簇数)
selected_n_clusters = 2 # 示例值,根据图表调整
# 使用选择的簇数进行 Agglomerative Clustering 聚类
agglo = AgglomerativeClustering(n_clusters=selected_n_clusters, linkage='ward')
agglo_labels = agglo.fit_predict(X_scaled)
X['Agglo_Cluster'] = agglo_labels
# 使用 PCA 降维到 2D 进行可视化
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)
# Agglomerative Clustering 聚类结果可视化
plt.figure(figsize=(6, 5))
sns.scatterplot(x=X_pca[:, 0], y=X_pca[:, 1], hue=agglo_labels, palette='viridis')
plt.title(f'Agglomerative Clustering with n_clusters={selected_n_clusters} (PCA Visualization)')
plt.xlabel('PCA Component 1')
plt.ylabel('PCA Component 2')
plt.show()
# 打印 Agglomerative Clustering 聚类标签的分布
print(f"Agglomerative Cluster labels (n_clusters={selected_n_clusters}) added to X:")
print(X[['Agglo_Cluster']].value_counts())
Agglomerative Cluster labels (n_clusters=2) added to X:
Agglo_Cluster
0 208
1 95
Name: count, dtype: int64
过程可视化
# 层次聚类的树状图可视化
from scipy.cluster import hierarchy
import matplotlib.pyplot as plt
# 假设 X_scaled 是标准化后的数据
# 计算层次聚类的链接矩阵
Z = hierarchy.linkage(X_scaled, method='ward') # 'ward' 是常用的合并准则
# 绘制树状图
plt.figure(figsize=(10, 6))
hierarchy.dendrogram(Z, truncate_mode='level', p=3) # p 控制显示的层次深度
# hierarchy.dendrogram(Z, truncate_mode='level') # 不用p这个参数,可以显示全部的深度
plt.title('Dendrogram for Agglomerative Clustering')
plt.xlabel('Cluster Size')
plt.ylabel('Distance')
plt.show()