Prophet

更新时间: 2026-06-22 14:50:58

# ARMA/ARIMA统计模型的不足

  • ARMA,要求时序数据是稳定的,现实数据很难符合
  • ARIMA,模型为线性模型,无法处理非线性关系,同时要求数据点的间隔等长,比如X1和X2间隔一个小时,那么X2和X3也间隔一个小时
  • 如果数据缺失,则需要使用插值等方法来预估缺失值,然后再使用预估值来进行参数拟合,这样会引入噪音

# Facebook prophet工具

  • facebook开源的时间序列预测工具https://facebook.github.io/prophet/ (opens new window)
  • Prophet是一个基于相加模型(additive model)的时间预测,可以精准的拟合非线性的周期趋势
  • 对yearly、weekly和daily的周期性使用非线性拟合,亮点在于Prophet模型还添加了holidays(影响因子),可以很好的对节日(比如十一、春节等)带来的活跃数据的突变进行预测

# prophet的优势:

之前学的时间序列模型,有一个共同的弱点:处理不了突发的影响。
比如:
春节前后,大家都在买年货、转账,金额会突然暴涨
双十一,购物狂欢,申赎数据会出现异常高峰
十一黄金周,又是另一个高峰
这些节假日的影响,用ARIMA、SARIMA很难精确捕捉,因为它们变化不规律。Prophet就是来解决这个问题的。

  • 处理数据丢失问题
  • 趋势迁移问题(shifts in the trend)
  • 异常的数据点(outliers)

prophet模型:y(t)=g(t)+s(t)+h(t)+e
这叫相加模型,意思就是把一个复杂的时间序列,拆成几个部分加在一起:

符号 含义 打个比方
g(t) 趋势项 股票的长期走向,是涨是跌的大方向
s(t) 周期项 一周哪天高(周末低)、一年哪月高(年底高)
h(t) 节假日项 春节、十一、双十一带来的突然变化
e 误差项 其他乱七八糟的随机波动
  • g(t)代表趋势项,用来表示时间序列中非周期性的变化
    "趋势"就是数据长期往上走还是往下走。
    Prophet用两种方式来拟合趋势:

    • 第一种:饱和增长模型(像S形曲线)
      比如一个新产品刚上线,用户增长很快,后来增速放缓,慢慢趋于平稳
      有点像 logistic growth(逻辑增长)
    • 第二种:分段线性模型(像折线)
      把时间序列分成几段,每段分别用直线拟合
      优点是很灵活,能捕捉"增长突然加速"这种转折点
  • s(t)代表周期项,用来表示时间序列中的周期变化
    运用傅里叶级数作为周期项,使得预测模型具有灵活的周期效应
    Prophet默认处理三种周期:

    • yearly(年度周期):一年内的变化
    • weekly(周周期):一周内的变化,你蚂蚁金服数据有明显的周周期,周末金额低
    • daily(日周期):一天内的变化
  • h(t)代表活动效果项,用来表达时间序列中的一些异常活动,例节假日,购物节等
    这是Prophet的核心亮点。
    传统时间序列模型处理不了"每年日期不固定的节日",比如:

    • 春节(每年日期不同)
    • 母亲节(每年日期不同)
    • 感恩节
      但Prophet可以!你给它一张"节假日表格",告诉它"春节期间金额会暴增",它就能学习到这个规律。
    # 创建一个节假日表格
    holidays = pd.DataFrame({
        'holiday': '春节',  # 节日名称
        'ds': pd.to_datetime(['2014-01-31', '2015-02-19']),  # 节日日期
        'lower_window': -5,  # 春节前5天开始影响
        'upper_window': 5,   # 春节后5天结束影响
    })
    
    1
    2
    3
    4
    5
    6
    7

    这样Prophet就知道:春节前后这10天,数据可能会有异常波动。

  • e用来表示不能被模型所描述的异常误差,就是模型解释不了的随机波动,这个没什么好说的,任何模型都有。

# prophet工具使用:

# 安装
pip install prophet

# 导入
from prophet import Prophet

# 准备数据(Prophet要求列名叫ds和y)
df = pd.DataFrame({
    'ds': daily.index,           # 日期列
    'y': daily['total_purchase_amt']  # 要预测的值
})

# 创建模型
model = Prophet(
    yearly_seasonality=True,   # 年度周期
    weekly_seasonality=True,   # 周周期
    daily_seasonality=False,   # 日周期(你这个是日数据,不需要)
    holidays=holidays          # 加上节假日
)

