银行数据分类聚类实战:从数据仓库宽表到Python建模全流程

发布时间:2026/10/12 15:23:37
银行数据分类聚类实战:从数据仓库宽表到Python建模全流程 简介这是一份面向计算机专业学生与数据挖掘初学者的银行数据分类与聚类课程设计资源完整覆盖数据仓库与数据挖掘期末大作业所要求的数据理解、预处理、建模与评估全流程。资源围绕银行客户与交易数据展开使用Python实现分类模型与聚类算法并配套详细的实验报告适合作为课程设计、期末大作业的参考或直接复现。压缩包共122个文件约11.36MB核心包括10个Python脚本、32个Java程序、多份CSV银行数据文件以及5份PDF、3份docx论文与ppt演示文稿还包含数据挖掘工具项目配置与可视化结果图片结构清晰便于理解与二次修改。已有280人学习浏览代码经导师指导并高分通过可运行性有保障能帮助初学者快速上手数据预处理、特征分析、模型训练与结果可视化等完整流程。1. 数据仓库与数据挖掘课程作业为什么银行数据分类聚类卡住你的不是算法作为一边上班一边补数据仓库与数据挖掘课程的过来人我接到这个作业的第一反应是这还不简单拉一份银行客户数据调一下sklearn不就完事。结果呢第一个版本模型的AUC只有0.52和抛硬币没区别。后来静下心排查才意识到问题不在分类器和聚类算法而在我根本没有按数据仓库的思路把原始表加工成一张干净的客户宽表。这个标题真正考验的是两件事第一会不会用Python做数据加工与建模第二能不能把实验结果写成一份经得起答辩的实验报告。它适合正在选数据挖掘课、或者想快速跑通分类聚类闭环的初学者也适合给那些拿到数据先建模再说的人做一次流程纠偏。2. 数据仓库层先按星型模型构建银行客户宽表再谈挖掘很多同学做这个作业会把“数据仓库”四个字直接跳过认为那是数据库课的事和挖掘没关系。但恰恰是这一步最容易翻车。银行数据很少给你一张现成的客户宽表通常分散在客户基本信息表、存款流水表、贷款合同表、信用卡消费表里。如果你不先做聚合直接把多张表合并起来要么产生大量重复客户要么一个客户对应多行分类和聚类全部跑乱。数据仓库在课程作业里的作用是把这些零散的源表加工成一张“每行一个客户、每列一个特征”的挖掘用宽表。这个加工过程用SQL可以用Python也一样。2.1 从源表到宽表的聚合思路与最小Python实现我一般会先把源表读懂再决定如何聚合。以最常见的银行客户营销场景为例源表包括客户基本信息表字段有cust_id、age、gender、job、education、marital、balance存款流水表字段有cust_id、deposit_date、amount、channel贷款合同表字段有cust_id、loan_amount、loan_term、default_flag信用卡消费表字段有cust_id、txn_date、txn_amount、category。目标是每个cust_id一行。其中客户基本信息是主表其他表按cust_id做聚合。比如存款流水可以聚合成近3个月存款笔数、金额均值贷款合同可以聚合成贷款总金额、是否有违约信用卡消费可以聚合成月均消费额、消费类别数。下面这一段是我当时沉淀下来的模板import pandas as pd # 读取源表 cust pd.read_csv(bank_customer.csv, parse_dates[reg_date]) depot pd.read_csv(bank_deposit.csv, parse_dates[deposit_date]) loan pd.read_csv(bank_loan.csv) # 存款流水按客户聚合近3个月笔数、金额均值、最大单笔 depot[recent] (pd.Timestamp(2024-12-31) - depot[deposit_date]).dt.days 90 depot_agg depot.groupby(cust_id).agg( deposit_cnt_3m(amount, count), deposit_avg_3m(amount, mean), deposit_max(amount, max) ).reset_index() # 贷款合同按客户聚合总额、是否曾逾期 loan_agg loan.groupby(cust_id).agg( loan_total(loan_amount, sum), ever_default(default_flag, max) ).reset_index() # 宽表拼接one row per customer wide cust.merge(depot_agg, oncust_id, howleft) wide wide.merge(loan_agg, oncust_id, howleft)这段代码的核心逻辑是先用groupby把子表压到客户粒度再用merge左连接回主表。需要注意parse_dates会把日期读成时间戳后面计算“近3个月”时才能直接做减法。deposit_cnt_3m是近3个月存款笔数deposit_avg_3m是金额均值loan_total是贷款总额。这样处理之后每行才有独立的客户特征供挖掘。别小看这个操作数据仓库课程的“维度建模”思想落到实处就是这里。2.2 数据清洗里最容易忽略的缺失值与异常值处理宽表建好后下一步是清洗。很多初学者会用dropna()直接删行这在银行营销数据上是危险的。因为流失客户或新开户客户往往天然缺失贷款信息如果直接删除样本会严重偏向有贷款的客户最终分类模型学的全是“有没有贷款”这个字段本身。我建议分字段处理# 缺失值处理策略数值列用中位数填充类别列用众数填充 num_cols wide.select_dtypes(include[float64, int64]).columns cat_cols wide.select_dtypes(include[object, category]).columns for col in num_cols: wide[col] wide[col].fillna(wide[col].median()) for col in cat_cols: wide[col] wide[col].fillna(wide[col].mode()[0]) # 异常值处理对balance做95%分位截断 upper wide[balance].quantile(0.95) wide[balance_clip] wide[balance].clip(upperupper)中位数比均值更抗异常值众数对类别字段是稳定的。clip(upperupper)是把过大的余额压到95分位而不是删除这个操作对后面的聚类尤其重要否则一两个超级客户的极端值会把聚类中心带偏。注意我没有把所有缺失都填成0因为0在银行业务里是有业务含义的填0会让模型把“无贷款”和“贷款额为0”混在一起。2.3 特征工程把年龄收入变成适合分类聚类的形式原始字段一般不能直接进模型。像job这种类别字段需要one-hot编码age可以分箱income和balance这类长尾分布字段最好取对数。这里有一个课程作业里经常被扣分的点如果分类使用逻辑回归连续变量不归一化会导致正则项惩罚不均匀如果聚类使用KMeans不做标准化则欧氏距离会被量纲大的字段主导。我常在分类前做转换# 年龄分箱把连续值变成有序类别 wide[age_bin] pd.cut(wide[age], bins[0, 30, 40, 50, 100], labels[30, 30-40, 40-50, 50]) wide pd.get_dummies(wide, columns[age_bin, job, education], drop_firstTrue) # 长尾字段取对数 import numpy as np wide[balance_log] np.log1p(wide[balance_clip]) wide[income_log] np.log1p(wide[income])pd.cut把年龄切成四段get_dummies自动把类别字段展开成0/1列drop_firstTrue可以去掉每个特征下第一个类别避免多重共线性。np.log1p针对0值和负值都能处理银行数据里balance有人就是负数直接取log会报错。这一步做完宽表才是真正可以喂给算法的样子。3. 分类实现用随机森林和逻辑回归预测银行客户响应宽表就绪后进入这个作业的核心重头分类。银行场景的分类任务一般是预测客户会不会响应营销活动或者会不会违约。这个作业标题只写了“银行数据分类”没有指定算法所以我会建议你至少跑两个模型做对比一个逻辑回归一个随机森林。逻辑回归解释性强写实验报告时能说清楚权重含义随机森林拟合能力强能把AUC撑上去。两个模型对比着写报告才饱满。3.1 数据集划分分层抽样是银行标签不平衡的关键银行数据集的标签通常很偏。营销响应率往往5%-10%违约率更低。如果你用train_test_split默认的随机切分很幸运的时候测试集里可能一个正样本都分不到模型评估完全失真。我一般会用stratify强制训练集和测试集保持和全量数据相同的正负比例from sklearn.model_selection import train_test_split feature_cols [c for c in wide.columns if c not in [cust_id, target]] X wide[feature_cols] y wide[target] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy )stratifyy是关键sklearn会按y的类别比例采样使训练集和测试集里正样本占比都接近全量数据。random_state42不是玄学它保证你每次运行得到同样的切分结果写实验报告时数据可复现。没有这一步你调参调到后面很可能会怀疑人生——同一个参数跑两遍结果不一样问题就出在随机切分上。3.2 随机森林分类必调的三个参数与过拟合边界随机森林在课程作业里是性价比最高的模型不用归一化不用做太复杂的特征工程只需要把维度压到合理范围。我的参数起步是from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, roc_auc_score model RandomForestClassifier( n_estimators200, max_depth8, min_samples_leaf10, random_state42, class_weightbalanced ) model.fit(X_train, y_train) y_pred model.predict(X_test) y_prob model.predict_proba(X_test)[:, 1] print(classification_report(y_test, y_pred)) print(AUC:, roc_auc_score(y_test, y_prob))n_estimators200是树的数量课程数据量不大200足够再大只是增加训练时间。max_depth8限制树深防止对训练集死记硬背。min_samples_leaf10要求叶子节点至少10个样本是抑制过拟合最有效的参数。class_weightbalanced自动给少数类更高权重解决标签不平衡。这里的predict_proba返回的是一个二维数组[:, 1]取的是预测为正类的概率AUC计算必须用概率不能用0/1标签这也是课程作业里最常见的低级错误。3.3 分类评估别只盯着准确率看召回率和AUC银行场景里准确率高不代表模型好。假设响应率只有5%模型把所有客户都预测为“不响应”准确率依然95%但它毫无价值。所以实验报告里必须展示混淆矩阵、精确率、召回率、F1和AUC。我一般会补一段可视化代码把ROC曲线画出来from sklearn.metrics import roc_curve import matplotlib.pyplot as plt fpr, tpr, _ roc_curve(y_test, y_prob) plt.plot(fpr, tpr, labelfRF AUC{roc_auc_score(y_test, y_prob):.3f}) plt.plot([0, 1], [0, 1], k--) plt.xlabel(False Positive Rate) plt.ylabel(True Positive Rate) plt.legend()这段代码画出来的ROC曲线能直观说明模型在“误伤率”和“命中率”之间的取舍。老师在答辩时最容易问的是“你的召回率达到多少调高阈值会发生什么”因为银行做营销预算有限往往更在意能不能把潜在响应客户找出来而不是保持高精确率。4. 聚类实现KMeans与层次聚类给银行客户画像分类是把人分成“要/不要”两类聚类则是无监督地把客户分成几群。课程作业要求的聚类常见目标是客户分群和画像比如把客户按资产、交易行为分成稳健型、活跃型和潜力型。注意分类模型里的特征工程不能直接拿来聚类分类为了区分标签聚类为了找距离所以标准化的处理完全不同。如果直接拿上一章拼接好的X去跑KMeans会被age、balance_log、one-hot后的job列等不同量纲的字段干扰。balance_log的取值可能是1-10job的0/1取值是0或1欧氏距离计算时量纲大的字段会主导聚类中心。课程作业里用“欧氏聚类”的说法指的就是KMeans的欧氏距离度量必须先缩放。4.1 聚类前的特征缩放为什么必须用StandardScalerKMeans内部算的是欧氏距离age和balance_log一个单位是“岁”一个单位是“对数比例”直接相加毫无意义。我用StandardScaler把所有数值列变成均值0、标准差1的分布from sklearn.preprocessing import StandardScaler cluster_cols [age, balance_log, income_log, deposit_cnt_3m, loan_total, txn_avg_amount] scaler StandardScaler() X_cluster scaler.fit_transform(wide[cluster_cols])为什么不是MinMaxScalerKMeans对异常值的抗性差而StandardScaler标准化后还能保留异常样本的分布形状MinMaxScaler会把异常值压缩到靠近1看似解决实则扭曲。fit_transform返回的是一个numpy数组后面聚类代码直接使用这个数组。注意这里fit_transform的时机一定要用全量数据进行fit而不是先划分训练集因为聚类没有“测试集”概念聚出来的群体本质就是描述全量数据。4.2 用肘部法和轮廓系数确定聚类个数KKMeans最烦的问题就是K怎么选。我见过同学直接拍脑袋定n_clusters4问为什么只说“觉得合适”这在实验报告里站不住脚。常见做法是同时看肘部法和轮廓系数from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score import matplotlib.pyplot as plt inertia [] silhouette [] K_range range(2, 11) for k in K_range: km KMeans(n_clustersk, random_state42, n_init10) labels km.fit_predict(X_cluster) inertia.append(km.inertia_) silhouette.append(silhouette_score(X_cluster, labels)) plt.subplot(1, 2, 1) plt.plot(K_range, inertia, o-) plt.subplot(1, 2, 2) plt.plot(K_range, silhouette, o-)inertia_是所有样本到所属聚类中心的距离平方和它随K增大单调下降曲线出现拐点的位置就是合理的K。silhouette_score的范围是[-1,1]越接近1越好一般取曲线最高点。但要注意轮廓系数高了不代表业务上可解释。有时候K3轮廓系数最高但分出来的三群特征差异不明显K5轮廓系数稍低却能在画像里清晰区分高净值、中产、年轻白领和退休群体。实验报告里我会把两种指标都贴出来再结合业务定义解释为什么选了某个K。4.3 KMeans聚类结果落地给每个群贴上业务标签确定K后重新训练并输出每个群体的特征均值这是实验报告里最出彩的表格k 5 km KMeans(n_clustersk, random_state42, n_init10) wide[cluster] km.fit_predict(X_cluster) cluster_profile wide.groupby(cluster).agg( count(cust_id, count), avg_balance(balance_log, mean), avg_income(income_log, mean), avg_deposit_cnt(deposit_cnt_3m, mean), avg_loan(loan_total, mean) ).round(2)groupby(cluster).agg计算每个簇的特征均值结果可以直接贴在实验报告里。n_init10表示KMeans会跑10次不同初始化选最优结果避免局部最优。这里我踩过一次坑如果不设random_state每次运行聚类结果可能完全不同报告里的群体画像上次是这个数、这次又是另一个数老师一查就露馅。所以聚类也一样要固定随机种子。4.4 层次聚类什么时候值得用以及内存爆掉怎么办作业里如果只要求“聚类”KMeans通常够用。但有些老师会点名要求“层次聚类”或者建议对比两种方法。层次聚类能画出树状图解释性很强但它的复杂度是O(n²)银行客户数据动不动几万行直接跑会慢到怀疑人生。常见做法是先用KMeans粗分几十个簇再把簇中心拿去做层次聚类。这一步一定要写进实验报告否则老师以为你在胡说。from scipy.cluster.hierarchy import linkage, dendrogram, fcluster # 先用KMeans压缩到40个簇再对中心做层次聚类 km_temp KMeans(n_clusters40, random_state42, n_init10) temp_centers km_temp.fit_transform(X_cluster) Z linkage(temp_centers, methodward) dendrogram(Z, truncate_modelevel, p4)linkage接收的是簇中心矩阵methodward是方差聚类倾向于合并出紧凑的簇。dendrogram的画法如果不加truncate_modelevel会在样本量几百时画出一大团黑根本看不清。p4只显示四层图才可读。对于几万行数据这是一个标准办法避免层次聚类直接跑在全量矩阵上把内存吃光。5. 常见问题排查数据挖掘作业最容易翻车的5个细节这一章是血泪经验合集。分类和聚类本身代码没几行但作业里最容易扣分、最让同学挠头的问题基本都出在数据和评估的边界上。我按照自己踩过的坑整理了最典型的五种。5.1 脏数据翻车成绩全凭空赋值但特征都是空现象模型跑完实验报告里每个人都写了一堆特征值但数据源文件里某一列全是NaN。原因dropna()一刀切把缺失多的字段全部删掉后面只能靠捏造。解决处理缺失值之前先统计缺失比例。我一般这样做miss_ratio wide.isnull().mean().sort_values(ascendingFalse) print(miss_ratio[miss_ratio 0.2])超过20%缺失的连续变量优先看能不能从其他表推导无源可推的直接做成“是否有值”的分类特征不要硬填。比如贷款金额缺失可以新增has_loan列缺失为0这样模型能学到“没有贷款记录的人”也是一种有效状态。5.2 分类器翻车用了全量数据做标准化后再划分测试集现象模型AUC看起来很高一到实际预测就废。原因在划分训练集之前用全量数据做了fit_transform测试集的信息间接进入了训练过程这种泄漏在作业中极常见。解决训练集和测试集必须完全隔离。正确写法是scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)fit_transform只在训练集上调用测试集只调用transform用的是训练集学到的均值和方差。逻辑回归、KMeans的预处理同样要遵守这条规则。写实验报告时这一步会作为“数据预处理”小节单列严格说明在哪一步划分数据集。5.3 KMeans翻车聚类中心的初始种子没固定现象同一份数据跑两次KMeans轮廓系数差不多但每个群的样本数完全变了。原因KMeans采用随机初始化没有设random_state。解决所有带随机性的算法必须固定random_state并且在实验报告里注明。分类、聚类都是同一套逻辑。固定种子后任何读者在同样版本环境里跑都能得到一致结果这既是复现实验的第一步也是课程作业“过程可验证”的最低要求。5.4 结果评估翻车AUC用了0/1标签而不是概率现象AUC只有0.5左右怎么调都上不去。原因roc_auc_score(y_test, model.predict(X_test))把预测的0/1标签传进去了而AUC需要的是预测正类的概率也就是predict_proba的输出。解决如果目标是用AUC挑模型就只用概率。model.predict(X_test)是卡阈值后的结果适合算混淆矩阵和业务损失不适合算AUC。这个问题我见过不下十次几乎成了数据挖掘作业的“保留节目”。5.5 实验报告翻车结果贴图代码全部隐藏现象报告里只有准确率数字和一张混淆矩阵没有任何过程。原因把课程作业当公司汇报只想展示结果。解决实验报告必须保证每一步可复现。至少要有数据来源与字段说明、宽表构建SQL或Python片段、缺失值处理规则、特征处理函数、模型参数、随机种子、交叉验证结果。每画一张图旁边配一个核心代码块老师追问时能立刻定位。6. 实验报告写作与模型验证让结论经得起答辩6.1 实验报告的结构数据仓库与数据挖掘的课程作业实验报告不能只写“我用Python做了一个分类”。我一般会这样组织业务理解与问题定义说明为什么要区分客户、数据源说明与宽表设计画一个简单的星型连接、数据清洗与特征处理给关键代码、分类模型实验至少两个模型对比、聚类实验K的选择与画像、结论与不足。每一节都要有一个可复现的结果比如表格里的AUC值、轮廓系数、聚类群体特征均值。6.2 用交叉验证和特征重要性支撑结论最后一招也是我自己的习惯当实验定了最终模型之后不要只用一组训练/测试集补一个交叉验证证明你的模型不是运气好。比如随机森林跑5折交叉验证记录每折的AUC均值和方差再打印特征重要性说清楚“为什么这些特征对银行客户响应影响最大”。这比反复刷参数更能体现对数据挖掘的理解。from sklearn.model_selection import cross_val_score import numpy as np cv_scores cross_val_score(model, X_train, y_train, cv5, scoringroc_auc) print(fCV AUC: {np.mean(cv_scores):.3f} ± {np.std(cv_scores):.3f}) importance pd.Series(model.feature_importances_, indexfeature_cols).sort_values(ascendingFalse) print(importance.head(10))交叉验证的均值能平滑单次划分的偶然性标准差说明模型在不同数据子集上的稳定性。特征重要性排在前面的变量才是你在答辩时能讲出业务理由的变量。比如deposit_cnt_3m排第一说明近3个月活跃存款的客户响应率显著更高这个结论放到给业务部门看的PPT里也是说得通的。写这份作业时我最大的教训是不要在拿到数据的当天就开始调参。先用半天把宽表做扎实、把清洗规则想清楚后面建模几乎是十分钟的事。而实验报告从第一天就要建好结构每天往里填结果而不是最后一天猛写。这样出来的作业自己能看懂老师也能信服。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询