Day 18 推断聚类后簇的类型

@浙大疏锦行

今日任务:

  1. 推断簇含义的2个思路:先选特征和后选特征
  2. 通过可视化图形借助ai定义簇的含义
  3. 通过精度判断特征工程价值
  4. 参考示例代码对心脏病数据集采取类似操作,并且评估特征工程后模型效果有无提升。

在Day 17 的学习中,了解了三种聚类算法的使用。但是如果仅得到聚类后的结果而不赋予实际含义,那么聚类将毫无意义。聚类回答了“是什么”的问题,而给聚类标签赋予实际意义则解决“为什么”的问题。

在赋予实际意义之前,需要有特定的特征,但是这个特征如何确定?

目前常用的思路有两种,分别是目标明确的设计型聚类探索驱动的解释型聚类:

  • 设计型:在聚类之前,根据背景知识选定研究目标,进而确定选取的特征,用来确定簇含义。
  • 解释型:聚类前不知道目的,尝试用探索的方式找到一些现象。因而选择使用全部特征进行聚类后,再将某特征聚类后得到的簇类别作为标签y,其余特征作为x,通过重要性排序得到关键特征,再赋予其含义。

在实际使用的过程中,可以将两种思路相结合:先采取思路二进行全面的筛选与探索,然后利用思路一进行深入的设计。

下面通过信贷数据集,进行思路2的实操。

KMeans聚类

将经过预处理的数据(注意标准化),对所有特征进行KMeans算法聚类(确定最佳k值)

#标准化数据
from sklearn.preprocessing import StandardScaler
standard_scacler = StandardScaler()
X_scaled = standard_scacler.fit_transform(X)
#划分数据集
from sklearn.model_selection import train_test_split
X = data.drop(['Credit Default'], axis=1)  # 特征,axis=1表示按列删除
y = data['Credit Default'] # 标签
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)  # 80%训练集,20%测试集
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score,calinski_harabasz_score,davies_bouldin_score
from sklearn.decomposition import PCA

#最佳k值的确定
k_range = range(2,11) #定义k的取值范围
inertia_scores = []
silhouette_scores = []
ch_scores = []
db_scores = []

for k in k_range:
    kmeans = KMeans(n_clusters=k,random_state=42) #实例化
    k_labels = kmeans.fit_predict(X_scaled) #训练
    #计算评估指标
    inertia = kmeans.inertia_ #惯性值
    silhouette = silhouette_score(X_scaled,k_labels)
    ch_score = calinski_harabasz_score(X_scaled,k_labels)
    db_score = davies_bouldin_score(X_scaled,k_labels)
    #添加
    inertia_scores.append(inertia) 
    silhouette_scores.append(silhouette)
    ch_scores.append(ch_score)
    db_scores.append(db_score)
    #打印参数
    print(f'k={k},惯性:{inertia:.2f}, 轮廓系数:{silhouette:.2f}, CH指数:{ch_score:.2f}, DB指数:{db_score:.2f}')


#可视化
plt.figure(figsize=(12,10))

#k与惯性
plt.subplot(2,2,1)
plt.plot(k_range,inertia_scores,marker='o')
plt.title('肘部法则')
plt.xlabel('k')
plt.ylabel('inertia_score')
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值进行训练,并使用PCA降维可视化
selected_k = 3 #此处便于后续分析,选择数值3,实际不是最佳值
#建模训练
kmeans = KMeans(n_clusters=selected_k,random_state=42)
k_labels = kmeans.fit_predict(X_scaled)
X['Kmeans_clusters'] = k_labels #将聚类结果添加入原始数据
#PCA降维到2D
pca = PCA(n_components=2) 
x_pca = pca.fit_transform(X_scaled)
#可视化
sns.scatterplot(x=x_pca[:,0],y=x_pca[:,1],hue=k_labels,palette='viridis')
plt.xlabel('pca_component_1')
plt.ylabel('pca_component_2')
plt.show()

建模训练与特征筛选

