Python校园一卡通消费行为分析实战:从数据清洗到聚类画像

发布时间:2026/9/1 0:33:38
Python校园一卡通消费行为分析实战:从数据清洗到聚类画像 简介本资源是一份面向计算机及相关专业本科生的Python课程设计与期末大作业实战项目聚焦高校学生校园消费行为的数据分析全流程实践。项目涵盖数据清洗、特征工程、消费聚类DFM模型应用、可视化呈现与行为洞察难度适中且经助教审定、导师指导认可评审得分98分适合课程作业提交与数据分析能力进阶训练。压缩包共8个文件含3个核心Python脚本model.py、analysis.py等实现建模与分析逻辑、1份详细Word文档说明含DFM模型原理与结果解读、1个原始数据集ZIP、1个依赖清单requirements.txt及README.md等辅助文件整体大小为10.07MB结构清晰、模块分工明确。目前已有164人学习下载所有代码均本地实测可运行附带完整结果集与注释开箱即用有效降低调试门槛助力快速完成高质量大作业交付。 校园一卡通里攒了那么多消费流水很多同学不知道还能拿来做点有意思的事。我最近刚完成了一个Python学生校园消费行为分析项目从原始刷卡数据到最终的聚类画像和可视化图表整套流程跑通之后成绩拿了优秀。这篇文章把我做这个项目的完整思路、关键代码、踩过的坑全部分享出来无论你是课程设计需要还是想锻炼数据分析实战能力都可以直接参考这条主线去复现和改进。1. 项目整体定位与设计思路1.1 这个项目到底解决什么问题校园消费数据分析本质上是一个典型的用户行为分析场景。每张一卡通的刷卡记录都包含了时间、地点、金额、消费类型这些关键字段这些数据叠加在一起就能回答很多实际业务问题学生平均每天在食堂花多少钱不同年级的月消费差异有多大哪些学生存在经济困难需要资助热门窗口的排队高峰集中在哪个时段我选这个题的初衷很直接数据干净、场景熟悉、算法链路完整。比起爬电商数据或者处理金融数据校园卡数据不需要各种反爬策略字段语义也足够清晰非常适合用来跑通“数据清洗→特征工程→建模分析→可视化→结论输出”的完整项目闭环。而且这个题目的延展性极好稍微改改字段名就能套用到超市会员分析、图书馆入馆分析、健身房客流分析等场景里。1.2 技术选型为什么是这套组合整个项目我用的核心工具是Python 3.8 Pandas NumPy Scikit-learn Matplotlib Seaborn可视化部分额外用到了PyECharts做交互式大屏。没有上Spark、Flink这类分布式框架原因很简单数据量在几十万到几百万条的级别Pandas的内存运算完全能hold住杀鸡不用牛刀。选择Pandas做数据清洗是因为它对表格数据的处理效率极高groupby、merge、pivot_table这些操作写起来非常顺手。Scikit-learn主要用来做KMeans聚类和数据标准化这属于机器学习里最基础但最实用的部分。可视化层面Matplotlib和Seaborn负责静态图表PyECharts负责生成HTML格式的交互图表可以直接在浏览器里查看。注意如果你的数据量超过500万条建议改用Polars或者Dask来替代Pandas否则内存占用会非常紧张。我在项目中期曾经因为数据膨胀导致Notebook内核崩溃过一次后面加了数据采样和字段裁剪才解决。1.3 项目目录结构与代码组织项目目录我按开发流程做了分层这样做的好处是后期维护和交报告时思路非常清晰campus_consumption_analysis/ ├── data/ │ ├── raw/ # 原始数据只读不改 │ ├── processed/ # 清洗后的中间数据 │ └── results/ # 最终输出结果 ├── src/ │ ├── data_loader.py # 数据加载与清洗函数 │ ├── feature_engineering.py # 特征工程模块 │ ├── modeling.py # 聚类与RFM模型 │ └── visualization.py # 图表生成模块 ├── notebooks/ │ └── analysis_flow.ipynb # 交互式分析主流程 └── output/ ├── figures/ # 静态图表输出 └── dashboard.html # 可视化大屏这种组织方式还有一个好处就是每个模块可以单独测试。比如数据清洗模块写完直接跑一遍确认输出没有脏数据再往下做特征工程不至于写完一大坨代码出bug时无从下手。2. 数据获取与预处理分析的地基2.1 原始数据结构与字段含义不同学校的校园卡系统导出的数据格式会有差异但核心字段基本一致。我这次用的数据集包含以下字段字段名类型说明示例student_idstr学号脱敏处理2021001234trade_timedatetime消费时间2023-09-12 12:03:45trade_amountfloat消费金额元8.50trade_typestr消费类型餐饮、超市、水费、网费merchantstr商户名称第一食堂、天猫超市campus_areastr校区位置东区、西区拿到数据之后的第一件事不是写代码而是先做数据概览用info()和describe()快速了解数据的规模、类型、缺失情况、分布范围。这一步能帮你判断数据质量也能提前发现是否存在明显异常。import pandas as pd # 读取原始数据 df pd.read_csv(data/raw/campus_card_records.csv, encodingutf-8) # 快速概览 print(df.shape) # 行数和列数 print(df.info()) # 字段类型和非空统计 print(df.describe()) # 数值字段的统计描述 print(df.isnull().sum()) # 缺失值统计2.2 清洗规则移除噪音和异常值数据清洗是整个项目中最花时间但又是最不出彩的环节。校园卡数据常见的问题有四种重复记录、缺失字段、异常金额、时间溢出。我建议的做法是制定一套明确的清洗规则而不是靠肉眼一个个查。重复记录方面我用trade_time、student_id、trade_amount三个字段联合去重保留第一条。这里有个细节要注意同一学生在同一秒在同一商户刷出相同金额大概率是系统重发或者重复计入但如果是分两次刷的两笔小额消费金额和时间戳都可能相同所以去重时要保留一个原始记录数做核对。异常值处理方面我设定了金额的合理范围0 trade_amount 200。低于等于0的交易记录是退款或系统异常大于200的消费在校园场景下比较罕见需要核对是否属于批量采购或设备押金等特殊交易。时间字段范围限定在学期内排除寒暑假和凌晨2点到5点这个几乎没人消费的时段用于统计睡眠时段消费行为时再单独分析不作为主分析的干扰项。缺失值处理策略如果student_id缺失直接删除该条记录因为无法关联到具体学生trade_amount缺失但其他字段完整可以用该学生的历史平均消费金额填充并打上标记timestamp缺失则用同商户相邻记录的平均时间插值填充这种情况极少见。提示清洗完数据后一定把每步删除了多少条记录、基于什么规则删除记录下来写进报告里。最后答辩或者提交课程报告的时候这部分能体现你对数据处理的理解深度这是拿高分的关键细节之一。2.3 特征工程从交易流水到学生行为画像原始数据是一行一行的交易流水一条记录只代表一次刷卡行为无法直接做聚类分析必须先从交易流水中聚合出每个学生的行为特征。这是整个项目最有技术含量的环节。我把特征分成四个维度消费水平、消费结构、消费时间规律、消费稳定性。消费水平维度每个学生按月计算总消费额、日均消费额、单笔平均消费额、月消费最大值和最小值。这些指标反映了学生的总体开销水平。消费结构维度按食堂、超市、水费、网费、医疗、其他这些类别统计每个学生的消费占比。这一步能区分出“食堂依赖型”和“多元消费型”学生。消费时间规律维度计算早餐时段、午餐时段、晚餐时段、夜宵时段的消费次数占比以及一周七天中每天的平均消费频次。这是判断学生作息规律性的重要依据。消费稳定性维度计算学生每周消费总额的标准差和变异系数。变异系数 标准差 / 平均值数值越大说明消费波动越大可能存在一定的经济压力或者消费习惯不稳定。特征构造的核心代码逻辑如下# 按学生聚合构造特征向量 student_features df.groupby(student_id).agg( total_amount(trade_amount, sum), avg_amount(trade_amount, mean), daily_avg(trade_amount, lambda x: x.sum() / x.index.date.nunique()), count_trans(trade_amount, count), max_amount(trade_amount, max), min_amount(trade_amount, min), std_amount(trade_amount, std), breakfast_cnt(trade_time, lambda x: sum(1 for t in x if 6 t.hour 10)), lunch_cnt(trade_time, lambda x: sum(1 for t in x if 10 t.hour 14)), dinner_cnt(trade_time, lambda x: sum(1 for t in x if 16 t.hour 20)), night_cnt(trade_time, lambda x: sum(1 for t in x if t.hour 20 or t.hour 6)) ).reset_index() # 构造衍生特征 student_features[avg_per_meal] student_features[[breakfast_cnt, lunch_cnt, dinner_cnt]].sum(axis1) student_features[night_ratio] student_features[night_cnt] / student_features[count_trans] student_features[cv] student_features[std_amount] / (student_features[avg_amount] 1e-6)2.4 数据集的划分与结果集说明我做分析时习惯把数据切成三段训练集、验证集和展示集。训练集用前两个完整学期的数据用来建模和确定聚类中心验证集用最近一个月的数据用来验证模型稳定性和特征有效性展示集随机抽取1000个学生的完整画像数据用于最终的可视化展示和结果输出。结果集方面我最终产出了三个层面的文件这也是项目标题里“结果集”的核心内容学生消费行为画像表每个学生一行包含所有特征字段和聚类标签CSV格式方便后续检查聚类结果汇总表包含每类的样本数、各特征均值、典型特征描述可视化图表集包括静态PNG图表和动态HTML大屏。3. 核心分析方法与建模过程3.1 消费结构分析谁在花、花在哪消费结构分析是整个项目里最能出内容的部分它可以拆出好几个维度的结论。首先是整体消费构成按交易类型汇总金额占比画一个饼图能看出学生的钱主要流向哪里。其次是食堂消费占比分布用直方图看学生群体在第一食堂、清真食堂、风味餐厅之间的选择偏好。这段时间维度同样值得深入比如按月统计人均消费趋势能看出开学季、考试周、月末这几个时间点的消费变化特点。我在分析中发现月初消费金额明显偏高月末最后一周日均消费下降约12%说明很多学生存在月初宽裕月末紧张的情况。从商户维度做Top10排行榜也是很直观的分析哪个食堂窗口最受欢迎、哪个超市销量最大这些结论虽然简单但对业务方比如后勤管理部门是有实际参考价值的。3.2 RFM模型在校园场景的适配改造RFM模型源于用户价值分析原始定义是Recency最近一次消费时间、Frequency消费频率、Monetary消费金额。直接套用到校园消费场景并不完全合适因为学生群体的消费行为和企业客户有本质差异所以我做了一些场景化改造。Recency改成最近一次消费距今的天数这个指标能反映学生的活跃度。Frequency改成平均每周消费次数而不是总消费次数这样可以消除学期长度不一致带来的影响。Monetary保持不变用月均消费金额代表消费能力。RFM的三个维度分别打分R值用三分位数切成1-3分F值和M值同样切分最后组合成RFM得分向量。这个得分可以不做聚类直接用规则分群比如“高活跃高消费”属于优质核心用户群“低活跃高消费”属于价格不敏感但黏性不足的群体。from datetime import datetime # 计算每个学生的RFM指标 ref_date df[trade_time].max() pd.Timedelta(days1) rfm df.groupby(student_id).agg( recency(trade_time, lambda x: (ref_date - x.max()).days), frequency(trade_time, count), monetary(trade_amount, sum) ).reset_index() # 对每个维度进行评分 rfm[R_score] pd.qcut(rfm[recency], 3, labels[3, 2, 1]) rfm[F_score] pd.qcut(rfm[frequency], 3, labels[1, 2, 3]) rfm[M_score] pd.qcut(rfm[monetary], 3, labels[1, 2, 3]) rfm[RFM_score] rfm[R_score].astype(int) * 100 \ rfm[F_score].astype(int) * 10 \ rfm[M_score].astype(int)3.3 KMeans聚类的参数确定与模型训练RFM模型能给出一个经验驱动的分群结果但想要数据自动发现行为模式还是需要聚类算法。我选的是KMeans理由很直接算法简单、解释性好、在低维特征下效果稳定。特征选择上我从构造的十几个特征里挑了六个进入聚类模型日均消费额、每周消费次数、食堂消费占比、夜宵消费占比、月消费变异系数、最近一次消费距今的天数。选这些特征的原因是它们分别从金额、频次、结构、规律性、活跃度五个维度描述了消费行为相关性适中没有明显的共线性问题。K值的确定我没拍脑袋而是用肘部法则加轮廓系数双重验证。肘部法则看SSE簇内误差平方和随K值变化的拐点轮廓系数看样本与自身簇内相似度和最近簇相似度的差。两个指标结合K4或K5都是可接受的范围我最终选择了K4因为第四类的人群画像特征非常鲜明而且各类别样本量不至于过小。训练前必须做数据标准化这一步非常关键。直接拿原始特征喂KMeans日均消费额这个量纲大的特征会直接主导距离计算其他维度全被淹没。我用StandardScaler做Z-score标准化把每个特征都拉到均值0、标准差1的尺度上。from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score # 选择特征列 features [daily_avg, weekly_cnt, canteen_ratio, night_ratio, cv, recency] X student_features[features].fillna(0) # 标准化 scaler StandardScaler() X_scaled scaler.fit_transform(X) # 训练KMeans kmeans KMeans(n_clusters4, random_state42, n_init10) labels kmeans.fit_predict(X_scaled) student_features[cluster] labels # 评估轮廓系数 score silhouette_score(X_scaled, labels) print(fSilhouette Score: {score:.4f})n_init参数我踩过坑。早期版本直接写默认值结果每次运行聚类结果都不一样后来查到Scikit-learn 1.2以上版本默认n_init10但旧版本默认是1聚类结果对初始中心点很敏感所以显式设置n_init10保证重复实验的可复现性random_state也固定下来。3.4 聚类结果的业务解读与人群画像拿到聚类标签只完成了技术环节的一半真正的价值在于把每一类人用业务语言描述出来。我统计了每一类在各特征维度上的均值然后对比总体均值归纳出每类的典型画像。第一类是“规律节俭型”日均消费低、食堂占比高、消费波动小、几乎不熬夜消费。这类学生占样本的30%左右作息规律消费习惯健康是典型的校园生活状态。第二类是“活跃社交型”日均消费中上、夜宵占比高、消费频次高、消费波动较大。他们经常在超市、奶茶店、风味餐厅消费社交活动丰富月末资金紧张的概率也偏高。第三类是“高消费型”所有金额指标都显著高于平均消费结构多元。这类学生经济条件较好每月消费总额可能是平均水平的两倍以上但消费习惯未必健康。第四类是“潜在关注型”日均消费极低、消费频次少、食堂占比高但总金额低、变异系数大。这类学生可能需要学校助学体系关注从数据分析的角度看是值得进一步核实的群体。注意聚类结果只能提示“可能存在需要关注的群体”并不能直接下结论说某个学生经济困难。做这类项目时数据分析师要克制住窥探和评判的倾向结论的表述要严谨措辞避免给人贴标签。4. 可视化展示与结果输出4.1 静态图表的绘制技巧Matplotlib和Seaborn负责输出论文和报告里能直接使用的静态图表。我这边的绘图原则是信息密度适中、配色统一、每个图只回答一个核心问题、导出300dpi以上的PNG格式。比较实用的是以下几类图消费趋势折线图按日或按周聚合人均消费金额用Seaborn的lineplot画加置信区间带消费结构堆叠柱状图按月份看不同消费类型的占比变化用stacked bar展示消费时段热力图横轴是星期纵轴是小时颜色深浅代表消费笔数能直观看到“周一中午12点食堂人数爆表”这类高峰聚类散点图用PCA把六维特征降到二维用不同颜色标记不同聚类直观看到群体分布。热力图是视觉冲击力最强的一个图我强烈推荐优先画。它能把一天24小时x一周7天的消费规律压缩在一张图里信息量很大的同时一眼就能看懂。import matplotlib.pyplot as plt import seaborn as sns plt.rcParams[font.sans-serif] [SimHei] # 中文显示 plt.rcParams[axes.unicode_minus] False # 负号显示 # 消费时段热力图 pivot df.pivot_table( indexhour, columnsweekday, valuestrade_amount, aggfuncmean ) plt.figure(figsize(12, 6)) sns.heatmap(pivot, cmapYlOrRd, annotFalse) plt.title(一周消费金额热力图) plt.xlabel(星期) plt.ylabel(小时) plt.savefig(output/figures/weekly_heatmap.png, dpi300, bbox_inchestight)4.2 交互式可视化大屏用PyECharts打造静态图表适合放在报告里但交互式大屏的展示效果在答辩现场会给人留下更深的印象。我用了PyECharts做了一个简易版大屏包含五个核心图表总消费趋势折线图、消费结构饼图、聚类人群散点图、时间热力图、学校商户Top10柱状图。PyECharts的用法和Matplotlib完全不同它是链式调用配置项核心逻辑是初始化一个图表对象然后通过set_global_opts设置全局配置再通过add系列方法传入数据。from pyecharts.charts import Bar, Pie, Line from pyecharts import options as opts # 商户消费Top10柱状图 bar ( Bar() .add_xaxis(merchant_top10[merchant].tolist()) .add_yaxis(消费金额元, merchant_top10[amount].round(2).tolist()) .set_global_opts( title_optsopts.TitleOpts(title商户消费Top10), xaxis_optsopts.AxisOpts(axislabel_optsopts.LabelOpts(rotate-30)) ) ) bar.render(output/dashboard/merchant_top10.html)生成HTML之后直接在浏览器打开就能看所有图表都支持鼠标悬停显示数值、缩放、拖拽交互体验比静态图好很多。如果有更高要求可以用Page类的DraggablePageLayout把所有图表放在同一个页面拖拽调整位置拼成真正的数据大屏。4.3 结果集的组织与交付项目标题里提到“结果集”我理解不只是一堆图表文件而是包含可复现、可核验、可拓展的全套分析产物。我在交付文件夹里放了五类东西清洗后的数据文件、特征工程后的画像表、聚类结果汇总表、所有图表的PNG和HTML版本、一个说明文档README.md。README里写清楚了数据来源格式、清洗规则、运行环境、复现步骤。这样任何一个人拿到项目后照着README的步骤运行main.py或analysis_flow.ipynb就能重新得到一模一样的图表和结论。这个细节在课程设计提交或者项目评审的时候非常加分体现的是工程化思维。5. 常见问题与排查技巧5.1 中文编码问题的完整处理链路pandas读取CSV或Excel时中文乱码是最常见的问题尤其是Windows下生成的CSV默认用GBK编码而Pandas默认用UTF-8解析读进来全是乱码。解决方案有两种一是read_csv时指定encoding参数二是文件本身编码乱了就先转码再读取。我的经验是读取时统一用encodingutf-8尝试报错就改用encodinggbk再不行就读取二进制用chardet自动检测编码。更保险的做法是拿excel格式的文件直接读pandas的read_excel背后是openpyxl或xlrd引擎对编码的处理比CSV更稳。import chardet # 自动检测编码 with open(data/raw/campus_card_records.csv, rb) as f: result chardet.detect(f.read(10000)) encoding result[encoding] df pd.read_csv(data/raw/campus_card_records.csv, encodingencoding)这个坑看起来很小但项目一开始如果没处理好后面所有图表的中文标题、中文标签都会跟着出问题延误很多时间。5.2 Matplotlib绘图中文字体缺失Matplotlib默认字体对中文支持很差直接画图的话中文会变成一个个方框。这个问题在Windows上相对好解决选个中文字体改rcParams就行在Linux服务器上则需要安装中文字体包比如fonts-wqy-zenhei或者Noto Sans CJK。还有一个小细节是负号显示设置了中文字体之后负号可能会变成方框或乱码需要同时设置axes.unicode_minus为False。这两行配置我放在所有绘图代码的开头算是标准开头模板了。5.3 聚类结果不稳定怎么处理如果跑多次聚类得到分群结果差异很大大概率是KMeans初始化敏感性导致的。解法前面说过显式设置random_state和n_init10。如果还不行可以考虑用MiniBatchKMeans或KMeans初始化方法后者能有效缓解这个问题。判断聚类稳定性还有一个办法比较不同random_state下得到的聚类标签的兰德指数或互信息如果多次运行的标签一致性低于90%说明聚类结构本身就不清晰可能需要重新审视特征选择或者K值设置。5.4 数据量过大导致内存不足如果你的数据做了全量导入内存又不够有几个实用策略分批读取用pandas的chunksize参数分批读入处理完再合并只保留需要分析的字段删除无关列对超大文件改用Parquet列式存储格式读入速度和压缩率都远比CSV好。我实际遇到过的一个场景是数据有800万行读CSV就花了5分钟内存占用到10GB后续操作经常卡顿。后来改用Parquet格式存储清洗后的数据读取时间压缩到20秒以内内存占用减少了约60%性价比极高。6. 项目扩展方向与进一步提升思路这个项目做完之后后续可以做的延伸方向非常多我这里列几个我研究过的思路供参考也是在课程设计答辩时可以主动展示的“技术视野”。第一个方向是引入时间序列模型做消费预测。基于每个学生过去几个月的消费记录用Prophet或者LSTM预测未来一个周期的消费金额这样可以实现异常消费的提前预警比如预测值明显低于历史正常水平系统自动生成提醒。这个方向需要更长的历史数据支撑代码复杂度也会上一个台阶。第二个方向是构建消费异常检测系统。用Isolation Forest、局部异常因子等方法识别消费行为偏离正常模式的学生可能存在的风险包括大额预充值后退款异常、校园卡的异地消费嫌疑——即同一张卡在极短时间内出现在相距较远的两个商户。这种分析方法在反欺诈领域更常用但校园场景也有真实需求。第三个方向是搭建实时看板服务。用Streamlit或Dash把整套分析流程封装成Web应用非技术人员也可以通过页面自助选择时段、校区、学院等维度动态查看消费统计结果。我在项目后半段用Streamlit做了一个简化版本效果非常好整个应用跑起来只要一行命令老师直接在浏览器里点点点就能看到全部图表。7. 收尾的一些体会我做完这整个项目最大的感受是数据分析项目里最耗时间的不是建模而是前期那些看起来琐碎不起眼的环节数据清洗、编码转换、特征构造、参数调试、结果校验。很多同学在写项目报告的时候喜欢把建模部分夸大但实际上真正决定项目质量的是前面对业务的理解和对数据的敬畏程度。从拿到原始流水数据到最终产出完整结果集我的粗粒度统计是数据清洗和质量校验占了约40%的时间特征工程占了30%真正的建模和可视化只占30%。这个比例值得每一个想做这个方向项目的同学参考。最后再分享一个小技巧做完分析之后一定要把关键结论写成一个一页纸的业务摘要不是技术报告而是用大白话描述“学生群体有哪些消费特征、是否存在值得关注的群体、食堂管理有哪些优化方向”。我在课程答辩的时候被问得最多的不是代码怎么写而是“这个分析结论对实际校园管理有什么参考价值”。能回答好这个问题比把代码写得天花乱坠更能拿高分。本文还有配套的精品资源点击获取

相关新闻