ARTICLE DETAIL

资讯详情

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

Zara销售预测实战:短周期快时尚数据建模与滚动预测全流程

Zara销售预测实战:短周期快时尚数据建模与滚动预测全流程 简介从数据清洗到销售预测这是一套基于Zara门店产品销售数据的完整Python实战方案面向机器学习初学者和商业分析人员也适合作为课程设计或数据分析入门竞赛的参考模板。资源共11个文件包含9个可直接运行的Python源码脚本、1个CSV数据集81.20 KB以及1份说明文档压缩包约28 KB轻量且目录清晰。目前已有95人学习该资源。脚本覆盖基础数据预处理、销售概览与探索性分析、店铺评论回顾、销量趋势分析和预测建模等环节借助pandas、matplotlib、seaborn与卡方检验等常用模块演示了从数据检查到模型评估的完整流程。其中时间序列与分类预测部分可用于理解销售预测的核心思路帮助读者避开常见的数据清洗和特征处理误区快速迁移到自有业务数据集。1. 为什么 Zara 的销售预测比一般零售更难Zara 的销售节奏是“小批量、多款式、高频上新”一件衣服的生命周期常常只有三到六周。传统零售用月度销量做预测在这里会直接失效等统计完上个月的滞销数据货已经错过了清仓窗口。所以做 Zara 商店销售数据分析预测重点不是把 MAE 压到几十件而是能不能在有限的历史窗口里捕捉到促销、上新和天气带来的短期脉冲。这类压缩包项目通常包含多个源代码和一份几十 KB 的销售数据集适合把“数据读取 - 清洗 - 特征构造 - 建模 - 滚动预测”整条链路跑一遍。对于想练手销售预测的数据分析师和算法工程师真正有价值的是理解各种预测方案在短生命周期场景下的取舍而不是追求某个指标的数字。下面我围绕一个典型的 Zara 销售分析预测项目从数据解压开始到滚动预测落地按实际工程顺序讲清楚每一步的做法和常见坑。2. 拆包与数据清洗把 81.20 KB 的原始销售数据变成可建模的结构2.1 解压与文件盘点9 个源代码的典型分工拿到一个 .zip 的项目包第一件事不是直接跑模型而是先确认压缩包内部的文件构成。使用 Python 的 zipfile 标准库可以快速列出内容这样能避免写死路径导致后续脚本运行失败。如果包里还有 README 或说明文档先读它很多项目会在里面标注数据字典和运行顺序。import zipfile from pathlib import Path project_zip Path(Zara销售分析预测实例.zip) with zipfile.ZipFile(project_zip, r) as zf: for file_info in zf.infolist(): print(f{file_info.filename} {file_info.file_size} bytes)代码里的zipfile.ZipFile以只读方式打开压缩包infolist()返回文件信息对象列表。注意file_info.file_size是压缩前的原始大小不是磁盘占用。执行后会列出包内文件和目录你通常会看到几个按序号命名的 Python 脚本外加一个销售数据 CSV。虽然每个项目的命名习惯不同但一般会覆盖数据加载、探索性分析、特征工程、模型训练和结果可视化这几个阶段。就算不同脚本之间有重复代码保留这种拆分方式也方便后续单独调试尤其是特征和模型两部分改动频繁拆开比揉在一起更省时间。提示如果压缩包内的数据集文件名带中文或空格不要用硬编码路径可以使用zf.extractall()解压后再通过Path.glob()定位避免编码问题。整个数据集只有 81.20 KB说明是抽样后的样本。面对这种规模的数据要意识到底层逻辑是“验证方案”而不是“刷榜”。真实业务中的销售明细动辄几个 GB但问题的核心结构是一样的缺失值、时间粒度、节假日效应。所以在这个包里跑通流程后迁移到生产环境时只需要替换数据源和调整连接参数整体代码结构可以复用。2.2 缺失值和异常值处理销售数据不能直接填 0Zara 的分销数据是按门店、日期、款式记录的经常出现某些门店某天没有销量的情况。这里的“0”要分清是真正没人买还是当天的销售记录没有上传。直接填 0 会导致数据集严重稀疏后续的滞后特征和均值统计都会失真。一般处理方法先看缺失比例再结合销售日期序列判断。import pandas as pd # 假设解压后得到销售明细 df pd.read_csv(zara_sales_detail.csv, parse_dates[date]) print(df.info()) missing df.isnull().sum() print(missing[missing 0]) print(重复记录数:, df.duplicated().sum())执行后会输出每一列的缺失计数和重复记录数。这个阶段不要急着删除更不要用全局均值填充。销售预测里的缺失通常有业务原因比如新开商场的数据开始日期晚、部分 SKU 中途下架。常见做法是把与日期无关的缺失用“未知”类别填充与销量相关的缺失则用上一个有效销售周的值前向填充。下面是一个常用的选择对照填充方式适用场景风险直接填 0确认停售或未铺设的 SKU把低销量 SKU 的高波动期误判为稳定全局均值填充几乎没有业务依据的数据抹掉季节性差异时间线前向填充短期未更新的记录长假前多个周期的累积误差按 SKU 滑动窗口平均值填充新品上架初期缺少历史窗口太短时仍会不稳定为什么不是线性插值因为销售序列不是连续的周末和周一的销量差距本身就很大插值出来的数字没有任何业务含义。前向填充保留“上一个真实值”对预测模型来说更安全至少不会凭空造出一个从未出现过的销售水平。处理完缺失值之后检查异常销量的方法是按 SKU 分组做百分位分析。比如对每天的销量计算 p99超过 p99 的多倍记录往往是大促或清仓的影响不能像模型训练里的离群点一样直接剔除。正确做法是把这些点单独标记为“promotion_flag”让模型自己学习促销期的销量放大系数。2.3 库存单位粒度与聚合粒度选择销售预测的粒度不同模型复杂度相差很大。预测单品、单门店、单日的销量数据噪声大很难直接用年份做特征预测全店日销量信息量又不足。常见做法是先在 SKU - 日期 - 门店粒度上做 EDA再聚合到 SKU - 周级别。粒度一旦定下来损失函数和评估口径也要跟着定否则后面比较模型结果会失真。weekly df.groupby([sku, store, pd.Grouper(keydate, freqW)])[sales].sum().reset_index() print(weekly.head())代码里的pd.Grouper(keydate, freqW)是按周分组W默认到周日如果业务周从周一开始用W-MON。聚合到周级别后周内日波动被抹平模型更容易学到 SKU 的长期趋势并且数据量缩小到七分之一。对 Zara 这类短周期商品周粒度是准确性和时效性的平衡点。如果目标是要做配货到门店的计划流程还可以继续按店铺维度拆分每个店一个模型但前提是各店至少要有 12 周以上的历史否则模型的系数很容易被少数几周的异常活动带偏。2.4 用 EDA 验证聚合结果聚合之后不能直接拿去训练应该先用分组统计和可视化的方式检查有没有空店铺、空周次。一个常见的检查是生成“周 x SKU”的透视表观察有多少格子是空的空比例超过 30% 的数据如果直接参与建模很多 SKU 会被当作长期无销量导致预测结果整体偏低。pivot weekly.pivot_table(indexdate, columnssku, valuessales, aggfuncsum) sparsity (pivot 0).mean().mean() print(f稀疏度: {sparsity:.2f}) import matplotlib.pyplot as plt weekly.groupby(date)[sales].sum().plot(titleWeekly Sales Trend) plt.xlabel(Week) plt.ylabel(Sales) plt.show()pivot_table里的aggfuncsum用于合并同一时间格内多个门店的销量(pivot 0).mean().mean()先按列计算零值比例再对所有列平均得到整体稀疏度。如果稀疏度高于 0.5建议考虑把“是否有销量”做成一个独立的二分类问题而不是只做销量回归因为两者需要的特征并不完全一样。趋势图的作用是快速看出是否存在明显的季节性和衰退期方便后续构造时间特征。3. 特征工程Zara 销售数据里哪些信号真的影响预测3.1 时间特征星期、月份、节假日与促销日历数据清洗完成后的第一步是构造时间特征。Python 的 pandas 可以快速从日期字段提取星期、月份、季度等但这些基础特征不足以支撑销售预测。Zara 的线下门店和线上渠道对节假日的反应不同线下门店受周末影响明显线上“双十一”“黑五”等活动日会带来几十倍的流量。所以除了星期和月份还要构建“是否周末”“距离上个大型活动多少天”“当前周次是否在促销期内”这类业务透视特征。df[weekday] df[date].dt.weekday df[is_weekend] df[weekday].isin([5, 6]).astype(int) df[month] df[date].dt.month # 假设从公司日历表得到每个日期是否处于活动期 df df.merge(calendar_df[[date, promotion, holiday]], ondate, howleft)这段代码中weekday的范围是 0-65 和 6 对应周六周日is_weekend是二值标记promotion和holiday从额外的日历表合并进来而不是靠模型自己猜。为什么要单独建日历表因为各个地区的节假日和促销时间不同写死在代码里很难维护后期调整活动时间时只需要改一张表。这也是销售预测项目中少数一定要业务方深度参与的特征工程环节。另外活动前两周通常会出现销量缓慢下滑等促销当天再冲高所以最好再构造一个days_to_promotion的倒计时特征像“距活动还有几天”这样的信号对树模型帮助很大。3.2 商品特征品类、定价区间与上市周数Zara 的商品特征是“属性表”和“价格表”合并进来的典型字段包括品类、颜色、尺寸、定价、原价等。直接用价格作为数值特征不理想因为不同品类的定价区间差异很大。常见做法是按 SKU 的价格分位划分成档位比如低价引流款、标准款、高价款。另一个重要特征是“上市周数”也就是从商品首次上架到当前日期的时间长度用来捕捉新品的热度衰减。first_sale df.groupby(sku)[date].min().rename(first_sale_date) df df.merge(first_sale, onsku, howleft) df[week_after_launch] (df[date] - df[first_sale_date]).dt.days // 7week_after_launch的单位是周dt.days // 7把天数转化成整周。这个特征对快时尚非常重要因为多数新款在第 3 到第 4 周销量到达峰值之后快速走低。如果不加它模型会把所有时间段的销量同等对待无法区分刚上新的 200 件和季末清仓的 200 件。价格带可以用pd.qcut划分但要注意先在全量商品上计算分位数避免每个批次单独计算导致划分不一致。特征名构造方式预测含义品类编码将品类列做 label encoding 或 one-hot不同品类生命周期差异价格带按商品原价划分 0/1/2/3 档高价款销量波动更大上市周数当前日期 - 首次上架日期按周计算快时尚上市 2-4 周达到峰值周环比存量上周库存 - 本周库存补货计划影响短期销量3.3 滞后特征与目标编码用历史销量预测未来销量销售预测的本质是用过去推断未来。给模型输入前 1 周、前 2 周、前 4 周的销量往往比任何复杂模型都有效。但这种滞后特征构造不当会带来严重的标签泄漏例如在逐日预测时模型可能会优先选择最近几天的销量而忽略其他特征遇到新品没有历史时会直接失效。因此要设定一个最短历史窗口阈值例如小于 3 周历史的样本从训练集中排除或者在特征里加一个“是否有历史数据”的标记。for lag in [1, 2, 4]: shifted min_3weeks.groupby([sku, store])[sales].shift(lag) min_3weeks[fsales_lag_{lag}_w] shifted min_3weeks[sales_lag_1_w].fillna(min_3weeks[sales_lag_1_w].median(), inplaceTrue)这里按 SKU 和门店分组对周销量做 shiftlag 为 1 表示往前推一周。fillna用中位数而不是 0是为了避免新品首周被当成特低价样本。滞后特征常见还有个坑直接使用原始销量在两个相邻周期之间会形成极高的自相关从而让模型忽略促销、天气等外生变量。需要在特征重要性上做监控如果 lag1 的重要性超过 70%说明模型退化成了“上期销量基本等于本期”这时应该考虑改预测对数销量或者增加差分特征。3.4 类别特征编码的工程选择销售数据里的品类、渠道、门店都属于高基数类别直接用 one-hot 会产生大量稀疏列在 81.20 KB 的小数据集上容易给模型带来噪声。常见做法是分两步先计算类别频率出现次数少于 10 次的统一归为“other”然后再用目标编码把类别映射成历史销量均值。目标编码有泄漏风险必须按时间拆分后的训练集单独统计不能把验证集信息带入。freq df[category].value_counts() rare_cats freq[freq 10].index df[category] df[category].replace(list(rare_cats), other)这段代码先把低频类别合并减少维度。目标编码的公式是category_mean df.groupby(category)[sales].transform(mean)但更稳妥的做法是对每个类别额外叠加一个先验系数(count * category_mean prior * overall_mean) / (count prior)这样可以避免只有一两条历史记录的类别直接吃掉一个极端的均值。这个平滑公式在销售预测中比纯均值更稳定尤其适合 Zara 这种新品频繁出现的场景。4. 模型选型与参数调优从线性回归到 LightGBM 的完整对比4.1 基准模型线性回归的实际效果先用一个最简单的线性回归作为基准不只是为了拿分数更是为了建立一个“必须打败的下限”。在销售预测里线性回归通常把销量当作各特征的线性加权和它无法自动处理销量分布的右偏所以一般要先对目标做 log1p 变换。如果特征里同时有数值和类别线性回归需要先做标准化否则价格这种大数值字段会把类别特征系数压低。from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error, mean_squared_error import numpy as np y np.log1p(train[sales]) lr LinearRegression() lr.fit(X_train, y) pred_log lr.predict(X_valid) mae mean_absolute_error(np.expm1(y_valid), np.expm1(pred_log)) print(fLinearRegression MAE: {mae:.3f})这里的log1p和expm1是成对使用的分别做log(1x)和exp(x)-1避免出现 log(0) 并且预测还原后不会有负值。线性回归的优势是可解释性强回归系数可以直接输出查看每个特征的量级劣势是它假设特征与销量之间存在线性关系遇到促销日的非线性放大时误差会急剧上升。如果线性回归在验证集上的 MAE 已经足够满足业务需求这个过程也说明当前特征和问题足够简单不需要立刻上树模型。4.2 时间序列交叉验证不用普通 KFold 的理由销售预测是严格按时间排序的数据普通 KFold 随机打乱样本会破坏时间依赖让模型通过“未来”数据学习“过去”的规律验证分数虚高。常见的做法是使用基于时间的滚动划分每次用前 70% 的时间窗口训练后 30% 验证然后依次向后滑动。scikit-learn 的TimeSeriesSplit正是以这种逻辑来切分的。from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits5) for train_idx, valid_idx in tscv.split(X): X_train_cv, X_valid_cv X.iloc[train_idx], X.iloc[valid_idx] y_train_cv, y_valid_cv y[train_idx], y[valid_idx] # 每一折都重新训练模型并记录验证指标TimeSeriesSplit的n_splits5表示生成 5 组训练集和验证集训练集在逐步扩大验证集永远在训练集之后。这种做法会执行 5 次完整训练对 81.20 KB 这种级别的数据很快但对生产级大数据会明显增加耗时。一个妥协办法是只跑最近 3 次滑动窗口保留最近的数据分布变化。注意切分之前要对整个数据集按日期排序并且不能把未来的促销信息合入训练集。两种方式的核心区别是切分方式训练集与验证集关系风险KFold随机打乱验证集可能出现在训练集之前数据泄漏指标虚高TimeSeriesSplit验证集严格在训练集之后更接近真实预测场景4.3 LightGBM 的必调参数与效果对比树模型是销售预测项目里最常见的落地选择LightGBM 相比 XGBoost 训练更快且自带类别特征处理。需要优先调整的参数包括num_leaves、learning_rate、min_data_in_leaf和feature_fraction。下面的配置是一个适合周粒度销售数据的稳定起步值。import lightgbm as lgb model lgb.LGBMRegressor( n_estimators300, learning_rate0.05, num_leaves31, min_data_in_leaf20, feature_fraction0.8, subsample0.8, random_state42 ) model.fit( X_train, y_train, eval_set[(X_valid, y_valid)], eval_metricmae, callbacks[lgb.early_stopping(50), lgb.log_evaluation(25)] )参数含义num_leaves控制树的复杂度周数据一般 15-31 起步min_data_in_leaf防止叶子节点样本太少值太小会导致过拟合feature_fraction和subsample是列采样和行采样用来增加随机性learning_rate越低需要更多提升轮数配合 early_stopping 避免训过头。eval_metricmae对应业务关心的误差比默认的 L2 损失更直观。跑完后可以用下面的对比表快速判断模型MAERMSE训练时间线性回归 (log1p)3.215.840.12sLightGBM 默认参数2.354.901.50sLightGBM 调参后2.084.213.20s注意表里的数据是示例值真正运行时的数值取决于数据集分布。重点观察 MAE 和 RMSE 的差距如果 RMSE 远大于 MAE说明模型在少数极端销量日上的误差很大这时候需要检查是不是促销期样本过少考虑用权重把促销日的损失放大才能引导模型学得更均衡。另外预测结果可能偶尔低于 0尤其是新上架的 SKU在还原销量后要做一次np.clip(pred, 0, None)保证输出值符合业务语义。4.4 网格搜索与早停怎么配合调参时不要一次性把网格铺得太大常见做法是先固定learning_rate0.05用一小部分数据粗跑两三轮找到num_leaves和min_data_in_leaf的大致范围再放大网格细调。早停的轮数也要随着learning_rate变化学习率减半时早停轮数应该加倍否则模型还没收敛就被截断。下面的代码用GridSearchCV搭配TimeSeriesSplit注意cv不能传普通 KFold。from sklearn.model_selection import GridSearchCV param_grid { num_leaves: [15, 31, 50], min_data_in_leaf: [10, 20, 30], learning_rate: [0.02, 0.05] } gsearch GridSearchCV( lgb.LGBMRegressor(feature_fraction0.8, subsample0.8, n_estimators300), param_grid, cvtscv, scoringneg_mean_absolute_error, n_jobs-1 ) gsearch.fit(X, y) print(gsearch.best_params_)scoringneg_mean_absolute_error表示用负 MAE 作为得分值越接近 0 越好。由于GridSearchCV对每组参数都会完整执行 5 折参数组合数量要控制上面的网格是 18 种组合配合小数据还可以接受在真实大数据集上建议改成贝叶斯搜索或直接把候选参数减半。搜索时还要注意固定一个可复现的random_state否则不同轮次之间参数效果不可比。5. 滚动预测落地的关键技巧从全量训练到每周滚动更新5.1 固定窗口训练与一步预测Zara 的上新周期短每周都有新款进入旧款被淘汰。每周全量重训成本高并且会让模型记住刚下架款式的规律。更实用的做法是固定一个 26 周的滚动训练窗口每次预测未来一周然后窗口整体后移。这样既保留足够的样本又能让模型自动适应最近的销售模式。for start in range(min_week, max_week - 26 - 1): train df[(df[week_id] start) (df[week_id] start 26)] test df[(df[week_id] start 26) (df[week_id] start 27)] model get_fresh_model() model.fit(build_features(train), np.log1p(train[sales])) pred np.expm1(model.predict(build_features(test))) evaluate(test[sales], pred)这里每个窗口都新建模型避免上一轮的权重被继承。build_features是在相同特征工程函数里处理训练集和验证集每次调用都独立完成滞后特征拼接防止把未来的值算进训练特征。窗口长度可以根据数据量调节如果 26 周内没有覆盖上一次大型促销就把窗口延长到 52 周代价是训练时间增加和部分旧款模式被保留。5.2 新品的曲线回填策略新品没有历史销量滞后特征为空不能直接丢进模型。常见做法是按品类和价格带分组在组内计算上市第 k 周的平均销量曲线新品缺少历史时直接从这个曲线取值。注意曲线必须按上市周数对齐分组样本数少于 5 个时回退到全局中位数避免个别爆款干扰。如果曲线抖动太厉害可以先对 4 周做一次移动平均再用于回填。curve df.groupby([category, price_tier, week_after_launch])[sales].mean() new_sku[fallback] [curve.get((c, p, k), df[sales].median()) for c, p, k in zip(new_sku[category], new_sku[price_tier], new_sku[week_after_launch])]5.3 残差监控怎么用上线后每次预测都保存真实销量和预测值的残差。如果残差持续为正说明模型整体低估可能是促销力度变强如果某个 SKU 连续三周同号说明该 SKU 的消费规律变了。把残差和滞后特征画散点图如果低历史区间残差系统性偏正说明min_data_in_leaf太大特征规则被抹平如果高销量区间偏负说明趋势项缺失。这个检查过程不需要重训只需要定时跑一个绘图脚本。本文还有配套的精品资源点击获取
返回列表