# 训练
model.fit(df)

# 预测未来30天
future = model.make_future_dataframe(periods=30)
forecast = model.predict(future)

# forecast里包含预测值,以及各个分解项(趋势、周期、节假日)
print(forecast[['ds', 'yhat', 'trend', 'weekly', 'yearly']])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# prophet模型:

  • Trend趋势,对时间序列中的趋势部分拟合分段线性函数,线性拟合会将特殊点和缺失数据的影响降到最小
    趋势,就是数据长期往哪个方向走。
    比如:
    某App用户量:1月100人 → 6月500人 → 12月1000人 → 这就是上升趋势
    某个衰退中的产品:每月都在下降 → 这就是下降趋势
    Prophet用分段线性函数来拟合趋势,意思就是把时间序列分成几段,每段分别用直线拟合。

  • 什么是分段线性拟合 普通线性拟合:整段时间用一条直线来描述
    分段线性拟合:把时间分成几段,每段用各自的直线

    为什么分段更好?
    假设你用一条直线拟合2019年的口罩销量:
    疫情前销量很低
    疫情后销量暴涨
    如果用一条直线,那"疫情前"的数据会被"疫情后"带偏,反过来也一样,预测就会不准。
    分段线性就聪明了,它知道"这里有个转折点",前后分开拟合。

  • 饱和增长
    通常情况下,增长会有最大容量限制,比如未来12个月某app在某地区的下载量,最大下载量要小于等于该地区手机用户总数
    基于这样的领域知识,分析师可以定义模型的容量限制为C(t)

    在代码里这样设定:

    df['cap'] = 100000000  # 设定上限(比如市场容量)
    df['floor'] = 0        # 设定下限
    
    model = Prophet(growth='logistic')
    model.fit(df)
    
    1
    2
    3
    4
    5
  • 突变点,随着突变点数量的增多,拟合变得更灵活。在研究趋势成分时,分析师要面临两个基本问题,即过拟合与欠拟合

    "突变点"就是趋势发生变化的时间点。
    例子:

    • 某产品1-6月一直上升,7月换了CEO,8月开始下滑
    • 7月就是"突变点"

    Prophet默认会自动识别突变点,识别逻辑大概是:

    1. 先用一条整体直线拟合数据
    2. 找出"偏离这条线最远"的几个点
    3. 这些点就是潜在的突变点
      但它不会一下子变得很灵活,而是有一个默认的灵活性。
  • changepoint_prior_scale参数,可以调整趋势的灵活性,解决过拟合/欠拟合,参数值越大,拟合的时间序列曲线越灵活
    你可以理解为"灵活性的权重"

    参数值 效果 适合场景
    值很小(默认0.05) 趋势变化很小,像一条平缓的曲线 数据很稳定,没有太多波动
    值很大(比如1.0) 趋势变化很剧烈,曲线很"抖" 数据经常有大起大落

    调参技巧:
    如果Prophet预测曲线太平滑,实际数据却有很多波动 → 过拟合 → 调大这个值
    如果Prophet预测曲线太"抖",实际数据却很平稳 → 欠拟合 → 调小这个值

    # 调大,让趋势更灵活
    model = Prophet(changepoint_prior_scale=0.1)
    
    1
    2
参数 控制什么 怎么用
growth 用什么形状的曲线 'linear'=直线(最常用)'logistic'=S形曲线(有上限)
changepoints 手动指定突变点在哪几天 比如changepoints=['2014-01-31', '2014-02-15']
n_changepoints 不手动指定时,自动识别几个突变点 默认25个,通常不用改
changepoint_prior_scale 突变点附近的曲线有多灵活 越大越"抖",越小越平缓

最常用的场景

# 场景1:数据稳定,没有明显突变
model = Prophet(growth='linear')

# 场景2:数据有容量上限(比如用户量不会超过网民总数)
df['cap'] = 1_000_000
model = Prophet(growth='logistic')

# 场景3:知道某个时间点趋势会变(比如政策发布日)
model = Prophet(changepoints=['2014-03-15'])

# 场景4:曲线太平滑,要让它更灵活
model = Prophet(changepoint_prior_scale=0.5)
1
2
3
4
5
6
7
8
9
10
11
12

# prophet的突变点分析

  1. 检测突变点
  • 先均匀放点:在时间序列的前80%里,均匀放25个"候选突变点"
  • 再筛选:用数学方法(叫Laplace分布,你可以理解为一种"惩罚机制")判断哪些点真的需要"突变",哪些只是普通波动
  • 留下显著的:最后保留下来的,就是真正需要分段的地方

