恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
机器学习银行客户细分实战:KMeans聚类与Python数据可视化全流程
首页
资讯中心
/
机器学习银行客户细分实战:KMeans聚类与Python数据可视化全流程
机器学习银行客户细分实战:KMeans聚类与Python数据可视化全流程
发布时间:2026/10/12 4:54:01
简介机器学习银行客户细分可视化项目定位服务于金融领域数据分析与机器学习入门开发者帮助理解如何通过算法对银行客户进行分群并直观呈现结果。资源打包为zip压缩包共2个文件包含1个可运行的Python脚本和1份德国信用客户CSV数据集压缩包整体仅17KB轻量紧凑便于快速下载与本地运行。目前已有68人学习浏览。脚本覆盖数据加载、基础清洗与聚类分析并自动生成柱状图、散点图等可视化视图可直接展示不同客群在收入水平、风险偏好、交易行为等维度上的分布差异。对于希望掌握客户细分建模思路与可视化技巧的读者这份资源提供了一套可完整运行的微型样例快速打通“数据—算法—可视化”的实践链路也适合作为课堂演示或自学实验的基础模板。1. 机器学习做银行客户细分这份可视化资源到底能解决什么机器学习做银行客户细分表面看是把客户的年龄、收入、信贷行为丢进模型里跑一遍就能出结果但真正拉开差距的往往不是聚类算法本身而是前期对数据字段的理解和清洗顺序。买过、下载过这类资源的人常有一个错觉拿到脚本就能一键出图。实际上大多数公开的客户分群案例数据要么缺行漏列、要么类别字段夹杂缺失值直接跑 KMeans 出来的图根本没法讲给业务听。这套资源的核心是两份文件——german_credit_data.csv德国信贷数据集和bank-customer-segmentation.py完整可运行的 Python 脚本解决的是字段不规整、类别特征多、没有现成标签的情况下怎么把客户拆成几个可解释的群体并输出让非技术同事也能看懂的图表。适合正在做机器学习课程设计的在校生、刚接触客户画像的金融科技从业者以及想快速搭数据可视化原型的工程师。2. 先搞清楚数据底细german_credit_data.csv 的字段、缺失与分布2.1 文件结构与主要字段先读懂这 10 个字段再跑脚本把压缩包解压后实际上对复现流程有用的就两个文件german_credit_data.csv和bank-customer-segmentation.py。你在运行脚本之前应该先打开 CSV 看一遍结构这一步能帮你避开后面一半的报错。文件里一条记录代表一个信贷客户总样本量是 1000 条字段覆盖了客户的基本属性、账户状态和信贷行为项目正文里面提到的收入水平、风险偏好、投资习惯这些在这个数据集里对应的是 Age、Job、Credit amount、Duration、Purpose 等字段。下面这张表把这套资源里最核心的字段整理成了我能直接对照的格式字段类型示例值业务含义Age数值22、45、67客户年龄Sex类别male / female性别Job类别数值编码0、1、2、3工作等级0 等级最低Housing类别own / rent / free住房状态Saving accounts类别little / moderate / quite rich / rich储蓄账户余额档位Checking account类别little / moderate / rich支票账户余额档位Credit amount数值2000 ~ 15000信贷金额Duration数值12、24、36贷款期限月Purpose类别car / education / radio/tv贷款用途Risk类别good / bad信用风险标签分群时不一定用需要注意 Job 这个字段在 CSV 里本来是一串 0~3 的整数但在跑聚类时我一般会把它当类别特征处理而不是连续数值。0 和 3 之间并没有真正的等差关系只是等级高低的序列。如果你不做处理直接参与距离计算模型会默认 Job0 和 Job3 的差值比 Job0 和 Job1 的差值大这在业务上讲不通。这个点在处理时很多人会漏后面预处理章节还会再提。2.2 缺失值分布Saving accounts 和 Checking account 你绕不过去拿到 CSV 第一步不是建模型是先看缺失。german_credit_data.csv这个数据集的缺失值很有规律——Saving accounts 和 Checking account 两组账户字段有缺失其他字段一般完整。缺失部分在实际业务里的含义不是数据丢了一行而是客户在当时的银行系统中没有对应账户记录也就是储蓄账户或支票账户为空。这种情况在做客户分群时非常常见银行老系统里存下来的客户资料大量字段是空的要么是渠道采集不到要么是客户根本没有开通对应业务。复现这个项目时我会在脚本里先跑一段缺失值统计代码确认现状这是任何客户细分项目的第一步import pandas as pd df pd.read_csv(german_credit_data.csv) print(数据集形状, df.shape) # 统计每个字段的缺失数量和非空数量 missing_info pd.DataFrame({ 缺失数: df.isnull().sum(), 非空数: df.count() }) print(missing_info[missing_info[缺失数] 0])逻辑说明isnull().sum()按列统计缺失值个数df.count()按列统计非空个数两者合起来看一眼就能确定哪些字段需要重点清洗。参数说明读取 CSV 时用的是pd.read_csv默认参数如果没有指定encoding多数情况下这个数据集的编码是 UTF-8不会乱码万一本地复现出现UnicodeDecodeError把读取行改成pd.read_csv(german_credit_data.csv, encodinglatin1)基本能解决。这一段的输出会直接告诉你缺失集中在账户字段且缺失量占比通常不到 10%处理手段不是删除行而是要把它当作一种“无账户”状态来处理。2.3 数值字段的分布Credit amount 和 Age 一眼看出量纲差距把数值字段分布拉出来看会发现一个直接影响聚类效果的问题——Credit amount 的数值范围波动很大从几千到几万而 Age 只有几十Duration 也就 12 到 48 之间。量纲差距意味着在欧式距离计算时Credit amount 会把 Age 和 Duration 的差异完全淹没。拿两个客户举例一个 20 岁贷款 3 万、一个 50 岁贷款 3000年龄差 30 在距离计算里只相当于金额差了 1聚类结果几乎完全由金额主导。这就是为什么在做 KMeans 之前必须做标准化这一步不是可选项。我用describe()把关键数值字段的分布拉出来验证了一遍numeric_cols [Age, Credit amount, Duration] print(df[numeric_cols].describe()) # 极差对比Credit amount 的 min 和 max 差距远大于其他两列describe()生成的表格里看一下 min 和 max 两行就能得出结论。在这个数据集里Credit amount 的 max 通常是 min 的好几倍Age 的范围则相对集中。结论是先标准化再建模至于用 StandardScaler 还是 MinMaxScaler一个在下一步缩放到 0~1一个让数据变成零均值单位方差在聚类场景下两者都行但用 MinMaxScaler 更容易保持 PCA 降维后图形的直观性。3. 特征工程不能偷懒缺失值、编码与归一化的处理顺序3.1 缺失值处理的具体方案填充 “none” 而不是删行上一章提到 Saving accounts 和 Checking account 存在缺失值处理手法看起来简单但方向和细节点很关键。常见做法是用空字符串或统一标记填充缺失值原因是这些缺失代表“没有账户”在业务上是一种真实状态不是一个坏数据。如果你用均值填充或中位数填充反而会伪造出一批实际上并不存在的“平均水平储户”聚类结果会失真。填充代码一般长这样# 将缺失的账户状态填充为标记值 df[Saving accounts] df[Saving accounts].fillna(none) df[Checking account] df[Checking account].fillna(none) print(df.isnull().sum())逻辑说明fillna(none)把缺失值替换成字符串none这样后面做独热编码时none会和其他账户状态一样被编码成一个独立类别不会因为缺失导致管道中断。参数说明fillna 可以传入字符串、数值或字典这里是按列传入固定字符串操作的是整列所有空值。注意不要用inplaceTrue这种老式写法直接赋值回原列更清晰不容易在后续代码里产生歧义。做完这一步再跑一遍isnull().sum()能确认空值已经清零这才算是完成了数据清洗的第一阶段。3.2 类别特征编码为什么客户分群全部用独热编码在 2.1 里我已经强调过 Job 字段的特殊性。实际上 Sex、Housing、Saving accounts、Checking account、Purpose 全部都属于无序类别处理上应该统一走独热编码。标签编码这种手法只适合有序类别——比如学历从小学、中学、大学这种天然排序的场景。信贷客户数据里这些字段没有排序关系male 到 female 之间的距离和 0 到 1 的整数没有任何对应逻辑。pd.get_dummies()是处理这类字段最常用的方式代码简短且不容易出错categorical_cols [Sex, Housing, Saving accounts, Checking account, Purpose, Job] df_encoded pd.get_dummies(df, columnscategorical_cols, drop_firstFalse) print(df_encoded.shape) # 列数会明显增加 print(df_encoded.columns.tolist())逻辑说明drop_firstFalse表示每个类别都会生成独立的 0/1 列比如 Sex 会生成 Sex_male 和 Sex_female 两列如果改成drop_firstTrue其中一列会被省略掉这对某些线性模型来说是防共线性的做法但在 KMeans 场景下省略和保留的聚类结果几乎没区别。取值上我习惯保留全部列因为后面做簇画像解读时每个簇里某一列的均值一眼就能对应回原始类别省去反向映射的步骤。如果get_dummies输出后发现列名带上了特殊字符建议对列名做一次规范化df_encoded.columns [col.replace( , _) for col in df_encoded.columns]这一步不是必须的但能避免后续在画图时列名里的空格被某些绘图函数识别成异常标签。3.3 数值特征归一化MinMaxScaler 还是 StandardScaler数值字段——Age、Credit amount、Duration——量纲差异大必须缩放。常见做法有两种MinMaxScaler 和 StandardScaler。银行信贷数据这种业务场景我一般用 MinMaxScaler原因有两个第一它把数据缩放到 0~1 区间之后 PCA 散点图的坐标范围可控第二信贷金额这类字段分布往往右偏MinMaxScaler 对这种非正态分布的容忍度比 StandardScaler 高一些。标准操作是先取数值列、做缩放、再和之前独热编码的 0/1 特征拼接起来from sklearn.preprocessing import MinMaxScaler import pandas as pd num_cols [Age, Credit amount, Duration] scaler MinMaxScaler() scaled_num scaler.fit_transform(df[num_cols]) scaled_num_df pd.DataFrame(scaled_num, columnsnum_cols, indexdf.index) # 拼接独热编码结果和缩放后的数值列 X pd.concat([df_encoded, scaled_num_df], axis1) print(X.shape) print(X.head())逻辑说明fit_transform在独热编码之后的特征矩阵上执行把数值列缩放到 0 和 1 之间。这里有一个容易翻车的细节一定要把独热编码得到的 DataFrame 里有原始数值列删除。如果你在get_dummies之前没有把 Age、Credit amount、Duration 排除在categorical_cols之外这列数值数据会一起被编码成哑变量而且原始数值列也还在 DataFrame 里。上面的写法里categorical_cols没有包含这三个名字所以数值列只出现一次再通过pd.concat拼接到最后。做完之后X.shape的列数应该是编码列数加 3这个数字心里有个数后面建模报错与否就靠它核对。4. KMeans 聚类与可视化输出从肘部法则到业务图表4.1 选 K 值肘部法则和轮廓系数搭配着用KMeans 最核心的超参数是簇数 K选多少没有标准答案。常见做法是先跑肘部法则看inertia_各样本到簇中心的距离平方和随 K 变化的拐点from sklearn.cluster import KMeans import matplotlib.pyplot as plt inertia_list [] for k in range(2, 11): model KMeans(n_clustersk, n_init10, random_state42) model.fit(X) inertia_list.append(model.inertia_) plt.plot(range(2, 11), inertia_list, markero) plt.xlabel(K) plt.ylabel(Inertia) plt.title(Elbow Method for K Selection) plt.show()逻辑说明model.inertia_是 KMeans 模型训练后自带的属性表示簇内误差平方和K 越大这个值越小因为簇越多、样本离中心越近。我们要找的是下降速度突然变缓的那个 K。实际跑一遍你会发现K 从 2 到 4 时下降明显4 之后趋于平缓通常选 4 或 5。参数说明n_init10会让算法用 10 个不同的初始中心点分别训练取最好结果避免因为初始化落入局部最优random_state42固定随机种子保证复现出的结果和脚本作者一致。如果只看肘部法则拿不准再叠加轮廓系数判断from sklearn.metrics import silhouette_score for k in range(2, 10): model KMeans(n_clustersk, n_init10, random_state42) labels model.fit_predict(X) score silhouette_score(X, labels) print(fK{k} - silhouette_score{score:.3f})轮廓系数范围在 -1 到 1 之间越接近 1 说明簇内越紧凑、簇间越分离。银行客户数据本身噪声很大轮廓系数通常落在 0.15~0.35 之间如果出现 0.1 以下优先检查数据有没有归一化。4.2 训练最终的 KMeans 模型并给客户打标签选定 K 之后重新训练一次模型把聚类结果直接挂回原始 DataFrame这一步是生成可视化图表的基底。我给客户数据新增一列叫做 Cluster这样每一行样本属于哪个群体就一清二楚了final_model KMeans(n_clusters4, n_init10, random_state42) df[Cluster] final_model.fit_predict(X) print(df.groupby(Cluster).agg({ Age: mean, Credit amount: mean, Duration: mean }).round(2))逻辑说明fit_predict(X)在训练的同时直接返回每条样本的簇标签一列 0、1、2、3 的整数。groupby(Cluster).agg(...)按簇分组计算均值把每个簇的年龄、金额、期限平均情况拉出来——这一步相当于提前看了业务画像能帮你判断分出来的簇是不是合理。参数说明这里agg传的是字典键是列名值是聚合函数还可以换成median看中位数信贷金额这类右偏字段用中位数更容易排除极端贷款客户的干扰。4.3 可视化输出PCA 降维散点图与多个业务维度的对比图脚本的另一个核心输出是可视化。二维散点图要能直观展示四个簇的分布就必须把高维特征压缩到二维。常见做法是用 PCA 降到 2 个主成分再把主成分坐标和簇标签结合画图from sklearn.decomposition import PCA import matplotlib.pyplot as plt pca PCA(n_components2) X_pca pca.fit_transform(X) plt.figure(figsize(10, 6)) scatter plt.scatter(X_pca[:, 0], X_pca[:, 1], cdf[Cluster], cmapviridis, s20, alpha0.8) plt.colorbar(scatter) plt.xlabel(Principal Component 1) plt.ylabel(Principal Component 2) plt.title(KMeans Clustering Result (PCA Projection)) plt.show()逻辑说明PCA(n_components2)把高维特征投影到两个主成分方向X_pca[:, 0]和X_pca[:, 1]分别是降维后的横纵坐标df[Cluster]作为颜色类别传入c参数四个簇用四种颜色区分。参数说明s20控制散点大小样本量 1000 的时候点太小看不出来、太大叠成一片alpha0.8控制透明度点多了能减轻重叠cmapviridis是色图名称换成Set1之类的高对比色更适合打印。散点图只能说明分得开分不开业务解读还得靠每个簇的特征对比。接下来用箱线图或柱状图把不同簇的 Age、Credit amount、Duration 分布对比出来这一步是给业务方汇报时的关键材料import seaborn as sns fig, axes plt.subplots(1, 3, figsize(16, 5)) for ax, col in zip(axes, [Age, Credit amount, Duration]): sns.boxplot(datadf, xCluster, ycol, axax) ax.set_title(f{col} by Cluster) plt.tight_layout() plt.show()这块是脚本里体现可视化价值的部分柱状图能看到不同簇的均值差异箱线图能看到分布和离群点热图则能同时对比所有数值字段的关系。跑完这几步脚本bank-customer-segmentation.py的核心流程就完整复现出来了。5. 避坑排查银行分群脚本跑不通的五个真实原因5.1 聚类结果聚成一团簇中心互相重叠现象画 PCA 散点图时发现四个簇基本重叠左中右没有明显区分轮廓系数低到 0.05 左右。原因数据没有做归一化。KMeans 基于距离计算簇内平方误差Credit amount 上千上万的量级完全支配了 Age 和 Duration 的差异距离被大数值特征主导所有样本在“金额”这个方向上被拉长别的方向压缩成一团。独热编码后的 0/1 列本身量纲一致但和未缩放的数值列拼在一起后数值列的绝对值压过了一切。解决在拟合 KMeans 之前对数值列做 MinMaxScaler 或 StandardScaler把三个字段统一到相同尺度再拼接类别编码。做完之后轮廓系数通常能提升到 0.15 以上散点图会出现明显的簇边界。这个坑是所有聚类项目里最常见的一个特征拼接后先看一眼X.describe()确认每列的量纲都在合理范围再进模型。5.2 图表中文乱码所有标签变成方框现象用 matplotlib 画图时标题和坐标轴标签凡是中文都显示成 “口口口”。原因matplotlib 默认字体不支持中文字符。在 Windows 或 Linux 环境下默认字体一般是 DejaVu Sans没有中文字形遇到中文直接画成方框。解决在脚本开头设置字体和负号显示import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False注意参数里的[SimHei]在 Windows 下有效如果是 Linux 服务器改成[WenQuanYi Zen Hei]或[Noto Sans CJK SC]。axes.unicode_minus设为 False 是为了让坐标轴的负号正常显示。如果你把这些 rcParams 写在了画图之后才执行图已经生成不会生效。5.3 报错 KeyError: Saving accounts 或者列名对不上现象运行pd.get_dummies(df, columns[Saving accounts])时报 KeyError提示找不到列或者在拼接特征矩阵时维度对不上。原因CSV 文件读取时列名里可能带了不可见字符比如 BOM 头、空格不同操作系统的文本编辑器在保存时容易带进去。最常见的版本是列名为Saving accounts但实际读进来是Saving accounts末尾带空格直接按名字取列就报错。解决跑一段清理列名的代码df.columns df.columns.str.strip().str.replace( , _)这段逻辑把列名首尾空格去掉并把中间空格替换成下划线后续所有引用统一用新名字。接着用df.columns.tolist()打印真实列名确认和你预期的一致后再继续。如果还是报错八成是列名大小写对不上检查原 CSV 的表头用的是什么写法。5.4 肘部法则图没有明显拐点不知道该选几个簇现象运行肘部法则代码后inertia 曲线从 K2 到 K10 一路下滑没有任何一个位置有明显折角。原因银行客户数据维度高、噪声大本来就不像教科书里那样有清晰的拐点。加上特征里类别字段做了独热编码列数膨胀高维空间的距离分布更加平滑肘部效果被稀释得非常弱。解决不要只依赖肘部法则把轮廓系数曲线和业务约束结合起来选 K。具体来说先跑一组 K2~6 的轮廓系数选最高点如果两个候选值轮廓系数接近选较小的 K——分群数越少越容易给业务方讲清楚。我在实际项目里的习惯是给客户看一张 K 值与轮廓系数的对比表让业务自己去权衡通常 4 到 5 簇对银行场景最合理。5.5 分出来的簇不知道怎么解释业务方不认可现象聚类结果出来了四个簇在散点图上分得挺开但拿到业务会上一问每个簇的客户特征是什么、怎么针对性营销答不上来。原因只做了聚类和散点图没有做簇画像聚合分析。散点图适合看分群效果不适合回答业务问题缺少按簇分组的特征均值表就没有办法把模型结果翻译成业务语言。解决在聚类后强制做一次聚合统计把每个簇在各字段上的均值、中位数、占比拉成一张表再人工给每个簇命名profile df.groupby(Cluster).agg({ Age: median, Credit amount: median, Duration: median, Risk: lambda x: (x good).mean() }).round(2) print(profile)这样每个簇就有了具体画像比如“中年、大额贷款、短期限、低风险客户”聚类结果才算真正可落地。从那以后我每跑一个分群项目都会强制走一遍“先看数据、再标准化、再选 K、最后做聚合画像”的流程顺序一变输出就变。希望帮到你。本文还有配套的精品资源点击获取