
简介面向计算机专业毕业设计、课程设计与期末大作业场景这份学生校园消费行为分析项目基于Python实现提供从数据清洗到行为建模的完整代码与说明文档。项目经导师评审为99分代码完整可运行特别适合希望快速上手数据分析实战的初学者。资源包共8个文件压缩包约10.08MB其中3个Python源码文件覆盖初始化、模型与分析模块配套requirements.txt依赖清单、README说明、基于DFM模型的学生消费行为分析Word文档及校园消费数据集目录结构清晰便于按模块复现。目前已有188人学习下载既能直接作为毕业设计交付也可对照文档理解消费行为分析流程、替换数据集进行二次扩展是一份兼具完成度与教学价值的实战资料。1. 这份源码把校园一卡通数据变成了可解释的用户画像每年毕业季都能看到大量选题撞车的“校园消费分析”多数停在画饼图、柱状图的阶段。这套基于 Python 的校园消费行为分析项目真正的价值不在那些常规统计图表而在于用DFM 模型完成了从“流水数据”到“用户分群”的完整转化先将一卡通消费流水清洗成特征宽表再按离散度、频次、金额三个维度刻画每个学生的消费习惯最后通过聚类把人群拆成可解释的几类。对于计算机相关专业的学生这不仅是能直接提交的毕业设计更是一套可以替换数据集、改两个参数就能复用到其他场景的模板。下面是完整源码包中的核心思路与可运行细节包含数据集、说明文档与一整套 Python 分析脚本。2. 从一卡通流水到特征宽表数据清洗与预处理的完整链路2.1 原始数据里最常见的三个坑拿到项目自带的高校校园消费行为数据集第一件事不是写分析代码而是先探查数据形态。一卡通系统导出的流水表通常存在三个共性问题金额字段被存成字符串、交易时间跨越多个月份、同一个学生在不同表里的学号格式不一致。这套源码里给出了针对这些问题的标准处理路径。import pandas as pd import numpy as np from datetime import datetime # 读取原始消费流水 df pd.read_csv(campus_consumption.csv, encodinggbk) # 1. 金额字段清洗去除人民币符号与空格强制转数值 df[trade_amount] ( df[trade_amount] .astype(str) .str.replace(¥, ) .str.strip() .astype(float) ) # 2. 时间字段解析统一为 datetime 类型便于按小时/月份聚合 df[trade_time] pd.to_datetime(df[trade_time], format%Y-%m-%d %H:%M:%S) # 3. 异常值过滤金额小于等于0或大于单日消费上限的记录直接剔除 df df[(df[trade_amount] 0) (df[trade_amount] 500)]这里有几个参数值得注意。encodinggbk是国内高校导出的 Excel/CSV 最常见的编码换成utf-8大概率直接报UnicodeDecodeError。金额上限 500 元并非拍脑袋校园食堂单笔消费极少超过这个值但个别机器故障或退费记录会产生几千元的异常行。pd.to_datetime的format参数建议显式指定不指定时 Pandas 会自动推断但面对混合格式数据时性能会下降一个量级。2.2 跨表合并与画像字段构造清洗后的流水表还需要与学生信息表、商户档口表做关联才能支撑后续的“消费行为”分析而不只是“消费金额”统计。源码中的analysis.py主要完成三张表的合并逻辑。# 学生信息表学号、性别、年级、学院 stu_info pd.read_csv(student_info.csv, encodinggbk) stu_info[student_id] stu_info[student_id].astype(str).str.strip() # 商户档口表商户编号、档口名称、所属食堂/商圈 merchant_info pd.read_csv(merchant_info.csv, encodinggbk) # 三表合并流水 学生 商户 merged df.merge(stu_info, onstudent_id, howleft) merged merged.merge(merchant_info, onmerchant_id, howleft) # 构造时间特征星期几、小时段早餐/午餐/晚餐/夜宵 merged[weekday] merged[trade_time].dt.weekday merged[hour] merged[trade_time].dt.hour def meal_type(hour): if 6 hour 10: return breakfast elif 10 hour 15: return lunch elif 15 hour 21: return dinner else: return supper merged[meal] merged[hour].apply(meal_type)这段代码的用意在于把“一次交易”提升为“一个行为片段”。merge时统一使用howleft以流水表为主表避免因学生信息缺失导致整行丢失。时间段划分粒度可以根据学校食堂实际开放时间调整比如有些高校晚餐持续到 22 点可以把dinner上限放宽。这个meal字段是后续分析“消费习惯稳定性”的关键输入。2.3 特征宽表的最终形态完成基础清洗与合并后需要按学生维度聚合生成特征宽表。每个学生一行每列是一个统计特征。源码在model.py中给出了特征构造模板核心逻辑如下。# 按学生ID聚合构造模型所需的特征列 feature_df merged.groupby(student_id).agg( total_amount(trade_amount, sum), trade_count(trade_amount, count), unique_merchant(merchant_id, nunique), avg_amount(trade_amount, mean), std_amount(trade_amount, std), breakfast_count(meal, lambda x: (x breakfast).sum()), dinner_count(meal, lambda x: (x dinner).sum()), night_count(meal, lambda x: (x supper).sum()), ).reset_index() # 构造比例型特征早餐占比、晚餐占比、单商户黏性 feature_df[breakfast_ratio] feature_df[breakfast_count] / feature_df[trade_count] feature_df[dinner_ratio] feature_df[dinner_count] / feature_df[trade_count] feature_df[merchant_diversity] feature_df[unique_merchant] / feature_df[trade_count] # 缺失值处理std_amount 可能为 NaN只有一笔消费的学生 feature_df feature_df.fillna(0)到这里就已经得到了一个可供模型消费的宽表行为特征、金额特征、商户交互特征完整齐全。需要重点说明的是std_amount这个特征它表示消费金额的波动程度。一个每天稳定在食堂吃两顿饭的学生这个值很低而经常点外卖、偶尔去超市囤货的学生这个值会明显偏高。它是 DFM 模型中“离散度”维度的直接数据来源。3. DFM 模型从原理到实现离散度、频次与金额的组合刻画3.1 为什么是 DFM 而不是 RFM做过用户画像的人对 RFM 模型不会陌生——最近一次消费、消费频率、消费金额三个维度。但 RFM 是针对零售、电商场景设计的核心假设是“用户会流失所以要关注最近一次消费时间”。校园一卡通场景里在校学生按学期稳定在校不存在传统意义上的“流失”概念R维度基本失真。DFM 把R替换为D即消费离散度Dispersion。这个替换逻辑很直接观察一个学生的消费时间是否规律、消费金额是否稳定、消费场所是否集中。规律型学生的时间离散度低、场所集中度高活跃型学生的时间离散度高、场所分散。替换后的 DFM 更贴近校园场景的真实行为差异。# DFM 三特征计算D离散度、F频次、M金额 dfm_data feature_df.copy() # D1: 消费时间离散度 - 用小时字段的标准差衡量 hour_std merged.groupby(student_id)[hour].std() # D2: 消费场所多样性 - 用商户数 / 消费次数衡量 dfm_data[d_dispersion] dfm_data[std_amount].fillna(0) # F: 频次 - 总消费次数直接取值 dfm_data[f_frequency] dfm_data[trade_count] # M: 金额 - 总消费金额直接取值 dfm_data[m_amount] dfm_data[total_amount] # 构造最终 DFM 特征矩阵 dfm_final dfm_data[[student_id, d_dispersion, f_frequency, m_amount]]这段代码里std_amount充当了离散度的代理变量。严格意义上消费金额标准差与消费时间波动是两个概念但在实际项目中金额波动大的学生通常时间规律性也差两者有较强的正相关性。如果希望时间维度的离散度更精确可以计算每个学生消费小时的熵或标准差。3.2 聚类分群与参数选择DFM 特征构造完成后进入用户分群环节。源码使用的是经典的 KMeans 聚类调参重点是聚类数 K 的选择。这里不能盲目使用默认参数需要用肘部法则辅助判断。from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans import matplotlib.pyplot as plt # 标准化三特征量纲不同必须标准化后再聚类 scaler StandardScaler() dfm_scaled scaler.fit_transform(dfm_final[[d_dispersion, f_frequency, m_amount]]) # 肘部法则绘制不同K值下的簇内误差平方和 inertia [] k_range range(2, 9) for k in k_range: km KMeans(n_clustersk, random_state42, n_init10) km.fit(dfm_scaled) inertia.append(km.inertia_) plt.figure(figsize(8, 5)) plt.plot(list(k_range), inertia, markero) plt.xlabel(K) plt.ylabel(Inertia) plt.savefig(elbow_curve.png, dpi150)n_init10是值得注意的参数它表示 KMeans 算法用 10 组不同的初始质心分别运行最终返回最优结果。不设置这个参数时Sklearn 新版本里默认值是 10但老版本行为不同。random_state42保证结果可复现——毕业设计答辩时评委可能会让你现场重跑固定随机种子能避免每次结果都不一样。从肘部曲线确定 K 值后执行最终聚类并给每个类别打业务标签。通常校园消费数据跑出来 4 类左右比较理想高消费活跃型、规律食堂型、节俭型、夜间活跃型。# 最终聚类假设肘部曲线显示 K4 kmeans KMeans(n_clusters4, random_state42, n_init10) dfm_final[cluster] kmeans.fit_predict(dfm_scaled) # 查看每个簇的均值特征用于人工打标签 cluster_profile dfm_final.groupby(cluster)[[d_dispersion, f_frequency, m_amount]].mean() print(cluster_profile)聚类完成后每个学生的cluster标签就生成了。这个标签可以直接回写到数据库也可以导出为 CSV 给后续可视化模块使用。3.3 从聚类结果到业务解释聚类只给出分组编号不给业务含义。需要人工根据每个簇的特征均值来命名。举例来说如果一个簇的m_amount显著高于总体均值、f_frequency也偏高、d_dispersion中高可以命名为“高消费活跃型”另一个簇m_amount低、f_frequency高、d_dispersion很低则是“规律食堂型”。这个过程往往需要结合学校的实际情况微调比如部分学校有校内超市、水果店消费频次的高低可能受到宿舍距离的影响。4. 可视化呈现与结果解读把模型输出变成答辩能讲的图表4.1 分群结果的三维可视化聚类结果只有变成直观的图表才能体现分析价值。源码中提供了三维散点图和热力图两种核心可视化方案。三维散点图用于展示聚类效果热力图用于展示不同分群的特征倾向。from mpl_toolkits.mplot3d import Axes3D fig plt.figure(figsize(10, 7)) ax fig.add_subplot(111, projection3d) scatter ax.scatter( dfm_final[d_dispersion], dfm_final[f_frequency], dfm_final[m_amount], cdfm_final[cluster], cmapviridis, s20, alpha0.6, ) ax.set_xlabel(Dispersion) ax.set_ylabel(Frequency) ax.set_zlabel(Monetary) plt.savefig(dfm_cluster_3d.png, dpi150)这段代码将 KMeans 聚类的结果直接映射到三维空间的颜色上。cmapviridis是色盲友好的配色方案比默认的jet更适合论文插图。三维投影的视角可能需要手动调整才能找到最佳观察角度可以先运行后在弹窗中旋转到合适的视角再保存。4.2 热力图揭示群体消费时段偏好另一个分析维度是不同分群在早餐、午餐、晚餐、夜宵四个时段的消费偏好。这里需要回到merged表按cluster和meal做交叉统计。# 将聚类标签回写到流水表 merged merged.merge( dfm_final[[student_id, cluster]], onstudent_id, howleft, ) # 交叉表各分群在各餐段的平均消费次数 meal_cluster merged.pivot_table( indexcluster, columnsmeal, valuestrade_amount, aggfunccount, fill_value0, ) # 归一化横向比较各分群内部的时间偏好 meal_cluster_norm meal_cluster.div(meal_cluster.sum(axis1), axis0) # 绘制热力图 import seaborn as sns plt.figure(figsize(9, 5)) sns.heatmap(meal_cluster_norm, annotTrue, fmt.2f, cmapYlOrRd) plt.xlabel(Meal Type) plt.ylabel(Cluster) plt.savefig(cluster_meal_heatmap.png, dpi150)热力图揭示的信息往往比数字更有说服力夜间活跃型的夜宵占比可能达到 15% 到 20%而规律食堂型的早餐占比可能超过 30%。这些分布在答辩现场可以直接转化为管理建议比如食堂档口可根据不同分群的活跃时段调整出餐排班。4.3 箱线图对比消费金额分布差异除了分群间的差异消费金额的组间分布对比也是论文中的一个核心图表。plt.figure(figsize(10, 6)) sns.boxplot( datadfm_final, xcluster, ym_amount, paletteSet2, ) plt.xlabel(Cluster) plt.ylabel(Total Consumption Amount) plt.savefig(cluster_amount_boxplot.png, dpi150)箱线图能从统计分布层面展示不同群体的消费能力差异。如果某个簇的中位数和上四分位数都显著高于其他簇那这个群体的“高消费”标签就有了数据支撑。推荐在论文中配上箱线图与前面热力图形成“宏观分群 微观时段”的双层证据链。5. 说明文档里的隐藏信息这套项目跑通后的落地要点5.1 从源码到复现你需要额外做的事项目的说明文档里有完整的环境依赖清单。实际运行时需要注意 Python 版本兼容性项目源码基于较新的 Pandas 版本编写但 Python 3.7 与 Python 3.10 在部分 API 上会有细微差异。建议使用 Python 3.8 或 3.9 环境运行配合文档中列出的依赖版本可以最大限度地避免环境问题。# 建议的安装方式 python -m venv venv source venv/bin/activate # Windows 下执行 venv\Scripts\activate pip install pandas numpy matplotlib seaborn scikit-learn jupyter如果安装时速度较慢可以换用国内镜像源。pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pandas numpy matplotlib seaborn scikit-learn jupyter这条命令是常见的加速方式。环境装好后按src/init.py、src/model.py、src/analysis.py的顺序依次执行即可。5.2 秒懂项目结构的捷径项目的README.md把整个代码结构说明得很清楚src/init.py负责数据读取和清洗src/model.py负责特征工程和聚类建模src/analysis.py负责可视化和结果输出。这个分层方式非常符合毕业设计的代码规范要求——数据、模型、展示分离每一层职责单一。论文的第三章和第四章可以直接按照这三个模块展开写说明文档里也有对应的框架建议。5.3 数据集字段与自定义数据的替换方法项目自带的高校校园消费行为数据集涵盖了学号、交易时间、交易金额、商户编号、商户类型等字段。如果要换成自己学校的数据需保证字段名一致或者修改init.py中的列名映射。重点提醒一点student_id必须以字符串格式读入否则学号中可能存在的零开头编号比如2023010101会被 Pandas 自动转成数值导致前导零丢失后续合并会产生大量匹配失败记录。这是新手最容易踩的坑之一。5.4 答辩现场的操作预案完成代码运行后答辩演示时建议按以下顺序操作先播放数据清洗前后的对比展示异常值剔除的数量然后展示 DFM 特征构造逻辑和聚类散点图最后用热力图和箱线图收尾。整套流程控制在 8 分钟以内。如果评委问到“为什么选 KMeans 而不是 DBSCAN”可以从样本量、特征维度、簇形状假设三个角度回答KMeans 适合中小规模数据、计算快、结果稳定且在标准化后的欧氏空间里表现足够好DBSCAN 依赖密度参数对一卡通这类特征分布不均匀的数据调参成本更高效果未必更好。6. 把演示项目变成能扛住追问的完整分析闭环6.1 用轮廓系数量化分群质量KMeans 的肘部法则只能给出主观判断如果评委追问“你的分群效果到底好不好”可以用轮廓系数给出定量答案。轮廓系数取值为 -1 到 1越接近 1 表示簇内紧密、簇间分离越好。from sklearn.metrics import silhouette_score # 计算 K4 时的轮廓系数 sil_score silhouette_score(dfm_scaled, dfm_final[cluster]) print(fSilhouette Score: {sil_score:.4f}) # 对比不同 K 值的轮廓系数 for k in range(2, 9): km KMeans(n_clustersk, random_state42, n_init10) labels km.fit_predict(dfm_scaled) print(fK{k}, Silhouette{silhouette_score(dfm_scaled, labels):.4f})校园消费数据跑出来的轮廓系数通常在 0.3 到 0.5 之间这是一个可以接受的范围。如果低于 0.2说明分群边界不清晰需要回看特征工程是否存在问题。轮廓系数的计算基于样本两两距离数据量大时耗时明显可以按 30% 的比例采样后再计算。6.2 群体画像落地从分析到报告的输出当模型训练完毕把聚类结果输出为一页群体画像描述对毕设论文来说非常加分。这里有一个技巧对每一个分群输出消费金额排名前五的商户以及各餐段消费占比最高的两个时段然后将这些信息拼接成自然语言描述。# 各分群 TOP 商户 for c in dfm_final[cluster].unique(): cluster_merchants ( merged[merged[cluster] c] .groupby(merchant_name)[trade_amount] .agg([count, sum]) .sort_values(sum, ascendingFalse) .head(5) ) print(f\nCluster {c} Top Merchants:) print(cluster_merchants)这一步骤把分析结果从“数据结论”升级为“业务洞察”。比如某一分群最喜欢光顾超市而非食堂就可以进一步推断该群体的生活方式倾向。6.3 把项目复用到其他校园场景总结一个值得记住的复用点这套代码里的 DFM 模型并不只适用于校园一卡通。任何带时间戳、金额、用户 ID 的数据集——比如图书馆门禁记录、实验室门禁记录、校园网流量日志——都可以套用相同的清洗、特征构造、聚类三步走流程。只需要修改meal_type函数中的时间段定义以及重新审视异常值过滤条件即可。替换数据集后说明文档中的分析框架和论文结构可以直接延续使用这是这套源码包在当前毕业设计生态里最有性价比的地方。如果你拿到的是其他学校的数据集可以先运行analysis.py生成基础统计报表再根据报表结果微调模型参数。整个流程从数据清洗到最终图表输出大约需要完整跑一遍中间遇到字段名不匹配的情况直接查看README.md中的字段映射说明即可。本文还有配套的精品资源点击获取