☰
银行客户数据分类与聚类:Python数据挖掘课程作业完整实战
2026/10/12 19:42:06 网站建设 项目流程

简介:这是一份数据仓库与数据挖掘课程的高分期末大作业,基于Python实现银行数据分类与数据聚类,并配有完整实验报告。项目经导师指导并被评审为99分,代码完整可运行,主要面向计算机相关专业正在准备课程设计或期末大作业的学生,也适合需要项目实战练习的初学者。资源包共122个文件,大小约11.36MB,以Python脚本、CSV数据文件、PDF/Word/PPT报告、数据仓库建模相关文件(如AS数据库、维度、多维数据集)及部分Java/XML辅助文件等为主,目录结构清晰。目前已有280人学习/下载。读者可从中学到银行场景下的数据预处理、特征构造、分类与聚类算法实现及结果分析思路;包内包含账户、客户、交易、订单等多份CSV数据集,便于直接复现实验;实验报告详细记录了设计过程、算法对比和结论,可作为课程作业撰写的参考模板。

1. 银行数据分类与聚类:这份Python课程作业为什么值得拿去复现

数据仓库与数据挖掘这门课的期末大作业,我选了银行客户数据做分类和聚类,用Python从数据清洗一路写到实验报告,最后评审给了99分。很多人以为“分类+聚类”是个大工程,实际拆开看,核心建模代码只有两百行不到,真正耗时间的反而是数据预处理和参数调优。这份资源把客户、账户、订单、交易四类原始表、对应的清洗后新表、以及完整可运行的Python源码和实验报告都打包好了。它能帮你解决课程设计里最典型的问题:不知道从哪下手、合并表翻车、聚类结果无法解释。适合正在赶数据仓库与数据挖掘期末作业的计算机专业学生,也适合想把银行场景数据挖掘流程完整跑一遍的初学者。你不必从零设计,照着改数据就能复现。

2. 从原始CSV到分析数据集:这张银行数据仓库作业里的表和预处理

拿到资源第一个动作不是跑代码,而是把文件清单看明白。作业里核心CSV是 client.csv、account.csv、order.csv、trans.csv,后面带 _new 的对应清洗后版本,比如 trans_new.csv、order_new.csv、account_new.csv、client_new.csv。我的习惯是先打开每个CSV看前几行和字段类型,别急着写合并逻辑。这个项目里的 AccoutAnalytic.asdatabase 和 AccoutAnalytic.configsettings 是数据仓库分析工程的配置文件,我当时在实验报告里用它对应数据仓库建模过程,虽然Python复现时用不到这个文件,但答辩导师一眼就看到你是按数据仓库流程走的。

2.1 先认清这堆文件:原始表、新表和一个容易被忽略的Frogs数据集

先列一个文件对应关系,这一步能救命。我当时就吃过亏,以为Frogs_MFCCs.csv是多余文件,放在一边没管,后来导师问“你的聚类实验是不是只跑了一组数据”,我才把它补进去,做了一组音频特征的聚类对比实验,反而成了加分项。

文件作用备注
client.csv客户信息表含客户ID、年龄、收入等
account.csv账户表一个客户可能对应多个账户
order.csv订单表含订单金额、订单时间
trans.csv交易流水表含交易金额、交易类型、时间
client_new.csv 等清洗后的建模数据分类和聚类直接基于new表
Frogs_MFCCs.csv青蛙鸣叫的MFCC特征用于聚类算法补充对比

看清文件定位后,下一步就是读数据。注意银行模拟数据里的字段名在不同版本里可能不一样,比如有的叫 customer_id,有的叫 client_id,读进来后先打印 columns.tolist(),别想当然。

import pandas as pd # 读取原始表,encoding 参数根据实际情况调整,Windows 下常见 gbk client = pd.read_csv('client.csv', encoding='utf-8') account = pd.read_csv('account.csv', encoding='utf-8') order = pd.read_csv('order.csv', encoding='utf-8') trans = pd.read_csv('trans.csv', encoding='utf-8') # 打印字段名和缺失值,这是清洗的起点 print('client columns:', client.columns.tolist()) print('client 缺失值统计:') print(client.isnull().sum())

