☰
销售预测实战
2026/10/11 5:07:07 网站建设 项目流程

我用 Python 预测 10 家门店未来 30 天销量:时间序列最容易踩的坑都在这里

通过网盘分享的文件:02 销售预测实战项目_完整版.zip 链接:
https://pan.baidu.com/s/1-WKULhDrKn-Mv0oLmn45rA?pwd=43q7 提取码: 43q7

2 年、7310 条门店流水,从趋势分解到备货决策的完整链路。含代码,数据在文末(开箱即跑)。

这是我做的第二个完整实战项目。第一个是电商行为分析的漏斗+RFM,这次换了个更有挑战的方向:预测。

为什么说更有挑战?因为预测要求模型对未来负责。描述过去可以慢慢分析,预测错了就是真金白银的损失——备货多了积压占资金,备货少了断货丢客人。

而时间序列预测里,有个坑能让你的模型"线下 95 分、上线 30 分",很多人栽在上面还不知道为什么。

一、数据说明

某连锁零售企业 10 家门店、2 年(2024-2025)的日销售数据:

字段含义
store_id门店编号(S01~S10)
date日期
sales当日销售额(万元)
城市 / 门店类型门店属性(旗舰店/标准店/社区店)

规模:7,310 条记录,总销售额 33,917 万元。数据为教学模拟,但业务结构完全真实(后面你会看到)。

二、先看清规律,再谈模型

拿到时间序列,第一步不是建模,是看。

图里那条浅色的日线噪声很大,但蓝色的 30 日滚动平均一出来,趋势立刻清楚了:整体在涨。

这是看时间序列的第一个技巧:日线看趋势等于自虐,直接上滚动平均。

total=df.groupby("date")["sales"].sum()ax.plot(total.index,total.rolling(30,center=True).mean())# 关键

周季节:零售最强的规律

周一平均 3.76 万元,周六 5.57 万元——周末比周初高约 48%。

这个发现直接决定了一件事:备货绝不能按"日均"一刀切,必须按星期几分开算。

更细心的观察:右图显示各门店周末强度不同——社区店更依赖周末客流,标准店相对平缓。这是后面"分门店建模"的依据。

年季节:夏冬双峰

6-8 月和 10-12 月是两个高峰,3-4 月最低。典型零售结构。

还有一类重要事件:大促日(双11、618、国庆、春节前)会形成脉冲式尖峰。

关键认知:这些尖峰不是噪声,是信号。新手常犯的错是把它们当异常值删掉——那等于把模型最需要学的信息扔了。正确做法是做成显式特征。

三、时间序列分解:把规律拆成零件

把序列拆成三层:趋势 × 周季节 × 残差。分解后如果残差还是白噪声,说明规律提取干净了。

ACF:用数据决定滞后阶数

高手的做法不是"我猜 lag_7 有用",而是让数据说话:

ACF 在7、14、21、28处出现尖峰——这是周季节的"指纹"。它直接告诉我们:必须加 7 的倍数作为特征。

defacf(x,nlags=30):x=np.asarray(x,float);x=x[~np.isnan(x)];x=x-x.mean()n=len(x);denom=np.sum(x**2)return[np.sum(x[:n-k]*x[k:])/denomforkinrange(nlags+1)]

四、特征工程:这里是分水岭

我构建了三类特征:

类型特征捕捉
日历星期几、月份、是否周末、是否大促季节与事件
滞后lag_1/2/3/7/14/28自相关(惯性)
滚动近7/14/30天均值与标准差近期水平与波动

结果符合直觉:roll_mean_7(近7日均值)与lag_7(上周同日)相关性最高——"近期水平 + 周同期"是预测的两大核心信号。

⚠️ 重点:数据穿越(Data Leakage)

这是时间序列第一大坑,我见过太多人栽在这里。

看这段代码,你觉得有问题吗?

df["ma7"]=df["sales"].rolling(7).mean()

有。默认的rolling(7)是包含当前行的——也就是说,"今天"的特征里混进了"今天"的真实销量。而我们的任务是预测今天。等于考试前偷看了答案。

正确写法:

df["ma7"]=df["sales"].shift(1).rolling(7).mean()# 先整体后移,再滚动

