
简介这套基于Python实现的信用卡客户高风险识别源码包主要面向毕业设计及金融风控实训场景围绕历史信用风险、经济风险、收入风险三个维度构建客户属性通过K-Means聚类训练识别模型并借助雷达图展示可视化结果。压缩包共7个文件包含5个Python脚本覆盖数据探索、异常值清洗、模型构建与绘图模块、1个CSV数据集以及1份README说明文档整体体积仅861KB结构紧凑便于查阅。目前已有283人学习或下载代码均经运行测试通过遇到问题还可获得远程讲解支持。完成学习后可掌握风控场景下的聚类分群方法、手肘法确定最优K值、数据预处理与结果可视化等完整思路适合计科、人工智能、自动化等专业学生用于项目二次开发或课设、毕设演示。1. 信用卡高风险识别K-Means 建模与雷达图可视化的完整落地做信用卡客户风险识别很多同学一上来就奔着逻辑回归或 XGBoost 去但这份基于 Python 的毕业设计源码走的是另一条路——用 K-Means 无监督聚类把客户按风险画像切成簇再配合雷达图把每个簇的特征直接画出来。整个项目从历史信用风险、经济风险、收入风险三个维度构建属性覆盖数据探索、数据清洗、手肘法选 K 值、聚类训练与结果可视化的完整流程适合计科、人工智能、自动化等专业的课设和毕设场景。源码包含 task1.py、task2.py、task3.py、main.py、Radar.py 等脚本和一份 credit_card.csv 数据文件代码经过验证可运行你拿到的是一套从数据到结论都能讲清楚的教学型项目而不是一个跑不通的黑匣子。2. 数据清洗与维度构建把原始信贷数据变成模型能吃的特征2.1 credit_card.csv 里到底有什么先做数据探索拿到项目后别急着跑模型第一步是打开 data 目录下的 credit_card.csv搞清楚每一列的真实含义。信用卡客户风险识别的前提是数据质量这份数据里既有客户的历史信用表现比如是否逾期、是否有呆账也有客户的经济状况比如个人收入和家庭收入。项目在 task1.py 里做的事就是先把这些字段的分布情况摸一遍。import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(data/credit_card.csv) print(df.head()) print(df.info()) print(df.describe()) # 把瑕疵户高风险客户的分布单独画出来 bad df[df[is_bad] 1] print(瑕疵户数量:, len(bad), 占比: {:.2f}%.format(len(bad) / len(df) * 100)) # 分别查看逾期、呆账在瑕疵户中的分布 for col in [overdue_days, bad_debt_amount]: print(col, bad[col].describe())这段代码的逻辑很直接先用info()看每列是否有缺失值用describe()看数值列的均值、极值和分位数再把高风险客户单独筛出来观察。参数上is_bad是标签列1 表示高风险瑕疵户0 表示正常客户overdue_days是逾期天数bad_debt_amount是呆账金额。这一步的核心目的就是验证数据是否值得继续做下去——如果瑕疵户占比过低或者某些字段缺失太多后续聚类出来的结果就没有说服力。我一般会额外打印df.isnull().sum()来确认每列缺失情况如果缺失比例超过 30%就不要硬留这个特征了。数据探索阶段最容易翻车的地方在于你只看平均值不看分布比如逾期天数平均只有 5 天但 90 分位数已经到 60 天了这种长尾分布会直接影响聚类的效果。2.2 删除无效记录与单位统一清洗的边界在哪里原始数据里经常会混入一些无效或错误的记录比如收入字段出现负数、家庭收入和个人收入的数量级不一致甚至同一字段在不同行里单位不同。这个项目在数据清洗阶段做了两件关键的事删除无效记录、把收入单位统一为万元。# 删除收入为负或为零的记录 df df[df[personal_income] 0] df df[df[family_income] 0] # 统一收入单位为万元 # 假设原始数据里 personal_income 和 family_income 部分单位为元部分为万元 df.loc[df[personal_income] 100, personal_income] df[personal_income] / 10000 df.loc[df[family_income] 100, family_income] df[family_income] / 10000 print(df[[personal_income, family_income]].describe())这段代码的判断逻辑是基于业务常识的正常个人月收入不太可能超过 100 万所以把大于 100 的记录统一除以 10000 转为万元。同理family_income也做相同处理。如果你拿到的是自己的数据这个阈值要根据业务场景调整——比如某些高净值客户的家庭年收入确实超过 100 万那你就要先看原始单位再决定是否缩放。清洗的边界在于删数据要克制只删明确错误的记录不要因为某个字段异常就整行丢弃。比如逾期天数为 0 的客户可能是优质客户不能删但收入为负的客户基本可以确定是数据录入错误删掉不影响整体分布。3. K-Means 模型构建手肘法定 K 值与聚类训练3.1 为什么选 K-Means 而不是逻辑回归这个项目的核心建模方式是 K-Means很多人会疑惑风险识别不都是分类问题吗为什么用无监督聚类关键在于数据标注的粒度。信用风险评估里你往往只知道自己关注的高风险客户有哪些特征但并不知道客户到底该被分成几类风险等级。K-Means 能把客户按特征空间的距离自然分组每组代表一种风险画像之后再映射到低风险、中风险、高风险标签上。from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler # 选取特征列 features [history_risk_score, economic_risk_score, income_risk_score] X df[features].values # 标准化聚类对量纲敏感必须归一化 scaler StandardScaler() X_scaled scaler.fit_transform(X) # 手肘法找最优 K 值 import matplotlib.pyplot as plt inertia [] for k in range(1, 11): kmeans KMeans(n_clustersk, random_state42, n_init10) kmeans.fit(X_scaled) inertia.append(kmeans.inertia_) plt.plot(range(1, 11), inertia, markero) plt.xlabel(K) plt.ylabel(Inertia) plt.title(Elbow Method) plt.show()我这里用到的三个特征——history_risk_score、economic_risk_score、income_risk_score——是项目从原始字段聚合出来的评分列分别对应历史信用风险、经济风险、收入风险。标准化这步是必须的StandardScaler会把每个特征变成均值为 0 方差为 1 的分布否则收入这种量级大的特征会在距离计算中主导聚类结果。n_init10表示 K-Means 会随机初始化 10 次取最优结果random_state42保证每次运行结果一致方便论文复现。手肘法的判断标准是找 inertia 下降幅度明显放缓的拐点。如果曲线没有明显拐点可以结合业务含义选 K3对应低中高三档风险等级。3.2 task2.py 与 task3.py 中聚类训练与标签映射实际训练在 task2.py 中完成task3.py 则负责把聚类结果回贴到原始数据并做后续分析。这里要特别注意一件事聚类出来的簇编号是 0、1、2它们本身没有风险含义需要你手动映射到业务标签。# 假设手肘法确定 K3 kmeans KMeans(n_clusters3, random_state42, n_init10) kmeans.fit(X_scaled) # 给每个样本打上簇标签 df[cluster] kmeans.labels_ # 按簇统计特征均值判断哪个簇风险最高 cluster_profile df.groupby(cluster)[features].mean() print(cluster_profile) # 根据业务逻辑手动映射簇编号 - 风险等级 # 特征均值越高风险越高 risk_mapping {0: 低风险, 1: 中风险, 2: 高风险} # 具体映射需要根据 cluster_profile 的输出调整 df[risk_level] df[cluster].map(risk_mapping)这段代码的关键在于风险映射不是自动完成的你必须先看cluster_profile的输出再决定哪个簇对应高风险。常见做法是看均值最高的簇映射为高风险。如果cluster_profile里簇 0 的三个特征均值都最高那就把 0 映射为高风险而不是像我示例代码里默认写的{0: 低风险}。这种细节在答辩时很容易被问到建议提前把cluster_profile打印出来贴在论文里。聚类结果出来后还需要验证稳定性。K-Means 对初始点敏感虽然n_init10缓解了这个问题但我建议你换几个random_state跑一遍看每个样本的簇归属是否变化剧烈。如果某个样本在不同随机种子下被分到不同簇说明它处于聚类边界需要人工核查。4. Radar.py 雷达图可视化把聚类结果变成答辩能用的图4.1 雷达图的设计逻辑三个维度一个簇一条线项目里的 Radar.py 负责把每个风险簇的特征向量绘制成雷达图。雷达图适合展示多维特征在不同簇之间的对比尤其是三个维度的场景非常直观。高风险簇会在三个轴上整体外扩低风险簇则整体内缩一眼就能看出差异。import numpy as np import matplotlib.pyplot as plt def plot_radar(kmeans_model, scaler, feature_names): # 获取每个簇的中心点标准化后的坐标 centers kmeans_model.cluster_centers_ # 反标准化回原始尺度让图上的数值可解释 centers_original scaler.inverse_transform(centers) # 设置雷达图角度 angles np.linspace(0, 2 * np.pi, len(feature_names), endpointFalse).tolist() angles angles[:1] # 闭合图形 fig, ax plt.subplots(figsize(6, 6), subplot_kwdict(polarTrue)) for i, center in enumerate(centers_original): values center.tolist() values values[:1] ax.plot(angles, values, labelfCluster {i}) ax.fill(angles, values, alpha0.15) ax.set_xticks(angles[:-1]) ax.set_xticklabels(feature_names) ax.legend(locupper right) plt.show()这段代码的核心逻辑是取cluster_centers_作为每个簇的特征均值然后用inverse_transform把标准化后的坐标还原成原始数值。这一步很重要因为你答辩时不可能说“簇 0 在标准化后的坐标是 1.2”你要说的是“簇 0 的平均经济风险分是 78 分”。angles的生成方式是雷达图的标准写法3 个维度对应 3 个轴最后把首尾相连形成闭合多边形。颜色填充的alpha0.15是透明度参数用来避免多条线互相遮挡时看不清轮廓。如果簇数超过 4 个雷达图会变得拥挤这时候可以只画高风险和低风险两个簇的对比图。4.2 可视化输出与结果保存让图和数据对得上绘制完雷达图之后需要同步保存聚类结果到 CSV 文件方便后续在论文或答辩 PPT 中引用。导出数据时要把聚类标签、风险等级和原始特征列放在一起这样你写报告时可以直接查某一类客户的典型画像。# 保存带聚类标签和风险等级的完整数据 output_cols [personal_income, family_income, overdue_days, bad_debt_amount, history_risk_score, economic_risk_score, income_risk_score, cluster, risk_level] df[output_cols].to_csv(data/credit_card_with_risk.csv, indexFalse) # 同时保存每个簇的中心点画像 cluster_centers_df pd.DataFrame(centers_original, columnsfeatures) cluster_centers_df[cluster] range(len(centers_original)) cluster_centers_df.to_csv(data/cluster_profile.csv, indexFalse)保存文件的命名建议带上时间戳或版本号比如credit_card_with_risk_v1.csv这样你不会在反复调试后分不清哪个文件是最新结果。我在实际做这类项目时会把原始数据、清洗后数据、聚类结果数据分成三个目录存放避免一份 CSV 从头盖到尾。5. 避坑指南信用卡风险识别项目最常见的五个坑5.1 现象聚类结果每次跑都不一样原因K-Means 初始化中心点是随机的如果random_state没有固定每次运行得到的簇归属可能不同。解决在KMeans()中固定random_state42同时设置n_init10甚至更高让算法从多个初始点中选最优结果。如果你已经运行了多次且结果不同不要慌固定参数后重新跑一遍论文中的截图用同一组参数的结果。5.2 现象手肘法完全找不到拐点原因特征分布本身比较均匀inertia 曲线平滑下降没有明显的肘部或者特征没有标准化量纲差异导致距离计算失真。解决先确认StandardScaler是否应用再看特征数量——如果只有 3 个特征且相关性不高曲线平滑是正常的。此时直接按业务含义选 K3低、中、高风险并说明选 3 的理由是风控场景一般分三档手肘法作为辅助验证。5.3 现象某个簇的样本数量极少比如只有 5 个样本原因数据中瑕疵户本身占比低如果 K 值设得太大小簇会被切得很碎。解决先查数据里is_bad 1的比例如果不足 5%建议用KMeans聚类后把占比低于 1% 的簇合并到最近的簇或者直接改用 K2高风险/低风险。在论文里一定要写清楚样本分布否则答辩老师会追问为什么高风险簇这么小。5.4 现象收入和逾期天数的量级差异导致聚类完全看收入原因personal_income是几十万的量级overdue_days是几十的量级聚类距离被收入主导。解决所有特征参与聚类前必须标准化。除了StandardScaler也可以用MinMaxScaler把数据压缩到 [0, 1] 区间。标准化后如果你还想看原始尺度的特征值用inverse_transform还原但聚类过程必须在标准化后的空间里进行。5.5 现象雷达图显示三个簇的轮廓几乎重叠原因选择的特征区分度太低或者清洗后数据分布太集中没有明显的簇结构。解决回到特征构建阶段检查history_risk_score、economic_risk_score、income_risk_score这三个评分列是不是由原始字段线性加权得到的。如果是可以考虑加入交互特征比如“收入比”个人收入/家庭收入它比绝对收入更能反映客户的经济独立性。雷达图重叠说明聚类本身区分度不足你的重点应该放在特征构建上而不是调 K 值。6. 模型验证与边界分析手肘法之外的第二重保险聚类模型不像是分类模型有准确率的天然度量K-Means 的评估通常用轮廓系数Silhouette Coefficient来衡量簇内紧凑度和簇间分离度。这个指标在毕设答辩时很加分它能证明你选的 K 值不仅是手肘法找的还经过了量化验证。from sklearn.metrics import silhouette_score silhouette_scores [] for k in range(2, 11): kmeans KMeans(n_clustersk, random_state42, n_init10) labels kmeans.fit_predict(X_scaled) score silhouette_score(X_scaled, labels) silhouette_scores.append(score) for k, score in zip(range(2, 11), silhouette_scores): print(fK{k}, Silhouette Score{score:.4f})轮廓系数取值范围是 [-1, 1]大于 0.5 说明聚类结构比较明显低于 0.3 则说明数据本身没有清晰的簇结构。我跑过的经验是这份信用卡数据在 K3 时轮廓系数通常在 0.35 到 0.5 之间属于“可用但要结合业务解释”的水平。如果你的结果低于 0.3不要硬撑着用 K-Means 的结论可以尝试先用 PCA 把高维特征压缩到两维再看聚类有时候降维后轮廓系数会提升。验证维度之外聚类结果的业务合理性检查也很重要。把每个簇的overdue_days和bad_debt_amount均值打印出来对照你设定的风险等级——如果“低风险”簇的平均逾期天数反而高于“高风险”簇说明特征和业务语义是冲突的要么是特征选错了要么是清洗阶段出了问题。从那以后我每次做聚类项目都会强制把 silhouette_score 和 cluster_profile 一起跑出来两个结果对不上就回退到数据清洗阶段重新查绝不硬着头皮出图。这个习惯帮我避开了很多“图好看但结论经不起问”的尴尬场面希望帮到你。本文还有配套的精品资源点击获取