【AI指导】Python实现prophet模型的业绩预测
以下是一个使用Facebook Prophet模型进行业绩预测的Python代码示例。Prophet适用于具有强季节性特征的时间序列数据预测,如销售业绩、用户增长等场景。
安装依赖
确保已安装prophet
和pandas
库。未安装可通过以下命令安装:
(win+R键打开cmd)
pip install prophet pandas matplotlib
pip install plotly
完整代码
###——————————————————第一步,把数据整理成“两列”import pandas as pd
from prophet import Prophetdf = pd.read_excel('daily_sales.xlsx', parse_dates=['date'])# 只要两列核心,格式必须包含两列且列名为ds和y
df = df[['date', 'revenue']].rename(columns={'date': 'ds', 'revenue': 'y'})# 缺失值随手填,按日期排序后线性插值,然后默认打印前5行
df = df.sort_values('ds').interpolate()
print(df.head())###——————————————————第二步,把节假日 & 促销活动喂给模型# 1) 节假日表(可一次性写死,也可从原始 is_holiday=1 抽)
'''
各字段含义(官方文档关键词)
holiday:字符串,给这组节假日起个名字,同一名字可多年复用。
ds:日期列,必须是 pandas datetime64[ns] 类型。
lower_window:向前影响几天(负值表示“节前”)。
upper_window:向后影响几天(正值表示“节后”)。
** Prophet 会把 [ds + lower_window, ds + upper_window] 这段区间全部打上节假日指标 **。
'''
holiday = pd.read_excel('holidays.xlsx',parse_dates=['ds']) # 确保 ds 是 datetime,这里是历史+未来的节假日表# assign 写法(一步完成,链式)
holiday = holiday.assign(lower_window=0, upper_window=0) # 当天+节后0天
'''
assign 是 pandas 的“链式”加列语法糖,作用等价于 df['新列'] = 值,但返回一个新 DataFrame,原表不动,适合继续链式写下去。# 传统写法
holiday['lower_window'] = 0
holiday['upper_window'] = 1
'''# 2) 促销表(抽 is_promo=1 的日期)
promo = pd.read_excel('daily_sales.xlsx', parse_dates=['date'])
promo = promo[promo['is_promo'] == 1][['date']]
'''
第一个中括号 按条件过滤行
第二个中括号 挑选列;写成 [['date']](列表套字符串)而不是 'date',是为了保持 DataFrame 结构——后者只会返回 Series
'''
promo['holiday'] = 'promo'
promo['lower_window'] = 0
promo['upper_window'] = 0
promo.rename(columns={'date': 'ds'}, inplace=True)
'''
inplace=True 不返回新对象,直接改原 df
也就是把 promo 自己的 'date' 列当场改成 'ds',不再生成新表。
'''# 合并
holidays_all = pd.concat([holiday, promo])
'''
把两个 DataFrame 纵向拼在一起,形成一张总节假日表,让 Prophet 一次性识别「国家法定假 + 促销日」两种事件。
两表列名一定要对齐;若 promo 忘了加 lower/upper_window,先 assign 再 concat。
同一 ds 日期如果两边都出现,Prophet 会保留两行,节日效应会叠加(一般没问题)。
'''###——————————————————第三步,30 秒建立 Prophet 模型
model = Prophet(yearly_seasonality=True,weekly_seasonality=True,daily_seasonality=False, # 日业绩不用日内季节holidays=holidays_all,seasonality_mode='multiplicative', # 业绩随趋势放大changepoint_prior_scale=0.05, # 拐点灵活度holidays_prior_scale=5) # 节假日效应更强,新版本为holidays# 加入自定义促销回归量(可选)
df['promo_flag'] = pd.read_excel('daily_sales.xlsx')['is_promo']
model.add_regressor('promo_flag', mode='multiplicative') # 默认 additive
'''
节假日表只能把「已知促销日」标成 1、未知日标 0,效应大小固定。
实际业务里促销强度、折扣力度、渠道差异很大,想让它「自己学系数」,就把促销列当外生回归器扔进去。
mode='additive' → 效应是 加减(默认)
mode='multiplicative' → 效应是 乘系数(销量随基数放大时选它)
'''
#拟合
model.fit(df)###——————————————————第四步,生成未来 90 天并预测
# 1. 未来时间框架
future = model.make_future_dataframe(periods=90,freq='D')
'''
把训练时用的日期列 ds 往后顺延 90 天,返回一张只有 ds 列的新 DataFrame,叫 future
freq='D' → 频率为日粒度(1 天 1 行)
如果你的训练集是按 周/月/小时,把 freq 对应改成 'W' / 'M' / 'H' 即可
此外,如果用了外生回归器(add_regressor('promo'))或节假日表,你要自己把对应列补进去
'''# 2. 把未来 promo 填进去(没有就补 0)
# 如果已知某几天要做促销,直接改 1:
# future.loc[future['ds']=='2024-06-18', 'promo'] = 1
promo_future = pd.read_excel('promo_future.xlsx', parse_dates=['ds'])
future['promo_flag'] = 0 # 先全部填 0
future = future.merge(promo_future.assign(promo_flag=1),on='ds', how='left', suffixes=('', '_new'))
future['promo_flag'] = future['promo_flag_new'].fillna(future['promo_flag']).astype(int)
future = future.drop(columns='promo_flag_new')model.interval_width = 0.8 # 默认是 0.8(80%)forecast = model.predict(future)
'''
interval_width 就是 置信区间的“宽度”(Confidence Level),告诉 Prophet:
你预测时给我多大的概率区间?
默认 0.8 → 80% 置信区间(yhat_lower 到 yhat_upper 覆盖真实值 80% 的可能性)。
interval_width 越大,上下界越宽,模型越保守;越小,区间越窄,风险越高。
''''''
打印后预测结果的最后10天
print(forecast[['ds', 'yhat', 'promo']].tail(10))
'''
#文件保存结果,\表示“这行还没完,下一行是同一句话”。
forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper', 'promo']] \.to_excel('完整预测结果.xlsx', index=False)###——————————————————第五步,画图验收
import matplotlib.pyplot as plt
from matplotlib import rcParams
# ① 让 matplotlib 用系统里自带的 SimHei(黑体)
rcParams['font.family'] = 'sans-serif'
rcParams['font.sans-serif'] = ['SimHei'] # Windows 黑体
rcParams['axes.unicode_minus'] = False # 解决负号显示成方块的问题model.plot(forecast, figsize=(12,6))
plt.title('90 天业绩预测')
plt.show()# 再看成分:趋势、周季节、年季节、节假日
model.plot_components(forecast)
plt.show()
部分代码解释
1.数据准备
Prophet 只认两列:
ds
:日期(yyyy-mm-dd)y
:当日业绩(数值)
假设你的原始文件叫 daily_sales.xlsx
,长这样:
date | revenue | is_promo | is_holiday |
---|---|---|---|
2021-01-01 | 12000 | 0 | 1 |
2021-01-02 | 8500 | 0 | 0 |
… | … | … | … |
2.模型参数
以下三个参数决定了 Prophet 怎样“拆”你的时间序列——季节效应是加还是乘、拐点可以多灵活、节假日/促销的权重放多大。下面逐个拆给你看:
seasonality_mode='multiplicative'
官方默认'additive'
(加法)。加法:
y = trend + seasonality + holiday
乘法:
y = trend × (1 + seasonality) × (1 + holiday)
什么时候用乘法?
业务明显“旺季更旺、淡季更淡”——销售额绝对值随趋势增长而放大,比如 10 万/月基数时春节 +50% 得 15 万;100 万/月基数时同样 +50% 得 150 万。
若用加法,春节永远固定 +5 万,就低估高峰、高估低谷。
➜ 零售、电商、旅游、广告收入等量级随趋势上涨的场景,优先multiplicative
。
changepoint_prior_scale=0.05
控制 趋势拐点(changepoint)的灵活度。值越大 → 允许拐得越频繁,曲线贴近训练点,容易过拟合。
值越小 → 拐点少,趋势线更平滑,可能欠拟合。
默认 0.05。
经验:业务历史突变少(传统线下零售)可再降到 0.01-0.03。
互联网产品频繁活动、政策改来改去可提高到 0.1-0.3。
调参时看 MAPE / MAE 是否改善,同时可视化m.changepoints
确认拐点数量合理。
holiday_prior_scale=10
控制 节假日/促销系数估计的“方差”大小。
Prophet 对节假日效应使用 正态先验:β_holiday ~ N(0, σ²) ,其中 σ = holiday_prior_scale
值越大 → σ 越大 → 允许节假日峰值更高或更低(系数绝对值可以很大)。
值越小 → 效应被压缩到接近 0,节日几乎不波动。
默认 1。
电商/零售大促常常当天销量翻几倍,就把holiday_prior_scale
调到 5-20,让模型敢把系数放大;否则预测会被“拉扁”,显著低估大促峰值。
反之,若节假日只比平日 +5%,用默认值甚至 0.1 即可。
调参口诀
场景 | seasonality_mode | changepoint_prior_scale | holiday_prior_scale |
---|---|---|---|
稳定线下店 | additive | 0.01-0.05 | 0.5-2 |
日常电商 | multiplicative | 0.05-0.1 | 5-10 |
高频活动/爆品 | multiplicative | 0.1-0.3 | 10-20 |
步骤:先定 mode → 再 grid-search / 手动调两个 scale,看验证集 MAPE 最小即可。
3.merge解释:
future = future.merge(promo_future.assign(promo=1), on='ds', how='left', suffixes=('', '_new'))
把 merge
想成 “贴便利贴” 的动作:
on='ds'
→ 按日期对齐(两张表都有的同一天,才贴在一起)。how='left'
→ 以左边表(future 日历)为准,右边没有的日期就空着(NaN)。suffixes=('', '_new')
→ 如果两边出现同名列,左边保持原名,右边加_new
后缀,避免重名冲突。
一张图秒懂:
future(左) | promo_future.assign(promo=1)(右) | 贴完结果(how='left') |
---|---|---|
2025-11-10 | 2025-11-11 promo=1 | 2025-11-10 promo=0 promo_new=NaN |
2025-11-11 | 2025-11-11 promo=0 promo_new=1.0 ← 贴到 | |
2025-11-12 | 2025-11-12 promo=0 promo_new=NaN |
左边日历 一行没少(left 保证完整性)
只有日期匹配的那一行被贴上了
promo=1
,其余日期promo_new
是 NaN,后面用fillna
再补 0 即可
4.fillna解释:
future['promo'] = future['promo_new'].fillna(future['promo']).astype(int)
这行代码只有 3 个动作,拆开看就懂了:
future['promo_new'].fillna(future['promo'])
把
promo_new
里的 NaN(没贴到促销的日期)用 同一行原来的promo
值(0)填上。结果:促销日 → 1,其余日 → 0。
.astype(int)
把 0.0 / 1.0 这些小数转成 整型 0/1,Prophet 外生回归器要求数值类型。
future['promo'] = ...
把处理好的 0/1 写回
promo
列,覆盖原来的全 0 列,得到最终「未来每天是否促销」特征。
一句话:
“用贴过来的 1 覆盖促销日,其余日继续保留 0,并把小数变整数。”
5.forecast解释:
forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper', 'promo']]中的'yhat', 'yhat_lower', 'yhat_upper' 指什么?
Prophet 给每个预测点都配了三条数:
yhat
点预测值(模型最可能的结果),也就是你平时说的“预测销量/营收”。yhat_lower
置信区间下限,默认 80% 区间(可改)。
真实值有 80% 概率 ≥ 这个数。yhat_upper
置信区间上限,同理,真实值有 80% 概率 ≤ 这个数。
直观例子(80% 区间):
ds | yhat | yhat_lower | yhat_upper |
---|---|---|---|
2025-10-01 | 1000 | 850 | 1150 |
→ 模型认为 2025-10-01 的销量最可能是 1000,但 80% 的可能会落在 850~1150 之间。
关键参数说明
yearly_seasonality
:是否启用年季节性(适合年度周期数据)。weekly_seasonality
:是否启用周季节性(适合周周期数据)。changepoint_prior_scale
:调整趋势灵活度(值越大模型对突变越敏感)。
输出说明
yhat
:预测值。yhat_lower
/yhat_upper
:预测值的置信区间下限/上限。- 可视化图表包含历史数据、预测结果及季节性分解组件。
注意事项
- 输入数据必须包含
ds
(日期列)和y
(数值列),且为Pandas DataFrame格式。 - 默认使用加法季节性模型,若业绩变化幅度随时间增大,可设置
seasonality_mode='multiplicative'
。 - 对于节假日效应,可通过
add_country_holidays(country_name='CN')
添加中国节假日。
输出结果
一眼就能回答老板 3 个问题:
整体趋势往上还是往下?
礼拜几是高峰?
节假日/促销到底多卖了多少?
快速调参口诀(以后用)
现象 | 调哪个参数 |
---|---|
曲线太平滑,没跟上拐点 | changepoint_prior_scale 调大(0.05→0.2) |
节假日效应不明显 | holiday_prior_scale 调大(10→20) |
季节幅度随业绩变大 | seasonality_mode='multiplicative' (已设) |
长期预测越来越“飘” | 缩短预测跨度 or 加更多历史促销做回归量 |
下一步你可以
先把上面代码跑通,把图截给老板看 baseline;
把“促销计划”填到 future 表里,就能回答“如果 618 连做 3 天促销,预计会冲多少”;
想再准一点 → 把价格、流量、广告费当额外
add_regressor
扔进去。