聚类后的分析:推断簇的类型
知识点回顾:
- 推断簇含义的2个思路:先选特征和后选特征
- 通过可视化图形借助ai定义簇的含义
- 科研逻辑闭环:通过精度判断特征工程价值
作业:参考示例代码对心脏病数据集采取类似操作,并且评估特征工程后模型效果有无提升。
1、昨日知识回顾
导入查看数据,并对数据做聚类
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, 14) # 测试 k 从 2 到 13
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}")
k=2, 惯性: 3618.96, 轮廓系数: 0.213, CH 指数: 77.02, DB 指数: 1.880
k=3, 惯性: 3345.83, 轮廓系数: 0.140, CH 指数: 53.76, DB 指数: 2.379
k=4, 惯性: 3199.83, 轮廓系数: 0.129, CH 指数: 41.90, DB 指数: 2.721
k=5, 惯性: 3023.51, 轮廓系数: 0.120, CH 指数: 37.49, DB 指数: 2.503
k=6, 惯性: 2903.05, 轮廓系数: 0.108, CH 指数: 33.60, DB 指数: 2.368
k=7, 惯性: 2776.87, 轮廓系数: 0.110, CH 指数: 31.41, DB 指数: 2.272
k=8, 惯性: 2658.48, 轮廓系数: 0.113, CH 指数: 29.91, DB 指数: 2.168
k=9, 惯性: 2552.45, 轮廓系数: 0.119, CH 指数: 28.69, DB 指数: 2.084
k=10, 惯性: 2516.31, 轮廓系数: 0.116, CH 指数: 26.25, DB 指数: 2.023
k=11, 惯性: 2433.93, 轮廓系数: 0.116, CH 指数: 25.33, DB 指数: 2.009
k=12, 惯性: 2373.75, 轮廓系数: 0.122, CH 指数: 24.20, DB 指数: 1.977
k=13, 惯性: 2304.04, 轮廓系数: 0.135, CH 指数: 23.50, DB 指数: 1.909
根据inertia_value、sh_value、ch_value和db_value挑选k值
# 提示用户选择 k 值
selected_k = 2
# 使用选择的 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,plette='viridis')
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=2) added to X:
KMeans_Cluster
0 194
1 109
Name: count, dtype: int64
2.聚类后,给簇赋予实际含义
一般当你赋予实际含义的时候,你需要根据某几个特征来赋予,但是源数据特征很多,如何选择特征呢?有2种思路:
1. 你最开始聚类的时候,就选择了你想最后用来确定簇含义的特征,那么你需要选择一些特征来进行聚类,那么你最后确定簇含义的特征就是这几个特征,而非全部。如你想聚类消费者购买习惯,那么他过去的消费记录、购买记录、购买金额等等,这些特征都与消费者购买习惯有关,你可以使用这些特征来确定簇含义,一些其他的特征,如消费者年龄,工作行业则不考虑。----适用于你本身就有构造某些明确含义的特征的情况。
2. 最开始用全部特征来聚类,把其余特征作为 x,聚类得到的簇类别作为标签构建监督模型,进而根据重要性筛选特征,来确定要根据哪些特征赋予含义。---使用于你想构造什么,目前还不清楚。
建立随机森林模型,使用所有数据训练模型
使用Shap库对模型特征重要性进行解释分析
shap.initjs()
# 初始化 SHAP 解释器
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(x1) # 这个计算耗时
print("--- 1. SHAP 特征重要性条形图 ---")
shap.summary_plot(shap_values[:, :, 0], x1, plot_type="bar",show=False) # 这里的show=False表示不直接显示图形,这样可以继续用plt来修改元素,不然就直接输出了
plt.title("SHAP Feature Importance (Bar Plot)")
plt.show()
--- 1. SHAP 特征重要性条形图 ---
离散型变量就像一颗颗分离的珠子,取值是明确分开的;而连续型变量就像一条连续的线,在一定范围内可以取到任意的值。下面判断一下这几个特征是离散型还是连续型
import pandas as pd
selected_features = ['thalach','exang','cp','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} 可能是连续型变量')
thalach 的唯一值数量: 91
thalach 可能是连续型变量
exang 的唯一值数量: 2
exang 可能是离散型变量
cp 的唯一值数量: 4
cp 可能是离散型变量
slope 的唯一值数量: 3
slope 可能是离散型变量
离散和连续都有,那我就离散直方图,连续箱线图。其实就是在老师的代码中加个判断就行。
import matplotlib.pyplot as plt
# 总样本中的前四个重要性的特征分布图
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
axes = axes.flatten()
for i, feature in enumerate(selected_features):
if feature=='thalach':
axes[i].boxplot(X[feature].dropna())
axes[i].set_title(f'boxplot of {feature}')
axes[i].set_xlabel(feature)
axes[i].set_ylabel('Frequency')
else:
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')
plt.tight_layout()
plt.show()
# 分别筛选出每个簇的数据
X_cluster0 = X[X['KMeans_Cluster'] == 0]
X_cluster1 = X[X['KMeans_Cluster'] == 1]
# 先绘制簇0的分布图
import matplotlib.pyplot as plt
# 总样本中的前四个重要性的特征分布图
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
axes = axes.flatten()
for i, feature in enumerate(selected_features):
if feature=='thalach':
axes[i].boxplot(X_cluster0 [feature].dropna())
axes[i].set_title(f'boxplot of {feature}')
axes[i].set_xlabel(feature)
axes[i].set_ylabel('Frequency')
else:
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')
plt.tight_layout()
plt.show()
plt.tight_layout()
plt.show()
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
axes = axes.flatten()
for i, feature in enumerate(selected_features):
if feature=='thalach':
axes[i].boxplot(X_cluster1 [feature].dropna())
axes[i].set_title(f'boxplot of {feature}')
axes[i].set_xlabel(feature)
axes[i].set_ylabel('Frequency')
else:
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')
plt.tight_layout()
plt.show()
plt.tight_layout()
plt.show()
最后的两张图,交给AI分析,以下是AI的分析结果:
thalach
(最大心率):箱线图显示中位数约 140,无异常值,整体心率集中在 100-160 区间。exang
(运动心绞痛):直方图中exang=0
(无)的频率远高于exang=1
(有),说明多数患者运动时无心绞痛。cp
(胸痛类型):cp=0
(无症状)占比最高,其次是cp=2
(非典型心绞痛),cp=1
(典型心绞痛)和cp=3
(非心绞痛性胸痛)较少。slope
(ST 段斜率):slope=1
(平坦)占比最高,slope=2
(上升)次之,slope=0
(下降)极少。
1. (对应第二张图)类别 1:低风险 / 早期心肌缺血
- 特征模式:
- 最大心率(
thalach
)较低(中位数 140),心脏储备功能较好; - 运动心绞痛(
exang
)罕见,胸痛(cp
)以 “无症状” 为主,ST 段斜率(slope
)以 “平坦” 为主。
- 最大心率(
- 医学解释:这类患者心脏对运动的耐受性较好,胸痛和心电图异常(ST 段变化)均不显著,提示冠状动脉狭窄较轻或处于疾病早期。
2. (对应第一张图)类别 2:中高风险 / 明显心肌缺血
- 特征模式:
- 最大心率(
thalach
)较高(中位数 160),可能因心脏代偿性增快(如冠状动脉供血不足时,心脏需更快跳动以维持供血); - 胸痛(
cp
)以 “非典型心绞痛” 为主,ST 段斜率(slope
)以 “上升” 为主(ST 段上升是心肌缺血的典型心电图表现)。
- 最大心率(
- 医学解释:这类患者运动时心脏负荷增加后,冠状动脉供血不足的问题更明显,心电图和症状均提示心肌缺血风险较高,可能处于冠心病进展期。
若要进一步确认聚类的医学意义,可结合临床诊断标签(如 “是否确诊冠心病”)进行验证: 1. 统计每个聚类中 “确诊冠心病” 的比例,看是否与特征风险程度匹配; 2. 对比医生对患者的分级(如 “低危 / 中危 / 高危”),验证聚类结果是否与临床评估一致。 若聚类结果与临床知识相符,说明算法有效识别了疾病的 “风险亚型”,可辅助医生进行个性化诊断。
3.评估聚类后模型精度效果有无提升
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
data1 = pd.read_csv('heart.csv') #读取数据
# 最开始也说了 很多调参函数自带交叉验证,甚至是必选的参数,你如果想要不交叉反而实现起来会麻烦很多
# 所以这里我们还是只划分一次数据集
from sklearn.model_selection import train_test_split
X_default= data1.drop(['target'], axis=1) # 特征,axis=1表示按列删除
y_default= data1['target'] # 标签
X_default_train,X_default_test,y_default_train,y_default_test=train_test_split(X_default, y_default, test_size=0.2, random_state=42)
print(X_default.columns)
print(X.columns)
Index(['age', 'sex', 'cp', 'trestbps', 'chol', 'fbs', 'restecg', 'thalach',
'exang', 'oldpeak', 'slope', 'ca', 'thal'],
dtype='object')
Index(['age', 'sex', 'cp', 'trestbps', 'chol', 'fbs', 'restecg', 'thalach',
'exang', 'oldpeak', 'slope', 'ca', 'thal', 'KMeans_Cluster'],
dtype='object')
比较聚类前后X与 X_default之间的特征数量差异,标签均设为y—target
# 1.跑基准模型
import time
from sklearn.ensemble import RandomForestClassifier #随机森林分类器
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score # 用于评估分类器性能的指标
from sklearn.metrics import classification_report, confusion_matrix #用于生成分类报告和混淆矩阵
rf_model_default=RandomForestClassifier(random_state=42)
start_time=time.time()
rf_model_default.fit(X_default_train,y_default_train)
rf_pred_base=rf_model_default.predict(X_default_test)
end_time=time.time()
print(f"随机森林训练和预测所耗费的时间是{end_time-start_time:.4f}")
print("随机森林的分类报告:")
print(classification_report(y_default_test,rf_pred_base))
print("随机森林的混淆矩阵:")
print(confusion_matrix(y_default_test,rf_pred_base))
随机森林训练和预测所耗费的时间是0.1810
随机森林的分类报告:
precision recall f1-score support
0 0.83 0.83 0.83 29
1 0.84 0.84 0.84 32
accuracy 0.84 61
macro avg 0.84 0.84 0.84 61
weighted avg 0.84 0.84 0.84 61
随机森林的混淆矩阵:
[[24 5]
[ 5 27]]
基准模型准确率0.84,精确率为0.84
rf_model_cluster=RandomForestClassifier(random_state=42)
start_time=time.time()
rf_model_cluster.fit(X_train,y_train)
rf_pred_cluster=rf_model_cluster.predict(X_test)
end_time=time.time()
print(f"随机森林训练和预测所耗费的时间是{end_time-start_time:.4f}")
print("随机森林的分类报告:")
print(classification_report(y_test,rf_pred_cluster))
print("随机森林的混淆矩阵:")
print(confusion_matrix(y_test,rf_pred_cluster))
随机森林训练和预测所耗费的时间是0.1412
随机森林的分类报告:
precision recall f1-score support
0 0.88 0.85 0.86 26
1 0.88 0.91 0.89 32
accuracy 0.88 58
macro avg 0.88 0.88 0.88 58
weighted avg 0.88 0.88 0.88 58
随机森林的混淆矩阵:
[[22 4]
[ 3 29]]
聚类后模型准确率0.88,精确率为0.88,效果更好