news 2026/10/3 8:52:55

K-Means聚类与雷达图:信用卡客户风险识别实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
K-Means聚类与雷达图:信用卡客户风险识别实战

简介:这套基于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_clusters=k, random_state=42, n_init=10) kmeans.fit(X_scaled) inertia.append(kmeans.inertia_) plt.plot(range(1, 11), inertia, marker='o') 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_init=10表示 K-Means 会随机初始化 10 次取最优结果,random_state=42保证每次运行结果一致,方便论文复现。

手肘法的判断标准是找 inertia 下降幅度明显放缓的拐点。如果曲线没有明显拐点,可以结合业务含义选 K=3,对应低中高三档风险等级。

3.2 task2.py 与 task3.py 中聚类训练与标签映射

实际训练在 task2.py 中完成,task3.py 则负责把聚类结果回贴到原始数据,并做后续分析。这里要特别注意一件事:聚类出来的簇编号是 0、1、2,它们本身没有风险含义,需要你手动映射到业务标签。

# 假设手肘法确定 K=3 kmeans = KMeans(n_clusters=3, random_state=42, n_init=10) 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_init=10缓解了这个问题,但我建议你换几个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), endpoint=False).tolist() angles += angles[:1] # 闭合图形 fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(polar=True)) for i, center in enumerate(centers_original): values = center.tolist() values += values[:1] ax.plot(angles, values, label=f'Cluster {i}') ax.fill(angles, values, alpha=0.15) ax.set_xticks(angles[:-1]) ax.set_xticklabels(feature_names) ax.legend(loc='upper right') plt.show()

这段代码的核心逻辑是取cluster_centers_作为每个簇的特征均值,然后用inverse_transform把标准化后的坐标还原成原始数值。这一步很重要,因为你答辩时不可能说“簇 0 在标准化后的坐标是 1.2”,你要说的是“簇 0 的平均经济风险分是 78 分”。angles的生成方式是雷达图的标准写法,3 个维度对应 3 个轴,最后把首尾相连形成闭合多边形。

颜色填充的alpha=0.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', index=False) # 同时保存每个簇的中心点画像 cluster_centers_df = pd.DataFrame(centers_original, columns=features) cluster_centers_df['cluster'] = range(len(centers_original)) cluster_centers_df.to_csv('data/cluster_profile.csv', index=False)

保存文件的命名建议带上时间戳或版本号,比如credit_card_with_risk_v1.csv,这样你不会在反复调试后分不清哪个文件是最新结果。我在实际做这类项目时,会把原始数据、清洗后数据、聚类结果数据分成三个目录存放,避免一份 CSV 从头盖到尾。

5. 避坑指南:信用卡风险识别项目最常见的五个坑

5.1 现象:聚类结果每次跑都不一样

原因:K-Means 初始化中心点是随机的,如果random_state没有固定,每次运行得到的簇归属可能不同。

解决:在KMeans()中固定random_state=42,同时设置n_init=10甚至更高,让算法从多个初始点中选最优结果。如果你已经运行了多次且结果不同,不要慌,固定参数后重新跑一遍,论文中的截图用同一组参数的结果。

5.2 现象:手肘法完全找不到拐点

原因:特征分布本身比较均匀,inertia 曲线平滑下降,没有明显的肘部;或者特征没有标准化,量纲差异导致距离计算失真。

解决:先确认StandardScaler是否应用,再看特征数量——如果只有 3 个特征且相关性不高,曲线平滑是正常的。此时直接按业务含义选 K=3(低、中、高风险),并说明选 3 的理由是风控场景一般分三档,手肘法作为辅助验证。

5.3 现象:某个簇的样本数量极少,比如只有 5 个样本

原因:数据中瑕疵户本身占比低,如果 K 值设得太大,小簇会被切得很碎。

解决:先查数据里is_bad == 1的比例,如果不足 5%,建议用KMeans聚类后把占比低于 1% 的簇合并到最近的簇,或者直接改用 K=2(高风险/低风险)。在论文里一定要写清楚样本分布,否则答辩老师会追问为什么高风险簇这么小。

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_clusters=k, random_state=42, n_init=10) 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(f'K={k}, Silhouette Score={score:.4f}')

轮廓系数取值范围是 [-1, 1],大于 0.5 说明聚类结构比较明显,低于 0.3 则说明数据本身没有清晰的簇结构。我跑过的经验是,这份信用卡数据在 K=3 时轮廓系数通常在 0.35 到 0.5 之间,属于“可用但要结合业务解释”的水平。如果你的结果低于 0.3,不要硬撑着用 K-Means 的结论,可以尝试先用 PCA 把高维特征压缩到两维再看聚类,有时候降维后轮廓系数会提升。

验证维度之外,聚类结果的业务合理性检查也很重要。把每个簇的overdue_days和bad_debt_amount均值打印出来,对照你设定的风险等级——如果“低风险”簇的平均逾期天数反而高于“高风险”簇,说明特征和业务语义是冲突的,要么是特征选错了,要么是清洗阶段出了问题。

从那以后,我每次做聚类项目都会强制把 silhouette_score 和 cluster_profile 一起跑出来,两个结果对不上就回退到数据清洗阶段重新查,绝不硬着头皮出图。这个习惯帮我避开了很多“图好看但结论经不起问”的尴尬场面,希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 8:51:57

微博评论情感分析:NB+SVM组合的完整实践与避坑指南

简介:这是一份基于朴素贝叶斯与支持向量机算法的微博评论情感分析可视化项目,面向计算机相关专业学生,可直接用于期末大作业、课程设计或项目实战练习。该作品出自大三学生之手,经导师指导并获得99分高分,代码结构完整…

作者头像 李华
网站建设 2026/10/3 8:51:48

Python多特征电力负荷预测实战:LSTM+Attention融合气象与日历因子

简介:本资源是一套基于Python实现的多特征电力负荷预测深度学习源码,面向能源系统开发人员、电力AI算法初学者及高校相关专业学生,解决电网负荷精准建模与短期预测的实际问题。压缩包共9个文件,含3个核心Python脚本(模…

作者头像 李华
网站建设 2026/10/3 8:50:59

知识图谱电影问答系统:从CSV数据清洗到Neo4j Cypher查询的Python实战

简介:一份面向计算机专业毕业设计的高分完整项目,基于Python构建知识图谱电影问答系统,覆盖数据清洗、实体构建、图谱存储与问答交互的完整开发链路。项目经导师指导并获九十九分评审,代码完整、可直接运行,即使刚入门…

作者头像 李华
网站建设 2026/10/3 8:50:36

豆包工作如何完成从需求分析到成果交付的长任务

豆包工作是豆包品牌下面向个人、团队和企业的智能体工作平台,核心价值是帮助用户完成文档、表格、PPT、网页和系统搭建等复杂任务,把想法转化为可交付的工作成果。不同于只能单次问答的普通AI,豆包工作可以承接跨多环节的长任务,从…

作者头像 李华
网站建设 2026/10/3 8:50:27

【生产力】Jev:让 AI 接手那些每天重复的小判断

导读:一封邮件该交给谁?一份资料值得读吗?一个请求能否自动处理?每天拖慢工作的,常常是成百上千次这样的小判断。Jev 尝试把它们变成软件可以直接使用的结果:选择类别、给出评分、估计某个条件是否成立。它…

作者头像 李华