day 18 通过聚类进行特征工程,得到新特征

一、聚类得到的新特征如何赋予含义? 

思路一:基于预定义特征进行聚类与含义赋予

假设你明确知道自己想要研究的方向,比如 “消费者购买习惯”。在这种情况下:

  • 聚类特征选择:一开始就挑选与 “消费者购买习惯” 紧密相关的特征来进行聚类分析。像 “过去的购买记录”(比如购买的商品种类)、“购买金额” 等特征,它们直接反映了消费者的购买行为模式。而像 “消费者年龄”“工作行业” 这些特征与购买习惯的直接关联性较弱,所以在聚类过程中暂不考虑。
  • 簇含义确定:通过使用这些与购买习惯相关的特征完成聚类后,得到的各个簇自然就基于这些特征产生了差异。比如,某个簇中的消费者可能都具有高消费金额、高消费频率且集中购买某几类商品的特点,那么你就可以将这个簇赋予 “高消费、高频且偏好特定商品的消费者群体” 这样的含义。

这种思路适用于已经清楚知道自己想要构造具有明确含义的簇,并且能够明确指出与该含义相关的特征。

思路二:先全特征聚类,再通过监督模型筛选特征赋予含义

当你刚开始时并不清楚要构造什么含义的簇,不知道哪些特征会对最终的簇含义起关键作用时:

  • 全特征聚类:首先使用数据集中的全部特征进行聚类分析。这样做可以充分利用所有信息,让聚类算法自由地发现数据中的潜在结构。
  • 构建监督模型:完成聚类分析后,把聚类所得到的各个簇类别(例如簇 1、簇 2、簇 3 等)当作标签。接下来在构建监督学习模型时,把数据集中一部分特征分离出来作为“待考察特征(我们最初认为与定义簇的含义紧密相关的因素)”,将除这部分特征之外的其他所有特征作为输入变量,用 x 表示。
  • 特征筛选与含义赋予:根据监督模型给出的特征重要性排序,筛选出对区分不同簇类别最为重要的那些特征。这些被筛选出来的特征就是你用来赋予每个簇实际含义的依据。比如,若监督模型显示 “是否会员” 对划分簇很重要,那么结合 “待考察特征”,我们可能将某个簇定义为 “年龄在 30 - 40 岁、居住在一线城市、消费金额较高、购买频率中等的男性会员群体”。

这种思路适用于对要构造的簇含义没有明确想法,需要通过数据分析来探索哪些特征能帮助定义有意义的簇。

二、聚类流程

# 先运行之前预处理好的代码
import pandas as pd
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('data.csv')    #读取数据

# 先筛选字符串变量 
discrete_features = data.select_dtypes(include=['object']).columns.tolist()
# Home Ownership 标签编码
home_ownership_mapping = {
    'Own Home': 1,
    'Rent': 2,
    'Have Mortgage': 3,
    'Home Mortgage': 4
}
data['Home Ownership'] = data['Home Ownership'].map(home_ownership_mapping)

# Years in current job 标签编码
years_in_job_mapping = {
    '< 1 year': 1,
    '1 year': 2,
    '2 years': 3,
    '3 years': 4,
    '4 years': 5,
    '5 years': 6,
    '6 years': 7,
    '7 years': 8,
    '8 years': 9,
    '9 years': 10,
    '10+ years': 11
}
data['Years in current job'] = data['Years in current job'].map(years_in_job_mapping)

# Purpose 独热编码,记得需要将bool类型转换为数值
data = pd.get_dummies(data, columns=['Purpose'])