症状:线下评估漂亮得不像话(R² 0.99),一上线就崩。看到"好得不真实"的分数,第一反应就该查数据穿越。

还有两个隐蔽的穿越:

# ❌ 归一化用了全量数据(含未来)scaler.fit(df[feats])scaler.fit(train[feats])# ✅# ❌ 目标编码用了全量标签df["store_mean"]=df.groupby("store")["sales"].transform("mean")# 含未来# ✅ 只用训练集统计再映射

五、评估:为什么不能随机切分

时间序列不能用随机 K 折交叉验证。

因为随机切分会把未来数据放进训练集,模型"见过未来",评估结果虚高。正确做法有三种:

方式可用性
随机 K 折❌ 严重穿越
时间切分(前80%训练)✅ 简单可用
滚动向前验证✅✅最贴近真实上线

滚动向前验证长这样:

窗口1: [训练 1~100] → 预测 101~107 窗口2: [训练 1~107] → 预测 108~114 窗口3: [训练 1~114] → 预测 115~121

每个窗口都用"当时可得的数据"训练,模拟业务真实节奏。多个窗口的平均表现才可信。

别忘了建立基线

任何模型都必须先打败朴素基线,否则毫无价值:

  • 上周同日(lag_7):“下周六 ≈ 上周六”
  • 近7日均值(roll_mean_7)

零售数据的自相关很强,这两个基线往往已经"不太差"。如果复杂模型打不过lag_7,说明模型没用,别上线。

六、建模对比

我用 Ridge 回归和随机森林做了对比:

模型特点
Ridge 回归系数可解释,roll_mean_7/lag_7权重最大
随机森林更强,能捕捉"双11 且周六"这类交互效应

两者都能跟上销量的起伏节奏(周末高、促销尖),但都在极端促销日低估——这是所有预测模型的共同局限。

补充一个树模型的固有缺陷:随机森林无法外推——预测值不会超出训练集见过的范围。遇到持续增长或新高的场景会系统性低估。Ridge 没这个问题,但要保证趋势能线性外推。

七、找错:误差分析比调参有用

模型跑完,最重要的一步是分析它在哪错。

误差在门店间不均匀:社区店误差明显更大(销售波动本来就大)。一刀切的备货策略一定在这类门店出问题。

周末误差高于工作日——周末人流不确定性更强。结论:周末备货的安全系数必须单独上调。

八、从预测到决策:这才是业务买单的地方

老板不关心 MAE 是 0.8 还是 0.75,他关心该备多少货。

先给不确定性:只报"预测 5.2 万"是不专业的,要说"预测 5.2 万,大概率在 4.6~5.8 万之间"。

安全库存公式

建议备货 = 预测值 + z × 预测误差标准差 z = 1.28 → 缺货率约 10% z = 1.65 → 约 5% z = 2.33 → 约 1%

为什么用 P90 而不是均值?

因为备货是不对称风险:

失误后果严重度
备少了断货、丢客、伤口碑高
备多了占资金、临期损耗中

所以取预测分布的高分位。这是风险偏好驱动的决定,不是统计教条。

关键在于:没有"最优解",只有"业务能接受的平衡点"。数据分析师的职责是把权衡量化清楚(满足率 vs 超备比例),让业务方决策,而不是替他拍脑袋。

九、结论汇总

#发现行动
1模型优于"上周同日"基线先在误差最小的 3 家门店试点
2周末误差更高周末安全系数上调 5%~10%
3社区店误差最大保留人工复核
4大促日系统性低估"模型 + 人工规则"双轨制
5门店间差异大分门店定制安全系数
6夏冬双峰旺季前提前备货
780% 预测区间有效按 P90 下达,控制缺货率约 10%

十、写在最后

做完这个项目,我最想分享的不是模型技巧,而是三个认知:

  1. 时间序列的坑都在"细节"里——随机切分、滚动含当前值、归一化用全量,任何一个都能让你的模型上线即崩;
  2. 必须建立基线——打败不了lag_7的模型没有价值,别急着调参;
  3. 承认模型边界——大促、新品、结构突变,模型都搞不定。主动说明边界,比夸大能力更能赢得信任。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询