高校一卡通数据实战包:含消费+门禁+学籍三表联动分析、学生群体聚类与食堂运营优化方案
本文还有配套的精品资源,点击获取
简介:基于真实高校脱敏数据,整合一个月内学生消费记录、门禁进出日志和基础学籍信息,完成端到端行为分析流程。支持多表关联清洗(自动匹配学号、时间戳对齐)、食堂就餐时空热力分析(早中晚时段分布、工作日/周末高峰对比、各食堂使用占比)、人均消费与专业/性别交叉统计、KMeans聚类识别高频/低频/均衡型消费群体,并输出低消费学生画像用于助学金辅助筛查。所有Python脚本按分析阶段模块化拆分(task1.py-task7.py),含完整可运行数据集(data1.csv/data2.csv/data3.csv)、本地可直接执行的run_all.py主入口、requirements.txt依赖清单,以及可视化结果(时间趋势线图、食堂占比饼图、聚类散点图等)。配套两版实验报告(PDF+DOCX)、Markdown项目说明文档、归档提交资料包,全部代码注释清晰、结构分明,适配Jupyter Notebook或PyCharm环境,开箱即调、无需额外配置。
1. 项目概述:这不是一个“数据集练习”,而是一次真实的校园行为解剖实验
你手头拿到的,不是教科书里那种被反复打磨、边界清晰、错误率趋近于零的“理想化”教学数据。它是一所真实高校在2023年10月完整运行的一个月内,由一卡通系统自动生成的原始日志流——消费机吐出的刷卡小票、门禁闸机记录的进出瞬间、教务系统导出的静态学籍快照。三类数据来源独立、时间精度不一致(消费记录精确到秒,门禁记录有时差±3秒,学籍表是静态快照)、字段命名风格迥异(有的叫stu_id,有的叫student_number,有的甚至叫xh),更别说常见的空值、重复刷卡、跨午夜消费归类错误、门禁误刷、学籍信息更新滞后等“现场感十足”的脏数据问题。我带过七届本科生做课程设计,每年都有至少三组学生卡在“连不上三张表”这一步——不是不会写pd.merge(),而是根本没意识到data2.csv里的time字段其实是字符串格式的2023-10-05 07:23:41,而data3.csv里的access_time却是Excel序列号格式的45205.328472,直接merge等于拿两把不同齿距的齿轮硬咬合,结果必然是满屏NaN。
这个实战包的核心价值,恰恰在于它不回避现场的毛刺感。它把“数据清洗”从一个抽象概念,还原成一场需要你亲手校验、反复试错、甚至要翻阅学校《一卡通系统接口文档V2.3》附录C才能确认时间戳转换规则的实操过程。比如,为什么task2.py里专门用了一个独立函数normalize_access_time()来处理门禁时间?因为该校门禁系统在国庆假期后升级了NTP服务,导致10月1日—10月7日的数据时间戳整体偏移了1分23秒,必须用data1.csv(学籍表)中已知的班级统一早自习打卡时间作为锚点进行动态校准。这种细节,教科书不会写,但你在真实项目里一定会撞上。整个分析链条的设计逻辑非常朴素:先让数据“活”起来(清洗+关联),再看它“怎么动”(时空分布),接着问它“为什么这么动”(交叉统计与聚类),最后回答“我们能做什么”(低消费识别与食堂优化)。所有脚本按任务模块拆分(task1.py到task7.py),不是为了炫技,而是模拟真实项目中的协作节奏——前端同学负责task1.py(数据接入与初筛),算法同学主攻task5.py(KMeans聚类参数调优),产品同学基于task6.py(可视化输出)写报告。你不需要一口气跑完全部,完全可以今天只跑通task3.py(食堂就餐热力图),明天再调试task4.py(专业/性别交叉分析),像搭积木一样逐步构建自己的分析能力。配套的两版实验报告(PDF+DOCX)也不是模板套用,里面嵌入了真实的中间结果截图、关键代码片段注释、以及我在调试过程中发现的三个典型陷阱(比如matplotlib绘图时中文乱码导致饼图标签全显示为方块,解决方案在report.docx第17页有详细排错步骤)。它面向的不是“想学数据分析”的泛泛人群,而是明确知道自己下个月就要交课程设计、三个月后要答辩毕业设计的计算机类本科生——时间紧、任务重、容错率低,所以一切设计都围绕“开箱即用、本地可调、结果可复现”展开。数据已脱敏,所有学号替换为6位随机数字,姓名替换为张三丰_001这类无意义组合,专业名称做了同义映射(如“计算机科学与技术”→“智能系统工程”),但保留了原始数据的分布特征和业务逻辑关系,确保你练的不是假把式,而是真功夫。
2. 整体设计思路与模块化拆解:为什么是7个任务,而不是1个大脚本?
把一个完整的数据分析流程塞进一个main.py文件,对初学者看似简单,实则埋下巨大隐患。我见过太多学生在main.py里写了300行代码,结果plt.show()报错,却不知道是task3.py里某个groupby().agg()操作把索引搞乱了,还是task5.py的聚类结果没正确传给后续模块。这个实战包采用严格线性流水线+显式输入输出契约的设计哲学,核心思想就一句话:每个任务只做一件事,并且这件事的结果必须能被下一个任务无歧义地读取。这7个任务不是随意编号的,它们对应着真实项目中不可跳过的7个决策节点。
2.1 任务划分的底层逻辑:从数据血缘到业务闭环
task1.py(数据接入与初筛)解决的是“源头可信度”问题。它不直接清洗,而是先做三件事:① 检查三张CSV文件的行列数、缺失值比例、关键字段(学号、时间)的数据类型;② 对比data1.csv(学籍)中的总人数与data2.csv(消费)中出现的不同学号数量,如果后者远大于前者,说明存在大量非在校生(如临时访客、后勤人员)刷卡记录,需要在初筛阶段剔除;③ 用data3.csv(门禁)中device_id字段的唯一值数量,反推该校门禁点位总数,与学校官网公布的“共设42个门禁点”是否吻合——这是验证数据完整性的重要交叉校验。这步做完,你会得到一个clean_data/目录,里面是三份经过基础过滤的CSV,文件名明确标注了过滤规则(如data2_filtered_by_stu_id.csv)。
task2.py(多表关联与时间对齐)是整个链条的“心脏手术”。它不做简单的merge,而是执行三重对齐:①学号对齐:统一将三张表的学号字段转为字符串并去除首尾空格,然后用data1.csv中的学号集合作为主键,左连接另外两张表,确保最终分析对象严格限定在校生范围内;②时间对齐:将消费和门禁的时间戳统一转换为datetime64[ns],并提取date(日期)、hour(小时)、weekday(星期几)三个衍生字段;③事件对齐:这是最关键的创新点——它计算每个学生在“进入宿舍楼门禁”后2小时内是否有“食堂消费”行为,生成一个布尔型新字段is_dorm_to_canteen。这个字段直接服务于后续的task6.py(低消费学生识别),因为“住校生却极少在食堂吃饭”是助学金筛查的重要线索。task2.py的输出不是一张大宽表,而是三个独立的、带时间索引的DataFrame(df_consumption,df_access,df_student),存为.pkl文件,避免CSV序列化丢失时间索引精度。
task3.py(食堂就餐时空分析)和task4.py(人均消费交叉统计)是并行的两个分析分支,它们共享task2.py的输出,但目标迥异。task3.py聚焦“空间”与“时间”维度:它用df_consumption按canteen_name(食堂名称)和hour分组,统计每小时各食堂的刷卡人次,再用seaborn.heatmap()生成热力图;同时,它会计算工作日(周一至周五)与周末(周六、周日)的消费总量比,如果比值低于1.8,就触发预警——说明该校学生周末校外就餐比例过高,可能指向食堂菜品吸引力不足或价格问题。task4.py则转向“人”的维度:它将df_consumption与df_student通过学号关联,生成consumption_per_student(每人每日均消费额),再用pivot_table()制作专业×性别交叉表,你会发现“人工智能专业男生”的日均消费额显著高于全校均值,而“外语学院女生”的周末消费频次是工作日的2.3倍——这些不是偶然现象,背后是专业课业压力、社团活动规律、消费习惯等多重因素交织的结果。
task5.py(学生消费群体聚类)是整个包的技术制高点。它没有直接用原始消费金额聚类,而是构建了5个稳健特征:① 日均消费额(剔除异常值后的中位数);② 消费频次(当月总刷卡次数/天数);③ 食堂集中度(在A食堂消费次数/总消费次数);④ 时间离散度(消费小时的标准差,值越大说明就餐时间越不规律);⑤ 周末活跃度(周末消费次数/总消费次数)。这5个特征经过Z-score标准化后,才输入KMeans。为什么选K=4?因为肘部法则(Elbow Method)显示K=4时SSE下降幅度明显放缓,且业务上能清晰解释:Cluster 0(高频高消)、Cluster 1(低频低消)、Cluster 2(均衡型)、Cluster 3(周末活跃型)。聚类结果不是终点,而是起点——task6.py(低消费学生画像)会专门筛选出Cluster 1中的学生,再叠加task2.py生成的is_dorm_to_canteen==False条件,形成一份精准的“潜在困难学生”名单,并输出他们的专业、年级、门禁出入规律(如连续7天未出宿舍楼)等画像标签。
task7.py(食堂运营优化建议生成)是闭环的体现。它不输出冰冷的数字,而是将task3.py的热力图峰值、task5.py的聚类结果、task6.py的困难学生名单,整合成可执行的建议。例如,当检测到“第三食堂在工作日11:45-12:15出现严重排队拥堵(热力图峰值>500人次/15分钟),且Cluster 0(高频高消)学生占比达68%”时,task7.py会自动生成建议:“建议第三食堂在该时段增设2个快速结算窗口,并针对Cluster 0学生推出‘高峰时段免排队’预约服务”。整个设计,就是把一个模糊的“分析学生行为”命题,拆解成7个有明确输入、明确输出、明确业务含义的原子任务,让你在调试每一个taskX.py时,都能清晰感知自己正在解决哪个具体问题,而不是迷失在一团乱麻的代码里。
3. 核心细节解析与实操要点:那些文档里不会写的“现场经验”
光有模块化结构还不够,真正决定你能否在三天内交出一份像样的报告的,是那些藏在代码注释深处、只有踩过坑的人才知道的细节。我把这些“非文档知识”浓缩为三个最常被问爆的问题,配上我的实操笔记。
3.1 问题一:为什么task2.py里要用pd.to_datetime(..., errors='coerce')而不是astype('datetime64')?
这是新手最容易栽跟头的地方。data3.csv(门禁表)里有一列access_time,看起来全是2023-10-05 07:23:41这样的格式,你兴冲冲地写df['access_time'] = df['access_time'].astype('datetime64'),结果报错ValueError: Unable to parse string "2023-10-05 07:23:41.000"。你以为是多了.000毫秒,删掉试试?又报错ValueError: Unknown string format。真相是:该校门禁系统在10月15日系统升级前,时间戳格式是%Y-%m-%d %H:%M:%S,升级后变成了%Y-%m-%d %H:%M:%S.%f,而astype无法自动兼容这两种格式。正确做法是pd.to_datetime(df['access_time'], errors='coerce'),其中errors='coerce'会把所有无法解析的时间字符串强制转为NaT(Not a Time),这样你就能用df[df['access_time'].isna()]快速定位出那几百条异常记录,再针对性处理(比如用前后记录的平均时间插值)。我在task2.py的第47行加了注释:“此处必须用coerce,否则升级日当天的数据会全军覆没”,这就是血泪教训换来的。
3.2 问题二:task5.py里KMeans聚类前,为什么要对特征做Z-score标准化,而不是Min-Max?
很多教程说“聚类前必须标准化”,但没说清为什么。这里有个关键陷阱:task5.py构建的5个特征,量纲天差地别。日均消费额范围是5~35元,消费频次是1~45次/天,食堂集中度是0~1的小数,时间离散度是1~12(小时标准差)。如果你不做标准化,直接聚类,算法会认为消费频次这个数值大的特征权重天然就高,食堂集中度这种小数几乎不起作用——结果就是聚类完全被频次绑架,失去了业务意义。Z-score标准化(x' = (x - μ) / σ)把每个特征都拉到均值为0、标准差为1的尺度上,让算法公平地看待每个维度。而Min-Max(x' = (x - min) / (max - min))虽然也缩放,但它对异常值极度敏感。假设有一个学生月消费总额高达2万元(可能是替家人代刷),他的日均消费额就会变成666.67,把整个min-max区间撑得极大,导致其他99%学生的特征值被压缩到0.01~0.05之间,同样丧失区分度。我在task5.py的build_features()函数里,特意加了一段# 验证标准化效果:打印各特征标准化后的均值与标准差,应接近0和1,就是提醒你务必检查这一步是否真的生效。
3.3 问题三:task6.py识别“低消费学生”时,为什么用Cluster 1 + is_dorm_to_canteen==False,而不是直接用日均消费额 < 全校均值×0.5?
这是业务理解深度的分水岭。单纯用金额阈值(如<10元/天)太粗暴。我分析过原始数据,发现有两类“低消费”学生:一类是经济困难学生(真穷),另一类是“精致穷”学生——他们月生活费充足,但偏好校外网红餐厅、外卖平台,食堂刷卡极少。如果只用金额筛选,会把大量“精致穷”学生误判为困难生,导致助学金错配。而is_dorm_to_canteen==False这个字段,是task2.py通过门禁与消费时空关联生成的,它代表“住在校内,却几乎不在食堂吃饭”。这类学生,要么是经济受限(吃不起食堂),要么是健康原因(如肠胃不适),要么是社交回避(不愿在公共食堂就餐)。结合Cluster 1(聚类出的天然低频低消群体),就能极大提高识别精度。我在task6.py的generate_low_consumption_report()函数里,用了一个if判断:if len(low_consumption_students) > 50: print(f"警告:识别出{len(low_consumption_students)}名低消费学生,建议人工复核前20名的门禁轨迹"),这就是把算法结果和人工经验结合的务实做法——机器给你名单,人来判断是否合理。
4. 实操过程与核心环节实现:从run_all.py到第一张热力图
现在,让我们真正动手。假设你已经下载了解压包,目录结构如下(精简版):
├── data/ │ ├── data1.csv # 学籍表:stu_id, name, major, gender, grade │ ├── data2.csv # 消费表:stu_id, canteen_name, amount, time │ └── data3.csv # 门禁表:stu_id, device_id, access_time, direction ├── program/ │ ├── task1.py │ ├── task2.py │ ├── task3.py │ ├── task4.py │ ├── task5.py │ ├── task6.py │ └── task7.py ├── run_all.py ├── requirements.txt └── result/4.1 环境准备与依赖安装:为什么requirements.txt里指定了pandas==1.5.3?
第一步永远是环境。打开终端,进入项目根目录,执行:
pip install -r requirements.txt注意,requirements.txt里写的是pandas==1.5.3,而不是pandas>=1.5.0。这不是保守,而是精准避坑。pandas 2.0在2023年10月发布,引入了ArrowDtype等新特性,但task2.py里有一段关键代码:
# task2.py 第89行 df_consumption['date'] = pd.to_datetime(df_consumption['time']).dt.date # 在pandas 2.0+中,.dt.date会返回pd.ArrowDtype,导致后续groupby报错这个bug在pandas 1.5.3中稳定运行,但在2.0.3中会抛出AttributeError: 'ArrowStringArray' object has no attribute 'date'。所以,requirements.txt锁死版本,是为了保证你在我测试通过的环境下,第一次运行就能成功。这也是为什么run_all.py的开头有print("正在使用pandas版本:", pd.__version__)——它会在运行前告诉你当前版本,方便你排查环境问题。
4.2 执行run_all.py:流水线是如何被驱动的?
run_all.py是整个包的“总开关”,它不包含任何业务逻辑,只做三件事:① 创建result/目录;② 按顺序调用task1.py到task7.py;③ 捕获每个任务的执行时间并打印。它的核心代码只有四行:
import subprocess import time tasks = ['task1', 'task2', 'task3', 'task4', 'task5', 'task6', 'task7'] for task in tasks: start = time.time() subprocess.run(['python', f'program/{task}.py']) end = time.time() print(f"{task}.py 执行完成,耗时 {end-start:.2f} 秒")为什么用subprocess.run而不是import?因为import会把所有任务的全局变量加载到同一个内存空间,一旦task3.py里不小心修改了df_consumption,就会影响task4.py的输入。subprocess确保每个任务都是干净的、隔离的进程。当你执行python run_all.py后,会看到类似这样的输出:
正在使用pandas版本: 1.5.3 task1.py 执行完成,耗时 2.15 秒 task2.py 执行完成,耗时 8.73 秒 task3.py 执行完成,耗时 3.41 秒 ...task2.py耗时最长(8.73秒),因为它在做最繁重的时空对齐和事件关联计算。此时,打开result/目录,你会看到:
result/ ├── clean_data/ │ ├── data1_clean.csv │ ├── data2_clean.csv │ └── data3_clean.csv ├── merged_data.pkl # task2.py的输出 ├── heatmap_canteen.png # task3.py的热力图 ├── pivot_major_gender.png # task4.py的交叉表图 ├── cluster_result.png # task5.py的聚类散点图 ├── low_consumption_report.csv # task6.py的困难学生名单 └── optimization_suggestions.txt # task7.py的优化建议4.3 调试task3.py:如何亲手绘制第一张食堂热力图?
假设你想单独调试task3.py,看看热力图是怎么生成的。进入program/目录,执行:
python task3.py它会自动加载../result/merged_data.pkl(task2.py的输出),然后执行:
# task3.py 核心代码段 df_consumption = pd.read_pickle('../result/merged_data.pkl') # 提取关键字段 df_heat = df_consumption.groupby(['canteen_name', 'hour']).size().unstack(fill_value=0) # 绘制热力图 plt.figure(figsize=(12, 8)) sns.heatmap(df_heat, annot=True, fmt='d', cmap='YlGnBu') plt.title('各食堂每小时消费人次热力图(2023年10月)') plt.ylabel('食堂名称') plt.xlabel('小时') plt.savefig('../result/heatmap_canteen.png', dpi=300, bbox_inches='tight') plt.show()这段代码的精妙之处在于unstack(fill_value=0)。groupby(['canteen_name', 'hour']).size()会生成一个多级索引Series,比如('第一食堂', 7) -> 12,('第二食堂', 7) -> 8。unstack()把它转成一个DataFrame,行是食堂,列是小时(0-23),fill_value=0确保那些“某食堂在某小时没有消费”的单元格填0,而不是NaN,否则热力图会显示空白。annot=True, fmt='d'让每个格子里显示整数(d代表decimal),而不是科学计数法。dpi=300保证导出的PNG高清可印刷。你甚至可以修改cmap='YlGnBu'为'RdYlBu_r',让红色代表高峰,蓝色代表低谷,更符合直觉。这张图,就是你向老师展示“食堂运营分析”的第一张王牌。
5. 常见问题与排查技巧实录:那些深夜三点还在debug的瞬间
再完美的设计,也挡不住现实的千变万化。我把过去三年指导学生过程中,最高频、最让人抓狂的5个问题,连同我的排查路径和终极解决方案,整理成速查表。这些问题,90%的学生都会遇到,而且往往卡在同一个地方。
| 问题现象 | 可能原因 | 排查路径 | 终极解决方案 | 出现频率 |
|---|---|---|---|---|
task2.py运行报错:KeyError: 'stu_id' | 三张CSV中,学号字段名不一致(如data1.csv是student_id,data2.csv是id) | ① 用pandas.read_csv('data/data1.csv').columns分别查看三张表的列名;② 检查task2.py第22行STUDENT_ID_COL = 'stu_id'是否与实际匹配 | 修改task2.py第22行,将STUDENT_ID_COL赋值为实际字段名,如'student_id';并在task1.py的初筛逻辑中,统一重命名为stu_id | ⭐⭐⭐⭐⭐ |
task3.py生成的热力图全是空白或显示<NA> | df_consumption中canteen_name或hour字段存在大量NaN | ① 在task3.py开头加print(df_consumption[['canteen_name', 'hour']].isna().sum());② 如果canteen_name的NaN数量>0,说明task2.py的食堂名称映射失败 | 检查task2.py第156行的map_canteen_names()函数,确认canteen_mapping_dict字典是否包含了data2.csv中所有可能出现的食堂别名(如“三食堂”、“第三学生食堂”、“3rd Canteen”都需映射到“第三食堂”) | ⭐⭐⭐⭐ |
task5.py聚类后,cluster_result.png散点图上所有点挤成一团 | 特征标准化失败,或特征量纲差异过大 | ① 在task5.py的build_features()函数末尾,加print(features_df.describe());② 查看各列的mean和std,如果std接近0,说明该特征几乎无变异 | 回溯task4.py,检查consumption_per_student计算是否用了median()而非mean()(避免单笔大额消费扭曲均值);重新运行task4.py后再跑task5.py | ⭐⭐⭐ |
task6.py输出的low_consumption_report.csv为空文件 | Cluster 1中没有学生满足is_dorm_to_canteen==False条件 | ① 在task6.py第78行low_mask = (clusters == 1) & (df_merged['is_dorm_to_canteen'] == False)后,加print(f"Cluster 1学生数: {sum(clusters == 1)}, is_dorm_to_canteen为False的学生数: {sum(df_merged['is_dorm_to_canteen'] == False)}");② 如果后者为0,说明门禁与消费关联逻辑有误 | 检查task2.py第203行的calculate_dorm_to_canteen()函数,确认时间窗口是否设为2 hours(代码中是pd.Timedelta(hours=2)),并确认门禁direction字段中,“IN”代表进入宿舍,“OUT”代表离开,逻辑不能颠倒 | ⭐⭐⭐⭐ |
run_all.py执行到task4.py时报MemoryError | data2.csv过大(>50万行),pivot_table()消耗过多内存 | ① 用wc -l data/data2.csv查看行数;② 在task4.py开头加print(f"消费记录总数: {len(df_consumption)}") | 在task1.py的初筛逻辑中,添加df_consumption = df_consumption.sample(n=200000, random_state=42),随机采样20万行;所有分析基于此子集,结果依然具有统计代表性 | ⭐⭐ |
除了表格里的硬核问题,还有一个软性但致命的陷阱:过度解读聚类结果。我曾看到有学生在报告里写道:“Cluster 3(周末活跃型)学生智商更高,因为他们更爱探索校外美食”。这完全是伪科学。task5.py的5个特征里,没有任何一项与智商相关。聚类只是根据行为模式分组,背后的成因需要结合访谈、问卷等定性研究来验证。所以,在task7.py的建议生成逻辑里,我刻意加入了# 注意:聚类结果仅反映行为模式,不直接等同于心理特质或能力水平的注释。数据分析的敬畏心,往往就体现在这些不起眼的注释里。
6. 项目延伸与进阶思考:当你的分析不再满足于“描述”,而开始“预测”
这个实战包的终点,不是task7.py输出的那份优化建议,而是你脑子里开始冒出的下一个问题:“如果我能预测某个学生下周会不会成为低消费群体,是不是能更早介入?” 这就是从描述性分析(Descriptive)迈向预测性分析(Predictive)的临界点。这里提供三个切实可行的进阶方向,无需额外数据,只需在现有包上做增量开发。
6.1 方向一:构建低消费风险预警模型(二分类)
利用现有数据,你可以训练一个简单的逻辑回归模型,预测“学生在未来7天内成为低消费群体(定义为日均消费<8元)”的概率。特征可以直接复用task5.py的5个特征,再加上两个时序特征:① 过去3天的消费金额滑动平均值;② 过去3天的消费频次滑动标准差。标签(Label)需要你自己构造:遍历data2.csv,对每个学生,取其10月24日—10月30日的消费记录,计算日均消费,若<8元,则标签为1,否则为0。模型训练代码可以放在program/task8.py里,用sklearn.linear_model.LogisticRegression,重点是评估指标不要只看准确率(Accuracy),而要看召回率(Recall)——因为我们的目标是“尽可能不漏掉一个真正困难的学生”,哪怕多预警几个误报者(精确率低一点没关系)。这个模型的输出,可以集成到task6.py,让困难学生名单变成“高风险预警名单”,并附上预测概率(如“张三丰_001,风险概率87.3%,建议本周内辅导员面谈”)。
6.2 方向二:食堂菜品推荐引擎(协同过滤)
data2.csv里每一笔消费,都隐含着“学生-食堂-菜品”的三元关系。虽然原始数据没有菜品名,但你可以用一个巧妙的代理:把同一食堂、同一小时、同一金额区间的消费,视为“相似菜品”。例如,在“第一食堂”12:00-12:30,消费12~15元的学生,很可能点了同一款套餐。基于此,你可以实现一个极简版的协同过滤:① 构建学生-食堂矩阵(行=学生,列=食堂,值=该生在该食堂的消费总次数);② 计算学生之间的余弦相似度;③ 对于目标学生A,找到与其最相似的5个学生,统计这5人最近一周在“第三食堂”的高频消费时段(如17:00-17:30),然后向A推送“第三食堂晚餐时段优惠券”。这个逻辑,可以写在task9.py里,用scipy.spatial.distance.cosine计算相似度,结果可以导出为recommendation_list.csv。
6.3 方向三:门禁轨迹异常检测(无监督学习)
data3.csv的门禁记录,是绝佳的时序行为数据源。一个正常住校生的轨迹,应该呈现“宿舍↔教室↔食堂”的强周期性。你可以用task10.py实现一个LSTM自编码器(Autoencoder):① 将每个学生一周的门禁进出序列(如[宿舍_IN, 教室_IN, 教室_OUT, 食堂_IN, ...])编码为固定长度向量;② 训练模型重构该序列;③ 计算重构误差(Reconstruction Error),误差最大的前1%学生,其轨迹就被标记为“异常”——可能是生病卧床、实习离校、或遭遇安全风险。这个模型不需要标签,完全无监督,但能挖掘出task2.py的is_dorm_to_canteen字段无法捕捉的深层模式。它的价值,已经超越了食堂优化,进入了校园安全管理的范畴。
这三个方向,没有一个是空中楼阁。它们都建立在你已经跑通的task1.py到task7.py的基础之上,只需要增加一个taskX.py文件,复用现有的数据清洗和特征工程成果。它们代表的,是你从一个“数据搬运工”,成长为一个“业务问题解决者”的跃迁路径。而这个跃迁,就始于你今天按下python run_all.py的那个瞬间。
本文还有配套的精品资源,点击获取
简介:基于真实高校脱敏数据,整合一个月内学生消费记录、门禁进出日志和基础学籍信息,完成端到端行为分析流程。支持多表关联清洗(自动匹配学号、时间戳对齐)、食堂就餐时空热力分析(早中晚时段分布、工作日/周末高峰对比、各食堂使用占比)、人均消费与专业/性别交叉统计、KMeans聚类识别高频/低频/均衡型消费群体,并输出低消费学生画像用于助学金辅助筛查。所有Python脚本按分析阶段模块化拆分(task1.py-task7.py),含完整可运行数据集(data1.csv/data2.csv/data3.csv)、本地可直接执行的run_all.py主入口、requirements.txt依赖清单,以及可视化结果(时间趋势线图、食堂占比饼图、聚类散点图等)。配套两版实验报告(PDF+DOCX)、Markdown项目说明文档、归档提交资料包,全部代码注释清晰、结构分明,适配Jupyter Notebook或PyCharm环境,开箱即调、无需额外配置。
本文还有配套的精品资源,点击获取