为什么要"前80%"而不是全部?因为最后20%要留着验证模型,不能让模型偷看答案。

  1. 分段趋势建模 趋势公式(以线性趋势为例):

δs:突变点s处的斜率变化量。
γ:保证函数连续的偏移量(避免突变点处的跳跃)。

  1. 预测未来趋势
    未来时间段的趋势延续最后一个突变点后的斜率(即假设未来趋势不再突变)。

# 案例

假设某产品的日销量在以下时间点发生突变:

  • 2024-01-01:初始增长(斜率=0.5)。
  • 2024-04-01:因促销活动,斜率突增至1.2。
  • 2024-07-01:市场竞争加剧,斜率降至0.3。
# 导入 pandas 用于处理数据框(DataFrame)和时间序列。  
import pandas as pd
# 导入 numpy 用于生成数学序列、随机数和数组拼接。  
import numpy as np
# 导入 matplotlib.pyplot 用于绘制数据图表。  
import matplotlib.pyplot as plt
from prophet import Prophet

# 生成日期范围
dates = pd.date_range(start='2024-01-01', end='2024-12-31')
n_days = len(dates)

# 模拟趋势突变
np.random.seed(42)
trend = np.concatenate([
    0.5 * np.arange(91),  # 1月-3月:斜率0.5,共91天
    0.5 * 91 + 1.2 * np.arange(91),  # 4月-6月:斜率1.2,共91天
    0.5 * 91 + 1.2 * 91 + 0.3 * np.arange(n_days - 91 - 91)  # 7月-12月:斜率0.3
])

# 添加季节性噪声
# np.linspace(0, 10 * np.pi, n_days) 在 0 到 10π 之间生成 n_days 个等间距的点。因为正弦函数周期是 2π,10π 代表 5 个完整的周期。
# 5 * np.sin(...) 生成一个振幅为 5 的正弦波,模拟数据中的周期性(季节性)波动。  
y = trend + 5 * np.sin(np.linspace(0, 10 * np.pi, n_days))
df = pd.DataFrame({'ds': dates, 'y': y})

# 可视化原始数据
plt.figure(figsize=(12, 4))
plt.plot(df['ds'], df['y'], label='Actual')
plt.title("Simulated Data with Changepoints")
plt.legend()
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

这段代码的主要目的是生成一段带有“趋势突变点”和“季节性波动”的模拟时间序列数据,并将其可视化。这通常用于测试时间序列预测算法(如 Prophet 模型)对数据趋势变化的捕捉能力。

## 训练 prophet 模型
# 初始化模型
model = Prophet(
    yearly_seasonality=True,      # 开启年度季节性以捕捉长周期波动
    weekly_seasonality=False,     # 关闭周季节性,因为数据没有7天周期特征
    changepoint_prior_scale=0.05, # 修正:使用默认灵敏度,防止过拟合
    changepoint_range=0.9         # 在前90%数据中检测突变点
)

# 拟合数据
# 将模拟数据喂给模型,让模型学习其中的趋势和季节性规律。  
model.fit(df)

# 创建未来30天的预测  
# 创建一个包含历史数据和未来 30 天的数据框。  
future = model.make_future_dataframe(periods=30)
# 模型根据学到的规律,对未来 30 天进行预测。  
forecast = model.predict(future)

## 可视化突变点与预测
# 绘制预测结果图(包含历史数据和未来预测)。  
fig1 = model.plot(forecast)
plt.title("Forecast with Changepoints")
# 标记突变点,在图表上画出红色的竖线,标记模型自动检测到的趋势突变点。  
add_changepoints_to_plot(fig1.gca(), model, forecast)
# 展示图表,观察模型是否准确捕捉到了 4 月和 7 月的趋势变化。  
plt.show()

# 分解趋势和季节性
# plot_components 会将预测结果拆解为三个部分分别展示:
# 趋势(Trend):数据整体的增长或下降趋势。
# 年度季节性(Yearly Seasonality):长周期的波动规律。
# 周季节性(Weekly Seasonality):由于我们关闭了它,这里不会显示。
fig2 = model.plot_components(forecast)
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35



可以看到,模型捕捉到了4月和7月的突变

Prophet的突变点机制能自动适应趋势变化,非常适合业务场景中突发事件的建模。
传统ARIMA模型,假设趋势是平滑变化的,无法灵活处理突发性趋势转折。