这里有一个关键参数:encoding。如果读文件时出现 UnicodeDecodeError,不要急着转成 gbk,先试 utf-8,再试 gbk,最后试 gb18030。gb18030 是 GBK 的超集,银行导出的中文数据用它基本不会出错。缺失值统计要逐列看,经验是里面对缺失率超过30%的列,要么删除,要么用代表值填充,千万不要一把 dropna 把行删光,客户表的行太少会导致后面合并结果失真。

2.2 清洗与合并:把四张表合成一张宽表,别急着建模

数据仓库课程作业的重点之一是“抽取-转换-装载”,即使你全用Python实现,也要在实验报告里把过程写清楚。我当时处理四张表的顺序是:先单表清洗,再按依赖关系聚合,最后合并成宽表。订单表和交易表相对于客户表都是一对多,如果直接把 account 或 trans merge 到 client 上,行数会爆炸。所以必须先聚合。

# 订单金额转数值类型,非法值转成NaN order['amount'] = pd.to_numeric(order['amount'], errors='coerce') # 交易表按账户ID聚合,得到每个账户的交易次数和累计金额 trans_agg = trans.groupby('account_id').agg( trans_count=('trans_id', 'count'), trans_amount_sum=('amount', 'sum') ).reset_index()

逻辑说明:groupby 是为了把“一笔交易”变成“每个账户的交易总量”,这样后面的 merge 才不会出现一对一变一对多。agg 里 trans_id 统计数量,amount 求和,字段名可以自己定义。errors='coerce' 的意思是把无法解析成数字的乱码直接变成 NaN,避免整体类型转换报错。注意这里 trans 表里的账户ID一定要和 account 表的账户ID是同一个命名,如果有后缀差异,先做 strip 或 replace。

接下来做两层合并:先把交易聚合结果合到账户表,再把账户表合到客户表。

account_merged = account.merge(trans_agg, on='account_id', how='left') wide_table = client.merge(account_merged, on='client_id', how='left') print('合并后宽表 shape:', wide_table.shape) print('客户表 shape:', client.shape)

参数说明:how='left' 以左表为基准,保留所有客户记录;如果客户没有匹配的账户或者交易,对应字段会变成 NaN。用 left join 而不是 inner join,是因为银行客户中必然有“有客户档案但没有交易记录”的人,直接 inner 会把这些人丢掉,聚类时样本量少一截。合并完成后立刻打印 shape 对比,如果宽表行数明显大于客户表行数,说明上面聚合有问题,最常见的是 account 表本身存在多个账户没聚合,需要先对 account 表做一次 groupby。这个检查动作我每次都会做,不吃亏。

3. 银行客户分类实战:构造标签、训练模型与评估的三步走

分类是这份课程作业的主线之一,目标是预测或识别“高价值客户”。困难点是原始表里没有现成的标签,所以要先构造标签,再做分类。不要一上来就套模型,标签怎么来、为什么这样定义,实验报告里必须讲清楚。

3.1 分类目标与标签构造:没有“好客户”标签怎么办

银行数据挖掘里最常见的套路是规则打标。我当时用三个条件同时满足来定义高价值客户:账户余额大于所有客户的75%分位数、交易笔数大于75%分位数、累计交易金额大于75%分位数。这样既解释了业务含义,又不会让正负样本比例过于失衡。

balance_th = wide_table['balance'].quantile(0.75) count_th = wide_table['trans_count'].quantile(0.75) amount_th = wide_table['trans_amount_sum'].quantile(0.75) high_value = ( (wide_table['balance'] >= balance_th) & (wide_table['trans_count'] >= count_th) & (wide_table['trans_amount_sum'] >= amount_th) ).astype(int) wide_table['high_value'] = high_value print(wide_table['high_value'].value_counts())

逻辑说明:三个条件同时成立才标记为1,取 0.75 分位是一个中性阈值。quantile 的好处是自动适应数据分布,不用拍脑袋定“余额大于1万”这种绝对数字。问题在于如果你的原始表缺失值太多,balance 大量是 NaN,那么比较结果全是 False,标签会变成全0。所以打标之前必须检查这三列的非空数量。如果正样本太少,把 0.75 改成 0.6;如果正负样本接近,反而要调高到 0.9,把少数标签定义得严格一些。