将聚类后的簇类别作为标签y,剩余特征作为x。然后选择模型进行训练,借助shap库进行特征重要性的排序。

#特征筛选
#特征与标签划分
x1 = X.drop(columns=['Kmeans_clusters'],axis=1)
y1 = X['Kmeans_clusters']
from sklearn.ensemble import RandomForestClassifier
rf_model = RandomForestClassifier(random_state=42) #实例化
rf_model.fit(x1,y1) #训练
import shap
explainer = shap.TreeExplainer(model=rf_model) #初始化解释器
shap_values = explainer.shap_values(X=x1) #计算shap值

#绘图
shap.initjs()#初始化JavaScript可视化功能,确保后续可视化图表正常工作
shap.summary_plot(shap_values[:,:,0],x1,plot_type='bar',show=False)
plt.title('特征重要性排序条形图')
plt.show()

通过特征重要性条形图可以发现,前几个特征的shap值较高。为方便后续画图,选取前4个特征。这里判断了这几个特征的数据类型,可能是为了后续可视化特征分布的图形选择。

#特征的数据类型判断
selected_features = ['Purpose_debt consolidation','Bankruptcies','Number of Credit Problems','Purpose_other']
#判断特征为离散型还是连续型
for i in selected_features:
    unique_counts = X[i].nunique() # 唯一值指的是在某一列或某个特征中,不重复出现的值
    print(f'{i}有{unique_counts}个变量') # 连续型变量通常有很多唯一值,而离散型变量的唯一值较少
    if unique_counts < 10: # 这里 10 是一个经验阈值,可以根据实际情况调整
        print(f'{i}为离散特征')
    else:
        print(f'{i}为连续特征')

可视化与含义赋予

找到关键特征后,需要将每个簇别对应的特征分布展示出来,从而分析其中的特点,得到对应的“用户画像”。

具体而言,先筛选出不同簇的DataFrame,然后分别绘制对应的特征分布图即可。

#提取不同的簇别
x_cluster0 = X[X['Kmeans_clusters']==0]
x_cluster1 = X[X['Kmeans_clusters']==1]
x_cluster2 = X[X['Kmeans_clusters']==2]

#可视化簇别
#簇别0
fig,axes = plt.subplots(2,2,figsize=(12,10))
axes = axes.flatten()

for index,value in enumerate(selected_features):
    sns.histplot(x_cluster0[value],ax=axes[index],bins=20)#使用countplot,由于float型,导致跑得很慢
    axes[index].set_xlabel(f'{value}')
    axes[index].set_title(f'The Countplot of {value}')

plt.tight_layout()
plt.show()
#可视化簇别
#簇别1
fig,axes = plt.subplots(2,2,figsize=(12,10))
axes = axes.flatten()

for index,value in enumerate(selected_features):
    sns.histplot(x_cluster1[value],ax=axes[index],bins=20)#使用countplot,由于float型,导致跑得很慢
    axes[index].set_xlabel(f'{value}')
    axes[index].set_title(f'The Countplot of {value}')

plt.tight_layout()
plt.show()
#可视化簇别
#簇别2
fig,axes = plt.subplots(2,2,figsize=(12,10))
axes = axes.flatten()

for index,value in enumerate(selected_features):
    sns.histplot(x_cluster2[value],ax=axes[index],bins=20)#使用countplot,由于float型,导致跑得很慢
    axes[index].set_xlabel(f'{value}')
    axes[index].set_title(f'The Countplot of {value}')

plt.tight_layout()
plt.show()

完成可视化操作后,就需要对图进行分析,无论是借助自己的相关知识还是AI辅助分析,最终基本上可以得到不同簇别的特点,从而构建新的标签。后续只需按照之前的步骤进行处理(独热编码),建模训练,对比特征提取前后的效果,看模型的精度是否有所提高。

下面分别是簇别0,簇别1及簇别2的特征分布图

下面是借助AI分析得到的簇别含义的定义:

