尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

校园食堂消费数据分析实战:从数据清洗到统计检验全流程解析

校园食堂消费数据分析实战:从数据清洗到统计检验全流程解析 1. 作业选题与整体思路拆解1.1 为什么把“食堂消费数据”作为这次作业的切入点“朱佳毅作业5”这个名字看着简单实际上是我这门数据分析课程里的第五次综合作业。前四次作业分别练了数据清洗、SQL查询、基础统计分析和可视化第五次的要求是“独立完成一个完整的数据分析小项目并且把结论讲清楚”。我当时的想法很简单与其找个网上的现成数据集不如选一个自己身边、自己能理解业务背景的数据这样分析起来不至于两眼一抹黑。后来我选了校园食堂的刷卡消费记录。这个数据有几个天然优势第一食堂消费数据在校园场景里非常典型它包含时间、地点、金额、用户属性等信息结构足够完整能支撑多维度分析第二数据量适中一天几千条记录既不会因为太小没有分析价值也不会因为太大导致个人电脑跑不动第三业务背景我非常熟悉——学生什么时候吃饭、喜欢去哪个窗口、平均花多少钱、有没有人不吃早餐这些日常经验能帮我验证分析结果是否合理。这个选题思路我可以直接分享给正在为“作业不知道做什么”发愁的朋友最有价值的数据分析项目不一定来自什么高大上的公开数据集往往是你身边最熟悉、最容易被忽略的数据。你越了解业务背景越能提出好问题也越容易发现数据里隐藏的规律。相反如果你选一个完全陌生的领域数据光是理解字段含义就要耗费大量时间更别提做出有深度的结论了。1.2 作业目标的设定与分析框架定下选题之后我没有急着写代码而是先花了一晚上把作业目标拆成四个问题学生的日均消费水平是多少不同性别、不同年级之间有没有明显差异一周之内食堂的消费高峰期分布在哪几天一天之内早中晚三餐的消费高峰分别是什么时候窗口或菜品的受欢迎程度如何哪些窗口的客单价高哪些窗口的复购率高是否存在异常的消费记录比如单笔金额远超平均水平、半夜刷卡等可疑行为这些是否可能是数据录入错误这四个问题分别对应描述性分析、趋势分析、结构分析和异常检测基本覆盖了一门数据分析课程要求的所有核心知识点。我当时的想法是作业不是越炫越好关键是能展示完整的分析流程——从数据获取、清洗、探索性分析到建模或统计检验再到可视化和结论输出每一步都要有据可查。分析框架上我按“数据理解—数据清洗—探索性分析—交叉验证—结论输出”五步走。这样做的好处是每一步都有明确的交付物不至于写着写着就跑偏了。比如数据理解阶段我要求自己必须回答“每张表有哪些字段、每列的含义是什么、数据量多大、有没有缺失值”这些问题全部在开始清洗之前搞清楚。等到真正写代码时因为前期功课扎实几乎没有出现“这个字段到底什么意思”的返工情况。2. 数据准备与清洗的实操细节2.1 数据采集与字段说明这次作业用到的数据是从学校信息化中心申请到的脱敏后的食堂刷卡流水时间跨度一个月一共包含大约18万条记录。每条记录包含几个核心字段用户编号已经脱敏处理看不到具体学号、性别、年级、消费金额、消费时间、消费窗口编号、窗口名称、消费类型早/午/晚餐。拿到数据的第一件事不是急着写分析代码而是先做一个基础的数据概要。我习惯用Pandas的info()和describe()快速看数据的基本情况import pandas as pd df pd.read_csv(canteen_data.csv, encodingutf-8) print(df.info()) print(df.describe())这一步能发现很多问题。我这次的数据里就出现了三处明显的缺失一是“窗口名称”字段有几百条记录为空经核实是部分新窗口还没完成信息录入二是“性别”字段有少量空值三是“消费金额”出现了几条0元的记录明显不合理。如果缺失值不处理后续做分组统计时这些脏数据会影响结果尤其是金额为0的记录会把客单价的均值拉低。2.2 缺失值与异常值的处理策略缺失值的处理要分情况讨论不能一刀切地删除或者填零。我的做法如下对于“窗口名称”缺失的记录我查了这部分记录对应的“消费窗口编号”发现它们都指向同一批新开的窗口于是根据编号规则手动补全了窗口名称。如果是真实企业项目这一步需要和业务方确认不能自己瞎猜。对于“性别”空值因为这部分记录占非常小的比例不到0.5%而且性别在后续分析里是一个重要的分组维度我选择直接删除这些记录避免用填充法引入噪声。对于金额为0的记录我先检查了这些记录的时间分布和窗口分布发现它们基本集中在一个特定窗口经过核实是“免费汤”窗口业务上确实存在0元消费。这种不算异常属于正常的业务场景我单独打了一个“免费商品”标签没有删除。异常值处理上我重点看了一个指标单笔消费金额。食堂早餐的平均消费在5元左右午餐和晚餐在15元左右正常单笔很少超过50元。我用了IQR四分位距方法检测离群值Q1 df[amount].quantile(0.25) Q3 df[amount].quantile(0.75) IQR Q3 - Q1 lower_bound Q1 - 1.5 * IQR upper_bound Q3 1.5 * IQR outliers df[(df[amount] lower_bound) | (df[amount] upper_bound)] print(f检测到 {len(outliers)} 条离群记录)结果发现只有几十条记录超过50元最高的一笔是188元。我挨个看了这些记录发现188元那笔是一个窗口的“宴请套餐”消费不是录入错误。但有一笔凌晨3点消费35元的记录非常可疑后来核实是系统测试数据我直接删掉了。这里有个很重要的经验想分享异常值不等于错误值。很多新手一看到离群点就急着删除实际上离群点可能在提示一个重要业务场景。正确的做法是先理解为什么会有这个离群点再决定是保留、修正还是删除。我把这个思考过程写进了作业报告里老师给的评价是“处理思路清楚有业务sense”。3. 核心分析过程与可视化实现3.1 消费水平的整体画像数据处理干净之后我做的第一层分析是整体消费水平的描述性统计。这里我用了一个非常直观的思路先看总量再看均值和中位数的差异最后分维度拆解。整体来看样本用户覆盖了全校所有年级的在校生月均消费次数在58次左右也就是平均每天接近2次食堂消费。月均消费金额约523元单次消费的中位数是9元均值是10.6元。这里中位数低于均值说明消费金额的分布是右偏的大部分人单次消费低于平均值只有少数人单次消费很高把均值拉上去了。分组统计时发现了一个有趣的现象分组月均消费金额日均消费次数平均客单价男生568元2.1次11.3元女生478元1.8次9.8元大一491元1.9次10.1元大二514元2.0次10.5元大三547元2.0次10.9元大四538元2.2次9.9元男生消费金额明显高于女生主要原因是客单价更高男生饭量大点的菜更多。年级维度上大三学生的消费金额最高大四学生虽然消费次数最多但客单价反而低——我推测是因为大四学生临近毕业不少人在外实习或聚餐在食堂吃饭更多是图便宜方便。3.2 时间维度的消费规律挖掘时间维度的分析是这次作业里最有意思的部分。我主要是看两个维度一是一个月内每天的消费总额变化二是一天之内按小时统计的消费频次分布。先看后者。把全天24小时分成24个桶统计每个小时的消费笔数结果非常清晰地呈现出三餐节奏早餐高峰出现在7点到8点午餐高峰在11点到13点晚餐高峰在17点到19点。其中午餐高峰最集中11点到12点这一个小时的消费笔数几乎是全天总量的25%。这说明食堂的运营压力主要在午饭时段如果食堂想优化排队体验优先考虑的应该是分流午餐时段的人流。按周几来看我发现周五、周六的晚餐消费明显下降周日的午餐和晚餐又回升。这个结果符合直觉——周末很多学生外出活动。但如果只看月汇总数据这些波动就会被“平均”掉所以在作业里我特意保留了这种细颗粒度的观察而不是只给一个笼统的“一周内消费平稳”的结论。时间维度分析给了一个额外收获通过对比“一天内消费笔数曲线”和“一天内消费金额曲线”能看出早餐虽然笔数不少但金额占比远低于午餐和晚餐。这为后面食堂窗口的备餐策略提供了参考——早餐应该保证出餐速度快午餐则要保证菜品种类和供给量。3.3 窗口维度的结构与复购分析窗口分析我用的是“波士顿矩阵”的思路把每个窗口按“日均消费笔数”和“平均客单价”两个维度分成四类高流量高单价明星窗口、高流量低单价走量窗口、低流量高单价利润窗口、低流量低单价长尾窗口。先统计每个窗口的日均笔数和客单价然后画一个散点图用中位数把坐标轴切成四象限。结果很有意思最受欢迎的窗口并不是单价最高的而是一个以面食为主打的窗口日均消费笔数是全食堂平均水平的3倍以上但客单价只有9.2元。分析下来这个窗口的优势在于出餐速度快标准化流程不用等、价格亲民、翻台率高。而那些客单价超过20元的窗口虽然单笔利润高但日均笔数不到明星窗口的六分之一。复购率是另一个容易被忽略的维度。我定义了一个月的“用户复购率”统计某窗口有多少用户在这一个月内消费了10次以上再除以总消费人数。结果显示早餐窗口的复购率普遍高于晚餐窗口因为吃早餐的人通常会稳定选同一家而晚餐的随机性更高。这个发现如果反馈给食堂运营方可以用来做精准营销——比如对早餐高频用户推送新品试吃券大概率能带来转化。3.4 可视化实现与踩坑记录可视化的过程其实没有想象中顺滑。我用的主要工具是Matplotlib和Seaborn好在绘图代码不算复杂但有三个坑值得单独拿出来说。第一个坑是中文乱码。我第一次画图时图表标题和坐标轴标签全是方块。解决方案是在代码开头加两行设置plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False这行代码设置了默认字体为黑体同时解决负号显示异常的问题。如果是Windows环境SimHei一般没问题Mac环境换成[AppleGothic]或者[PingFang SC]更合适。第二个坑是热力图的颜色映射。我在画“星期几 vs 小时”的热力图时默认的色带对比不明显导致高峰期和低谷期看起来颜色差不多。后来换用vmax参数手动设定色带上限并把cmap改成YlOrRd才让峰值时段真正“红”起来import seaborn as sns import matplotlib.pyplot as plt plt.figure(figsize(12, 6)) sns.heatmap(pivot_table, cmapYlOrRd, annotTrue, fmtd, linewidths0.5, vmax300) plt.title(周内时段消费频次热力图) plt.show()第三个坑是文字重叠。画窗口散点图时因为部分窗口名称很长plt.text标注坐标时几个标签叠在一起根本看不清。我的解法是写了一个循环根据点的位置动态调整标签偏移量同时配合adjust_text这个第三方小库自动避让。这段代码不复杂但确实能省不少手动调标签的时间。4. 统计检验与结论验证4.1 用独立样本t检验验证性别差异描述性统计里已经看到男女消费金额有差距但这个差距到底是真实差异还是随机波动需要用统计检验来验证。这里我用的是独立样本t检验原假设是“男女生均消费金额没有显著差异”。from scipy import stats male_amount df[df[gender] 男][amount] female_amount df[df[gender] 女][amount] t_stat, p_value stats.ttest_ind(male_amount, female_amount) print(ft统计量: {t_stat:.4f}, p值: {p_value:.4e})跑下来的结果p值远小于0.05说明性别差异在统计上显著。这其实是一个很容易理解的结果样本量超过10万条的时候哪怕两组均值只差1元钱t检验也可能判定为显著。所以光有p值还不够我还计算了效应量Cohens d结果显示效应量为0.35属于中等偏小的效应。这意味着性别差异“显著”但“不大”。这个细节在作业汇报时被老师重点表扬了因为这体现了对统计检验的理解深度很多同学跑完t检验看到p值小于0.05就觉得万事大吉但实际上大样本场景下p值对微小差异极其敏感必须结合效应量判断实际意义。4.2 卡方检验在窗口偏好中的应用我做的第二个检验是卡方检验用来验证不同年级的学生在窗口选择上是否存在显著差异。这里构造的是一个列联表行是年级列是窗口类型面食、米饭、麻辣烫、饮品、西餐等单元格是消费笔数。检验结果显示p值同样远小于0.05说明年级和窗口类型之间有显著关联。进一步看列联表的百分比发现大一新生在米饭窗口的占比明显高于其他年级而大四学生在饮品和西餐窗口的占比更高。一个可能的解释是新生更习惯传统的正餐结构高年级学生消费选择更多元。这个结论如果和前面大四客单价低的现象结合起来看能形成一条很有意思的叙事线——年级越高食堂消费行为越“非主流”。卡方检验的唯一问题是它只能告诉我们“是否有关联”不能告诉我们“关联强度”。所以我又补充了Cramérs V系数算出来是0.21属于中等关联强度被年级差异解释的部分有限。这种“检验显著性评估效应量”的组合写法能让结论更扎实也方便在汇报时应对老师的追问。4.3 结论的交叉验证方法单一维度的分析很容易出错所以我给自己定了一个规矩每个核心结论至少要用两个不同角度去验证。以“午餐是全天高峰”这个结论为例我同时看了三个数据维度按小时统计消费笔数曲线午餐峰值明显高于早晚餐按“周几x小时”热力图工作日的午餐高峰尤其突出按窗口分类统计米饭类窗口在午餐时段消耗量最大。三个维度相互印证结论才敢写进最终报告。另一个例子是“大四学生食堂消费次数最多但客单价低”这个发现单纯看平均值可能被极端值干扰。我做了稳健性校验用中位数替代均值重新计算客单价发现结论依然成立。这种交叉验证的方法论我认为比具体代码更有价值也是从“会跑数据”到“会做研究”的关键一步。5. 常见问题与排查技巧实录5.1 数据分析作业最容易踩的四个坑这次作业从头到尾花了一周时间其中将近三分之一的时间都花在排查各种问题上。我把最典型的四个坑整理出来如果你也在做类似的数据分析项目大概率能避开第一字符编码问题。读CSV文件时如果编码不对轻则中文变成乱码重则直接报UnicodeDecodeError。我这次就遇到过UTF-8编码的CSV被系统自动识别成GBK的情况解决办法是读取时显式指定编码或者用一个简单脚本自动探测编码。第二日期时间格式混乱。原始数据里日期是2024/04/01这种格式时间却是7:23这种格式两者合成一个时间戳字段时很容易出错。建议所有时间字段统一用pd.to_datetime()转换并且指定format参数避免歧义。第三分组聚合时误用groupby。比如我想统计“每个窗口每个月的总销售额”如果直接写df.groupby(window_name)[amount].sum()等于把所有月份的数据混在一起了。正确写法是按窗口和月份两个字段一起分组df[month] pd.to_datetime(df[time]).dt.month monthly_window_sales df.groupby([window_name, month])[amount].sum().reset_index()第四数据量暴增导致的性能问题。虽然这个数据集不算大但当我把样本按小时拆开做循环聚合时耗时还是明显上升。后来我意识到应该用向量化操作而不是for循环把几百行代码简化成两行运行时间从几分钟压缩到几秒。这个优化思路在大数据处理里尤其重要能用Pandas内置函数解决的问题尽量别自己写循环。5.2 一份能打的高分作业长什么样作业提交后我拿到一个不错的分数。复盘下来我觉得它和普通作业拉开差距的关键有三点第一分析流程的完整性。从数据清洗到统计检验再到业务解读每个环节都有明确输出并且每一步之间都有逻辑衔接而不是“为了用某个方法而用”。第二结论的可解释性强。我没有堆砌一堆图表和数字而是把每个分析结果都转化成了一句业务语言。比如“早餐客单价低但复购率高适合做套餐组合营销”这种话比一堆p值更容易让人留下印象。第三过程的可复现性。我把所有代码整理成了一个干净的Jupyter Notebook单元格按分析顺序排列通过简单的描述文字把代码之间的逻辑串起来。老师想看某段分析时直接点开对应单元格就能看懂。这一点看似不起眼但特别能体现工程化素养。5.3 数据分析之外的三点心得最后还想聊几点作业之外的体会。如果你不是学生而是在职场上做类似的分析工作这几条经验可能更有参考价值一是业务理解永远比工具重要。这个作业里最有价值的发现——“午餐是高峰期食堂应该优先优化午餐排队”——并不是靠什么高深算法得出的而是靠对数据的常识性解读。工具只是放大器方向错了跑得再快也没用。二是分析报告要讲“故事线”。我在作业报告里特意设置了一条线索从“谁在吃”到“何时吃”再到“吃什么、在哪里吃”最后落到“怎么优化”。这种叙事结构让读者能跟随分析思路走而不是被一堆孤立的图表淹没。写任何分析报告前先想清楚主线是什么。三是多做稳健性检查。当时整个分析完成后我随机抽了三天数据重新跑了一遍主要结论确认结果没有因为个别日期的特殊事件比如某个节假日的促销活动而失真。虽然这种做法增加了工作量但能防止因为数据中的隐藏偏误而得出错误结论。6. 后续还能怎么扩展这次作业完成之后我其实还想做两个方向的扩展但因为时间限制没有落地。如果你对食堂消费数据这个题材感兴趣可以考虑继续往下挖。第一个方向是预测建模。比如根据用户过去两周的消费记录预测他明天会不会去食堂、大概会花多少钱。这个属于消费行为预测的范畴可以用机器学习里的分类或回归模型来做。虽然预测结果不一定特别准但能帮助你理解哪些特征是影响消费行为的关键因素。第二个方向是用户画像与分群。用RFM模型最近一次消费时间、消费频率、消费金额或者聚类算法把学生分成“高频高额型”“低频廉价型”“周末爆发型”等几个群组再针对每个群组制定不同的运营策略。这种用户分层的方法在电商和外卖平台里已经是标配套在食堂场景上也有很强的现实意义。第三个方向是把数据扩到更长的时间跨度。我只用了一个月的数据没法分析季节变化和节假日影响。如果能拿到一年甚至两年的数据就能看出学生的消费习惯如何随学期推进而演变比如考试周的消费结构会不会改变、开学季和期末食堂的人流差异有多大这些结论对食堂的资源配置会非常有参考价值。我个人的习惯是一个分析项目不要做到“写完报告”就停止多问自己一句“这个结论如果给运营方看他们能拿来做什么”。换个视角看数据你能发现的东西往往比作业要求的多得多。
返回列表