拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

Python校园消费行为分析:清洗建模可视化全链路实战 简介本资源是一套完整的基于Python的学生校园消费行为分析实战项目面向数据分析初学者、高校课程设计学生及教育管理相关从业者聚焦真实校园消费场景下的数据挖掘与业务洞察。项目涵盖数据采集、清洗、探索性分析、可视化呈现及消费行为建模含聚类与关联规则全流程助力读者掌握Pandas、Matplotlib、Seaborn、Scikit-learn等核心库的工程化应用。压缩包共20个文件包含4个Python主程序脚本实现各分析模块、4个CSV原始与中间数据集、9张PNG3张JPG结果图表含消费分布热力图、聚类散点图、关联规则网络图等整体大小20.37MB结构清晰、即开即用。目前已有794人学习下载提供可直接运行的源码、带标注的多维度结果图及结构化数据集显著降低复现门槛适合用于课程作业、毕业设计或校园服务优化方案支撑。1. 学生校园消费行为分析不是画几张图就叫“分析”而是用真实数据跑通清洗→建模→可解释结果的完整闭环你有没有见过这样的“分析报告”Excel 导入后直接df.describe()画个柱状图配几句“学生月均消费约 850 元”“食堂消费占比最高”然后戛然而止这不是分析是数据快照。而这份基于Python的学生校园消费行为分析源码数据结果集.zip是我在三所高校后勤处实测落地过的完整 pipeline——它从原始 CSVdata1.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_timeISO 格式时间戳但存在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], errorscoerce) # 先标记缺失行 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参数说明errorscoerce将非法时间如25:30转为NaTdropna().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 6h10: breakfast elif 10h13: lunch ...识别早餐/午餐/宵夜场景食堂 vs 商超偏好分离school_week(trans_time - datetime(2023,9,1)).days // 7 1新学期第几周观察消费习惯随学期推进的变化is_exam_week基于教务处课表日期范围匹配考试周消费降级如减少外卖增加咖啡采购time_since_last同student_id下前一条记录的时间差秒衡量消费密集度识别“高频小额” vs “低频大额”用户is_night22h or h6夜间消费如网吧、便利店风险监控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_clusters5是经肘部法则Elbow Method和轮廓系数Silhouette Score双重验证的最优解task2_X4.py第 87 行起有完整验证代码。3.2 关联规则挖掘Apriori 在校园场景的定制化改造task3_X1.py执行关联规则但不是对全量商品做mlxtend.frequent_patterns.apriori。校园消费的“商品”是merchant_name直接跑会得到无意义规则如“一食堂-二楼”→“一食堂-一楼”。因此做了三层过滤商户层级抽象将merchant_name映射到merchant_category如“一食堂-二楼-麻辣香锅”→“食堂正餐”“蜜雪冰城-东门”→“饮品”共 9 大类时间窗口限定只挖掘同一student_id在2 小时内的连续消费组合模拟真实决策链路支持度阈值动态调整对高频类食堂设min_support0.05对低频类打印店设min_support0.005避免规则被高频项淹没。最终输出的task3_X1.png中规则{饮品, 面包} → {零食}支持度 0.12置信度 0.83——这直接支撑了商家联合促销奶茶店旁设面包柜。3.3 模型可解释性用 SHAP 值解释聚类归属task3_X2.py引入 SHAPSHapley 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(axis1, keepdimsTrue) # 归一化为概率 # 初始化 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_display10)参数说明X_sampled是从训练集随机采样的 100 条记录保证解释效率max_display10限制显示 top10 影响因子避免图表过载。task3_X2.png清晰显示该学生被归为“尝鲜型”的主因是trans_count_week18远高于均值 5.2和merchant_category_diversity7覆盖 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 为蓝色。一眼看出“饮品→零食”是强正向引导lift2.1而“打印→零食”是弱相关lift1.03task3_X4.png规则支持度-置信度散点图并用plt.axhline(y0.7, cr, 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(axis1) # 每类准确率 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], errorscoerce) # 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], errorscoerce) 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 doesnt 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, replaceFalse)] X_target_scaled scaler.transform(X_target.reshape(1, -1)) # 单样本也需 transform5.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(mysqlpymysql://user:pwdhost: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()哪怕只是确认一切正常。希望帮到你。本文还有配套的精品资源点击获取
返回列表