ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于KMeans的银行客户细分全流程:从数据清洗到决策树规则解释

基于KMeans的银行客户细分全流程:从数据清洗到决策树规则解释 简介面向银行与金融机构数据分析师、机器学习入门者的一份实战资源聚焦客户细分场景利用聚类等算法结合可视化工具帮助理解客户行为差异并优化产品推荐与风险管理。压缩包体积仅17KB包含2个文件一个Python脚本用于完整的数据清洗、特征构建、模型训练与可视化输出一个CSV文件提供德国银行信贷客户样本数据便于直接运行与结果复现。目前已有67人学习下载。脚本内置柱状图、散点图、热图等多种图表生成逻辑可直观呈现不同客户群体在收入水平、风险偏好、交易频率等维度上的分布与群间差异同时展示了聚类数量选择、轮廓系数评估与结果解读的关键思路。学习者既可掌握客户细分的标准分析流程也可基于自带数据调整参数、扩展特征形成属于自己的可视化分析方案用于课程设计、毕业设计或实际业务探索直观感受机器学习在金融场景下的落地价值。1. 没有标签也能分客群聚类先把客户“切开”银行手里最不缺的就是数据但最缺的往往是“标注”。真实业务里客户经理大脑里的“高净值客户”“价格敏感型”“风险偏好型”这些概念系统里并没有一张现成的标签表——它只记录交易、账户、借贷这些原始痕迹。这正是bank-customer-segmentation.py这类脚本存在的理由用无监督聚类把没有标注的客户数据先切成几个行为模式一致的群体再靠可视化把这些群体的画像摊开给业务方看。本文基于german_credit_data.csv和对应的 Python 脚本完整走一遍从字段清洗、特征编码、KMeans 聚类、PCA 降维可视化到决策树反推分群规则的全流程。适合正在做客户画像、营销响应建模或信贷风控特征工程的开发者和数据分析师看的是“这批数据到手之后究竟哪些坑要先踩哪些参数要试”。2. german_credit_data 字段解读与预处理直接喂聚类算法几乎必翻车2.1 这个数据集里到底有什么german_credit_data.csv是信贷申请记录的经典样本脚本里用它来做客户细分字段虽然不多但类型很杂——连续数值、有序类别、无序类别混在一起。动手之前先把字段结构摸清楚比写代码更重要。字段名类型含义常见取值Age连续年龄19~75Sex类别性别male / femaleJob有序类别工作等级0~3越高越稳定Housing类别住房类型own / rent / freeSaving accounts有序类别储蓄账户等级little / moderate / quite rich / richChecking account有序类别支票账户等级little / moderate / richCredit amount连续信贷金额欧元Duration连续贷款期限月数Purpose类别贷款用途car / education / vacation 等细看就会发现Age的范围是 19 到 75Credit amount可能从几百到几万欧元量纲不在一个数量级上而Job、Saving accounts这类带顺序的类别又不能和Sex、Purpose这种纯粹的分类用同一种编码方式。如果直接把 DataFrame 丢给 KMeans欧氏距离会被Credit amount主导年龄和储蓄等级的信息几乎起不了作用。2.2 清洗与编码先处理缺失再统一量纲原 CSV 文件里比较常见的缺失位置在Saving accounts和Checking account两列很多申请人没有开户记录对应的单元格就是空的。常见做法是用该列众数填充原因很简单聚类对缺失值零容忍而这两列本身是有序类别众数代表“大多数人的水平”不会引入极端偏差。import pandas as pd import numpy as np from sklearn.impute import SimpleImputer from sklearn.preprocessing import StandardScaler, OrdinalEncoder df pd.read_csv(german_credit_data.csv) print(df.isnull().sum()) # Saving accounts 和 Checking account 通常存在缺失采用众数填充 imputer SimpleImputer(strategymost_frequent) df[[Saving accounts, Checking account]] imputer.fit_transform( df[[Saving accounts, Checking account]] ) # 有序类别按级别人工指定顺序 ordered_cols [Job, Saving accounts, Checking account] job_order [0, 1, 2, 3] saving_order [little, moderate, quite rich, rich] checking_order [little, moderate, rich] ord_encoder OrdinalEncoder( categories[job_order, saving_order, checking_order] ) df_ordered ord_encoder.fit_transform(df[ordered_cols]) # 无序类别直接用 one-hot df_unordered pd.get_dummies( df[[Sex, Housing, Purpose]], prefix[sex, housing, purpose] ) # 连续特征保留原值后续统一做标准化 df_numeric df[[Age, Credit amount, Duration]].copy() # 组装成模型输入矩阵 X pd.concat( [ pd.DataFrame(df_ordered, columnsordered_cols), df_unordered.reset_index(dropTrue), df_numeric.reset_index(dropTrue), ], axis1, ) # 标准化让连续变量和编码变量的尺度可比 scaler StandardScaler() X_scaled scaler.fit_transform(X)逻辑说明OrdinalEncoder的关键在于categories参数——必须手动传入顺序否则算法按字母序排列moderate会排在little前面语义就错了。Sex、Housing、Purpose没有天然顺序用 one-hot 编码展开成 0/1 列避免给虚数关系。StandardScaler对所有列统一做零均值单位方差变换这一步最容易被忽略但恰恰决定了 KMeans 是“按信用额度分群”还是“按综合画像分群”。参数说明SimpleImputer的strategymost_frequent只适合类别列连续列缺失应该用中位数OrdinalEncoder传入的顺序列表必须和columns一一对应顺序错位会直接导致数据语义错乱。标准化之后所有特征的方差都为 1KMeans 的损失函数不再偏向某个高数值列。3. 聚类训练与参数寻优用肘部法则和轮廓系数锁定 K 值3.1 为什么选 KMeans 而不是 DBSCAN客户细分场景下KMeans 是默认首选原因有三个数据量在万级以内时训练极快聚类结果用cluster_centers_可以直接输出每个群体的“中心画像”对球形分布的群体效果稳定。DBSCAN 的优势是不需要预设 K 值、能识别噪声点但银行客户数据在标准化之后往往没有清晰的密度差异调eps和min_samples的成本远比 KMeans 高产出却不容易解释。KMeans 的本质是使每个样本到所属簇中心的距离平方和最小化属于硬聚类。它假设每个簇在特征空间里是凸的因此如果客户群体存在明显的长尾分布建议先做 PCA 可视化再决定是否换模型。3.2 肘部法则画出 SSE 曲线再拍板K 值不是越大越好。当 K 小于真实簇数时每增加一个簇组内平方和SSE会大幅下降超过真实簇数后新增簇分掉的样本太少SSE 下降幅度趋缓曲线会呈现一个“肘部”拐点。手肘位置就是候选 K 值。from sklearn.cluster import KMeans import matplotlib.pyplot as plt sse [] k_range range(2, 11) for k in k_range: model KMeans(n_clustersk, n_init10, random_state42) model.fit(X_scaled) sse.append(model.inertia_) plt.figure(figsize(8, 5)) plt.plot(k_range, sse, markero) plt.xlabel(K) plt.ylabel(SSE) plt.title(KMeans 肘部曲线) plt.grid(True) plt.savefig(elbow.png, dpi150)model.inertia_就是该 K 值下的 SSE 总和。脚本里分别跑了 K2 到 K10每次都用相同的random_state42保证对比公平。画出曲线后通常会看到 K4 或 K5 处出现明显拐点再往后曲线趋于平缓。这里强调n_init10是为了避免 KMeans 因随机初始化陷入局部最优——每个 K 值下算法独立跑 10 次取损失最小的那一次作为最终结果。注意random_state 一定要固定否则团队成员各自跑一遍画出来的肘部曲线形状可能完全不一样后面所有分析结果都无法复现。3.3 轮廓系数筛选SSE 之外的第二道校验肘部法则判断的是“下降趋势变了”但有时候曲线比较平滑肉眼不好找拐点。这时候用轮廓系数做交叉验证。某个样本的轮廓系数 簇外平均距离 - 簇内平均距离/ 两者中较大者取值在 -1 到 1 之间越接近 1 表示样本离自己簇内成员近、离其他簇远聚类效果好。from sklearn.metrics import silhouette_score silhouette_scores [] for k in k_range: model KMeans(n_clustersk, n_init10, random_state42) labels model.fit_predict(X_scaled) sil silhouette_score(X_scaled, labels) silhouette_scores.append(sil) for k, s in zip(k_range, silhouette_scores): print(fK{k}, silhouette{s:.4f})常见做法是输出每个 K 值对应的轮廓系数结合肘部曲线一起看。如果某个 K 的 SSE 降幅明显变小轮廓系数又高于相邻值基本可以确定是合理的分群数量。真实项目里这一步不要自动化——不同业务目标对 K 的偏好不同营销团队希望分群更细以差异化投放风控团队则往往追求更少的、稳定的高风险群体。3.4 PCA 降维可视化把高维分群画成二维散点图预处理后的特征维度在 20 个左右直接画图不现实。bank-customer-segmentation.py里配套的绘图逻辑通常是把 KMeans 聚类结果和 PCA 降维结果拼接在一起再按不同颜色散点展示。PCA 的可解释性比常用的 t-SNE 更好——t-SNE 能拉开视觉距离但坐标轴失去了真实线性组合的意义业务方看了说不出所以然。from sklearn.decomposition import PCA # 聚类 final_k 4 kmeans KMeans(n_clustersfinal_k, n_init10, random_state42) labels kmeans.fit_predict(X_scaled) # 降维到 2 维用于可视化 pca PCA(n_components2, random_state42) X_pca pca.fit_transform(X_scaled) plt.figure(figsize(10, 6)) scatter plt.scatter( X_pca[:, 0], X_pca[:, 1], clabels, cmapviridis, alpha0.7, s30 ) plt.colorbar(scatter) plt.xlabel(PC1) plt.ylabel(PC2) plt.title(KMeans 聚类结果 PCA 可视化) plt.savefig(cluster_pca.png, dpi150)参数说明PCA(n_components2)保留前两个主成分通常能解释约 40%~60% 的方差。如果两个主成分累计解释率太低说明原始特征间的信息较分散散点图上看不出干净的分簇边界是正常的不要因此否定聚类效果。可视化只用来辅助确认“簇之间有分离趋势”不是模型评价指标。4. 决策树反推分群规则把聚类标签翻译给业务听4.1 聚类结果本身没有业务含义KMeans 输出的标签 0、1、2、3 只是簇的编号不携带任何语义。和数据打交道的人知道cluster_centers_每个维度的均值但业务方关心的是“这些客户到底是谁”。要回答这个问题有一个脱胎于模型可解释性的通用做法把聚类得到的标签当作目标变量原始特征作输入训练一棵深度受限的决策树。为什么这么做决策树天然输出 if-else 规则而受限深度能保证规则宽度足够小。用全量特征训练 KMeans 时每个聚类中心是由全部特征共同定义的但通过决策树的反推可以找到区分不同簇的关键特征子集。from sklearn.tree import DecisionTreeClassifier, plot_tree # 使用预处理后的特征标签为聚类结果 clf DecisionTreeClassifier( max_depth3, min_samples_leaf50, random_state42 ) clf.fit(X, labels) # 输出特征重要度 importance pd.DataFrame({ feature: X.columns, importance: clf.feature_importances_ }).sort_values(importance, ascendingFalse) print(importance.head(10)) # 绘制决策树 plt.figure(figsize(18, 8)) plot_tree( clf, feature_namesX.columns, class_names[fCluster_{i} for i in range(final_k)], filledTrue, fontsize10 ) plt.savefig(decision_tree_rules.png, dpi150)这里的X是未经标准化的原始特征 DataFrame因为决策树是阈值型分裂器不受量纲影响不需要标准化。max_depth3限制树的深度让规则控制在不超过 8 条路径的水平min_samples_leaf50是叶节点最小样本数防止某个分群结果只覆盖个位数客户那样产出的规则没有统计意义。4.2 典型分群规则解读示例决策树画出来之后从根节点到叶子节点的路径就是规则。以固定random_state42的一个典型结果为例路径特征分裂条件指向集群可解读画像Credit amount 3266Cluster_2小额信贷客户储蓄水平中等Credit amount 3266, Duration 24右子树Cluster_0大额长期信贷支票账户充裕Age 42, Purpose retraining / car右子树Cluster_3中年再培训或购车人群这棵树拿给业务方看时话术就非常直接Credit amount是区分客户群的第一关键变量金额高的客户在选择贷款用途和期限上的行为模式更接近属于“大额谨慎型”金额低且存款水平中等的客户则呈现更高的价格敏感度。这些结论比单纯展示簇中心点更让人信服。提示如果决策树第一个分裂特征不是 Credit amount 而是某个 one-hot 编码的 Purpose 列说明该用途类别的区分度极高要回到原始数据确认样本量是否足够避免小样本偶然性被放大。5. 另一种验证视角用轮廓系数做结果稳定性检验5.1 单次聚类不可信多次聚类才能暴露问题KMeans 对初始质心随机且数据分布不对称时即使固定random_state不同的种子也可能在误差很小的情况下生成不同的簇分配。生产中至少要做一次稳定性检验对同一份数据换 5 到 10 个随机种子跑同样的 K 值比较每个样本被划分到不同簇的比例。高稳定性意味着样本在各次运行中几乎总是被分到同一个簇或语义相近的簇。若某类客户的标签频繁跳跃说明他们游走在簇边界上单靠行为特征无法稳健区分。这类客户在营销上恰恰最有价值——他们处于决策边界容易被针对性的产品组合撬动。import random n_runs 10 label_matrix [] for seed in range(n_runs): km KMeans(n_clustersfinal_k, n_init10, random_stateseed) label_matrix.append(km.fit_predict(X_scaled)) label_matrix np.array(label_matrix) # 计算每对样本在多次运行中落入同一簇的概率 n_samples X_scaled.shape[0] cooccurrence np.zeros((n_samples, n_samples)) for seed_idx in range(n_runs): labels_run label_matrix[seed_idx] for i in range(n_samples): cooccurrence[i] (labels_run labels_run[i]) cooccurrence / n_runs print(共现矩阵均值:, cooccurrence.mean()) print(共现矩阵中低于0.7的样本对占比:, (cooccurrence 0.7).sum() / (n_samples * n_samples))如果共现矩阵中低于 0.7 的样本对占比超过 30%就说明聚类结果对初始种子敏感当前特征组合的分群边界太模糊。常见做法是去调整特征集把噪声偏大的卡片特征删掉再试一轮。5.2 用簇中心点做“立体可视化”PCA 散点图只能看分布形态看不出每个簇的具体特征取值。一个比散点图更适合汇报的技巧把 KMeans 的cluster_centers_连同各簇的均值、标准差打出来横向比较。这比 scatter 图更能回答“这群人到底是谁”。centers kmeans.cluster_centers_ # centers 是标准化后的坐标需要还原到原始特征 centers_original scaler.inverse_transform(centers) center_df pd.DataFrame( centers_original, columnsX.columns ) center_df[cluster] range(final_k) # 挑选业务方最关心的字段做对比 report_cols [ Age, Credit amount, Duration, Job, Saving accounts, Checking account ] print(center_df[report_cols].T)还原后的簇中心可以按业务习惯排序平均信贷金额最高的簇如果储蓄水平均值也不低说明这部分客户属于“有实力但杠杆高”的群体适合推大额低息产品平均金额最低但持续时间最长的簇则要警惕“小额长期借贷”信号对应着潜在的资金链紧张。提示cluster_centers_ 输出顺序与 fit 时的特征列一致标准化逆变换必须使用同一个 scaler 实例否则数值还原完全错乱。5.3 落地上线前的最后一步可视化工作完成后把labels合并回原始 DataFrame导出成带分群标签的宽表方便后续对接营销系统或风控规则引擎。建议在导出的同时保留pca_1、pca_2两列一旦业务方质疑分群的合理性可以直接重新生成散点图定位问题群体而不必重新跑全流程。本文还有配套的精品资源点击获取
返回列表