下月备货算不准?Python时间序列预测实战
"下个月咱大概能卖多少?备多少货合适?"
之前我习惯拍脑袋:上个月卖200,这个月估个210,理由全凭感觉。如果直接拿去定目标的话,要么压一仓库存,要么中途断货,两头不讨好。
销量不是随机数,它有自己的节奏。
后面翻了过去18个月的销量,发现里面藏着两样东西:一个慢慢往上走的趋势,一个每年都差不多的季节波动。比如一到年底2个月就猛涨,春节后大幅回落,用行业术语说就是"基于历史数据预判未来"叫预测性分析,是商业分析里单独的一类,核心就是时间序列。
把这两个规律拆开看,预测就可以分成三步走。
第一步:把历史销量读进来
用 pandas,一段加法模型就够用,不用装任何额外包。趋势用移动平均抹平,季节用同月均值算出来。
import pandas as pdimport numpy as np# 过去18个月销量,换成你自己的数据sales = pd.Series( [120, 135, 150, 142, 160, 178, 190, 175, 168, 185, 200, 230, 130, 145, 158, 150, 172, 188], index=pd.date_range("2025-01-01", periods=18, freq="MS"))# 趋势项:12期移动平均,抹平波动看大方向trend = sales.rolling(12, min_periods=6).mean()# 季节项:去掉趋势后,看每个月相对趋势的偏离detrend = sales - trendseason = detrend.groupby(detrend.index.month).mean().reindex(range(1, 13)).fillna(0)
第二步:趋势外推,季节叠回
趋势用最近斜率往外推,季节用历史均值加回去,一个函数全部搞定。
def forecast_monthly(sales, periods=3): trend = sales.rolling(12, min_periods=6).mean() detrend = sales - trend season = detrend.groupby(detrend.index.month).mean().reindex(range(1, 13)).fillna(0) tc = trend.dropna() slope = tc.diff().mean() # 每月平均增量 last = tc.iloc[-1] future = pd.date_range(sales.index[-1] + pd.offsets.MonthBegin(1), periods=periods, freq="MS") fc = [round(last + slope * (i + 1) + season.get(m.month, 0)) for i, m in enumerate(future)] return pd.Series(fc, index=future, name="预测销量")print(forecast_monthly(sales, 3))
跑出来下三个月大概是 213、197、191。八月份因为季节回落,预测值反而比七月低。这正是趋势和季节"打架"的结果,如果拍脑袋很容易漏掉这一层。
第三步:别光看预测,先回测
预测完一定先验证准不准。把最后三个月藏起来当"未知",用前面的数据预测,再和真实值比误差。
pred = forecast_monthly(sales.iloc[:-3], 3)true = sales.iloc[-3:]mape = (abs(pred - true) / true).mean() * 100print(f"回测MAPE={mape:.1f}%") # 约 10%
这版示例数据回测下来 MAPE 在 10% 左右,也就是平均误差一成。可以接受,但不能当成精确值。
避坑
数据至少要攒够 12 个月。如果只有半年数据,季节项根本算不出来,预测就是瞎蒙了。
大促和节假日要单独标记。别让它污染"正常月"的季节值,不然每年都多算一笔。
MAPE 超过 15% 就别当真了。先查一下数据有没有异常缺录,千万别直接拿去定目标。
预测是给你定目标的参考值,不是圣旨。要结合业务判断再做拍板,比任何模型都稳。