主要特征风险等级客户画像建议策略
0债务整合 + 1次破产 + 多信用问题高风险财务困难、曾破产、需重组审慎放贷,加强贷后监控,考虑更高利率或担保
1其他用途 + 无破产 + 无信用问题低风险收入稳定、信用良好、消费驱动可放宽审批,推荐长期产品
2债务整合 + 无破产 + 无信用问题低风险信用优秀、主动优化债务可优先授信,提供优惠利率或自动化服务

作业:心脏病数据集练习

针对心脏病数据集,练习上面的操作流程。

数据预处理与KMeans聚类,day17已练习过。只说明今日的内容。

#特征筛选
#特征与标签划分
x1 = X.drop(columns=['KMeans_clusters'],axis=1)
y1 = X['KMeans_clusters']
from sklearn.ensemble import RandomForestClassifier
rf_model = RandomForestClassifier(random_state=42) #实例化
rf_model.fit(x1,y1) #训练
import shap
explainer = shap.TreeExplainer(model=rf_model) #初始化解释器
shap_values = explainer.shap_values(X=x1) #计算shap值
#绘图
shap.initjs()#初始化JavaScript可视化功能,确保后续可视化图表正常工作
shap.summary_plot(shap_values[:,:,0],x1,plot_type='bar',show=False)
plt.title('特征重要性排序条形图')
plt.show()

#特征的数据类型判断
selected_features = ['thalach','trestbps','oldpeak','chol']
#判断特征为离散型还是连续型
for i in selected_features:
    unique_counts = X[i].nunique() # 唯一值指的是在某一列或某个特征中,不重复出现的值
    print(f'{i}有{unique_counts}个变量') # 连续型变量通常有很多唯一值,而离散型变量的唯一值较少
    if unique_counts < 10: # 这里 10 是一个经验阈值,可以根据实际情况调整
        print(f'{i}为离散特征')
    else:
        print(f'{i}为连续特征')
#提取不同的簇别
x_cluster0 = X[X['KMeans_clusters']==0]
x_cluster1 = X[X['KMeans_clusters']==1]
x_cluster2 = X[X['KMeans_clusters']==2]
x_cluster3 = X[X['KMeans_clusters']==3]

#可视化簇别
#簇别0
fig,axes = plt.subplots(2,2,figsize=(12,10))
axes = axes.flatten()

for index,value in enumerate(selected_features):
    sns.histplot(x_cluster0[value],ax=axes[index],bins=20)#连续特征
    axes[index].set_xlabel(f'{value}')
    axes[index].set_title(f'The Countplot of {value}')

plt.tight_layout()
plt.show()

剩下的几个簇别,绘图类似,最后得到下面四个图:

通过AI分析后得到下面的结果:

thalachtrestbpsoldpeakchol临床画像风险等级
0偏低正常轻微缺血正常轻度心功能不全中等
1正常偏高偏高无缺血偏高高血压+高血脂中高
2显著偏高正常偏低无缺血正常心血管健康
3偏低偏高显著缺血偏高多重风险+心肌缺血

