ARTICLE DETAIL

资讯详情

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

披萨订单数据集实战:从数据清洗到特征工程与销量预测

披萨订单数据集实战:从数据清洗到特征工程与销量预测 简介一份围绕披萨订单数据集的机器学习实战包面向有一定Python基础、想系统训练数据分析与建模能力的读者。案例覆盖从数据预处理、EDA可视化到决策树回归、网格搜索、交叉验证、聚类和时间序列分解等完整流程适合作为课堂作业、竞赛入门或简历项目的参考。压缩包共21个文件含19个可运行的.py源代码、1个约7.96MB的pizza_sales.csv原始数据集和1个readme.txt说明压缩后仅714KB脚本按分析主题拆分便于按需查阅与复现。目前已有43人学习。代码手工整理、无语法错误综合运用Pandas、Scikit-learn、Seaborn/Matplotlib、Plotly及SciPy/Statsmodels等工具融入统计检验和交互式图表。读者可从数据清洗起步逐步理解回归模型在销售预测中的应用并掌握评估、调参与业务洞察的完整方法省去数据获取和环境配置成本。1. 披萨订单数据集一份自带业务逻辑的机器学习入门资源先说结论这份披萨订单数据集不是那种扫一眼就懂的“玩具数据”。它包含订单表、明细表、披萨类型表之间的关联关系有日期、时间、数量、单价、总价、类别、尺寸等多个字段。用 Python 做机器学习分析时你能同时练到数据清洗、特征工程、时间序列处理和回归预测一整条链路。项目里附带的 19 个源代码文件正好对应从读数据到出预测结果的完整流程7.96 MB 的数据集也足够撑起多次实验不用自己去网上东拼西凑。适合两类人一类是刚学完 Python 基础、想找个真实业务数据集练手的入门者另一类是准备面试、需要拿一个完整分析项目做作品集的同学。它最大的价值在于——订单数据里天然带着“星期几、几点钟、什么品类卖得好”这些可挖掘的业务特征模型能不能预测准拼的就是特征做得好不好而不是模型有多高级。2. 数据长什么样从 CSV 读取到字段含义梳理2.1 打开压缩包后先建立文件清单拿到 zip 包后第一步不是急着跑代码而是把文件结构理清楚。通常这类资源解压后的目录大概是这样的pizza_order_project/ ├── data/ │ ├── orders.csv │ ├── order_details.csv │ └── pizzas.csv ├── notebooks/ │ ├── 01_数据加载与探索.ipynb │ └── 02_可视化分析.ipynb ├── scripts/ │ ├── data_preprocess.py │ ├── feature_engineering.py │ ├── train_model.py │ └── predict.py └── README.md我一般会先打开 README 看它声明的字段说明再手动用 Excel 或者 DataFrame 快速瞄一眼每个 CSV 的表头和前几行。这样做的好处是避免后面写代码时对字段名想当然——比如有的文件里叫pizza_id有的叫pizza_type_id拼写和含义差一点join 的时候就会出问题。实际读取时我习惯先把三个表都读进来分别打印 shape 和 dtypes确认数据类型是否符合预期。日期列在 CSV 里通常是字符串必须显式转成 datetime 类型否则后续做时间序列特征时会直接报错或者得到错误结果。2.2 关联表结构与主键逻辑披萨订单这类数据集通常是星型结构orders是事实表记录每个订单的日期、时间和总价order_details是明细表记录每个订单里具体点了什么披萨、数量多少pizzas和pizza_types是维度表记录披萨的尺寸、类别、配料信息。import pandas as pd orders pd.read_csv(data/orders.csv) order_details pd.read_csv(data/order_details.csv) pizzas pd.read_csv(data/pizzas.csv) # 查看表结构和字段类型 print(orders.shape, order_details.shape, pizzas.shape) print(orders.dtypes) print(order_details.dtypes) print(pizzas.dtypes) # 确认关联字段是否有空值 print(orders.isnull().sum()) print(order_details.isnull().sum())这段代码做的事很简单但很重要先确认三个表的行数是否在一个数量级内再检查关键字段类型最后看空值分布。实际数据里pizza_types表可能包含配料字符串字段这个字段在特征工程阶段会变成文本长度、配料数量等衍生特征。注意主键关联的时候先确认order_id在两个表里的数据类型一致——一个读成了 int一个读成了字符串join 时匹配不上是常事。做了这一步之后你会在脑子里建立起一张图订单和明细是一对多关系披萨和披萨类型是一对一或一对多关系。后面做特征聚合时该在哪个粒度上 groupby就取决于这张关系图。3. 数据清洗与预处理把脏数据变成可喂给模型的样子3.1 缺失值与异常值处理策略真实数据集从来不会干干净净。我打开订单数据后首先会看几件事日期范围是否完整、单价和数量有没有零值或负数、订单明细表里有没有重复行。披萨订单场景里最常见的坑是部分订单可能包含“取消”状态的行或者同一种披萨在同一订单中出现多行本该合并成一行但没合并。# 去除重复行 order_details order_details.drop_duplicates() # 过滤掉数量为0或负数的记录 order_details order_details[order_details[quantity] 0] # 检查订单日期范围 orders[order_date] pd.to_datetime(orders[order_date]) print(orders[order_date].min(), orders[order_date].max()) # 计算订单总价与明细总价是否一致 detail_total order_details.groupby(order_id)[total_price].sum().reset_index() detail_total.columns [order_id, calc_total] merged orders.merge(detail_total, onorder_id, howleft) inconsistency merged[abs(merged[calc_total] - merged[total_price]) 0.01] print(f价格不一致的订单数: {len(inconsistency)})这段代码末尾的“订单总价 vs 明细总价核对”是很多人会跳过的步骤但我觉得它是清洗阶段最有价值的一步。如果发现大量订单的明细汇总和订单表里的总价对不上说明原始数据有录入误差或者订单表本身还包含额外费用比如配送费、折扣。此时就要决定是删掉这些不一致的行还是以明细汇总为准重建总价。常见做法是以明细表为准因为明细是流水记录错误率更低。缺失值处理上披萨数据量的缺失情况通常不严重。个别pizza_type或者ingredients字段缺失时我一般用众数填充或者直接删掉占比很小的缺失行。不建议用均值填充文本类字段这对后续特征工程反而有害。3.2 时间字段拆解与业务分组订单数据的核心价值在时间。原始的order_date和order_time字段如果不做拆分对模型来说只是一个时间戳提取不出任何周期性规律。所以特征工程的第一个动作就是把时间拆成年、月、日、星期几、小时、是否为周末等。orders[order_date] pd.to_datetime(orders[order_date]) orders[order_time] pd.to_datetime(orders[order_time], format%H:%M:%S) orders[hour] orders[order_time].dt.hour orders[weekday] orders[order_date].dt.weekday # 周一0 orders[is_weekend] orders[weekday].apply(lambda x: 1 if x 5 else 0) orders[month] orders[order_date].dt.month orders[day_of_month] orders[order_date].dt.day # 将时间窗口划分成业务时段 def time_period(hour): if 6 hour 11: return morning elif 11 hour 14: return lunch_rush elif 14 hour 17: return afternoon elif 17 hour 21: return dinner_rush else: return night orders[time_period] orders[hour].apply(time_period)weekday是 0 到 6 的整数比直接用字符串“星期一”更适合作为模型输入。time_period的划分不是固定的你可以根据数据分布调整阈值——如果发现某个门店的午餐高峰在 10:30 开始就把 11 改成 10。这种业务分组特征对树模型特别友好它把连续变量转成了有业务含义的离散变量模型很容易学出“晚餐高峰时段销量高”这类规则。我一般在做这一步的同时顺手做一次探索性可视化按小时画销量柱状图、按星期画平均订单量折线图。这能验证字段拆分得对不对——如果画出来的图没有明显的双峰或者周期性说明时间字段可能有解析问题需要回头检查。4. 特征工程到模型训练从聚合特征到预测披萨销量4.1 聚合特征构建把明细表变成训练样本建模之前先想清楚一个问题预测目标是什么如果是预测“未来某天某个时段的总销量”那训练样本就应该按小时或按天聚合如果是预测“某类披萨的销量”那就要在明细表上按类别聚合。特征工程的粒度决定了模型能回答什么问题。以“按小时预测总订单量”为例训练样本的构造逻辑是把明细表按订单日期小时聚合得到每个时段的总订单数和总披萨数量再关联当天是星期几、是否周末、是否节假日等外部特征。# 按小时聚合订单量 order_details order_details.merge( orders[[order_id, order_date, hour, weekday, is_weekend]], onorder_id, howleft ) hourly_sales order_details.groupby([order_date, hour]).agg( total_orders(order_id, nunique), total_pizzas(quantity, sum), total_revenue(total_price, sum) ).reset_index() # 添加时间特征 hourly_sales[weekday] pd.to_datetime(hourly_sales[order_date]).dt.weekday hourly_sales[is_weekend] hourly_sales[weekday].apply(lambda x: 1 if x 5 else 0) hourly_sales[hour] hourly_sales[hour].astype(int) # 添加滞后特征前一天同时段的销量 hourly_sales hourly_sales.sort_values([order_date, hour]) hourly_sales[lag_1_day_sales] hourly_sales.groupby(hour)[total_orders].shift(1) print(hourly_sales.head(10))shift(1)在这里是精髓。按小时分组后 shift拿的是每个小时“前一天同一小时”的销量而不是上一行的销量。这两者区别很大用错了滞后特征直接让模型学到错误的时间依赖。groupby(hour)保证 shift 只在同一小时窗口内移动跨日期序列的连续性就靠这个操作维持。聚合后的数据集行数会比明细表少很多。这时候可以做几次简单的可视化确认聚合结果没有异常断层——比如某一天的数据缺失导致滞后特征全是 NaN这种要提前处理。4.2 训练集划分与模型选型披萨销量预测本质上是回归问题。常用的候选模型有线性回归作为基线、随机森林、XGBoost、LightGBM。我一般先用线性回归搭一个基线看 RMSE 大概是什么水平然后直接上 LightGBM 对比提升幅度。import lightgbm as lgb from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error # 丢弃NaN的滞后特征行 model_data hourly_sales.dropna(subset[lag_1_day_sales]).copy() features [hour, weekday, is_weekend, lag_1_day_sales] X model_data[features] y model_data[total_orders] # 按时间顺序划分防止数据泄露 split_idx int(len(X) * 0.8) X_train, X_val X.iloc[:split_idx], X.iloc[split_idx:] y_train, y_val y.iloc[:split_idx], y.iloc[split_idx:] model lgb.LGBMRegressor( n_estimators200, learning_rate0.05, max_depth5, num_leaves15, random_state42 ) model.fit(X_train, y_train) y_pred model.predict(X_val) rmse mean_squared_error(y_val, y_pred, squaredFalse) print(f验证集 RMSE: {rmse:.2f})注意训练集划分用了“按时间顺序切分”而不是随机切分。时间序列数据如果用train_test_split默认的随机切分未来数据会混进训练集模型评估结果会虚高这个坑在第 5 章会重点讲。squaredFalse参数把 MSE 转成 RMSE单位是“订单数”业务上更好解释。num_leaves15对应max_depth5这个配套关系是 LightGBM 的典型设置。叶子数超过2^max_depth时模型容易过拟合新手常犯的错是只调max_depth不改num_leaves导致模型能力超出预期。在披萨订单这种样本量不算大的数据集上小树配合低学习率效果更稳。4.3 特征重要性读取与业务验证模型跑通后一定要看特征重要性。这能验证之前做的特征工程有没有生效也能反向帮我们理解业务规律。# 获取特征重要性 importance pd.DataFrame({ feature: features, importance: model.feature_importances_ }).sort_values(importance, ascendingFalse) print(importance) # 查看模型对时间的理解是否合理 import matplotlib.pyplot as plt hour_range pd.DataFrame({ hour: list(range(10, 22)), weekday: [2] * 12, is_weekend: [0] * 12, lag_1_day_sales: [20] * 12 }) pred_sales model.predict(hour_range) plt.plot(hour_range[hour], pred_sales) plt.xlabel(Hour) plt.ylabel(Predicted Orders) plt.show()如果lag_1_day_sales的特征重要性排在第一位说明销量确实有周期性昨天的同一时段销量对今天有很强的参考价值。如果hour排第一说明时间段的区分度比历史数据更强。这个输出结果写进分析报告里是最有说服力的素材——它证明你不是把数据喂给黑匣子就完事而是真的理解了每个特征的作用。用pandas.DataFrame包装特征重要性输出而不是直接打印数组后面做可视化会方便很多。5. 避坑与常见问题数据泄露、时间格式与采样偏差5.1 数据泄露随机划分训练集导致评估虚高现象用train_test_split(X, y, test_size0.2, random_state42)随机划分后模型在验证集上表现非常好RMSE 很低但一旦拿最新的真实订单去预测误差立刻变大。原因时间序列数据里相邻日期的样本具有高度相似性。随机划分时同一天或相邻日期的样本分别出现在训练集和验证集中模型相当于“见过”未来数据的相似版本评估结果自然虚高。披萨订单这种按天记录的数据这个效应尤其明显。解决强制执行时间顺序划分——先按日期排序取前 80% 做训练后 20% 做验证。如果要做交叉验证用TimeSeriesSplit而不是KFold。我习惯把划分逻辑封装成一个带日期参数的小工具函数每次建模前强制走一遍避免临时用错。5.2 日期时间解析静默失败现象代码不报错但画图时发现订单量的时间分布完全不对——比如所有订单都集中在一个小时或者日期排序错乱。原因CSV 里的时间字段统一被 pandas 推断为字符串某些行的时间格式不标准比如15:00:00变成了15:0:00直接pd.to_datetime解析时这些行变成了NaT后续聚合统计默认忽略NaT导致数据量减少且分布异常。解决解析时间时加上errorscoerce再显式检查isna的比例。不要相信 pandas 的自动推断手动指定format%H:%M:%S能提前暴露格式问题。5.3 小时聚合时跨天数据错位现象滞后特征算出来后发现凌晨 0 点到 2 点的预测值异常偏高甚至比午餐高峰还高。原因披萨订单数据里凌晨时段本身单量极少某些天可能完全没有订单。groupby(hour).shift(1)遇到缺失的前一天数据时会返回 NaN默认删除后模型没见过这些“稀疏时段”的真实分布预测值就倾向于使用滞后值的扩散结果。解决凌晨时段单独处理或者直接删除凌晨 0-6 点的数据再建模。对这个场景删除比填充更好——凌晨单量低到对业务决策没有参考价值留着只会干扰模型学习正常时段规律。5.4 价格一致性校验被忽略现象模型加进total_price相关特征后准确率反而下降且特征重要性里total_price排名很低。原因明细表里某几行total_price和quantity * unit_price不一致——可能是原始录入时单价变了但总价没更新导致模型学到的是错误的相关性。解决在预处理阶段加入价格一致性校验对不一致的样本统一按quantity * unit_price重算总价并记录修正了多少行。这些修正记录在写报告时还能作为数据质量的佐证。6. 进阶给模型加一处“后悔药”验证用 SHAP 解释预测逻辑模型训练完、评估指标也不错但离“能说服别人”还差一步。很多人拿到 RMSE 就结束了但其实用一个稍微进阶的手段——SHAP 值分析能看出模型到底靠什么特征做决策这对于业务方理解预测结果至关重要。import shap # 用训练好的模型构建 SHAP explainer explainer shap.TreeExplainer(model) shap_values explainer.shap_values(X_val) # 输出每个特征对预测的贡献方向 shap.summary_plot(shap_values, X_val, feature_namesfeatures)shap.TreeExplainer对 LightGBM 这类树模型是零成本集成的不需要额外训练模型直接拿现有模型做解释。输出图里每个点代表一个样本横轴是 SHAP 值表示该特征对这个样本预测结果的贡献方向和大小。如果hour的 SHAP 值在 11-13 点区间内集中为正值说明午餐高峰对预测的拉动作用被模型明确学到了——这个结论可以直接写进报告里。使用 SHAP 前要确认版本兼容性。新版本的shap.Explainer(model, X_val)和旧版TreeExplainer接口有差异如果你的环境报AttributeError退回用shap.TreeExplainer大概率能跑通。这个库和 pandas 的版本冲突偶尔会出现建议在虚拟环境里单独安装。除了 SHAP另一个值得做的小实验是验证滞后特征的时间窗敏感性。把lag_1_day_sales换成“过去 7 天同一小时的平均销量”再和原模型对比 RMSE。这个对比能提供一个有价值的结论披萨订单的周期性到底以一周为周期还是以一天为周期。如果 7 天均值特征效果更好说明周周期性更明显如果 1 天滞后效果更好说明日周期性占主导。两种结果写进报告里都是亮点因为它展示了你对时间特征的理解不是停留在表面。这几步走完之后你会收获一份完整的训练脚本加载 → 清洗 → 聚合 → 建模 → 解释、一组可复现的实验结果以及一个拿得出手的 SHAP 可视化。每次跑完模型我都强制自己走一遍 SHAP哪怕只是扫一眼图——我已经不止一次靠这个发现了特征之间的隐藏交互比如“周末晚上”这个组合的预测贡献值明显大于“周末”和“晚上”分别的价值之和。这种发现光看 RMSE 永远看不到。希望这些经验能帮你在同样的数据上少走几步弯路。本文还有配套的精品资源点击获取
返回列表