☰
Python校园一卡通消费行为分析实战:从数据清洗到聚类画像
2026/10/12 18:54:38 网站建设 项目流程

简介:本资源是一份面向计算机及相关专业本科生的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学号(脱敏处理)2021001234
trade_timedatetime消费时间2023-09-12 12:03:45
trade_amountfloat消费金额(元)8.50
trade_typestr消费类型餐饮、超市、水费、网费
merchantstr商户名称第一食堂、天猫超市
campus_areastr校区位置东区、西区

拿到数据之后的第一件事不是写代码,而是先做数据概览,用info()和describe()快速了解数据的规模、类型、缺失情况、分布范围。这一步能帮你判断数据质量,也能提前发现是否存在明显异常。

import pandas as pd # 读取原始数据 df = pd.read_csv('data/raw/campus_card_records.csv', encoding='utf-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(axis=1) 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(days=1) 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值变化的拐点,轮廓系数看样本与自身簇内相似度和最近簇相似度的差。两个指标结合,K=4或K=5都是可接受的范围,我最终选择了K=4,因为第四类的人群画像特征非常鲜明,而且各类别样本量不至于过小。

训练前必须做数据标准化,这一步非常关键。直接拿原始特征喂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_clusters=4, random_state=42, n_init=10) labels = kmeans.fit_predict(X_scaled) student_features['cluster'] = labels # 评估轮廓系数 score = silhouette_score(X_scaled, labels) print(f'Silhouette Score: {score:.4f}')

n_init参数我踩过坑。早期版本直接写默认值,结果每次运行聚类结果都不一样,后来查到Scikit-learn 1.2以上版本默认n_init=10,但旧版本默认是1,聚类结果对初始中心点很敏感,所以显式设置n_init=10保证重复实验的可复现性,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( index='hour', columns='weekday', values='trade_amount', aggfunc='mean' ) plt.figure(figsize=(12, 6)) sns.heatmap(pivot, cmap='YlOrRd', annot=False) plt.title('一周消费金额热力图') plt.xlabel('星期') plt.ylabel('小时') plt.savefig('output/figures/weekly_heatmap.png', dpi=300, bbox_inches='tight')

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_opts=opts.TitleOpts(title='商户消费Top10'), xaxis_opts=opts.AxisOpts(axislabel_opts=opts.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参数,二是文件本身编码乱了就先转码再读取。

我的经验是读取时统一用encoding='utf-8'尝试,报错就改用encoding='gbk',再不行就读取二进制用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', encoding=encoding)

这个坑看起来很小,但项目一开始如果没处理好,后面所有图表的中文标题、中文标签都会跟着出问题,延误很多时间。

5.2 Matplotlib绘图中文字体缺失

Matplotlib默认字体对中文支持很差,直接画图的话中文会变成一个个方框。这个问题在Windows上相对好解决,选个中文字体改rcParams就行;在Linux服务器上则需要安装中文字体包,比如fonts-wqy-zenhei或者Noto Sans CJK。

还有一个小细节是负号显示,设置了中文字体之后,负号可能会变成方框或乱码,需要同时设置axes.unicode_minus为False。这两行配置我放在所有绘图代码的开头,算是标准开头模板了。

5.3 聚类结果不稳定怎么处理

如果跑多次聚类得到分群结果差异很大,大概率是KMeans初始化敏感性导致的。解法前面说过:显式设置random_state和n_init=10。如果还不行,可以考虑用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%。这个比例值得每一个想做这个方向项目的同学参考。

最后再分享一个小技巧:做完分析之后,一定要把关键结论写成一个一页纸的业务摘要,不是技术报告,而是用大白话描述“学生群体有哪些消费特征、是否存在值得关注的群体、食堂管理有哪些优化方向”。我在课程答辩的时候,被问得最多的不是代码怎么写,而是“这个分析结论对实际校园管理有什么参考价值”。能回答好这个问题,比把代码写得天花乱坠更能拿高分。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询