简介:这份资源是面向计算机相关专业学生的数据仓库与数据挖掘课程作业完整包,围绕银行数据分类与数据聚类两大任务展开,适合正在准备课程设计、期末大作业或需要项目实战练习的学习者参考。包内共122个文件,以csv数据集、py源码、java程序、dim维度文件为主,另含pdf与docx实验报告、pptx演示文稿及png结果图,压缩包约11.36MB,目录结构清晰,便于按模块查阅。该作业经导师指导并认可通过,评审分99分,代码完整可运行,对新手较为友好。读者可从中获取银行数据分类与聚类的完整实现思路、实验报告撰写框架、数据集处理流程以及结果可视化参考,既能对照复现实验,也能借鉴报告结构与分析方法,用于自身课程作业的快速搭建与查漏补缺。目前已有280人学习下载。
1. 银行客户数据分类与聚类:一份课程作业背后的完整工程链路
银行客户流失预测、信用评分、理财产品推荐,这些场景背后都指向同一件事:把客户数据整理成可分析的结构,再用分类或聚类算法从中提取规律。这份课程作业标题里的「数据仓库与数据挖掘」,落到代码层面就是两件事——用 Python 把银行客户数据清洗、变换、加载成适合建模的宽表,然后分别跑通一个监督分类任务和一个无监督聚类任务,最后用实验报告把参数、指标和结论串起来。适合正在做课程设计的学生,也适合想补全「数据仓库到建模」这条链路的初级数据工程师。很多人卡在环境配置和库版本上,其实核心代码不到两百行,真正花时间的是数据理解、特征处理和结果解释。
2. 数据仓库层:银行客户宽表的构建与特征工程
2.1 为什么银行数据要先做仓库层再做挖掘
银行原始数据通常散落在核心交易系统、CRM 和渠道日志里,直接拿原始表跑模型会踩三个坑:字段口径不一致、时间粒度混乱、缺失值掩盖业务含义。数据仓库层的价值在于把「客户 ID」作为主键,把交易频次、资产余额、产品持有数、最近一次交易间隔这些指标按统一时间窗口聚合,形成一张客户粒度宽表。这一步不做,后面的分类和聚类都是在噪声上拟合。
常见做法是用 pandas 做轻量级 ETL:从 CSV 或数据库读取原始流水,按客户 ID 分组聚合,生成衍生特征。银行场景下我一般会保留这几类字段:人口属性(年龄、职业、婚姻状况)、资产属性(活期余额、定期余额、理财持仓)、行为属性(近 30 天交易笔数、近 90 天登录次数、最近交易距今天数)、标签字段(是否流失、信用等级)。宽表建好后,分类任务用标签字段做监督学习,聚类任务则把标签字段剔除,只留特征做无监督分组。
2.2 用 pandas 构建客户宽表的最小代码
import pandas as pd import numpy as np # 读取原始交易流水和客户基本信息 trans = pd.read_csv('bank_transactions.csv', parse_dates=['trans_time']) cust = pd.read_csv('bank_customers.csv') # 按客户聚合交易行为特征 trans_agg = trans.groupby('cust_id').agg( trans_count_30d=('trans_amount', lambda x: (x.index >= (pd.Timestamp.now() - pd.Timedelta(days=30))).sum()), avg_amount=('trans_amount', 'mean'), max_amount=('trans_amount', 'max'), last_trans_days=('trans_time', lambda x: (pd.Timestamp.now() - x.max()).days) ).reset_index() # 合并客户属性与行为特征 wide_table = cust.merge(trans_agg, on='cust_id', how='left') # 缺失值填充:数值型用中位数,类别型用众数 num_cols = wide_table.select_dtypes(include=[np.number]).columns cat_cols = wide_table.select_dtypes(include=['object']).columns wide_table[num_cols] = wide_table[num_cols].fillna(wide_table[num_cols].median()) wide_table[cat_cols] = wide_table[cat_cols].fillna(wide_table[cat_cols].mode().iloc[0]) wide_table.to_csv('bank_wide_table.csv', index=False) print(wide_table.shape)这段代码的逻辑是:先按客户 ID 把交易流水聚合成行为指标,再与客户属性表左连接,保证每个客户一行。trans_count_30d用 lambda 计算近 30 天交易笔数,last_trans_days计算最近一次交易距今天数,这两个特征在流失预测中通常权重较高。缺失值处理上,数值型用中位数而不是均值,是因为银行资产类字段常有长尾分布,均值会被极端值拉偏。类别型用众数填充,适合职业、婚姻状况这类低基数字段。
参数方面,时间窗口 30 天和 90 天不是固定的,要看业务周期。信用卡场景常用 30 天,理财场景可能拉长到 90 天。如果数据量超过百万行,groupby 聚合会变慢,可以改用 polars 或 DuckDB,语法接近但执行引擎是列式的,速度能快三到五倍。
2.3 特征编码与标准化:分类和聚类要分开处理
分类任务和聚类任务对特征预处理的要求不同。分类模型(如逻辑回归、随机森林)可以处理原始数值和独热编码后的类别特征,但聚类算法基于距离度量,必须先把所有特征缩放到同一量纲,否则资产余额这种大数值会主导距离计算。
from sklearn.preprocessing import StandardScaler, LabelEncoder # 分类任务:类别特征做标签编码或独热编码 df_cls = wide_table.copy() le = LabelEncoder() df_cls['job_code'] = le.fit_transform(df_cls['job']) df_cls = pd.get_dummies(df_cls, columns=['marital'], prefix='marital') # 聚类任务:数值特征标准化,剔除标签列 df_clu = wide_table.select_dtypes(include=[np.number]).drop(columns=['is_churn'], errors='ignore') scaler = StandardScaler() df_clu_scaled = pd.DataFrame(scaler.fit_transform(df_clu), columns=df_clu.columns)分类任务里,LabelEncoder适合有序类别(如信用等级),get_dummies适合无序类别(如婚姻状况)。聚类任务里,StandardScaler把每个特征变成均值 0、方差 1 的分布,这是 K-Means 和层次聚类的标准前置步骤。注意drop(columns=['is_churn'])这一步不能漏,否则聚类会把标签信息泄露进去,导致分组结果看起来很好但实际没有业务意义。
3. 分类任务:用随机森林和逻辑回归预测银行客户流失
3.1 分类目标定义与数据集划分
银行数据分类的典型目标是流失预测:根据客户属性、资产和行为特征,判断未来一段时间内是否会流失。标签字段is_churn通常定义为「连续 N 天无交易且资产余额低于阈值」。这个定义直接影响正负样本比例,如果流失客户只占 5%,模型会偏向预测多数类,需要用类别权重或过采样来平衡。
数据集划分用分层抽样,保证训练集和测试集的类别比例一致。常见比例是 7:3 或 8:2,课程作业数据量不大时用 7:3 足够。划分前要打乱顺序,避免原始数据按时间排序导致分布偏移。
from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report, roc_auc_score # 准备特征和标签 X = df_cls.drop(columns=['cust_id', 'is_churn', 'job', 'marital'], errors='ignore') y = df_cls['is_churn'] # 分层划分 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42, stratify=y ) # 随机森林:处理非线性关系,自带特征重要性 rf = RandomForestClassifier( n_estimators=200, max_depth=8, min_samples_leaf=5, class_weight='balanced', random_state=42 ) rf.fit(X_train, y_train) y_pred_rf = rf.predict(X_test) y_prob_rf = rf.predict_proba(X_test)[:, 1] print("随机森林 AUC:", roc_auc_score(y_test, y_prob_rf)) print(classification_report(y_test, y_pred_rf)) # 逻辑回归:可解释性强,适合看特征方向 lr = LogisticRegression(max_iter=1000, class_weight='balanced', random_state=42) lr.fit(X_train, y_train) y_prob_lr = lr.predict_proba(X_test)[:, 1] print("逻辑回归 AUC:", roc_auc_score(y_test, y_prob_lr))随机森林的n_estimators=200是树的数量,太少会欠拟合,太多训练变慢但精度提升有限,200 到 500 是常见区间。max_depth=8控制树深,防止过拟合,银行数据特征维度不高时 6 到 10 比较合适。min_samples_leaf=5保证叶子节点至少有 5 个样本,避免模型记住噪声。class_weight='balanced'自动按类别频率反比加权,解决流失样本少的问题。
逻辑回归的max_iter=1000是优化器迭代上限,默认 100 可能不收敛,尤其特征标准化后。class_weight='balanced'同样用于类别不平衡。逻辑回归的优势是系数可以直接解释:某个特征系数为正且绝对值大,说明该特征增加会提高流失概率。
3.2 分类模型评估:AUC、召回率和业务阈值
分类任务不能只看准确率。银行流失预测中,正类(流失)样本少,模型全预测为「不流失」也能拿到 95% 准确率,但毫无价值。核心指标是 AUC 和召回率。AUC 衡量模型排序能力,0.5 是随机猜,0.8 以上算可用。召回率衡量流失客户中被抓出来的比例,银行场景下召回率比精确率更重要,因为漏掉一个流失客户的成本远高于误判一个非流失客户。
from sklearn.metrics import confusion_matrix, precision_recall_curve # 调整阈值看召回率和精确率的权衡 precisions, recalls, thresholds = precision_recall_curve(y_test, y_prob_rf) # 找到召回率不低于 0.7 时的最大阈值 idx = np.where(recalls[:-1] >= 0.7)[0] if len(idx) > 0: best_threshold = thresholds[idx[-1]] print(f"召回率>=0.7时阈值: {best_threshold:.3f}") # 按业务阈值生成最终预测 y_pred_custom = (y_prob_rf >= 0.35).astype(int) print(confusion_matrix(y_test, y_pred_custom))默认阈值 0.5 不一定最优。如果业务要求召回率不低于 70%,可以把阈值降到 0.35 左右,代价是精确率下降。这个权衡要用混淆矩阵和业务成本来定,不是算法能自动决定的。课程作业里可以画出 P-R 曲线,标注不同阈值对应的点,说明选择理由。
3.3 特征重要性解读与实验报告写法
随机森林训练完后,feature_importances_给出每个特征的重要性分数。银行场景下通常last_trans_days、trans_count_30d、avg_amount排在前列,说明最近交易行为和资产水平是流失的主要信号。实验报告里不要只贴重要性排序,要结合业务解释:比如「最近交易间隔超过 60 天的客户流失概率是 30 天以内客户的 3.2 倍」,这种结论比单纯列数字更有说服力。
报告结构建议按「数据描述 → 预处理 → 模型选择 → 参数调优 → 结果对比 → 业务建议」组织。模型对比部分用表格列出逻辑回归和随机森林的 AUC、召回率、精确率,并说明为什么最终选某个模型。如果随机森林 AUC 更高但逻辑回归可解释性更好,可以两个都保留,分别用于不同场景。
4. 聚类任务:K-Means 与层次聚类做银行客户分群
4.1 聚类前必须想清楚的事:距离度量和 K 值选择
聚类和分类的本质区别是:分类有标签,聚类没有。银行客户分群的目标不是预测某个已知结果,而是发现自然形成的客户群体,比如高净值低活跃、低净值高频交易、中等资产稳定型。K-Means 用欧氏距离,适合数值型特征且簇形状接近球形。层次聚类用树状结构,适合探索小样本的层次关系,但计算复杂度高,数据量大时优先用 K-Means。
K 值选择是聚类最常翻车的地方。业务上可能希望分 3 到 5 群,但数据本身的最优簇数要用肘部法或轮廓系数来验证。肘部法看惯性(簇内平方和)随 K 增加的下降曲线,拐点处是候选 K 值。轮廓系数衡量簇内紧密度和簇间分离度,取值 -1 到 1,越接近 1 越好。
from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score import matplotlib.pyplot as plt inertias = [] sil_scores = [] K_range = range(2, 9) for k in K_range: km = KMeans(n_clusters=k, random_state=42, n_init=10) labels = km.fit_predict(df_clu_scaled) inertias.append(km.inertia_) sil_scores.append(silhouette_score(df_clu_scaled, labels)) # 输出肘部法和轮廓系数结果 for k, inertia, sil in zip(K_range, inertias, sil_scores): print(f"K={k}, 惯性={inertia:.1f}, 轮廓系数={sil:.3f}")n_init=10表示用不同初始质心跑 10 次,取最优结果,避免陷入局部最优。K-Means 对初始质心敏感,random_state=42保证结果可复现。轮廓系数在 K=2 时通常最高,因为二分类问题最简单,但业务上可能需要更细的分群,这时候要结合惯性下降幅度和业务解释力来定。
4.2 K-Means 聚类实现与簇特征画像
选定 K 值后,跑最终聚类并给每个簇打标签。关键不是聚类本身,而是聚类后对每个簇的特征均值做对比,用业务语言描述这群客户是谁。
# 假设肘部法和轮廓系数指向 K=4 km_final = KMeans(n_clusters=4, random_state=42, n_init=10) df_clu_scaled['cluster'] = km_final.fit_predict(df_clu_scaled) # 把簇标签合并回原始尺度数据做画像 df_profile = wide_table.copy() df_profile['cluster'] = df_clu_scaled['cluster'] # 计算每个簇的特征均值 profile = df_profile.groupby('cluster').agg( age_mean=('age', 'mean'), balance_mean=('balance', 'mean'), trans_count_mean=('trans_count_30d', 'mean'), last_trans_mean=('last_trans_days', 'mean'), churn_rate=('is_churn', 'mean'), count=('cust_id', 'count') ).round(2) print(profile)画像表里churn_rate是事后加进去的验证列,聚类时没用,但可以用来检查分群是否有业务区分度。如果某个簇的流失率显著高于其他簇,说明聚类捕捉到了有意义的模式。count列看每个簇的客户数,如果某个簇只有几十个客户,可能是异常值聚集,需要检查是否有极端数据未处理。
4.3 层次聚类做小样本验证与树状图解读
层次聚类适合在 K-Means 之前做探索,或者数据量小于几千行时直接使用。凝聚层次聚类从每个样本一个簇开始,逐步合并最近的簇,最终形成一棵树。树状图的横轴是样本或簇,纵轴是合并距离,切一刀就得到指定数量的簇。
from scipy.cluster.hierarchy import dendrogram, linkage, fcluster # 用 Ward 方法做层次聚类,适合数值特征 Z = linkage(df_clu_scaled, method='ward') # 按距离阈值切分,得到簇标签 clusters_hc = fcluster(Z, t=4, criterion='maxclust') df_clu_scaled['cluster_hc'] = clusters_hc # 绘制树状图(课程作业报告里可截图) plt.figure(figsize=(12, 6)) dendrogram(Z, truncate_mode='lastp', p=20, show_leaf_counts=True) plt.title('银行客户层次聚类树状图') plt.xlabel('簇大小') plt.ylabel('合并距离') plt.show()method='ward'最小化合并后的簇内方差,适合欧氏距离。t=4表示切分成 4 个簇,criterion='maxclust'表示按最大簇数切。树状图里纵轴合并距离越大,说明簇之间差异越明显。如果某次合并的距离突然跳升,说明那一步之前的分簇比较自然。层次聚类的结果可以和 K-Means 对比,如果两种方法在 K=4 时簇成员重叠度高,说明分群稳定。
5. 避坑与排查:银行数据分类聚类中常见的五个翻车点
5.1 现象:模型 AUC 很高但业务方不认可
原因通常是数据泄露。比如用「是否持有某产品」预测「是否购买该产品」,或者用未来信息预测过去行为。银行数据里时间字段多,稍不注意就会把标签泄露进特征。解决方法是检查每个特征在预测时间点是否可得,用时间切分代替随机切分,训练集用早期数据,测试集用后期数据。
5.2 现象:K-Means 每次跑出来的分群结果不一样
原因是初始质心随机且n_init太小。K-Means 对初始点敏感,默认n_init=10在数据量大时可能不够。解决方法是把n_init调到 20 或 30,同时固定random_state。如果结果仍然不稳定,说明数据本身没有明显的簇结构,轮廓系数会很低,这时候强行聚类没有意义,应该回到特征工程阶段检查是否漏了关键维度。
5.3 现象:分类报告里召回率为 0
原因是类别极度不平衡且模型偏向多数类。银行流失数据里流失客户可能只占 2%,模型全预测「不流失」就能拿到 98% 准确率,但召回率为 0。解决方法是设置class_weight='balanced',或者用 SMOTE 过采样少数类。注意 SMOTE 只能在训练集上做,测试集保持原始分布,否则评估结果会虚高。
5.4 现象:聚类结果里某个簇只有几个样本
原因是异常值未处理。银行数据里可能有资产余额为负或交易笔数为 9999 的测试数据,标准化后这些点远离其他样本,K-Means 会单独给它们一个簇。解决方法是在聚类前用 IQR 或 Z-score 检测异常值,要么剔除,要么用盖帽法把极端值压到 1% 和 99% 分位数。注意分类任务对异常值不敏感,但聚类对距离敏感,所以异常值处理要分开做。
5.5 现象:实验报告里指标很好但代码换台机器跑不通
原因是环境依赖和随机种子没固定。scikit-learn版本不同,RandomForestClassifier的默认参数可能变化;numpy版本不同,随机数生成器行为可能不同。解决方法是在报告里写清库版本,用requirements.txt固定依赖,代码里所有涉及随机的步骤都设random_state。课程作业提交前在干净环境里跑一遍,确保从读取数据到输出结果全流程可复现。
6. 从课程作业到可复现实验:参数存档与结果验证的实操习惯
课程作业和真实项目的差距不在算法复杂度,而在可复现性。我做完这份银行数据分类聚类后养成了一个习惯:每次跑实验前,先把所有参数写进一个字典,存成 JSON 文件,和代码一起提交。这样过两周回头看报告,不用猜当时max_depth设的是 6 还是 8。
import json from datetime import datetime experiment_config = { "timestamp": datetime.now().isoformat(), "data_version": "bank_wide_table_v1", "classification": { "model": "RandomForest", "n_estimators": 200, "max_depth": 8, "min_samples_leaf": 5, "class_weight": "balanced", "test_size": 0.3, "random_state": 42 }, "clustering": { "algorithm": "KMeans", "n_clusters": 4, "n_init": 10, "random_state": 42, "scaler": "StandardScaler" }, "metrics": { "rf_auc": 0.84, "rf_recall": 0.72, "silhouette": 0.38 } } with open('experiment_config.json', 'w', encoding='utf-8') as f: json.dump(experiment_config, f, ensure_ascii=False, indent=2)这个 JSON 文件不只是存档,还能直接用于结果验证。比如换一批数据后,用同样的参数跑一遍,对比 AUC 和轮廓系数的变化。如果 AUC 下降超过 5 个百分点,说明数据分布变了,需要重新做特征工程。如果轮廓系数从 0.38 降到 0.2 以下,说明簇结构变模糊了,K 值可能需要调整。
另一个实用技巧是给聚类结果加稳定性检验。用 bootstrap 抽样跑 20 次 K-Means,看每个样本的簇归属是否一致。如果某个样本在 20 次里有 15 次落在同一个簇,说明分群稳定;如果每次都在不同簇之间跳,说明这个样本处于边界地带,业务上需要单独关注。
from collections import Counter stability = [] for i in range(20): sample_idx = np.random.choice(len(df_clu_scaled), size=int(len(df_clu_scaled)*0.8), replace=False) km_boot = KMeans(n_clusters=4, random_state=i, n_init=10) labels_boot = km_boot.fit_predict(df_clu_scaled.iloc[sample_idx]) stability.append(Counter(labels_boot).most_common(1)[0][1] / len(sample_idx)) print(f"簇稳定性均值: {np.mean(stability):.3f}")如果稳定性均值低于 0.6,说明聚类结果对数据扰动敏感,报告里要如实写出来,而不是只挑一次好看的结果。银行场景下,分群结果通常要交给业务部门做营销策略,不稳定的分群会导致策略频繁变动,反而增加成本。
最后说一个我踩过的坑:课程作业里用train_test_split随机划分,AUC 跑到 0.9 以上,后来发现数据里有重复客户记录,同一个客户既在训练集又在测试集,模型等于抄答案。后来改成按客户 ID 去重后再划分,AUC 降到 0.82,这才是真实水平。做银行数据挖掘,先去重再划分,这个顺序不能反。希望帮到你。
本文还有配套的精品资源,点击获取