3.2 训练集划分与模型选择:逻辑回归和决策树怎么取舍

分类算法不需要很花哨,逻辑回归作为基线模型足够,因为它的输出可解释、计算快、不容易过拟合。决策树可以画出来,导师喜欢看到结构,所以我两个都跑了。有一点必须注意:逻辑回归必须做标准化,决策树不需要。如果你把用 sklearn 串联起来,StandardScaler 只 fit 在训练集上,不要对全量数据 fit,这是最常见的泄漏问题。

from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report, confusion_matrix features = ['age', 'income', 'balance', 'trans_count', 'trans_amount_sum'] X = wide_table[features].fillna(0) y = wide_table['high_value'] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) scaler = StandardScaler() X_train_s = scaler.fit_transform(X_train) X_test_s = scaler.transform(X_test) model = LogisticRegression(max_iter=1000, class_weight='balanced') model.fit(X_train_s, y_train) y_pred = model.predict(X_test_s) print(confusion_matrix(y_test, y_pred)) print(classification_report(y_test, y_pred))

逻辑说明:stratify=y 保证训练集和测试集中正负样本比例一致,避免某个子集全是0。random_state=42 固定随机种子,这个习惯直接决定了你实验报告里的截图能不能复现。class_weight='balanced' 会自动给少数类更大的权重,不然模型为了降低loss,往往会输出全0。max_iter 建议设成1000,逻辑回归默认迭代次数可能不够收敛,报ConvergenceWarning就是警告你迭代次数不够。

3.3 评估不是看准确率:用F1和混淆矩阵看清模型水平

高价值客户占比通常不到20%,如果只看 accuracy,即使模型把所有人都预测成0,准确率也有80%以上,没有意义。我交的报告里重点放混淆矩阵、precision、recall、F1。另外,我还打印了逻辑回归的特征系数,用绝对值大小解释哪个特征对标签影响最大。

coef_df = pd.DataFrame({ 'feature': features, 'coef': model.coef_[0] }).sort_values('coef', ascending=False) print(coef_df)

逻辑说明:coef_[0] 是二分类中正类的系数。标准化之后系数绝对值越大,该特征对“高价值客户”的影响越强。注意如果特征是负数,说明负向影响。我这里只作了示例,你自己跑的时候很可能发现 income 或 trans_count 排在最前面,这正好可以作为实验报告里的分析素材。决策树的 feature_importances_ 是另一种排序,两套结果放在一起对比,能看出模型对特征的认知是否一致。

4. 银行交易数据聚类实战:KMeans和层次聚类的完整实现

聚类的作用不是预测,而是找结构。银行数据里可以用账户余额、交易次数、累计交易金额把客户分成几类,比如“高频小额”“低频大额”“沉睡客户”。分类和聚类可以共用同一张宽表,但聚类前必须做标准化,这是最容易被忽略的前提。

4.1 选KMeans还是层次聚类:先看样本量再决定

KMeans 样本量大时快,适合几千条以上;层次聚类能画树状图,直观但计算量随样本量二次增长,适合小数据集。这份资源里银行宽表几千行,KMeans 是主力;Frogs_MFCCs.csv 样本量小,我用它演示层次聚类。如果想把层次聚类用在银行大样本上,可以先随机抽200条再画图,避免树状图糊成一团。

4.2 确定K值:肘部法则和轮廓系数一起看

聚类最常被导师追问的问题是“你为什么选K=4”。所以要把确定K值的过程写清楚:同时看 SSE 和轮廓系数。SSE 是手肘法,看折点;轮廓系数取-1到1,越大说明簇内紧凑、簇间分离越明显。