Prophet的解决方案:通过突变点检测,Prophet将时间序列分段建模,每段的趋势斜率可以独立调整。例如:产品销量因营销活动突然增长。或者经济指标因政策调整骤降。

# 用数学方法去拟合和预测数据中的“季节性”波动。

  • 季节性,拟合并预测季节的效果,基于傅里叶级数提出了一个灵活的模型:

    如何用数学方法去拟合和预测数据中的“季节性”波动。简单来说,就是模型怎么“学会”数据里那些周而复始的规律,比如每天、每周或每年的固定模式。

为什么需要“傅里叶级数”?

现实世界里的季节性,很少是完美的正弦波或余弦波。比如,气温变化、节假日销量波动,它们的形状可能很复杂、很不规则。

傅里叶级数的核心思想是:任何复杂的周期性函数,都可以被拆解成一系列简单的正弦波和余弦波的叠加。

想象一下,一个复杂的波形就像一幅画,而傅里叶级数就是用很多个不同频率、不同振幅的简单正弦/余弦波(就像不同粗细的画笔)来一笔一笔地把它“画”出来。

# 拆解公式

  • s(t): 这就是模型要拟合的季节性效果。比如在 t 这一天,季节性因素会给预测值带来多大的影响。
  • P: 这是周期。 如果你想拟合年度季节性,P 就是 365.25(考虑闰年)。
    如果你想拟合周度季节性,P 就是 7。
  • n: 这是阶数,从 1 开始一直到 N。n=1 代表频率最低、最平缓的那个基础波;n 越大,代表波的频率越高,波动越快。
  • an 和 bn: 这是模型需要学习的关键参数。
    n 控制第 n 个正弦波的“高度”(振幅)。
    bn 控制第 n 个余弦波的“高度”(振幅)。
    模型通过历史数据,自动算出这一系列 a 和 b 的最佳值,从而组合出最贴合数据的那个季节性曲线。

参数 N 是什么?为什么它很重要?

N 是傅里叶级数的阶数,你可以把它理解为模型的“灵活度”或“复杂度”。

  • N 值较低:
    模型只用少数几个低频的、平缓的波来拟合季节性。
    效果:得到的季节性曲线非常平滑,只能捕捉到大的、缓慢的变化趋势。
    适用场景:当你认为数据中那些快速的、细小的波动只是随机噪声,不值得去学习时,就把 N 设小一点。这可以防止模型“死记硬背”噪声,提高泛化能力。

  • N 值较高:
    模型会使用更多高频的、变化剧烈的波来拟合。
    效果:得到的季节性曲线非常灵活,可以捕捉到数据中很多细微的、快速的波动。
    适用场景:当你认为这些高频变化是真实存在的、有意义的模式(比如,一周内每天销量的精细变化),而不是噪声时,就把 N 设大一点,以提升预测精度。

# 活动效果项,即节假日和大事件

Prophet 模型如何处理节假日(Holidays)和特殊事件,以及与之相关的核心参数配置。在时间序列预测中,像春节、双十一促销或大型体育赛事等事件,往往会导致数据出现剧烈的突变,普通的趋势和季节性模型很难捕捉到这些规律。

# 节假日与特殊事件的处理机制

Prophet 允许分析师传入一个自定义的“事件列表”(包含过去和未来的日期)。模型会将这些特殊日期及其前后的时间段单独提取出来,通过拟合附加的参数来模拟这些事件对数据的具体影响(例如春节前后销量激增的幅度)。

季节和假日相关的参数:

参数 描述
yearly_seasonality 周期为年的季节性
weekly_seasonality 周期为周的季节性
daily_seasonality 周期为日的季节性
holidays 内置的节假日名称和日期
seasonality_priori_scale 改变季节模型的强度
holiday_prior_scale 改变假日模型的强度

# 核心参数详解

