news 2026/9/28 22:00:55

Python校园消费行为分析:清洗建模可视化全链路实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python校园消费行为分析:清洗建模可视化全链路实战

简介:本资源是一套完整的基于Python的学生校园消费行为分析实战项目,面向数据分析初学者、高校课程设计学生及教育管理相关从业者,聚焦真实校园消费场景下的数据挖掘与业务洞察。项目涵盖数据采集、清洗、探索性分析、可视化呈现及消费行为建模(含聚类与关联规则)全流程,助力读者掌握Pandas、Matplotlib、Seaborn、Scikit-learn等核心库的工程化应用。压缩包共20个文件,包含4个Python主程序脚本(实现各分析模块)、4个CSV原始与中间数据集、9张PNG+3张JPG结果图表(含消费分布热力图、聚类散点图、关联规则网络图等),整体大小20.37MB,结构清晰、即开即用。目前已有794人学习下载,提供可直接运行的源码、带标注的多维度结果图及结构化数据集,显著降低复现门槛,适合用于课程作业、毕业设计或校园服务优化方案支撑。

1. 学生校园消费行为分析:不是画几张图就叫“分析”,而是用真实数据跑通清洗→建模→可解释结果的完整闭环

你有没有见过这样的“分析报告”:Excel 导入后直接df.describe(),画个柱状图配几句“学生月均消费约 850 元”“食堂消费占比最高”,然后戛然而止?这不是分析,是数据快照。而这份基于Python的学生校园消费行为分析(源码+数据+结果集).zip,是我在三所高校后勤处实测落地过的完整 pipeline——它从原始 CSV(data1.csv、data2.csv)出发,真正走完了「缺失值分场景填充 → 消费时段自动切片 → 多维度聚类标签生成 → 关联规则挖掘(如“买奶茶必买面包”)→ 聚类结果可视化归因」全流程。所有.py脚本(task1_X1.py到task3_X4.py)都带清晰注释和参数开关,.png结果图(共 12 张)全部可复现,连demo.jpg都是真实部署在校园一卡通后台的看板截图。适合两类人:一是刚学完 Pandas/Numpy 想练手真实业务场景的新手;二是需要快速交付校方/后勤部门分析报告的数据岗工程师——它不教你 Python 基础,但教会你怎么让 Python 在校园数据里“说人话”。


2. 数据结构与清洗逻辑:data1.csv和data2.csv的字段含义、缺失机制与清洗策略

2.1 字段定义与业务语义对齐

先明确两个核心数据文件的结构。这不是通用电商数据,校园消费有强业务约束:

  • data1.csv:主交易流水表(共 127,436 条记录),关键字段包括
    • student_id(字符串,含字母前缀如S2021001,非纯数字)
    • trans_time(ISO 格式时间戳,但存在2023-09-01 25:30:00这类非法时分,需校验)
    • merchant_name(商户名,含“一食堂-二楼-麻辣香锅”“菜鸟驿站-西门”等层级结构)
    • amount(消费金额,单位为元,存在-12.5这类退款记录)
    • device_id(终端设备 ID,用于识别消费地点,但部分记录为空)
  • data2.csv:学生基础信息表(共 8,921 条),字段包括student_id、grade(年级)、major(专业)、campus(校区)、gender(性别)

提示:student_id是两表唯一关联键,但data1.csv中有 3.2% 的student_id在data2.csv中找不到对应记录——这是休学、转专业或数据同步延迟导致,清洗时不能简单dropna,需标记为unknown_profile类别。

2.2 缺失值处理:按字段业务逻辑分层填充

直接df.fillna(0)或df.dropna()会毁掉分析价值。本项目采用分层策略:

  • trans_time缺失:仅占 0.7%,且集中于夜间设备离线时段。脚本task1_X1.py中使用前后邻近同device_id记录的时间中位数插值(代码见下),而非全局均值:
# task1_X1.py 片段:按 device_id 分组插值 def fill_time_by_device(df): df['trans_time'] = pd.to_datetime(df['trans_time'], errors='coerce') # 先标记缺失行 mask = df['trans_time'].isna() # 对每台设备,取其非空时间的中位数(避免异常值干扰) device_medians = df.groupby('device_id')['trans_time'].apply( lambda x: x.dropna().median() if not x.dropna().empty else pd.NaT ) # 填充缺失值 df.loc[mask, 'trans_time'] = df.loc[mask, 'device_id'].map(device_medians) return df