from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler from sklearn.metrics import silhouette_score import matplotlib.pyplot as plt cluster_features = ['balance', 'trans_count', 'trans_amount_sum'] Xc = wide_table[cluster_features].fillna(0) scaler = StandardScaler() Xc_s = scaler.fit_transform(Xc) sse = [] sil = [] ks = range(2, 9) for k in ks: km = KMeans(n_clusters=k, random_state=42, n_init=10) labels = km.fit_predict(Xc_s) sse.append(km.inertia_) sil.append(silhouette_score(Xc_s, labels)) plt.plot(list(ks), sse, marker='o') plt.xlabel('k') plt.ylabel('SSE') plt.title('Elbow Method') plt.savefig('elbow.png') plt.close() plt.plot(list(ks), sil, marker='o') plt.xlabel('k') plt.ylabel('Silhouette Score') plt.title('Silhouette Score') plt.savefig('silhouette.png') plt.close()

逻辑说明:km.inertia_ 返回样本到所属簇中心的距离平方和,K增加时SSE必然下降,下降趋势变缓的K就是候选值。silhouette_score 直接输入标准化后的特征矩阵和聚类标签,KMeans 每次初始中心不同,所以 n_init=10 让它跑10次取最好结果。random_state=42 又是为了保证可复现,尤其是交作业时截图和代码输出必须对得上。

4.3 层次聚类画树状图:交作业最直观的一张图

层次聚类的代码量比KMeans小,但图的信息量很大。ward 方法按簇内离差平方和最小来合并,适合连续数值特征。我之前用 ord 图,吃了一次亏,换回 ward 后聚类结构明显清晰。

from scipy.cluster.hierarchy import linkage, dendrogram sample = Xc_s[:200] # 防止样本太大图糊掉 Z = linkage(sample, method='ward') plt.figure(figsize=(10, 6)) dendrogram(Z, truncate_mode='level', p=5) plt.title('Hierarchical Clustering Dendrogram') plt.savefig('dendrogram.png') plt.close()

逻辑说明:linkage 返回一个合并矩阵,dendrogram 根据它画图。truncate_mode='level', p=5 只显示顶部5层,避免底部叶子太多。sample 取前200条是随机抽样,不代表全量结构,如果数据分布不均匀,可能失真。更稳妥的做法是先评估全量KMeans的簇中心,再按簇分层抽样,但这会复杂一些。我建议作业里明确写上“为可视化抽取200条样本”,态度诚恳比硬扛全量好得多。

聚类之后不要只贴图,要解释每个簇的含义。你可以用 groupby 统计每个簇的均值,然后给簇命名。

Xc_labeled = Xc.copy() Xc_labeled['cluster'] = km.fit_predict(Xc_s) print(Xc_labeled.groupby('cluster').mean())

逻辑说明:fit_predict 是在同一个标准化的数据上重新聚类,注意这里的 km 是已经确定的某个K,比如K=4。groupby mean 输出每个簇在原始特征上的均值,对比这些均值就能定性描述:哪个簇余额高、哪个簇交易频率低。实验报告里把这张表放进去,分值会明显提高。

5. 避坑记录:Python环境、编码、数据偏斜和聚类翻车现场

这一段是我交作业过程中踩过的坑,按“现象→原因→解决”的顺序写,希望能帮你少走弯路。

5.1 乱码:CSV读进来全是“�”

现象:pd.read_csv 打开 client.csv,列名和内容变成乱码,或者直接报 UnicodeDecodeError。

原因:银行模拟数据在 Windows 下导出时,默认编码通常是 GBK 或 ANSI,而 pandas 在 Python3 里默认用 UTF-8 读,中文就崩了。

解决:读文件时指定 encoding='gbk',如果还不行就试 encoding='gb18030'。gb18030 是 GBK 的超集,兼容性更强。用之前可以先在记事本里打开原始CSV另存为 UTF-8,但最省事的还是改读文件参数。我这里要用一提:不要用 encoding_errors='ignore',它会静默吞掉乱码,你根本发现不了数据已经缺了一半。

5.2 行数爆炸:一对多表合并前忘了聚合

现象:客户表只有1000行,和交易表一 merge 变成8000行。

原因:一个客户对应多个账户,账户又对应多笔交易,直接把交易表 merge 到客户表,属于一对多关联,左表行数会被复制多次。

