起因
老板让"预测下季度营收"。专业的 forecasting 上 ARIMA / SARIMA / state
space model 调一周;我们要的是"现在马上有个基础数字 + 不太离谱"。
Facebook (Meta) 开源的 Prophet 是 GAM (Generalized Additive Model)
风格的 time series 工具,对"商业数据特征 (趋势 + 周期 + 节假日 + 异常点)"
极友好,几乎零调参就有不错效果。
装
uv add prophet
# Mac M 系列 / Win 装 prophet 偶尔遇 cmdstanpy 编译问题
# 解决:先 conda install cmdstanpy 再 pip install prophet
5 分钟 demo
import pandas as pd
from prophet import Prophet
# 数据格式:必须两列 ds (datetime) + y (value)
df = pd.read_csv('daily_revenue.csv')
# date,revenue
# 2023-01-01,1234.5
# 2023-01-02,1289.1
# ...
df.columns = ['ds', 'y']
# 模型
m = Prophet()
m.fit(df)
# 预测未来 90 天
future = m.make_future_dataframe(periods=90)
forecast = m.predict(future)
# yhat = 预测值;yhat_lower/upper = 80% 置信区间
print(forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail())
# 可视化
m.plot(forecast).savefig('forecast.png')
m.plot_components(forecast).savefig('components.png')
输出:
forecast.png:历史 + 预测曲线 + 置信带components.png:拆解成 trend / weekly / yearly 三个成分
调参为 0。
加节假日
中国春节 / 双 11 等是营业额异常点,加进 model 让预测更准:
holidays = pd.DataFrame({
'holiday': 'major_sale',
'ds': pd.to_datetime([
'2023-11-11', '2024-11-11', # 双 11
'2023-06-18', '2024-06-18', # 618
]),
'lower_window': -1, # 节前 1 天也算
'upper_window': 2, # 节后 2 天也算
})
m = Prophet(holidays=holidays)
m.fit(df)
或者内置节假日:
m.add_country_holidays(country_name='CN')
# 自动加入春节 / 国庆 / 五一等
多季节性
默认 yearly + weekly + daily。要月度 / 自定义周期:
m = Prophet()
m.add_seasonality(name='monthly', period=30.5, fourier_order=5)
m.fit(df)
fourier_order 是季节性的"灵活度"(越大越能拟合复杂周期;过高 overfit)。
外部回归量(exogenous)
业务量受外部因素影响:
df['ads_spend'] = ... # 广告投入
df['is_promotion'] = ... # 0/1 促销标志
m = Prophet()
m.add_regressor('ads_spend')
m.add_regressor('is_promotion')
m.fit(df)
# 预测时也要提供未来值
future['ads_spend'] = expected_ads_for_next_90d
future['is_promotion'] = expected_promo_flag
forecast = m.predict(future)
广告 / 促销作为协变量进 model,预测更贴合业务现实。
异常点
Prophet 对异常点(COVID 期间 / 单日大促)相对鲁棒(用 GAM + 平滑)。
不需要手工剔除。要进一步控制可以:
# 把已知异常段 mark 为 NaN 让 Prophet 忽略
df.loc[(df['ds'] >= '2020-02-01') & (df['ds'] <= '2020-05-01'), 'y'] = None
m.fit(df)
或者用 add_seasonality 处理"上下界" 让 trend 不被极端值影响。
cross-validation 评估
from prophet.diagnostics import cross_validation, performance_metrics
# initial 365 天训练,每 30 天滚动一次,预测 horizon=30 天
df_cv = cross_validation(m, initial='365 days', period='30 days', horizon='30 days')
metrics = performance_metrics(df_cv)
print(metrics[['horizon', 'mape', 'rmse']].head())
# horizon mape rmse
# 0 1 days 0.08 102.3
# 1 2 days 0.09 105.1
# ...
MAPE (Mean Absolute Percentage Error) < 10% 在大多数商业数据是
"还行",5% 算优秀。
与替代品对比
| Prophet | ARIMA / SARIMA | statsmodels | NeuralProphet | NHITS / TimeGPT | |
|---|---|---|---|---|---|
| 学习曲线 | 极低 | 高(要懂 ACF/PACF) | 高 | 中 | 低 |
| 调参量 | 几乎 0 | 多 | 多 | 中 | 几乎 0 |
| 自动季节性 | ✅ | 手动 | 手动 | ✅ | ✅ |
| 节假日 | ✅ | 手动 | 手动 | ✅ | ✅ |
| 多变量 | regressor | VAR | VAR | ✅ | ✅ |
| 高频数据 | 中(小时级) | ✅ | ✅ | ✅ | ✅ |
| 长期趋势 | 强 | 中 | 中 | 强 | 强 |
简单业务时间序列 → Prophet。
学术 / 严谨需求 → ARIMA / state space。
现代深度方法 → NeuralProphet / Darts / Nixtla statsforecast。
真实业务案例
我们用 Prophet 做月度营收预测:
- 训练数据:2 年日级营收
- 加节假日:春节 + 双 11 + 618
- 外部回归:广告预算 + 促销日
- horizon:90 天
vs 之前的"人拍" 预测:
| 人拍 | Prophet | Prophet + 节假日 + regressor | |
|---|---|---|---|
| MAPE | 25% | 12% | 7% |
| 工时 | 4 hour/月 | 10 min/月 | 30 min/月 |
不仅准、还省时。
输出多 model + ensemble
# 跑 3 个不同 changepoint_prior_scale 取均值
forecasts = []
for cps in [0.001, 0.05, 0.5]:
m = Prophet(changepoint_prior_scale=cps)
m.fit(df)
forecasts.append(m.predict(future)['yhat'])
ensemble = pd.concat(forecasts, axis=1).mean(axis=1)
不同超参对不同部分敏感;平均通常更稳。
何时不该用 Prophet
- 特别短的时间序列(< 2 季):信号不够,model 拟合差
- 复杂多变量交互:要用 LightGBM / 神经网络
- 极高频(毫秒级)实时:Prophet 慢,用 specialized 工具
- 概率性预测要严格 calibrated:bootstrap interval 仅近似
踩过的坑
-
列名必须 ds / y:用了别的名 Prophet 不识别。
df.rename(...)后再 fit。 -
datetime 时区:Prophet 内部假设 naive datetime(无时区)。
带 tz 会报错。df['ds'] = df['ds'].dt.tz_localize(None)。 -
make_future_dataframe(periods=90, freq='D'):默认日级;
月级数据要freq='M'。漏指定就把月数据当日数据预测。 -
logistic growth 需要 cap:用
growth='logistic'时必须给
cap列(上界),否则报错。 -
changepoint 自动检测不靠谱:业务有大转折(疫情 / 公司战略
变化)时,手动指定:
python m = Prophet(changepoints=['2020-02-01', '2022-06-15'])
登录后参与评论。