
简介机器学习在餐饮企业数据分析与预测中的完整实践资料包面向数据分析学习者、算法工程师以及需要做经营决策的餐饮从业者解决销售趋势预测、菜品推荐和用户流失分析等实际问题。资源文件共43个压缩包约1.19MB包含27个CSV数据集、6个Python脚本、7个PNG可视化图片、2个XLSX字段说明以及1个npz数据文件覆盖原始数据、清洗结果、特征工程与模型输出等完整链路。已有242人学习下载。内容提供顾客信息、菜品订单、营业额等多维度数据及详细字段说明代码模块涵盖数据获取、预处理、探索性分析、模型构建与评价、Apriori关联规则分析等可辅助读者从零复现餐饮数据分析全流程。可视化图表展示了时序特征和预测效果便于快速把握业务规律并迁移至其他行业应用。1. 餐饮企业综合数据分析及预测一份数据集能挖出多少经营决策餐饮老板最头疼的不是菜品难吃而是每天打烊后看着订单流水发愣明天该备多少货下个星期哪个菜该下架会员流失到什么程度该干预这些问题靠直觉拍板旺季断货淡季积压是常态。机器学习餐饮企业综合数据分析及预测含数据集及说明要解决的正是把流水、菜单、会员、评价这些散乱数据变成可执行的经营建议。严格说这不是一个现成的软件包而是一条完整的数据分析流水线从数据清洗、特征工程到营收预测、销量预测和会员流失预警每一步都有成熟的实现路径。适合手里有一家或多家门店、已经积累了几个月以上营业数据的从业者也适合想拿真实业务练手的数据分析初学者。这套方法跑通之后备货量、促销时机、会员召回这些决定毛利的事终于不再靠猜。2. 先把数据看懂再做模型餐饮数据集的结构、清洗与特征工程2.1 数据集常见的字段与业务含义餐饮企业的数据通常来自三个系统收银机POS产生的订单流水、会员系统产生的注册与消费记录、第三方外卖平台导出的配送订单。把这三大块合并成一张宽表是后续所有建模的基础。网上能找到的餐饮企业综合数据集字段大同小异常见的包括这几类字段类别典型字段业务含义建模价值订单维度订单号、下单时间、门店编号记录每一笔交易的时间与地点时间序列的粒度来源菜品维度菜品ID、菜品名称、品类、单价描述卖的是什么销量预测的Y变量数量金额数量、实收金额、折扣金额反映交易规模营收预测的直接目标会员维度会员ID、开卡时间、性别、生日描述消费者画像RFM分层与流失预警评价/质量评分、评价内容、投诉标签反映满意度和食安风险文本分类与情感分析需要注意数据集说明里的说明文档通常标注了字段含义和单位但真实业务数据里没有这么干净。我一般拿到数据的第一件事不是跑模型而是先按订单号和时间排序肉眼扫一遍数据的前几百行确认这个数据到底是日汇总还是订单明细。如果是明细日期字段精确到秒做时间序列预测时要先按天或按小时聚合如果是汇总还要确认汇总口径是按门店还是按品类。2.2 数据清洗的三个高频坑数据清洗看起来是体力活但餐饮数据有几个特殊的地方不处理干净后面模型再怎么调参都是白搭。第一个坑是重复订单。POS系统在网络抖动时会重传订单导致同一条记录出现两次。常见做法是按订单号加菜品ID去重但如果订单号本身就有重复就需要用下单时间加金额作为联合去重键import pandas as pd df pd.read_csv(restaurant_orders.csv, parse_dates[order_time]) # 1. 删除字段全完全相同的行 df df.drop_duplicates() # 2. 用业务键二次去重订单号菜品下单时间金额 df df.drop_duplicates( subset[order_id, dish_id, order_time, amount], keepfirst ) # 3. 检查金额是否为负退款单通常在同一个表里 refund_mask df[amount] 0 print(f退款记录数: {refund_mask.sum()})逻辑说明drop_duplicates是pandas最基础的去重手段第一步去掉完全一样的行第二步用业务键兜底。refund_mask找出退款记录这类记录后续要么单独分析要么从正常营收中剔除不能直接删掉因为退款本身就是一个值得预测的经营信号。第二个坑是营业时间不一致。餐饮店不是全年无休的春节、台风、装修都会导致某些天没有订单。如果直接把缺失日期填成0模型会把休息日和营业但没生意混为一谈。我的习惯是单独维护一张营业日历表标记每天是否营业、是否有促销活动然后把它和订单表做左连接。预测时模型需要知道目标日期是不是特殊日而不是靠历史数据瞎猜。第三个坑是菜品改名和停售。餐饮菜单经常调整同一个菜可能从酸辣土豆丝改成土豆丝辣菜品ID也会变。如果按菜品ID建特征改一次名就断了一次连续性。这个没有完美的自动解法我通常会把菜品ID映射到菜品家族ID把口味微调、摆盘变化归到同一个家族下保证时间序列连续。2.3 特征工程的几个必做变换餐饮数据的特征工程核心是回答哪些因素在影响明天的营收/销量常见特征可以分成三类时间特征、滞后特征、外部特征。时间特征是把日期拆出星期几、是否周末、是否节假日、是否促销日、月份。餐饮消费有强烈的星期效应工作日的午市和晚市走势完全不同周五晚和周六晚是两个峰值。这类特征用一个简单的函数就能生成def build_time_features(df): df df.copy() df[weekday] df[order_time].dt.weekday df[is_weekend] df[weekday].isin([5, 6]).astype(int) df[hour] df[order_time].dt.hour df[month] df[order_time].dt.month # 简单节假日标注周一至周日中法定节假日需要外部数据辅助 df[is_holiday] 0 return df逻辑说明weekday取值范围0到6其中5、6对应周六周日。hour字段对门店型餐饮很重要午市11-13点和晚市17-20点的销量结构完全不同如果做小时级预测hour是必加特征。is_holiday这里先置0真实场景中建议手动整理一份近三年的法定节假日表并合并进来这是餐饮预测里最值得花时间的特征之一。滞后特征是把历史销量搬到当前行。比如预测明天某菜品的销量可以取过去7天同菜品的平均销量、昨天销量、上周同星期几的销量。这类特征对模型的提升往往最大但也是数据泄漏的重灾区后面避坑章节会展开讲。外部特征包括天气温度、降雨量、附近是否有大型活动这些通常需要额外接接口数据集里没有的话可以留空不强求。特征工程的原则是先从时间特征和滞后特征入手把这两块的增益吃透再去碰外部数据否则很容易陷入特征越来越多、模型效果却原地踏步的窘境。3. 营收预测与菜品销量预测两个最实用模型的落地3.1 营收预测从线性回归起步的基准线营收预测是最能给老板交出成绩单的模型。它回答的是明天/下周大概能卖多少钱直接影响备货和排班。很多教程一上来就上XGBoost、LSTM但实际业务里线性回归和随机森林往往已经能拿到80%的效果而且可解释性好得多——老板问为什么预测这个数时你能指着特征说是因为上周同期下雨。先把订单数据按天聚合得到每天的营收序列daily_revenue ( df[df[amount] 0] # 剔除退款 .groupby(df[order_time].dt.date)[amount] .sum() .reset_index() ) daily_revenue.columns [date, revenue] daily_revenue build_time_features(daily_revenue) # 构造滞后特征昨天营收、上周同星期营收、7日均值 daily_revenue[rev_lag1] daily_revenue[revenue].shift(1) daily_revenue[rev_lag7] daily_revenue[revenue].shift(7) daily_revenue[rev_ma7] daily_revenue[revenue].rolling(7).mean() daily_revenue daily_revenue.dropna()逻辑说明shift(1)表示取前一天的值shift(7)表示取七天前的值rolling(7).mean()是过去七天的滚动均值。这三个滞后特征捕捉了短期惯性、周期性、趋势性。注意dropna会把前7行丢掉这是滞后特征必须付出的代价样本量不够时可以缩小滞后窗口。然后划分训练集和测试集。时间序列的划分不能用随机切分要按时间顺序否则就是作弊from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_percentage_error train daily_revenue.iloc[:-30] test daily_revenue.iloc[-30:] features [weekday, is_weekend, rev_lag1, rev_lag7, rev_ma7, month] model LinearRegression() model.fit(train[features], train[revenue]) pred model.predict(test[features]) mape mean_absolute_percentage_error(test[revenue], pred) print(f未来30天MAPE: {mape:.2%})参数说明iloc[:-30]表示拿最后30天做测试集因为餐饮数据的季节性周期是7天30天差不多能覆盖4个完整周期能看出模型在周维度上的稳定性。features列表里weekday是类别型特征线性回归直接吃数值也可以但更严谨的做法是进行one-hot编码否则周一和周二会被强行赋予线性关系。MAPE平均绝对百分比误差在营收预测里比RMSE更直观老板能直接听懂预测偏差几个点。线性回归跑完之后不要急着下结论先看两个东西一是MAPE的数值餐饮日营收预测能做到10%以内的MAPE已经不错超过20%说明特征或数据有问题二是看预测残差图如果某几天残差特别大去查那天是不是有促销或者附近修路这类事件往往是模型提升的下一个突破口。3.2 销量预测随机森林与时间特征的组合营收预测是总量预测销量预测则是单品预测它的业务价值在于精确备货——每天每种菜品该准备多少份。单品销量预测比营收预测难得多因为单个菜品的波动极大促销日可能翻倍天气热时麻辣菜品销量骤降。我一般不会上来就预测所有菜品而是先挑出销量排名前10的菜品单独建模型剩下的按品类聚合。单品销量预测的特征组和营收预测类似但要加上菜品自身的历史销量、同品类销量、折扣力度这里用随机森林而不是线性回归是因为单品销量和特征之间往往不是线性关系折扣打到7折可能只带来10%的增量打到5折就暴增50%这个拐点线性模型抓不住树模型天然能切出分段关系。from sklearn.ensemble import RandomForestRegressor dish df[df[dish_family_id] D001].copy() dish_daily ( dish.groupby(dish[order_time].dt.date)[quantity] .sum() .reset_index() ) dish_daily.columns [date, qty] dish_daily build_time_features(dish_daily) # 单品特有特征 dish_daily[qty_lag1] dish_daily[qty].shift(1) dish_daily[qty_lag7] dish_daily[qty].shift(7) dish_daily[dish_price] dish[price].iloc[0] # 价格变动时需按日期取 train dish_daily.iloc[:-14] test dish_daily.iloc[-14:] rf RandomForestRegressor( n_estimators300, max_depth8, min_samples_leaf3, random_state42 ) rf.fit(train[features [qty_lag1, qty_lag7]], train[qty]) pred rf.predict(test[features [qty_lag1, qty_lag7]])参数说明n_estimators300表示300棵树餐饮数据量级通常不大300棵是一个接近收敛的量再往上增加对精度提升微乎其微但推理变慢。max_depth8限制树的深度防止过拟合因为单品销量数据量可能只有几百行树太深会把训练集的噪声学进去。min_samples_leaf3强制每片叶子至少3个样本也是配合小样本的防过拟合手段。random_state42固定随机种子保证复现。qty_lag1和qty_lag7的构造逻辑同营收预测区别在于这里是菜品维度滞后特征吃的是同一道菜的历史销量。3.3 模型评估指标怎么选餐饮预测的评估指标选择和业务场景强相关不能一把尺子量到底。营收预测我推荐MAPE加MAE组合看MAPE看相对偏差MAE看绝对偏差——一天预测差2000元和一天差200元老板的感受完全不一样。单品销量预测更推荐用加权MAE把销量高的菜品权重放大因为畅销品少备一份和冷门品少备一份的损失完全不同。还要注意一个容易忽略的问题数据量对指标稳定性的影响。餐饮数据通常没有那么大几百条样本里test集只有几十条一次预测的MAPE波动会很大。我习惯的做法是跑3到5次不同切点的评估取均值和中位数而不是只看一次结果。如果每次的结果忽好忽坏说明模型本身不稳优先去补滞后特征和营业日信息而不是调参。4. 会员分层与流失预警分类任务的完整闭环4.1 会员RFM分层营收和销量预测解决的是货的问题会员分析解决的是人的问题。餐饮行业的会员价值差异极大一个每周来三次的熟客和一个注册后再也没来的沉睡会员运营策略完全不同。RFM最近消费时间、消费频率、消费金额是最经典的分层框架实现不复杂。先按会员ID聚合出三个指标member df[df[member_id].notna()].groupby(member_id).agg( last_order_date(order_time, max), order_count(order_id, nunique), total_spend(amount, sum), first_order_date(order_time, min) ).reset_index() # 计算R距今天数 ref_date df[order_time].max() member[recency_days] (ref_date - member[last_order_date]).dt.days member[frequency] member[order_count] member[monetary] member[total_spend] # 按分位数打标 member[R_score] pd.qcut(member[recency_days], 4, labels[4, 3, 2, 1]) member[F_score] pd.qcut(member[frequency], 4, labels[1, 2, 3, 4]) member[M_score] pd.qcut(member[monetary], 4, labels[1, 2, 3, 4])逻辑说明R表示最近一次消费距今几天R_score越高代表越活跃所以recency_days分位数越低打分越高。F对应消费频次M对应总金额这两个都是越大越好。四等分打1到4分最后把三个分数拼接成三段式的分群标签比如441就是高活跃高频次但低金额的会员对应的是常来但客单价低的用户运营策略应该是推荐高客单价套餐。注意pd.qcut的分位数会随数据集变化新数据加入后分层边界会漂移这是RFM的固有限制习惯上定期重算即可。4.2 流失预警的标签定义与模型训练RFM分层是描述性的流失预警则是预测性的目标未来30天内哪些会员有较大概率不再来消费。流失预警最关键的一步不是选模型而是定义流失这个标签。标签定义有两种思路。第一种是未复购预警把数据切成两个时间段用前一段时间特征期的会员行为做特征看后一段时间观察期内该会员有没有再来消费没有就标记为流失。这种思路最简单直观切分示意如下import datetime as dt feature_end ref_date - dt.timedelta(days30) label_end ref_date df[is_consumed] 1 # 在特征期内聚合会员行为 member_features ( df[df[order_time] feature_end] .groupby(member_id) .agg( freq_feat(order_id, nunique), spend_feat(amount, sum), recency_feat(order_time, max) ) ) # 在观察期内判断是否流失 member_labels ( df[df[order_time] feature_end] .groupby(member_id)[order_id] .nunique() .reset_index() ) member_labels[churned] (member_labels[order_id] 0).astype(int)逻辑说明feature_end是特征期的截止日label_end是数据末尾。df[order_time] feature_end用来筛出特征期的行为df[order_time] feature_end用来筛出观察期的复购情况。观察期内没有任何订单的会员标记为churned1有订单的标记为0。这里有个隐含设定能计算标签的会员必须至少有一次消费在特征期之前否则无法区分新注册未消费和老会员流失这个过滤条件在做merge时要用inner保证。模型训练阶段选择不做特殊化用逻辑回归和梯度提升树各跑一遍对比。逻辑回归的优势是系数可解释比如距离上次消费每多一天流失概率上升X个点这个结果老板听得懂梯度提升树如XGBoost或LightGBM的精度通常更高但解释性差。我在业务落地时通常双轨并行先用逻辑回归做解释性分析定格运营策略再用树模型做批量打分把高流失概率会员名单导给运营做定向召回。5. 餐饮数据分析避坑数据泄漏、节假日偏差与季节性误导数据泄漏是餐饮数据分析里最隐蔽、也最危险的错误轻则模型评估虚高重则上线后预测值完全偏离真实情况。我见过最典型的场景是把当天的促销活动信息放进特征里预测当天营收模型训练时的表现接近完美但真到了预测未来时根本没有当天的活动信息模型直接作废。更隐蔽的泄漏来自滞后特征的构造比如用未来7天的平均销量预测今天的销量这在代码上不容易发现需要逐个特征检查它是否在预测时刻一定可知。检查方法很简单假设现在是某一天晚上10点关掉电脑看看你手上的特征表里每一个字段是不是都能拿到拿不到的就有泄漏嫌疑。5.1 现象训练集MAPE为3%测试集MAPE变成30%有一次我在某个模拟项目X上做营收预测训练集的MAPE低到3%我心里觉得不对劲——餐饮行业的噪声很大通常在10%左右才合理。后来查了特征发现我无意中把当天的实际销量也当作特征放进去了。原因是做数据合并时把原始订单表和聚合特征表做了内连接导致未来信息被带进了训练集。解决方法是重新设计特征构造流程把特征表按时间点快照生成每个特征只依赖该时间点之前的数据测试集再用同样的流程离线重放。5.2 现象模型在节假日后一天大幅高估销量餐饮数据的季节性不只是春夏秋冬还包括每周、每天的节律以及法定节假日带来的脉冲波动。最折磨人的是节假日效应国庆假期前几天销量飙升假期后一天断崖式下跌用一个简单的星期特征完全抓不住。曾经有个模型在假期后的日期预测值比实际值高了接近两倍原因是模型把假期的高销量当成了趋势延续。解决方法是专门为节假日创建一个二值特征并限制滞后特征的窗口让模型知道节假日后的第二天不应该参照节假日的销量而应该参照正常周同期的销量。这个问题的本质是特征和业务日历没有对齐靠模型自己学习这种模式数据量不够时学不出来。5.3 现象预测新店时模型失效如果手里有多家门店的数据训练集里包含新店的早期数据模型预测新店未来时往往表现极差。原因很简单新店没有任何历史滞后特征shift生成的lag特征的值为空模型不知道该怎么预测。解决方法是直接用同商圈老店的数据做迁移或者给新店单独建一个简化模型——只用时间特征和商圈特征不做lag特征。某些数据集说明里提到跨店预测的场景但实际落地时我非常建议每家店单独训练模型除非门店数量实在太多且每店数据量不足。5.4 现象滚动均值特征导致预测严重滞后滚动均值特征rolling mean在时间序列里很有用但有个天然缺陷它对突变不敏感。当营收突然上涨时7日均值要等好几天才能跟上实际值导致预测值系统性偏低。我一般的做法是同时保留滚动均值和滞后值两个特征让模型自己学习在什么情况下更信任哪个。也可以用指数加权移动平均EWMA代替简单移动平均让近期的数据权重更大衰减速度可以用alpha参数控制alpha越大对近期数据越敏感顺便也缓解了滞后失真。5.5 现象拆分数据集时随机切分导致指标虚高时间序列数据的训练集和测试集必须按时间切有些初学者会习惯性地用train_test_split这个函数默认是随机切分的。一旦随机切分训练集里包含未来的数据测试集里包含过去的数据模型等于提前看到了答案。这在餐饮数据里极其危险因为菜品销量有强烈的趋势和周期随机切分会让模型偷看到周期规律。解决方式是强制使用按时间索引的切分比如按日期排序后取前80%为训练集、后20%为测试集。另外评估时也不能只用确定的那20%作为测试集最好是按时间顺序滚动多次评估这个技巧在最后一章展开。6. 用滚动时间窗口验证模型一套通用的验证技巧6.1 滚动时间窗口验证单次切分测试集有一个致命的问题测试集只覆盖了某个时间段比如11月但餐饮消费行为在11月和1月春节前的表现可能完全不同一次测试通过不代表模型在全年都稳定。我习惯用滚动时间窗口做验证代码框架如下def rolling_evaluate(df, features, target, min_train180, step30): results [] dates sorted(df[date].unique()) for test_end in range(min_train, len(dates), step): train_dates dates[:test_end] test_dates dates[test_end:test_endstep] if len(test_dates) 7: continue train df[df[date].isin(train_dates)] test df[df[date].isin(test_dates)] model LinearRegression() model.fit(train[features], train[target]) pred model.predict(test[features]) mape mean_absolute_percentage_error(test[target], pred) results.append({window_end: test_dates[-1], mape: mape}) return pd.DataFrame(results)逻辑说明这个函数从第min_train1个日期开始每次往后推step天做一次训练和预测预测完把窗口加大30天继续。返回的结果能画出MAE随时间的曲线如果某个时间段的误差特别大就去查当时发生了什么业务事件。min_train180表示至少需要半年的训练数据太短模型学不出季节性。step30表示每月验证一次这个频率能覆盖足够的周期又不至于计算量太大。这个技巧最大价值不是调参而是帮你看清模型在一年时间里的稳定性能做到这一点模型上线后的心理预期才踏实。6.2 营业日对齐滚动验证还有一个锦上添花的做法把测试集只留正常营业日剔除非营业日和促销日单独评估模型在常规日的精度。同时把异常日子的误差也单独统计——这能告诉你模型在非常规日到底偏了多少方便提前准备人工预案。这个细节看起来小但你真正向老板汇报模型效果时一句常规营业日误差5%节假日误差12%需要人工干预比一个笼统的平均数有说服力得多。6.3 把自己当成值班经理验证特征最后一个习惯我坚持了很久每构造完一个特征我就问自己一句今天晚上10点我站在收银台后面明天的数据里这个特征我能不能提前知道。lag1、lag7、星期几、是不是节假日这些都能知道未来三天天气当天的实际客流量这些一律不能。这个习惯帮我拦下了好多看似涨点、实则泄漏的特征。餐饮数据的分析说到底不是竞赛刷分是把预测函数稳稳地跑在业务上少一些自欺欺人多一些按时间线重放验证模型自然经得起真实经营的检验。餐饮数据分析及预测这个方向从数据清洗、特征工程到营收和菜品销量预测、再到会员流失预警是一套可以逐步落地的完整方案。很多从业者拿着某份数据集跑了一次线性回归就说我做完了但真正能投入业务的模型至少要在离线滚动验证中证明自己在不同季节、不同节假日下都稳定。我自己吃过数据泄漏的亏也经历过节假日预测翻车的尴尬这些坑总结下来就是一句话尊重时间顺序做到每步特征都可以在预测时刻真实获取。希望你从这套方法论里拿到的不只是一堆代码更是一套能应对真实餐饮业务波动的分析习惯这样你的模型才真正有用。希望帮到你。本文还有配套的精品资源点击获取