解决:先对子表做 groupby 聚合,再合并。我在2.2节写的就是这个流程。每次 merge 前检查一下键的唯一性:print(df['account_id'].is_unique),不是唯一就先聚合。这是数据仓库作业里最便宜的后悔药。

5.3 准确率虚高:分类模型只会预测多数类

现象:逻辑回归 accuracy 有0.94,但混淆矩阵显示全部预测为0,正样本一个没抓住。

原因:高价值客户占比只有10%左右,模型发现全部预测为0也能拿到很高的准确率,梯度下降没有动力去学少数类。

解决:train_test_split 增加 stratify=y;分类模型加 class_weight='balanced';评估改用 F1、recall 和混淆矩阵。做完这三步,模型才会真正开始学习少数类的边界。不要迷信准确率,尤其是正负样本比例悬殊的场景。

5.4 聚类结果毫无意义:量纲没标准化

现象:KMeans 轮廓系数在0附近,聚类结果画出来糊成一片,几个簇看不出区别。

原因:balance 是几万几十万的金额,trans_count 是几十,KMeans 用欧氏距离计算,金额直接把计数特征碾压,等于聚类只按一个维度切分。

解决:聚类前必须用 StandardScaler 或 MinMaxScaler 统一量纲。这一步做完,轮廓系数经常能从0.1涨到0.4。如果还不行,就减少特征,只保留余额、交易次数、累计金额这三个核心字段。聚类是“玄学”重灾区,90%的翻车都出在没标准化或特征选择太随意。

5.5 实验报告被质疑:只有代码没有数据仓库流程记录

现象:答辩时导师问“你的数据仓库设计在哪”,你只能指着几个CSV和代码,被认为脱离课程主题。

原因:这门课叫数据仓库与数据挖掘,不能只有挖掘,还要体现数据仓库的抽取、转换、装载过程。光是清洗代码不算数据仓库设计。

解决:实验报告里用表格展示每一步中间表的行数和字段变化,例如“原始客户表5000行→去重后4980行→合并账户后4980行”。AccoutAnalytic.asdatabase 和 AccoutAnalytic.configsettings 这类配置文件,即使不直接用,也可以在报告里说明它是对应数据仓库分析工程配置的。把read_csv和merge描述成ETL过程,每个步骤截一张图,导师就没有质疑的理由。

6. 复现验证与加分扩展:把这份课程设计变成你自己的东西

6.1 先跑通再改:三行命令验证源码

拿到资源后不要急着替换成自己的数据,先把环境装好跑通。Python版本建议3.8到3.10,太新有时会遇到sklearn依赖兼容问题。安装依赖后依次运行分类和聚类脚本。

pip install pandas scikit-learn matplotlib scipy python classification_demo.py python clustering_demo.py

跑通后看终端输出的F1和轮廓系数,应该和实验报告里的数值接近。如果差很多,先检查 random_state 是否设为42,再看看数据文件是不是用的 _new 版本。固定随机种子是我交作业前的标准动作,这能让实验结果稳定,报告截图和实际运行结果永远对得上。

6.2 让导师眼前一亮的小扩展:DBSCAN对比与特征重要性

复现只是及格线。如果你的作业想冲高分,我建议加一个DBSCAN聚类和KMeans做对比,再输出一张特征重要性排序图。DBSCAN不需要指定K值,能找出噪声点,但参数 epsilon 和 min_samples 很敏感。

from sklearn.ensemble import RandomForestClassifier rf = RandomForestClassifier(n_estimators=100, random_state=42) rf.fit(X_train, y_train) importances = dict(zip(features, rf.feature_importances_)) print(sorted(importances.items(), key=lambda x: x[1], reverse=True))

逻辑说明:随机森林的特征重要性可以直接告诉导师“哪个字段对高价值客户判别贡献最大”。这段代码加上一个柱状图,实验报告的讨论部分立刻有内容写。我在自己的作业里加了这部分,导师评语点名说“特征分析深入”,这也是99分的重要原因。

从那以后,我每次做数据挖掘作业都会固定随机种子,并在实验报告开头写明Python版本和依赖库版本。这个习惯让我避免了很多“重跑一次结果不一样”的尴尬。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询