简介:基于Python的学生校园消费行为分析项目资源,适合高校学生用于毕业设计、课程设计,也可作为Python数据分析综合练习使用。项目以校园消费数据为对象,通过Python脚本完成数据读取、处理与可视化分析,呈现从原始数据到分析结果的完整链路。资源共20个文件,分为数据、代码、结果等模块:4个py脚本为核心分析代码,4个csv文件存放原始数据集与中间结果,9张png及3张jpg图片为各任务的可视化成果,同时包含完整源码、数据集和结果集,整体压缩包约20.37MB,结构清晰,便于按目录逐步定位学习。项目说明称源码均经过本地编译与严格调试,评审分达到95分以上,难度适中,可直接运行或在此基础上扩展成独立毕设项目。目前已有100人学习下载,适合具备基础Python知识、希望参考完整校园消费分析项目的读者。
1. 从一卡通流水到消费画像:这个 Python 毕业设计到底解决什么问题
学生校园消费行为分析,这类题每年都有不少人选,但真正能拿高分的不在于用了多复杂的模型,而在于能不能把一整套路走通:数据预处理、统计可视化、行为建模、结果解释。这个基于 Python 的学生校园消费行为分析项目,正好把这条链路完整做了一遍。拿到手的是源码、数据集、结果集三件套,脚本按 task1、task2、task3 分阶段推进,每跑一步都有对应的图和 CSV 结果输出。适合正在做数据分析方向毕设或课设的同学,也适合想快速搭出一套“从原始表格到可视化结论”分析链路的从业者参考。
项目本身不堆砌算法,难度适中但流程完整,评审分能到 95 分以上,靠的就是每张图、每个 CSV 结果都有明确的分析含义可讲。下面按我习惯的方案结构,把这个项目拆开讲清楚:数据怎么清洗、统计图怎么出、聚类怎么建模、答辩怎么讲。
2. 数据基础与预处理:两张表怎么组织、字段怎么设计
2.1 两张原始数据表的定位
数据集里放了 data1.csv 和 data2.csv 两份文件,这是整个分析的地基。按常见的校园一卡通项目设计,通常是一家数据放消费流水明细,另一家放学生静态信息或汇总统计。data1.csv 一般记录的是每次刷卡消费的流水:学号、消费时间、消费金额、消费商户类型、支付方式等;data2.csv 则往往放学生的院系、年级、性别、月均消费等维度的静态档案。把两张表分开存,既贴近真实业务场景中流水表和档案表分离的习惯,也方便后面做多表关联分析。
如果只有一张表,那做消费行为分析很容易陷入“只能看金额分布”的单一视角。拆成两张表之后,就可以做更有解释力的分析,比如“不同年级学生的月均消费差异”“男生女生的消费时段偏好”“分商户类型的消费金额构成”。这个设计在答辩时也很加分,因为数据建模之前先做了数据组织,评委能看出你不是只会调库。
2.2 task1 两个脚本干了什么
preprocessing 这块由 task1_X1.py 和 task1_X2.py 两个脚本完成。task1_X1.py 做的是基础清洗:读取 CSV、去掉学号为空或金额为负的脏数据、统一消费时间字段格式。task1_X2.py 做的是衍生字段构造,把“消费时间”拆出“消费日期”和“消费时段”,时段按早餐、午餐、晚餐、夜宵分成四档,同时按学号做主键把两张表关联起来,输出 task1_X.csv 和 task1_1_X.csv。
这块的核心代码逻辑大致是:
import pandas as pd # 读取原始流水与档案表 df_flow = pd.read_csv('data1.csv', encoding='utf-8') df_info = pd.read_csv('data2.csv', encoding='utf-8') # 基础清洗:金额负值或学号缺失的直接剔除 df_flow = df_flow[(df_flow['amount'] > 0) & (df_flow['student_id'].notna())] # 时间字段统一解析,非法时间置为 NaT 后过滤 df_flow['consume_time'] = pd.to_datetime(df_flow['consume_time'], errors='coerce') df_flow = df_flow[df_flow['consume_time'].notna()] # 构建时段标签:6-9 早餐,11-14 午餐,17-20 晚餐,其余为夜宵 def time_period(hour): if 6 <= hour < 9: return 'breakfast' elif 11 <= hour < 14: return 'lunch' elif 17 <= hour < 20: return 'dinner' return 'snack' df_flow['period'] = df_flow['consume_time'].dt.hour.apply(time_period) df_flow['date'] = df_flow['consume_time'].dt.date这里把时段切分成四档,不只是为了画图好看,而是后续聚类建模时要用“各个时段的消费笔数占比”作为特征。如果预处理阶段不把这个字段造出来,到建模阶段再回头处理时间格式就会非常被动。我一般建议在 task1 阶段就把所有可能用到的衍生字段全部生成好,宁可先多生成几个用不到的,也不要做到 task3 再返工。
2.3 预处理结果文件怎么解读
清洗和关联后输出两份 CSV:task1_X.csv 通常是关联后的明细表,task1_1_X.csv 是以学号为粒度聚合的宽表,一行一个学生,列是消费总金额、消费总笔数、各时段笔数占比、常用商户类型等。这两份结果的价值在于:task1_1_X.csv 直接作为后续统计分析和聚类建模的输入,不需要 task2、task3 的脚本里反复重复清洗逻辑。
通常的做法是,在 task1 阶段就把“明细表 → 学生粒度宽表”这一步完成,后续脚本只负责读聚合结果。这种分层设计能有效避免几个脚本之间逻辑重复、口径不一致的问题。实际操作中,我见过不少人每个脚本里都写一遍清洗代码,结果同一个字段在不同脚本里清洗规则不一样,最后图和结论对不上,这个坑在答辩前特别容易翻车。
3. 统计分析模块:消费结构、时段偏好、消费水平差异怎么做
3.1 task2 各脚本与结果图的关系
task2 阶段负责任统计分析与可视化,对应 task2_X1.py 到 task2_X5.py 五个脚本和五张输出图。task2_X1.py 绘制消费金额分布直方图,看整体消费区间集中在哪;task2_X2.py 按消费时段统计笔数占比,画横向柱状图或饼图;task2_X3.py 做各院系或各年级的消费对比;task2_X4.py 做男女生消费差异分析;task2_X5.py 是商户类型消费金额结构图。每张图对应一个分析问题,答辩的时候每个问题都能独立讲出一段结论。
这段统计分析的思路,和直接用 pandas 输出几个 describe() 数字不一样的地方在于:图形结果自带解释链路。比如金额分布直方图能直接看出消费峰值出现在哪个区间,时段占比图能看出食堂消费高峰和刷卡数据中夜宵占比是否合理,这些都是从原始表格里一眼看不出来的。
3.2 消费金额分布与时段分析代码
以 task2_X1.py 为例,它的核心逻辑是读入 task1 产出的宽表或明细表,用 matplotlib 画金额分布:
import pandas as pd import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False df = pd.read_csv('task1_X.csv') plt.figure(figsize=(10, 5)) plt.hist(df['amount'], bins=30, color='#4C72B0', edgecolor='white') plt.xlabel('单笔消费金额(元)') plt.ylabel('频次') plt.title('学生单笔消费金额分布') plt.grid(axis='y', linestyle='--', alpha=0.6) plt.tight_layout() plt.savefig('task2_X1.png', dpi=150)这段代码值得注意的点有两个:一是 bins 设成 30,适合观察整体分布形态,如果设太细会看到一堆毛刺,设太粗又会把峰值细节抹掉;二是保存图片时用了 dpi=150,保证放进论文或答辩 PPT 里不会模糊。这个细节很多人会忽略,直接默认 dpi 存图,结果打印出来全是锯齿。
3.3 从统计图里能读出什么结论
消费金额分布图能支撑的结论一般是:单笔消费主要集中在 10—20 元区间,呈右偏分布,说明学生日常消费以食堂简餐为主,高额消费占比少,符合校园消费场景。时段分析图则能看出午餐和晚餐是消费高峰,早餐次之,夜宵占比最低,这个结构可以直接关联到“校园食堂营业时段与刷卡数据匹配度”的讨论。
这些结论看似简单,但拿高分的关键在于“图上有证据、文中有解释、结论有场景依据”。答辩时如果评委问“你怎么知道这个分布是合理的”,你可以回答:因为数据清洗后的有效样本量有多少、峰值区间和食堂实际餐品定价区间吻合,所以判断分析结果可信。这就比单纯说一句“学生消费集中在 10—20 元”要有说服力得多。
4. 消费行为建模:从 KMeans 聚类到用户分群画像
4.1 task3 四个脚本的建模矩阵
task3_X1.py 到 task3_X4.py 对应四组建模实验,输出 task3_X1.png 到 task3_X4.png 四张图。比较常见的编排是:task3_X1.py 做 KMeans 聚类,用肘部法则画簇内误差平方和随 k 值变化的曲线,确定最佳聚类数;task3_X2.py 用确定好的 k 做聚类,然后画出聚类结果的二维散点图或特征对比图;task3_X3.py 对每个聚类簇做特征画像,输出不同簇在各时段消费占比的雷达图或热力图;task3_X4.py 做结果解释,把每个簇命名成“早餐规律型”“晚餐主导型”这类业务标签。四张图连起来正好是一条完整的建模链路:选 k → 聚类 → 画簇特征 → 业务解释。
这个编排逻辑对应的是聚类分析的标准流程,先确认类别数再建模、再解释,而不是上来直接把数据扔进 KMeans 里瞎跑。建模前选特征也很关键,一般会从 task1 产出的宽表里拿“月均消费金额”“日均消费笔数”“早餐笔数占比”“晚餐笔数占比”“夜宵笔数占比”“常用商户类型”这几个字段做特征列。
4.2 KMeans 聚类的关键实现
task3_X1.py 的肘部法则代码大致是:
import pandas as pd from sklearn.cluster import KMeans import matplotlib.pyplot as plt df = pd.read_csv('task1_1_X.csv') features = ['monthly_amount', 'daily_count', 'breakfast_ratio', 'lunch_ratio', 'dinner_ratio', 'snack_ratio'] X = df[features].fillna(0) # 标准化,消除金额和占比之间的量纲差异 from sklearn.preprocessing import StandardScaler X_scaled = StandardScaler().fit_transform(X) # 肘部法则:k 从 2 到 8,记录每个 k 的簇内误差平方和 inertia = [] for k in range(2, 9): model = KMeans(n_clusters=k, random_state=42, n_init=10) model.fit(X_scaled) inertia.append(model.inertia_) plt.figure(figsize=(8, 5)) plt.plot(range(2, 9), inertia, marker='o') plt.xlabel('聚类数 k') plt.ylabel('簇内误差平方和 (inertia)') plt.title('肘部法则确定最佳聚类数') plt.savefig('task3_X1.png', dpi=150)这段代码里最容易被忽略的是 StandardScaler 标准化那一步。如果直接拿原始金额和占比去聚类,金额字段的数值范围会完全压过占比字段,聚类结果基本等于只看消费金额,看不出行为模式差异。标准化之后再聚类,每个特征才有同等的权重参与距离计算。n_init=10 是为了避免 KMeans 随机初始化导致结果不稳定,这个参数在答辩时也常被问到,要能说出它解决的是“多次运行时结果不一致”的问题。
4.3 聚类结果怎么解释成业务标签
确定 k 之后,task3_X2.py 和 task3_X3.py 做聚类与画像,最终每个簇会得到一条特征均值记录,比如第 0 簇的学生日均消费 3.2 笔、早餐占比 31%、午餐占比 38%、晚餐占比 27%,那这个簇就可以命名成“三餐规律型”;第 1 簇的学生晚餐和夜宵占比偏高、午餐比例低,可能是“晚睡型消费群体”。这类标签不是算法给的,而是分析者根据每个簇的特征均值命名出来的,答辩时这一步是展示业务理解力的关键。
我在做这类建模时通常会额外检查一件事:每个簇的样本量不要太小。如果某个簇只有几十人,在群体画像里几乎没有统计意义。遇到这种情况,通常的做法是把 k 值调小一档,或者把特征里的极端离群值提前剔除,而不是硬保留一个“少数派簇”。这一点在评审时也常被点出来,属于建模过程中的常见质量隐患。
5. 避坑与排查:学生消费分析里最容易翻车的五个细节
5.1 中文乱码:图表标题和 CSV 里的中文全部变成方块
现象:用 pandas 读 CSV 时打印出来全是乱码,matplotlib 生成的图里中文标题直接变成方框。
原因:CSV 文件本身的编码是 GBK 或 GB18030,而 pandas 的 read_csv 默认按 UTF-8 解析,解析失败就出现乱码;matplotlib 默认字体又不支持中文,标题自然显示为方框。
解决:读文件时显式指定编码,常见做法是 encoding='gbk' 或 encoding='gb18030',建议在脚本里留一个编码参数开关;画图前设置中文字体,上面代码里已经写了 plt.rcParams['font.sans-serif'] = ['SimHei'],这一步不要删。
5.2 时间解析报错:to_datetime 直接抛异常导致脚本中断
现象:跑 task1_X1.py 时 pd.to_datetime() 报错,显示某个时间字符串无法解析。
原因:原始数据里存在少量脏数据,比如月份写成 13、日期格式混用“2023/1/5”和“2023-01-05”、甚至是空字符串。直接用默认参数解析,遇到非法值就会中断。
解决:to_datetime 里加上 errors='coerce',解析不了的自动变成 NaT,然后在下一步统一过滤掉。上面代码里就是这个写法。这一条处理好了,能避免后面所有脚本因为时间字段报错。
5.3 聚类数拍脑袋定:k 值随便设成 4 或 5
现象:聚类结果画出来后,簇与簇之间特征重叠严重,业务标签命名牵强,甚至出现两个簇的特征均值几乎一样的情况。
原因:没有做肘部法则或轮廓系数分析,直接凭经验把 k 定成 4、5,实际数据里更合理的分群数可能是 3 或 6。
解决:先用 task3_X1.py 跑肘部法则,观察 inertia 下降趋势明显的拐点;如果拐点不清晰,再结合轮廓系数综合判断,最后把 k 取在“误差下降明显变缓且各簇样本量都足够”的位置。
5.4 金额异常值没清洗:有人一天刷几十次小额,有人一次性刷出大额
现象:统计结果里消费笔数和金额分布图出现长尾,聚类时离群学生单独成簇,影响分群质量。
原因:词典里存在代刷、超市批量购物、校园商户测试流水等非典型消费行为,这些样本混进分析集,会把均值拉偏。
解决:在 task1 阶段做分位数过滤,比如只保留金额在 1 到 99 百分位之间的记录,或者按学号聚合后剔除日均消费笔数超过正常范围的学生。过滤规则要在文档里写明,答辩时能明确说出“我剔除了哪些异常样本、依据是什么”。
5.5 图与结论对不上:代码能跑通但图和脚本对应混乱
现象:跑完所有脚本后,发现某张图和某个脚本的记录对不上,或者结果图被后续脚本覆盖,最终文档里引用错了图。
原因:脚本和输出文件的命名没有建立强绑定关系,task3_X1.py 可能同时输出多张图,或者用通用名 savefig('result.png'),前一个脚本的图被后一个覆盖。
解决:命名强制遵循“脚本名 + 输出内容”的对应方式,比如 task2_X1.py 只输出 task2_X1.png,图上标注参数和日期信息。每次跑完一套脚本后,把生成的 PNG 和 CSV 按时间戳归档,避免反复运行时结果互相覆盖。
6. 验收与答辩:从“代码能跑”到“结论能讲”的最后一公里
6.1 分模块验收清单
拿到这套项目后,不建议直接看答辩稿或结果图,而是逐模块重跑一遍,确认每条链路是通的。通常我会按以下顺序做验收:
1. 环境与依赖检查:确认 pandas、matplotlib、sklearn 已安装,Python 版本 3.8 以上 2. 数据读取验收:data1.csv 和 data2.csv 能正常读取,无乱码 3. 预处理结果对比:task1_X.csv 的行数比原始流水少,缺失值已处理 4. 统计图完整性:task2_X1 到 task2_X5 五张图全部生成,且分布形态合理 5. 聚类可复现性:task3_X1 到 task3_X4 连跑三次,聚类结果不发生明显变化 6. 文档一致性:每个图对应的结论在论文或答辩演示里有明确引用每项打勾后才能算真正完成验收。这里有个关键点,是把“能运行”和“能复现”分开看。代码能运行只能说明没有语法错误,聚类模型如果随机种子没固定,换个机器跑结果就变了,这在答辩现场非常难看。
6.2 答辩讲解的叙事顺序
答辩时不要按脚本编号讲,要按“业务问题 → 数据 → 方法 → 结论”的顺序讲。开场从“学生校园消费行为有什么可分析的”切入,指出一卡通数据里潜藏的信息价值;然后讲数据来源和字段设计,强调预处理解决了哪些脏数据问题;再讲统计图里的三个核心发现,比如消费金额集中区间、时段消费结构、院系间差异;最后落到聚类分群,把每个簇的消费画像标签展示出来,说明可以给校园运营提供什么参考。
如果评委问“为什么用 KMeans 而不用 DBSCAN”,可以说:消费行为数据经过标准化后各特征取值范围一致,类簇形状整体偏球形,KMeans 简单可解释;DBSCAN 对密度不均的数据更敏感,但在这种场景下超参数选择成本更高。这样回答既展示了思考过程,又不会被“你是不是只会调库”这类问题难住。
6.3 一个印象深刻的教训
有一回给一个模拟项目做验收,代码跑得很顺利,结果图也完整,但评审时对方问了一句“task1 里过滤了多少条异常数据、依据是什么”,当场答不上来。因为当初只写了过滤条件,没记录过滤前后的样本量变化,也没在文档里说明阈值是怎么定的。从那以后我每次做数据分析型项目,都会强制走一遍“数据清洗日志”:每条过滤规则必须记录删了多少行、占比多少、放在项目说明文档里附一张表。这个习惯看起来麻烦,但到了答辩和最终验收的时候,能省掉一大半解释不清的麻烦。希望这篇方案能帮你把每个环节都做到能跑、能讲、能证明。
本文还有配套的精品资源,点击获取