- 推断簇含义的2个思路:先选特征和后选特征
- 通过可视化图形借助ai定义簇的含义
- 科研逻辑闭环:通过精度判断特征工程价值
首先运行昨天的代码,利用KMeans来聚类
# 导入所需要的包
import pandas as pd #用于数据处理和分析,可处理表格数据。
import numpy as np #用于数值计算,提供了高效的数组操作。
import matplotlib.pyplot as plt #用于绘制各种类型的图表
import seaborn as sns #基于matplotlib的高级绘图库,能绘制更美观的统计图形。
# 设置中文字体(解决中文显示问题)
plt.rcParams['font.sans-serif'] = ['SimHei'] # Windows系统常用黑体字体
plt.rcParams['axes.unicode_minus'] = False # 正常显示负号
import os
# 设置输出目录
output_dir = r"C:\Users\Administrator\Pictures\heart"
os.makedirs(output_dir, exist_ok=True) # 自动创建目录
# 自定义图片保存函数
def save_current_plot(filename):
"""保存当前激活的图形到指定文件"""
plt.gcf().savefig(os.path.join(output_dir, filename),
bbox_inches='tight', # 避免裁剪
dpi=300, # 高清分辨率
facecolor='white') # 白色背景
plt.close() # 关闭当前图形释放内存
# 读取数据
data = pd.read_csv(r'daka\heart.csv')
# 数据集划分
# 划分训练集和测试集
from sklearn.model_selection import train_test_split
X = data.drop(['target'], axis=1) # 特征,axis=1表示按列删除(axis=0 是删除行)
y = data['target'] # 标签
# 聚类
import numpy as np
import pandas as pd
from sklearn.cluster import KMeans, DBSCAN, AgglomerativeClustering
from sklearn.metrics import silhouette_score, calinski_harabasz_score, davies_bouldin_score
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)
# KMeans 聚类
# 评估不同 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_) # 惯性(肘部法则) inertia_ = sse = wscc
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) # 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) # 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) # 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) # 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() # 调整子图间距
# save_current_plot("KMeans评估指标图.png") # 保存图片
# 提示用户选择 k 值
selected_k = 4
# 利用选择的 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')
save_current_plot("KMeans聚类结果可视化.png") # 保存图片
# 打印 KMeans 聚类标签的前几行
print(f"KMeans Cluster labels (k={selected_k}) added to X:")
print(X[['KMeans_Cluster']].value_counts())
print(X.columns)
# Index(['age', 'sex', 'cp', 'trestbps', 'chol', 'fbs', 'restecg', 'thalach',
# 'exang', 'oldpeak', 'slope', 'ca', 'thal', 'KMeans_Cluster'],
# dtype='object')
要对新列'KMeans_Cluster'中的簇类赋予实际的意义,有两种思路:
1.先选特征:提前想好要创建的新特征,聚类的时候就会选与这个特征相关的几个特征进行聚类,那么决定簇意义的也是这几个特征。
2.后选特征:即一开始用全部特征进行聚类,得到聚类的新列之后将其作为标签y,其余特征作为x构建监督模型,然后通过shap值来得到对聚类标签贡献比较大的几个特征,通过这几个特征为簇类赋予实际意义(利用ai)。
首先得到对聚类特征贡献最大的四个特征:
x1 = X.drop('KMeans_Cluster',axis=1 )# 删除聚类标签列
y1 = X['KMeans_Cluster']
# 构建随机森林,用shap重要性来筛选重要性
import shap
import numpy as np
from sklearn.ensemble import RandomForestClassifier # 随机森林分类器
model = RandomForestClassifier(n_estimators=100, random_state=42) # 初始化模型
model.fit(x1, y1) # 训练模型,无需划分训练集和测试集
# 初始化 SHAP 解释器
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(x1) # 这个计算耗时
print(shap_values.shape) # 第一维是样本数,第二维是特征数,第三维是类别数
# (303, 13, 4)
# 利用Bar Plot判断总样本中贡献值最大的四个特征
print("--- SHAP 特征重要性条形图 ---")
shap.summary_plot(shap_values[:, :, 0], x1, plot_type="bar",show=False) # 这里的show=False表示不直接显示图形,这样可以继续用plt来修改元素,不然就直接输出了
plt.title("SHAP Feature Importance (Bar Plot)")
save_current_plot("shap_km.png")
判断这四个特征是连续型还是离散型:
# 此时判断一下这几个特征是离散型还是连续型
selected_features = ['sex','age','thalach','slope']
for feature in selected_features:
unique_count = X[feature].nunique() # 统计该特征不重复值的个数
# 连续型变量通常有很多唯一值,而离散型变量的唯一值较少
print(f'{feature} 的唯一值数量: {unique_count}')
if unique_count < 10: # 这里 10 是一个经验阈值,可以根据实际情况调整
print(f'{feature} 可能是离散型变量')
else:
print(f'{feature} 可能是连续型变量')
# sex 的唯一值数量: 2
# sex 可能是离散型变量
# age 的唯一值数量: 41
# age 可能是连续型变量
# thalach 的唯一值数量: 91
# thalach 可能是连续型变量
# slope 的唯一值数量: 3
# slope 可能是离散型变量
绘制总样本中这四个特征的分布图
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
axes = axes.flatten()
for i, feature in enumerate(selected_features):
if i == 0 or i == 3: # 对于离散型变量,使用直方图
axes[i].hist(X[feature], bins=20)
axes[i].set_title(f'Histogram of {feature}')
axes[i].set_xlabel(feature)
axes[i].set_ylabel('Frequency')
else: # 对于连续型变量,使用箱线图
axes[i].boxplot(X[feature])
axes[i].set_title(f'Boxplot of {feature}')
axes[i].set_ylabel(feature)
plt.tight_layout()
save_current_plot("总样本中的前四个重要性的特征分布图.png") # 保存图片
再分别绘制出每个簇对应的这四个特征的分布图
簇0:
# 分别筛选出每个簇的数据
print(X[['KMeans_Cluster']].value_counts())
# 1 95
# 0 94
# 2 69
# 3 45
X_cluster0 = X[X['KMeans_Cluster'] == 0]
X_cluster1 = X[X['KMeans_Cluster'] == 1]
X_cluster2 = X[X['KMeans_Cluster'] == 2]
X_cluster3 = X[X['KMeans_Cluster'] == 3]
# 簇0中的前四个重要性的特征分布图
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
axes = axes.flatten()
for i, feature in enumerate(selected_features):
if i == 0 or i == 3: # 对于离散型变量,使用直方图
axes[i].hist(X_cluster0[feature], bins=20)
axes[i].set_title(f'Histogram of {feature}')
axes[i].set_xlabel(feature)
axes[i].set_ylabel('Frequency')
else: # 对于连续型变量,使用箱线图
axes[i].boxplot(X_cluster0[feature])
axes[i].set_title(f'Boxplot of {feature}')
axes[i].set_ylabel(feature)
plt.tight_layout()
save_current_plot("簇0中的前四个重要性的特征分布图.png") # 保存图片
簇1:
# 簇1中的前四个重要性的特征分布图
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
axes = axes.flatten()
for i, feature in enumerate(selected_features):
if i == 0 or i == 3: # 对于离散型变量,使用直方图
axes[i].hist(X_cluster1[feature], bins=20)
axes[i].set_title(f'Histogram of {feature}')
axes[i].set_xlabel(feature)
axes[i].set_ylabel('Frequency')
else: # 对于连续型变量,使用箱线图
axes[i].boxplot(X_cluster1[feature])
axes[i].set_title(f'Boxplot of {feature}')
axes[i].set_ylabel(feature)
plt.tight_layout()
save_current_plot("簇1中的前四个重要性的特征分布图.png") # 保存图片
簇2:
# 簇2中的前四个重要性的特征分布图
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
axes = axes.flatten()
for i, feature in enumerate(selected_features):
if i == 0 or i == 3: # 对于离散型变量,使用直方图
axes[i].hist(X_cluster2[feature], bins=20)
axes[i].set_title(f'Histogram of {feature}')
axes[i].set_xlabel(feature)
axes[i].set_ylabel('Frequency')
else: # 对于连续型变量,使用箱线图
axes[i].boxplot(X_cluster2[feature])
axes[i].set_title(f'Boxplot of {feature}')
axes[i].set_ylabel(feature)
plt.tight_layout()
save_current_plot("簇2中的前四个重要性的特征分布图.png") # 保存图片
簇3:
# 簇3中的前四个重要性的特征分布图
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
axes = axes.flatten()
for i, feature in enumerate(selected_features):
if i == 0 or i == 3: # 对于离散型变量,使用直方图
axes[i].hist(X_cluster3[feature], bins=20)
axes[i].set_title(f'Histogram of {feature}')
axes[i].set_xlabel(feature)
axes[i].set_ylabel('Frequency')
else: # 对于连续型变量,使用箱线图
axes[i].boxplot(X_cluster3[feature])
axes[i].set_title(f'Boxplot of {feature}')
axes[i].set_ylabel(feature)
plt.tight_layout()
save_current_plot("簇3中的前四个重要性的特征分布图.png") # 保存图片
最后,把这四个图发给ai,让ai分析:
1. 簇 0-男性中年心功能稳健型
核心特征:男性主导 + 年龄集中中年 + 心率(thalach)偏高且稳定 + 斜率(slope=2.0)占优(运动心电图 ST 段变化更 “良性”)
命名方向:突出 “男性 + 相对健康生理特征”
推荐标签:「男性中年心功能稳健型」
(解析:男性为主、年龄集中,心率表现好,斜率特征关联心肌供血稳定,暗示心功能相对 “稳健” )
2. 簇 1-男性心功能异质性高风险型
核心特征:男性为主 + 年龄跨度大(含异常值) + 心率离散度高(有低心率异常) + 斜率特征复杂(多类别分布)
命名方向:强调 “年龄与心功能异质性”
推荐标签:「男性心功能异质性高风险型」
(解析:男性群体中,年龄、心率波动大,斜率模式复杂,可能对应心功能 “异质性高、潜在风险多元” )
3. 簇 2-女性中年心功能典型型
核心特征:女性主导 + 年龄集中中年 + 心率 / 斜率呈现女性生理特征模式(与男性簇差异显著)
命名方向:突出 “女性生理特征典型性”
推荐标签:「女性中年心功能典型型」
(解析:女性为主、年龄集中,心率 / 斜率贴合女性生理基线,是 “女性群体心功能特征典型代表” )
4. 簇 3-心功能过渡性风险关注型
核心特征:性别分布过渡(男性多但女性占比升) + 年龄 / 心率有离散(含年轻个体、低心率异常) + 斜率复杂度中等
命名方向:体现 “过渡性与潜在风险”
推荐标签:「心功能过渡性风险关注型」
(解析:性别、生理指标(年龄 / 心率)有过渡特征,斜率模式隐含复杂度,需 “关注潜在风险” )