data2 = pd.read_csv("data.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就是独热编码后的特征名

# Term 0 - 1 映射
term_mapping = {
    'Short Term': 0,
    'Long Term': 1
}
data['Term'] = data['Term'].map(term_mapping)
data.rename(columns={'Term': 'Long Term'}, inplace=True) # 重命名列

# 把筛选出来的列名转换成列表
continuous_features = data.select_dtypes(include=['int64', 'float64']).columns.tolist()  
 
# 连续特征用众数补全
for feature in continuous_features:     
    mode_value = data[feature].mode()[0]            #获取该列的众数。
    data[feature].fillna(mode_value, inplace=True)          #用众数填充该列的缺失值,inplace=True表示直接在原数据上修改。

# 最开始也说了 很多调参函数自带交叉验证,甚至是必选的参数,你如果想要不交叉反而实现起来会麻烦很多
# 所以这里我们还是只划分一次数据集
from sklearn.model_selection import train_test_split
X = data.drop(['Credit Default'], axis=1)  # 特征,axis=1表示按列删除
y = data['Credit Default'] # 标签
# # 按照8:2划分训练集和测试集
# X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)  # 80%训练集,20%测试集

1、标准化数据

  • 不同特征可能具有不同的量纲和数值范围,标准化可以确保所有特征在相同的尺度上进行比较,避免某些特征主导聚类结果
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

2、选择合适的算法,根据评估指标(绘图)调参

  • KMeans:主要参数是聚类数 k。通常需要借助如肘部法则、轮廓系数等评估指标来确定。
  • DBSCAN:主要参数是邻域半径 eps 和最小点数 min_samples。需根据评估指标仔细调优。
  • 层次聚类:除了确定聚类数,linkage 准则(如 wardsinglecompleteaverage 等)也显著影响聚类结果,不同的准则会使簇间合并的方式不同,从而产生不同的聚类结构,因此也需要仔细调优。
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 值
selected_k = 3 # 这里选择3后面好分析,也可以根据图选择最佳的k值

# 使用选择的 k 值进行 KMeans 聚类
kmeans = KMeans(n_clusters=selected_k, random_state=42)
kmeans_labels = kmeans.fit_predict(X_scaled)
X['KMeans_Cluster'] = kmeans_labels

3、将聚类后的特征添加到原数据中

用聚类结果作为标签训练模型

X.columns
# 删除聚类标签列
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库查看重要特征

# shap.initjs() 会加载必要的 JavaScript 文件,确保这些可视化能够正常显示和交互。
shap.initjs()
# 初始化 SHAP 解释器
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(x1) # 这个计算耗时
shap_values=np.array(shap_values)
shap_values.shape 
# --- 1. SHAP 特征重要性条形图 (Summary Plot - Bar) ---
print("--- 1. SHAP 特征重要性条形图 ---")
#  这里的show=False表示不直接显示图形,这样可以继续用plt来修改元素,不然就直接输出
shap.summary_plot(shap_values[0, :, :], x1, plot_type="bar",show=False)  
plt.title("SHAP Feature Importance (Bar Plot)")
plt.show()

重要特征在整体数据的分布

# 此时判断一下这几个特征是离散型还是连续型
import pandas as pd
selected_features = ['Purpose_debt consolidation', 'Bankruptcies',
                     'Number of Credit Problems', 'Purpose_other']

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} 可能是连续型变量')

 

import matplotlib.pyplot as plt
# 总样本中的前四个重要性的特征分布图
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
# 将二维子图布局索引扁平化为一维索引,在遍历子图时,方便使用简单的循环索引
axes = axes.flatten()

for i, feature in enumerate(selected_features):
    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()

从图中可看出的信息:

  • 分布集中趋势:在 “Purpose_debt consolidation” 特征上,数据主要集中在 0.0 和 1.0 附近,说明大部分样本在债务整合目的方面倾向于这两个极端状态 ;“Bankruptcies” 特征集中在 0.0 ,表明多数样本无破产情况;“Number of Credit Problems” 集中在 0.0 ,意味着多数样本信用问题少;“Purpose_other” 集中在 0.0 ,说明在该 “其他目的” 特征上多数取值为 0 。

  • 离散程度:如 “Purpose_debt consolidation” 除了两个集中区域外,其他区域分布较少,离散程度相对较高;而像 “Bankruptcies” 在非 0.0 区域分布极少,离散程度低。

作用:

  • 了解数据全貌:能直观掌握各特征在整体数据中的分布情况,知晓数据的集中趋势、离散程度等基本特征,为后续分析奠定基础。例如,知道大部分样本信用问题少,可初步判断整体信用状况较好 。

  • 异常值检测:通过观察分布,可发现偏离主要分布区域的数据点,即异常值。

  • 特征对比:可以和特定簇上的分布作对比。对比整体分布和特定簇分布,能发现特定簇在各特征上与整体的差异。如某个簇的 “Bankruptcies” 分布与整体不同,可深入探究该簇特殊之处,辅助理解聚类结果的合理性和簇的特性 。