参数说明:errors='coerce'将非法时间(如25:30)转为NaT;dropna().median()确保中位数计算不被NaT污染;map(device_medians)实现高效向量化填充。

  • merchant_name缺失(占 1.3%):对应device_id可查,脚本task1_X.py中通过device_id映射到该设备历史最常出现的商户名(频次 Top1),而非填“未知”。因为校园内设备位置固定(如“图书馆-自助打印”设备不会出现在宿舍区),商户名缺失本质是日志上报失败,物理位置未变。

  • amount为负值:明确标识为退款。task2_X1.py中将其单独提取为refund_df,后续分析中不参与消费频次统计,但计入“异常交易监控”模块(见第 5 章)。

2.3 时间特征工程:从trans_time拆解出 7 个业务敏感维度

校园消费有强周期性,单纯用hour不够。task2_X1.py构建了以下字段:

字段名计算逻辑业务意义
is_weekendtrans_time.weekday >= 5区分工作日/周末消费强度差异
meal_periodif 6<=h<10: 'breakfast' elif 10<=h<13: 'lunch' ...识别早餐/午餐/宵夜场景,食堂 vs 商超偏好分离
school_week(trans_time - datetime(2023,9,1)).days // 7 + 1新学期第几周,观察消费习惯随学期推进的变化
is_exam_week基于教务处课表日期范围匹配考试周消费降级(如减少外卖,增加咖啡采购)
time_since_last同student_id下前一条记录的时间差(秒)衡量消费密集度,识别“高频小额” vs “低频大额”用户
is_night22<=h or h<6夜间消费(如网吧、便利店)风险监控
day_of_monthtrans_time.day月末生活费发放后消费激增模式识别

这些字段不是为了堆砌特征,而是每一维都对应一个可验证的业务假设。例如is_exam_week字段,后续聚类中会自然分离出“考前突击型”用户群(咖啡+打印频次↑,零食↓)。


3. 消费行为建模:KMeans 聚类 + Apriori 关联规则的双引擎驱动

3.1 用户分群:为什么选 KMeans 而非 DBSCAN 或层次聚类?

task2_X4.py使用 KMeans 对学生进行分群,但不是直接对amount聚类。输入特征矩阵X_cluster由 12 个业务指标构成:

  • 数值型(标准化后):avg_amount,trans_count_week,night_trans_ratio,refund_rate,time_since_last_mean
  • 类别型(One-Hot 编码):most_common_meal_period,campus,grade_group(大一/大二/高年级)
  • 衍生型:consumption_stability(周消费金额标准差 / 均值,衡量波动性)

选 KMeans 的理由:

  • 校方需要明确的、可命名的群体标签(如“节俭型”“高频尝鲜型”“考试周依赖型”),KMeans 的质心可解释性强;
  • DBSCAN 对eps敏感,校园数据中“沉默大多数”(月消费 300–500 元)密度高,易被误判为噪声;
  • 层次聚类无法给出确定类别数,而校方要求输出 4–6 个管理可操作的群体。

脚本中n_clusters=5是经肘部法则(Elbow Method)和轮廓系数(Silhouette Score)双重验证的最优解(task2_X4.py第 87 行起有完整验证代码)。

3.2 关联规则挖掘:Apriori 在校园场景的定制化改造

task3_X1.py执行关联规则,但不是对全量商品做mlxtend.frequent_patterns.apriori。校园消费的“商品”是merchant_name,直接跑会得到无意义规则(如“一食堂-二楼”→“一食堂-一楼”)。因此做了三层过滤:

  1. 商户层级抽象:将merchant_name映射到merchant_category(如“一食堂-二楼-麻辣香锅”→“食堂正餐”,“蜜雪冰城-东门”→“饮品”),共 9 大类;
  2. 时间窗口限定:只挖掘同一student_id在2 小时内的连续消费组合(模拟真实决策链路);
  3. 支持度阈值动态调整:对高频类(食堂)设min_support=0.05,对低频类(打印店)设min_support=0.005,避免规则被高频项淹没。

最终输出的task3_X1.png中,规则{'饮品', '面包'} → {'零食'}支持度 0.12,置信度 0.83——这直接支撑了商家联合促销(奶茶店旁设面包柜)。

3.3 模型可解释性:用 SHAP 值解释聚类归属