下面代码为什么运行到“corrmat = train_data.corr() ”时报错,报错信息为“ValueError: could not convert string to float: '2010-01-02'” import pandas as pd import seaborn as sns # seaborn就是在matplotlib基础上面的封装,方便直接传参数调用 import matplotlib.pyplot as plt import numpy as np import warnings # 所有警告类别类的基类 warnings.filterwarnings('ignore') # 读入数据 train_csv = 'trainOX.csv' train_data = pd.read_csv(train_csv) test_csv = 'test_noLabelOX.csv' test_data = pd.read_csv(test_csv) train_data.head(10) # 训练前10行 train_data.isnull().sum() # 处理缺失值 # 数据规范化 import time # 对时间的处理 # 定义了获取年份的函数,参数dt,返回值t.tm_year def getYear(dt): t = time.strptime(dt,'%Y-%m-%d') return t.tm_year # 定义了获取月份的函数,参数dt,返回值t.tm_mon def getMonth(dt): t = time.strptime(dt,'%Y-%m-%d') return t.tm_mon # 定义了获取日期的函数,参数dt,返回值t.tm_mday def getDay(dt): t = time.strptime(dt,'%Y-%m-%d') return t.tm_mday # 定义了获取星期的函数,参数dt,返回值t.tm_wday def getWeek(dt): t = time.strptime(dt, '%Y-%m-%d') return t.tm_wday # 一周的周几 # 利用train_data函数对日期数据进行处理 train_data['year']=train_data['date'].apply(getYear) train_data['month']=train_data['date'].apply(getMonth) train_data['day']=train_data['date'].apply(getDay) train_data['week']=train_data['date'].apply(getWeek) # 利用test_data函数对日期数据进行处理 test_data['year']=test_data['date'].apply(getYear) test_data['month']=test_data['date'].apply(getMonth) test_data['day']=test_data['date'].apply(getDay) test_data['week']=test_data['date'].apply(getWeek) # 拟合标准正态分布 from scipy.stats import norm sns.distplot(train_data['Label'],fit=norm) print("Skewness: %f"% train_data['Label'].skew()) # 返回峰值的不对称程度 print("Kurtosis: %f"% train_data['Label'].kurt()) # 返回数据的峰度 # 检验样本数据概率分布(例如正态分布) # 预测Label(pm2.5)值线性分布可能性检测,probplot函数计算一个当前样本最可能的线性分布 # 并用plt展示出来,我们可以直观的看到线性拟合程度并不好。 from scipy import stats fig = plt.figure() res = stats.probplot(train_data['Label'],plot=plt) # 默认检测是正态分布 train_data[train_data['Label']==0].head(10) # 做 log 转换。虽然值做 log 转换会出错,但是0值只有2条,可以去掉。 train_data = train_data.drop(train_data[train_data['Label'] == 0].index) # 删除某几列 train_data['Label_log']= np.log(train_data['Label']) sns.distplot(train_data['Label_log'],fit=norm) # 再次拟合标准正态分布 print("Skewness: %f" % train_data['Label_log'].skew()) print("Kurtosis: %f" % train_data['Label_log'].kurt()) res = stats.probplot(train_data['Label_log'], plot=plt) var = 'DEWP' # 沿着指定的轴将train_data['Label_log’]和train_data[var]]拼接到一起,axis=1表示左右拼接。 data = pd.concat([train_data['Label_log'], train_data[var]], axis=1) # scatter绘制散点数据,x,y为坐标数据,ylim=(0,10)限制范围 data.plot.scatter(x=var,y='Label_log',ylim=(0,10)) # 下面的程序同理 var = 'TEMP' data = pd.concat([train_data['Label_log'], train_data[var]], axis=1) data.plot.scatter(x=var,y='Label_log',ylim=(0,10)) var = 'Iws' data = pd.concat([train_data['Label_log'], train_data[var]], axis=1) data.plot.scatter(x=var,y='Label_log',ylim=(0,10)) var ='Ir' data = pd.concat([train_data['Label_log'], train_data[var]], axis=1) data.plot.scatter(x=var, y='Label_log',ylim=(0, 10)) var ='DEWP' data = pd.concat([train_data['Label_log'], train_data[var]], axis=1) f, ax = plt.subplots(figsize=(20,16)) fig = sns.boxplot(x=var,y='Label_log', data=data) fig.axis(ymin=0,ymax=10) # 相关性分析 # correlation matrix相关矩阵 corrmat = train_data.corr() # corrmat是相关性矩阵 f,ax = plt.subplots(figsize=(12,8)) # 绘制画布 sns.heatmap(corrmat,vmax=0.8,square=True) # 得到各特征图的热力图 k = 5 # 关系矩阵中将显示10个特征 cols_large = corrmat.nlargest(k,'Label_log')['Label_log'].index # 显示和Label_1og相关性最大的K项 cols_small = corrmat.nsmallest(k,'Label_log')['Label_log'].index # 显示机Label_log相关性最小的K项 cols =cols_large.append(cols_small) cols cm = np.corrcoef(train_data[cols].values.T) sns.set(rc = {"figure.figsize":(12,10)}) sns.set(font_scale=1.25) hm = sns.heatmap(cm, cbar=True, annot=True, square=True, fmt='.2f', annot_kws={'size':10}, yticklabels=cols.values, xticklabels=cols.values) plt.show() train_data['hour']=train_data['hour'].astype('float32') train_data['DEWP']=train_data['DEWP'].astype('float32') train_data['TEMp']=train_data['TEMp'].astype('float32') train_data['PREs']=train_data['PREs'].astype('float32') train_data['Iws']=train_data['Iws'].astype('float32') train_data['Is']=train_data['Is'].astype('float32') train_data['Ir']=train_data['Ir'].astype('float32') train_data['cbwd_NE']=train_data['cbwd_NE'].astype('float32') train_data['cbwd_NW']=train_data['cbwd_NW'].astype('float32') train_data['cbwd_SE']=train_data['cbwd_SE'].astype('float32') train_data['cbwd_cv']=train_data['cbwd_cv'].astype('float32') train_data['year']=train_data['year'].astype('float32') train_data['month']=train_data['month'].astype('float32') train_data['day']=train_data['day'].astype('float32') train_data['week']=train_data['week'].astype('float32') train_data['Label log']=train_data['Label log'].astype('float32') test_data['hour']=test_data['hour'].astype('float32') test_data['DEWP']=test_data['DEWP'].astype('float32') test_data['TEMP']=test_data['TEMP'].astype('float32') test_data['PRES']=test_data['PREs'].astype('float32') test_data['Iws']=test_data['Iws'].astype('float32') test_data['Is']=test_data['Is'].astype('float32') test_data['Ir']=test_data['Ir'].astype('float32') test_data['cbwd_NE']=test_data['cbwd_NE'].astype('float32') test_data['cbwd_NW']=test_data['cbwd_Nw'].astype('float32') test_data['cbwd_SE']=test_data['cbwd_SE'].astype('float32') test_data['cbwd_cv']=test_data['cbwd_cv'].astype('float32') test_data['year']=test_data['year'].astype('float32') test_data['month']=test_data['month'].astype('float32') test_data['day']=test_data['day'].astype('float32') test_data['week']=test_data['week'].astype('float32') train_data.columns from sklearn.linear_model import LinearRegression # 线性回归 from sklearn.model_selection import train_test_split # 拆分工具 from sklearn.metrics import mean_squared_error # 均方根误差 # train_data.drop(['date'],axis=1,inplace=True) # 删除无关属性 y=train_data['Label log'] # 类标签的转换 var =['hour','DEWP','TEMP','PRES','IWS','IS','Ir', 'cbwd_NE','cbwd_NW','cbwd_SE','cbwd_cv','year', 'month', 'day','week'] X=train_data[var] # 新的训练数据 X_train, X_val, y_train, y_val = train_test_split(X,y,test_size=0.2,random_state=42) # 对数据的拆分 reg = LinearRegression().fit(X_train,y_train) # 回归分析 y_val_pre=reg.predict(X_val) # 回归预测 y_val1 = y_val.reset_index(drop=True) print("Mean squared error:%.2f"% mean_squared_error(y_val1,y_val_pre)) df1 = pd.DataFrame(y_val_pre,columns =['p']) # 建立检验数据框 df1['r'] = y_val df1.to_csv("test1.csv",encoding = "utf-8",header=1,index=0) X_test = test_data[var] # 训练数据 y_test = reg.predict(X_test) # predict:数据预测 y_rel = np.round(np.exp(y_test)) # 对浮点数取整 df = pd.DataFrame(y_rel,columns =['pm2.5']) # DataFrame()创建一个DataFrame对象 df.to_csv("sample.csv",encoding = "utf-8",header=1,index=0) # to_csv保存csv文件
10-31
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值