重要特征在特定簇数据中的分布 

# 绘制出每个簇对应的这四个特征的分布图
X[['KMeans_Cluster']].value_counts()
# 分别筛选出每个簇的数据
# 只有在布尔序列中对应位置为 True 的行会被选中。
X_cluster0 = X[X['KMeans_Cluster'] == 0]
X_cluster1 = X[X['KMeans_Cluster'] == 1]
X_cluster2 = X[X['KMeans_Cluster'] == 2]

# 先绘制簇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):
    axes[i].hist(X_cluster0[feature], bins=20)
    axes[i].set_title(f'簇0的Histogram of {feature}')
    axes[i].set_xlabel(feature)
    axes[i].set_ylabel('Frequency')

plt.tight_layout()
plt.show()


# 绘制簇1的分布图
import matplotlib.pyplot as plt

# 总样本中的前四个重要性的特征分布图
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
axes = axes.flatten()

for i, feature in enumerate(selected_features):
    axes[i].hist(X_cluster1[feature], bins=20)
    axes[i].set_title(f'簇1的Histogram of {feature}')
    axes[i].set_xlabel(feature)
    axes[i].set_ylabel('Frequency')

plt.tight_layout()
plt.show()


# 绘制簇2的分布图
import matplotlib.pyplot as plt

# 总样本中的前四个重要性的特征分布图
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
axes = axes.flatten()

for i, feature in enumerate(selected_features):
    axes[i].hist(X_cluster2[feature], bins=20)
    axes[i].set_title(f'簇2的Histogram of {feature}')
    axes[i].set_xlabel(feature)
    axes[i].set_ylabel('Frequency')

plt.tight_layout()
plt.show()

通过可视化图形总结与定义簇

簇 0:优质信用稳健财务型

  • 债务整合目的(Purpose_debt consolidation):大部分集中在 1.0 左右,说明该簇中样本在债务整合目的这一特征上取值较为单一 。

  • 破产情况(Bankruptcies):集中在 0.0 附近,即该簇样本几乎不存在破产情况。

  • 信用问题数量(Number of Credit Problems):集中在 0.0 附近,即信用问题数量极少。

  • 其他目的(Purpose_other):集中在 0.0 附近,说明在 “其他目的” 这一特征上取值很低 。

  • 特征总结:信用状况良好,几乎没有债务整合需求、破产记录和其他特殊目的。

簇 1:较稳健但信用有分化财务型

  • 债务整合目的(Purpose_debt consolidation):集中在 0.0 附近,表明债务整合需求低 。

  • 破产情况(Bankruptcies):集中在 0.0 附近,即基本无破产情况 。

  • 信用问题数量(Number of Credit Problems):集中在 0.0 附近,1.0附近较少,说明信用问题少 。

  • 其他目的(Purpose_other):呈现出两个峰值,一个在 0.0 附近,另一个在 0.8 - 1.0 之间,说明在 “其他目的” 上存在一定的异质性 。

  • 特征总结:整体信用状况较好,债务整合和破产风险低,信用问题上存在个体差异,资金用途有一定分散性。信用问题和资金用途的分化情况使其区别于第一个簇。

簇 2:高风险财务困境型

  • 债务整合目的(Purpose_debt consolidation):分布较为分散,主要集中在 0.0 和 1.0 附近,说明在债务整合目的上存在两种倾向 。

  • 破产情况(Bankruptcies):集中在 1.0 附近,即存在一定比例的破产情况 。

  • 信用问题数量(Number of Credit Problems):集中在 1 - 2 之间,说明信用问题数量相对较多 。

  • 其他目的(Purpose_other):集中在 0.0 附近,少部分集中在 1.0 附近,存在一定差异。

  • 特征总结:债务合并需求分化,破产经历较多,信用问题普遍且严重,资金用途有差异。

现在就得到了一个全新的特征,完成了特征工程。后续对这个特征进行独热编码,然后重新建模训练,如果加了这个特征后模型精度提高,说明这个特征是有用的(科研逻辑闭环)。

4、选择t-SNE或者PCA进行2D或3D可视化

# 使用 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())

@浙大疏锦行 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值