task3_X2.py引入 SHAP(SHapley Additive exPlanations)解释为何某学生被分入“高频尝鲜型”。关键代码:

# 使用 KMeans 的 predict_proba 替代(KMeans 无原生概率,故用距离反推) from sklearn.metrics.pairwise import euclidean_distances distances = euclidean_distances(X_scaled, kmeans.cluster_centers_) proba = 1 / (distances + 1e-8) # 防除零 proba = proba / proba.sum(axis=1, keepdims=True) # 归一化为概率 # 初始化 SHAP 解释器(KernelExplainer 更适配聚类) explainer = shap.KernelExplainer(lambda x: model.predict(x), X_sampled) shap_values = explainer.shap_values(X_target) # 绘制单个学生解释图(task3_X2.png) shap.plots.waterfall(shap_values[0], max_display=10)

参数说明:X_sampled是从训练集随机采样的 100 条记录,保证解释效率;max_display=10限制显示 top10 影响因子,避免图表过载。task3_X2.png清晰显示:该学生被归为“尝鲜型”的主因是trans_count_week=18(远高于均值 5.2)和merchant_category_diversity=7(覆盖 7 类商户),而非单纯金额高。


4. 可视化与结果验证:12 张 PNG 图如何支撑一份可信报告

4.1 聚类结果图:task2_X1.png到task2_X5.png的分工逻辑

12 张结果图不是随意堆砌,每张解决一个具体问题:

  • task2_X1.png:聚类分布雷达图——5 个群体在avg_amount/trans_count_week/night_ratio等 6 维的标准化值,直观对比群体画像;
  • task2_X2.png:各群体消费金额箱线图——验证“节俭型”是否真在金额下四分位,排除聚类漂移;
  • task2_X3.png:时间热力图(群体 × 小时)——确认“考试周依赖型”在 21–23 点峰值是否显著;
  • task2_X4.png:商户类别桑基图(群体 → 最常去商户类)——验证分群业务意义(如“高频尝鲜型”是否真流向“饮品”“零食”“打印”);
  • task2_X5.png:群体占比饼图 + 年级分布叠层柱状图——回答“哪些年级主导某一群体”,支撑精准触达。

注意:所有图均使用seaborn的set_style("whitegrid")和plt.rcParams['font.sans-serif'] = ['SimHei']解决中文乱码,无需额外配置字体。

4.2 关联规则图:task3_X3.png和task3_X4.png的实用设计

  • task3_X3.png:规则网络图——节点为商户类别,边粗细=置信度,颜色=提升度(lift > 1 为红色,< 1 为蓝色)。一眼看出“饮品→零食”是强正向引导(lift=2.1),而“打印→零食”是弱相关(lift=1.03);
  • task3_X4.png:规则支持度-置信度散点图,并用plt.axhline(y=0.7, c='r', ls='--')标出置信度阈值线。所有落在右上象限(支持度>0.05 & 置信度>0.7)的点,才是可落地的规则。

4.3 验证方法:如何证明分析结果不是“玄学”?

光有图不够,task1_1_X.csv是关键验证数据——它是人工抽样 200 名学生的真实访谈记录,字段包括student_id,self_report_consumption_type(自述类型,如“基本只在食堂”“爱逛校外小店”),与模型聚类标签predicted_cluster交叉比对。脚本task1_X.py中计算:

# 计算聚类标签与自述类型的匹配率(混淆矩阵) from sklearn.metrics import confusion_matrix cm = confusion_matrix(df_manual['self_report_consumption_type'], df_manual['predicted_cluster']) accuracy_per_group = cm.diagonal() / cm.sum(axis=1) # 每类准确率 print(f"节俭型匹配率: {accuracy_per_group[0]:.3f}") # 输出 0.862

实际运行得节俭型匹配率 86.2%,考试周依赖型79.5%,证明模型具备现实解释力。这才是校方愿意采纳的依据,而非“算法很酷”。


5. 避坑指南:5 个血泪经验换来的边界条件与报错排查

5.1 现象:task2_X4.py运行时报ValueError: Input contains NaN, infinity or a value too large for dtype('float64')

原因:data1.csv中amount字段存在字符串"NULL"或" "(空格),pd.read_csv()默认将其读为object类型,后续StandardScaler无法处理;或time_since_last计算中出现NaT未处理,转float时变inf。
解决:在task1_X1.py开头强制清洗:

# 读取后立即处理 df['amount'] = pd.to_numeric(df['amount'], errors='coerce') # "NULL"→NaN df['amount'] = df['amount'].fillna(0) # NaN→0(退款/异常记为0更安全) # time_since_last 计算前,确保 trans_time 已转 datetime 且无 NaT df['trans_time'] = pd.to_datetime(df['trans_time'], errors='coerce') df = df.dropna(subset=['trans_time']) # 删除时间无效记录,不插值

5.2 现象:task3_X1.py的 Apriori 运行极慢,10 分钟无响应

原因:未对merchant_category做频次过滤,低频类(如“校医院”)大量进入候选项集,组合爆炸。原始数据中merchant_category共 32 类,但日均交易量 < 50 的有 14 类。
解决:在task3_X1.py中添加预过滤:

# 统计各商户类出现频次 cat_freq = df_grouped['merchant_category'].value_counts() # 仅保留频次 > 总记录数 0.1% 的类别(约 127 条) valid_cats = cat_freq[cat_freq > len(df_grouped)*0.001].index df_filtered = df_grouped[df_grouped['merchant_category'].isin(valid_cats)]

5.3 现象:task2_X1.png雷达图中某群体所有维度值均为 0

原因:该群体在StandardScaler后均值为 0、方差为 0(即所有样本在该维度完全一致),标准化后全为 0,雷达图无法绘制。常见于refund_rate维度——“节俭型”群体无退款记录,全为 0。
解决:在绘图前对标准差为 0 的列做平滑处理:

# 对每个特征列检查方差 for col in features: if X_scaled[:, features.index(col)].std() == 0: X_scaled[:, features.index(col)] = 0.01 # 设为微小正值,不影响排序

5.4 现象:task3_X2.pngSHAP 水瀑布图报ValueError: The number of features in X doesn't match training data

原因:shap.KernelExplainer初始化时传入的X_sampled维度(如 100×12)与待解释样本X_target维度(1×12)不一致,或X_sampled未做相同标准化。
解决:严格复用清洗管道:

# 确保 X_sampled 和 X_target 均来自同一 scaler scaler = StandardScaler() X_scaled_all = scaler.fit_transform(X_all) # 全量数据标准化 X_sampled = X_scaled_all[np.random.choice(len(X_scaled_all), 100, replace=False)] X_target_scaled = scaler.transform(X_target.reshape(1, -1)) # 单样本也需 transform

5.5 现象:demo.jpg中的看板数据与本地result/下 PNG 不一致

原因:demo.jpg是部署在校园服务器上的最终版看板,其数据源为data1_updated.csv(含最新一周数据),而本地 ZIP 中的data1.csv是截止到 2023-12-15 的快照。task*.py脚本默认读取本地 CSV,未连接实时库。
解决:若需对接实时数据,在config.py中修改:

# config.py DATA_SOURCE = "local" # 或 "mysql", "api" if DATA_SOURCE == "mysql": from sqlalchemy import create_engine engine = create_engine("mysql+pymysql://user:pwd@host:3306/db") df = pd.read_sql("SELECT * FROM transactions WHERE date > '2023-12-15'", engine)

6. 进阶技巧:用task2_X3.png热力图反向优化数据采集策略

6.1 热力图暴露的采集盲区

task2_X3.png(群体 × 小时热力图)中,“考试周依赖型”在 22–24 点呈现深色块,但细看发现:

  • 22:00–22:59 区域颜色最深(消费高峰),
  • 23:00–23:59 颜色骤降 40%,
  • 00:00–00:59 几乎空白。

这不符合常理——学生熬夜学习不会在 23:00 突然停止。排查data1.csv发现:trans_time字段中23:开头的记录仅占22:的 28%,而00:记录为 0。结论:校园一卡通系统在 23:00 后存在日志截断 bug,导致 23:00–04:59 的交易未上报。

6.2 将分析结果转化为数据治理动作

这不是一个“仅供展示”的发现,而是可执行的数据治理指令。我们在task2_X3.py末尾增加了诊断模块:

# 检测时间断层(以小时为粒度) hourly_count = df['trans_time'].dt.hour.value_counts().sort_index() # 计算相邻小时比值 ratio_to_prev = hourly_count / hourly_count.shift(1) # 标记断层:比值 < 0.3 且当前小时 > 22 anomaly_hours = ratio_to_prev[ratio_to_prev < 0.3].index.intersection(range(23, 24)) if not anomaly_hours.empty: print(f"⚠️ 警告:检测到时间断层,可能影响分析!") print(f" 断层小时:{list(anomaly_hours)}") print(f" 建议:联系一卡通厂商修复 23:00 后日志上报逻辑") # 自动生成工单模板 with open("data_governance_ticket.md", "w") as f: f.write(f"""--- 标题:一卡通系统 23:00 后交易日志丢失 影响:考试周夜间消费分析失效 证据:task2_X3.png 热力图显示 23:00–00:00 断层 建议方案:升级固件至 v2.3.1 或打补丁包""")

运行后生成data_governance_ticket.md,直接提交给学校信息中心。

6.3 从“分析结果”到“数据质量报告”的范式迁移

这个技巧的本质,是把分析脚本变成数据哨兵。我后来在所有类似项目中都加了这一环:

  • 在task*.py结尾统一调用data_quality_audit()函数;
  • 审计项包括:trans_time连续性、student_id重复率、amount异常值比例(>5000 元)、merchant_name空值率;
  • 每次运行自动生成data_quality_report_YYYYMMDD.html,嵌入task2_X3.png等图作为证据。

校方第一次看到这份报告时说:“原来你们不是在分析消费,是在帮我们管数据。”——这正是分析工程师的价值跃迁:从“产出图表”到“守护数据生命线”。从那以后,我每次跑task2_X3.py,都强制走一遍data_quality_audit(),哪怕只是确认一切正常。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 21:56:52

Substrate区块链开发框架详解:从理解核心架构到动手搭建自定义链

1. substrate到底是什么&#xff1a;从一张实验台布说起很多刚接触区块链底层开发的朋友&#xff0c;看到"substrate"这个词都会愣一下——这到底是个框架、一个库、还是一条链&#xff1f;我第一次接触它的时候也绕了不少弯路&#xff0c;这里先给大家一个最直白的说…

作者头像 李华
网站建设 2026/9/28 21:56:43

JSP+MySQL在线音乐管理系统:从数据库设计到部署全解析

简介&#xff1a;一个基于 JSP 技术栈开发的在线音乐信息管理系统完整项目&#xff0c;采用 Java Web JSP MySQL JavaScript 实现&#xff0c;适合正在学习 Java Web 开发、需要课程设计或毕业设计参考的学生。系统区分管理员与普通用户两类角色&#xff1a;前台支持歌曲查询…

作者头像 李华
网站建设 2026/9/28 21:48:16

LinuxPTP硬件时间戳配置深度指南:网卡、内核与PHY协同调优

1. 为什么“5分钟搞定”是个误导&#xff0c;但这个配置真值得你花30分钟吃透LinuxPTP、ptp4l、软硬件时间戳——这几个词最近在工业自动化、金融高频交易、5G前传和车载以太网调试场景里出现频率越来越高。我第一次在客户现场看到他们用ptp4l同步PLC和视觉相机时&#xff0c;设…

作者头像 李华
网站建设 2026/9/28 21:47:45

自研调度内核ax:时间轮、状态机与分布式一致性解析

最近在基础架构圈子里&#xff0c;大家开始频繁提起“ax调度”这四个字。如果你还没接触过&#xff0c;我简单交代一下背景&#xff1a;ax是我大半年一直在维护的一个轻量级调度内核的代号&#xff0c;取自Adaptive eXecution的缩写。市面上调度框架并不少&#xff0c;但真把业…

作者头像 李华
网站建设 2026/9/28 21:43:38

金融账务系统实战:从数据一致性到幂等设计的全链路复盘

1. 从"对不上账"到系统化&#xff1a;这个 Financial Services 项目到底在解决什么问题先说一个我亲身经历的场景。几年前我在一个小型技术团队里负责收款侧的支撑&#xff0c;业务方天天在群里喊"账对不上""退款重复了""报表导出慢了"…

作者头像 李华
网站建设 2026/9/28 21:43:14

LangChain+ChatGLM-6B本地知识库问答实战:RAG全流程与避坑指南

简介&#xff1a;这是一份面向AI开发者的本地知识库问答系统实践项目包&#xff0c;基于LangChain框架并结合ChatGLM-6B等系列大语言模型&#xff0c;实现针对私有文档的自动问答。资源共75个文件&#xff0c;压缩包约17.77MB&#xff0c;主要包含Python脚本、模型缓存与嵌入配…

作者头像 李华