表格中列出的参数是控制模型“趋势”、“季节性”和“节假日”强度的关键开关:

  • yearly_seasonality / weekly_seasonality / daily_seasonality:
    这三个参数分别控制年度、周度和日度的周期性规律。你可以将其设置为 True(开启)、False(关闭)或者一个具体的整数(代表傅里叶阶数,控制拟合的复杂程度)。例如,如果你的数据是月度销量,通常只需要开启年度季节性,而关闭周度和日度季节性。

  • holidays: 这是用来传入自定义节假日或特殊事件的参数。它要求传入一个包含特定列的 DataFrame,通常包括:

    • holiday:事件名称(如“春节”、“618大促”)。
    • ds:事件发生的具体日期。
    • lower_window / upper_window:事件影响的前后窗口期(例如春节前3天到节后7天都会受影响)。
    • 此外,Prophet 还支持通过 add_country_holidays(country_name='CN') 直接内置国家法定节假日。
  • seasonality_prior_scale(季节性先验尺度):
    这个参数用来改变季节模型的强度(灵活性)。

    • 值越大:模型对季节性变化的拟合越灵活,能捕捉到更细微的季节波动,但也更容易过拟合(把噪声当成了季节规律)。
    • 值越小:模型对季节性的估计越保守,季节性曲线会更平滑。
  • holiday_prior_scale(节假日先验尺度): 这个参数用来改变假日模型的强度。其逻辑与季节性参数类似:

    • 值较大:表示模型认为节假日对数据的影响非常大,预测曲线在节假日期间会有剧烈的调整。
    • 值较小:表示模型对节假日效应持保守态度,节假日对整体预测结果的调整幅度会被限制在一个较窄的范围内。

总结来说,在实际业务中,分析师首先通过 yearly/weekly/daily_seasonality 搭建数据的常规周期骨架,然后利用 holidays 注入春节、促销等业务知识,最后通过调节 prior_scale 参数来平衡模型对“规律”和“突发事件”的敏感度,从而得到最贴合业务实际的预测结果。

# 总结

  1. 模型定位与优缺点
    定位:Prophet 是专门为商业预测(如销量预测、流量预估)设计的。
    优点:自动化程度极高。它不需要像传统机器学习模型那样做复杂的特征工程,就能自动拆解出数据的趋势、季节性和节假日效应。
    不足:信息利用单一。它只能处理单变量时间序列(仅靠 ds 和 y 两列数据)。在预测销量时,它无法直接利用“商品价格”、“门店位置”、“促销力度”等外部协变量(尽管可以通过节假日参数勉强模拟部分促销活动)。

  2. 核心输入与标准工作流
    数据格式:Prophet 对输入数据有严格要求,必须是包含两列的 DataFrame:
    ds:时间戳(必须是 Pandas 的 datetime 格式)。
    y:真实值(需要预测的数值型指标)。

标准四步法:
初始化:m = Prophet(holidays=holidays),可传入自定义节假日。
训练:model.fit(df),让模型学习历史规律。
构建未来时间轴:future = model.make_future_dataframe(periods=365)。注意,这个 future 不仅包含未来 365 天,还包含了历史的所有时间戳。
预测:forecast = model.predict(future),输出包含历史和未来的完整预测结果。

  1. 预测结果解读(forecast 字段)
    预测输出的 forecast 是一个包含丰富信息的 DataFrame,核心字段如下:
    预测值:yhat(核心预测结果),yhat_lower 和 yhat_upper(置信区间的上下界)。
    趋势分解:trend(基础趋势),weekly(周季节性),yearly(年季节性)。

加法/乘法项:
additive_terms:加法模型下的总季节/节假日效应(等于 weekly + yearly + holidays)。
multiplicative_terms:乘法模型下的总效应。如果使用的是默认的加法模型,该列将为空。

  1. 趋势突变点(Changepoints) 现实世界的数据趋势往往会发生突然转折(如疫情爆发、政策调整),Prophet 能自动检测这些点:
    自动检测机制:默认在前 80% 的历史数据中,均匀分布 25 个潜在的突变点。

参数调优: n_changepoints:增加或减少潜在突变点的数量(如设为 30)。
changepoint_range:调整寻找突变点的历史数据比例(如设为 0.9,即在前 90% 数据中寻找)。
人工指定:如果你明确知道某天发生了大事件,可以通过 changepoints=['2014-01-01'] 强制模型在该点进行趋势调整。

  1. 高级参数:增长模式与季节性模式
    增长趋势(growth):
    'linear'(默认):假设数据呈线性无限增长。
    'logistic'(逻辑斯蒂增长):适用于有天花板(饱和值)的业务场景(如市场渗透率)。使用时必须在数据框 df 中增加一列 cap 来指定饱和上限,否则会报错。

季节性模式(seasonality_mode):
'additive'(默认,加法模型):假设季节波动的幅度是固定的,不随整体趋势变化。
'multiplicative'(乘法模型):假设季节波动的幅度会随着整体趋势的增大而按比例放大(例如,销量基数越大,春节带来的绝对增量就越大)。