目录

引言

Holt-Winters 模型,也称为三次指数平滑法,是一种在时间序列分析中广泛使用的预测技术。它由 Charles Holt 和 Peter Winters 于 1960 年开发,是对 Holt 二次指数平滑方法的改进。

Holt-Winters 模型用于预测表现出趋势和季节性的时间序列的未来值。该模型使用三个平滑参数,一个用于估计趋势,一个用于估计时间序列的水平或基线水平,另一个用于估计季节性。这些参数分别称为 α、β 和 γ。

Holt-Winters 模型是 Holt 二次指数平滑方法的扩展,后者仅使用两个平滑参数来估计时间序列的趋势和基线水平。Holt-Winters 模型通过添加第三个季节性平滑参数来提高预测的准确性。

Holt-Winters 模型的主要优点之一是易于实现,且不需要大量历史数据即可生成准确的预测。此外,该模型具有高度适应性,可以定制以适应各种具有季节性的时间序列。

然而,Holt-Winters 模型也有一些局限性。例如,该模型假设时间序列是平稳的且季节性是恒定的。如果时间序列不平稳或季节性不是恒定的,Holt-Winters 模型可能不是最合适的。

总的来说,Holt-Winters 模型是时间序列分析中一种有用且广泛使用的技术,尤其是在时间序列预期表现出恒定趋势和季节性时。

Holt-Winters 方法

Holt-Winters 季节性方法包括预测方程和三个平滑方程——一个用于水平 t\ell_{t},一个用于趋势 btb_t,以及一个用于季节性分量 sts_t,并有相应的平滑参数 α\alphaβ\beta^*γ\gamma。我们使用 mm 来表示季节性周期,即一年中的季节数量。例如,对于季度数据,m=4m=4;对于月度数据,m=12m=12。这两种变体的主要区别在于季节性分量的性质。

该方法有两种变体,区别在于季节性分量的性质。当季节性变化在整个序列中大致恒定时,首选加法方法;当季节性变化与序列的水平成比例变化时,首选乘法方法。使用加法方法,季节性分量以观测序列的绝对值表示,并且在水平方程中,通过减去季节性分量来对序列进行季节性调整。在每一年中,季节性分量的总和大约为零。使用乘法方法,季节性分量以相对值(百分比)表示,并通过除以季节性分量来对序列进行季节性调整。在每一年中,季节性分量的总和大约为 mm

Holt-Winters 加法方法

Holt-Winters 加法方法是一种时间序列预测技术,通过引入加法季节性分量扩展了 Holt-Winters 方法。它适用于表现出随时间变化的季节性模式的时间序列数据。

Holt-Winters 加法方法使用三个平滑参数——alpha (α)、beta (β) 和 gamma (γ)——来估计时间序列的水平、趋势和季节性分量。alpha 参数控制水平分量的平滑,beta 参数控制趋势分量的平滑,gamma 参数控制加法季节性分量的平滑。

预测过程包括三个步骤:首先,使用平滑参数和历史数据估计水平、趋势和季节性分量;其次,使用这些分量预测时间序列的未来值;第三,使用加法因子对预测值进行季节性调整。

Holt-Winters 加法方法的优点之一是它可以处理具有加法季节性分量的时间序列数据,这在许多实际应用中很常见。该方法也易于实现,并且可以扩展以处理具有变化季节性模式的时间序列数据。

然而,该方法也有一些局限性。它假设季节性模式是加法的,但这可能不适用于所有时间序列。此外,该方法需要足够的历史数据来准确估计平滑参数和季节性分量。

总的来说,Holt-Winters 加法方法是一种强大且广泛使用的预测技术,可用于生成具有加法季节性分量的时间序列数据的准确预测。该方法易于实现,并且可以扩展以处理具有变化季节性模式的时间序列数据。

加法方法的分量形式是

其中 kk(h1)/m(h-1)/m 的整数部分,这确保用于预测的季节性指数估计值来自样本的最后一年。水平方程显示了季节调整后的观测值 (ytstm)(y_{t} - s_{t-m}) 与时间 tt 的非季节性预测值 (t1+bt1)(\ell_{t-1}+b_{t-1}) 之间的加权平均。趋势方程与 Holt 的线性方法相同。季节性方程显示了当前季节性指数 (ytt1bt1)(y_{t}-\ell_{t-1}-b_{t-1}) 与去年同一季节的季节性指数(即 mm 时间周期之前)之间的加权平均。

季节性分量的方程通常表示为

st=γ(ytt)+(1γ)stm.s_{t} = \gamma^* (y_{t}-\ell_{t})+ (1-\gamma^*)s_{t-m}.

如果我们从上面分量形式的水平平滑方程中代入 t\ell_{t},我们得到

st=γ(1α)(ytt1bt1)+[1γ(1α)]stm,s_{t} = \gamma^*(1-\alpha) (y_{t}-\ell_{t-1}-b_{t-1})+ [1-\gamma^*(1-\alpha)]s_{t-m},

这与我们在此指定的季节性分量的平滑方程相同,其中 γ=γ(1α)\gamma=\gamma^*(1-\alpha)。通常的参数限制是 0γ10\le\gamma^*\le1,这转化为 0γ1α0\le\gamma\le 1-\alpha

Holt-Winters 乘法方法

Holt-Winters 乘法方法使用三个平滑参数——alpha (α)、beta (β) 和 gamma (γ)——来估计时间序列的水平、趋势和季节性分量。alpha 参数控制水平分量的平滑,beta 参数控制趋势分量的平滑,gamma 参数控制乘法季节性分量的平滑。

预测过程包括三个步骤:首先,使用平滑参数和历史数据估计水平、趋势和季节性分量;其次,使用这些分量预测时间序列的未来值;第三,使用乘法因子对预测值进行季节性调整。

Holt-Winters 乘法方法的优点之一是它可以处理具有乘法季节性分量的时间序列数据,这在许多实际应用中很常见。该方法也易于实现,并且可以扩展以处理具有变化季节性模式的时间序列数据。

然而,该方法也有一些局限性。它假设季节性模式是乘法的,但这可能不适用于所有时间序列。此外,该方法需要足够的历史数据来准确估计平滑参数和季节性分量。

总的来说,Holt-Winters 乘法方法是一种强大且广泛使用的预测技术,可用于生成具有乘法季节性分量的时间序列数据的准确预测。该方法易于实现,并且可以扩展以处理具有变化季节性模式的时间序列数据。

在乘法版本中,季节性的平均值等于一。如果季节性变化随着水平的增加而增加,则使用乘法方法。

ETS 分类中的数学模型

我希望读者能更清楚地了解 ETS 框架是如何建立在时间序列分解思想之上的。通过引入不同的分量、定义它们的类型并添加它们的更新方程,我们可以构建更好地捕捉时间序列关键特征的模型。但我们也应该考虑分量随时间可能发生的变化。“转移”或“状态”方程应该反映这种变化:它们解释了水平、趋势或季节性分量是如何演变的。

如第 2.2 节所述,考虑到不同类型的分量及其相互作用,我们在该分类中最终得到 30 个模型。表 1 和表 2 以数学形式总结了图 1 和图 2 中图形化显示的所有 30 个 ETS 模型,给出了测量方程和转移方程的公式。

表 1:加法误差 ETS 模型

非季节性加法乘法
无趋势yt=lt1+ϵtlt=lt1+αϵt\begin{aligned} &y_{t} = l_{t-1} + \epsilon_t \\ &l_t = l_{t-1} + \alpha \epsilon_t \end{aligned}yt=lt1+stm+ϵtlt=lt1+αϵtst=stm+γϵt\begin{aligned} &y_{t} = l_{t-1} + s_{t-m} + \epsilon_t \\ &l_t = l_{t-1} + \alpha \epsilon_t \\ &s_t = s_{t-m} + \gamma \epsilon_t \end{aligned}yt=lt1stm+ϵtlt=lt1+αϵtstmst=stm+γϵtlt1\begin{aligned} &y_{t} = l_{t-1} s_{t-m} + \epsilon_t \\ &l_t = l_{t-1} + \alpha \frac{\epsilon_t}{s_{t-m}} \\ &s_t = s_{t-m} + \gamma \frac{\epsilon_t}{l_{t-1}} \end{aligned}
加法yt=lt1+bt1+ϵtlt=lt1+bt1+αϵtbt=bt1+βϵt\begin{aligned} &y_{t} = l_{t-1} + b_{t-1} + \epsilon_t \\ &l_t = l_{t-1} + b_{t-1} + \alpha \epsilon_t \\ &b_t = b_{t-1} + \beta \epsilon_t \end{aligned}yt=lt1+bt1+stm+ϵtlt=lt1+bt1+αϵtbt=bt1+βϵtst=stm+γϵt\begin{aligned} &y_{t} = l_{t-1} + b_{t-1} + s_{t-m} + \epsilon_t \\ &l_t = l_{t-1} + b_{t-1} + \alpha \epsilon_t \\ &b_t = b_{t-1} + \beta \epsilon_t \\ &s_t = s_{t-m} + \gamma \epsilon_t \end{aligned}yt=(lt1+bt1)stm+ϵtlt=lt1+bt1+αϵtstmbt=bt1+βϵtstmst=stm+γϵtlt1+bt1\begin{aligned} &y_{t} = (l_{t-1} + b_{t-1}) s_{t-m} + \epsilon_t \\ &l_t = l_{t-1} + b_{t-1} + \alpha \frac{\epsilon_t}{s_{t-m}} \\ &b_t = b_{t-1} + \beta \frac{\epsilon_t}{s_{t-m}} \\ &s_t = s_{t-m} + \gamma \frac{\epsilon_t}{l_{t-1} + b_{t-1}} \end{aligned}
加性阻尼yt=lt1+ϕbt1+ϵtlt=lt1+ϕbt1+αϵtbt=ϕbt1+βϵt\begin{aligned} &y_{t} = l_{t-1} + \phi b_{t-1} + \epsilon_t \\ &l_t = l_{t-1} + \phi b_{t-1} + \alpha \epsilon_t \\ &b_t = \phi b_{t-1} + \beta \epsilon_t \end{aligned}yt=lt1+ϕbt1+stm+ϵtlt=lt1+ϕbt1+αϵtbt=ϕbt1+βϵtst=stm+γϵt\begin{aligned} &y_{t} = l_{t-1} + \phi b_{t-1} + s_{t-m} + \epsilon_t \\ &l_t = l_{t-1} + \phi b_{t-1} + \alpha \epsilon_t \\ &b_t = \phi b_{t-1} + \beta \epsilon_t \\ &s_t = s_{t-m} + \gamma \epsilon_t \end{aligned}yt=(lt1+ϕbt1)stm+ϵtlt=lt1+ϕbt1+αϵtstmbt=ϕbt1+βϵtstmst=stm+γϵtlt1+ϕbt1\begin{aligned} &y_{t} = (l_{t-1} + \phi b_{t-1}) s_{t-m} + \epsilon_t \\ &l_t = l_{t-1} + \phi b_{t-1} + \alpha \frac{\epsilon_t}{s_{t-m}} \\ &b_t = \phi b_{t-1} + \beta \frac{\epsilon_t}{s_{t-m}} \\ &s_t = s_{t-m} + \gamma \frac{\epsilon_t}{l_{t-1} + \phi b_{t-1}} \end{aligned}
乘法yt=lt1bt1+ϵtlt=lt1bt1+αϵtbt=bt1+βϵtlt1\begin{aligned} &y_{t} = l_{t-1} b_{t-1} + \epsilon_t \\ &l_t = l_{t-1} b_{t-1} + \alpha \epsilon_t \\ &b_t = b_{t-1} + \beta \frac{\epsilon_t}{l_{t-1}} \end{aligned}yt=lt1bt1+stm+ϵtlt=lt1bt1+αϵtbt=bt1+βϵtlt1st=stm+γϵt\begin{aligned} &y_{t} = l_{t-1} b_{t-1} + s_{t-m} + \epsilon_t \\ &l_t = l_{t-1} b_{t-1} + \alpha \epsilon_t \\ &b_t = b_{t-1} + \beta \frac{\epsilon_t}{l_{t-1}} \\ &s_t = s_{t-m} + \gamma \epsilon_t \end{aligned}yt=lt1bt1stm+ϵtlt=lt1bt1+αϵtstmbt=bt1+βϵtlt1stmst=stm+γϵtlt1bt1
\begin{aligned} &y_{t} = l_{t-1} b_{t-1} s_{t-m} + \epsilon_t \\ &l_t = l_{t-1} b_{t-1} + \alpha \frac{\epsilon_t}{s_{t-m}} \\ &b_t = b_{t-1} + \beta \frac{\epsilon_t}{l_{t-1}s_{t-m}} \\ &s_t = s_{t-m} + \gamma \frac{\epsilon_t}{l_{t-1} b_{t-1}} \end{aligned}乘法阻尼yt=lt1bt1ϕ+ϵtlt=lt1bt1ϕ+αϵtbt=bt1ϕ+βϵtlt1\begin{aligned} &y_{t} = l_{t-1} b_{t-1}^\phi + \epsilon_t \\ &l_t = l_{t-1} b_{t-1}^\phi + \alpha \epsilon_t \\ &b_t = b_{t-1}^\phi + \beta \frac{\epsilon_t}{l_{t-1}} \end{aligned}yt=lt1bt1ϕ+stm+ϵtlt=lt1bt1ϕ+αϵtbt=bt1ϕ+βϵtlt1st=stm+γϵt\begin{aligned} &y_{t} = l_{t-1} b_{t-1}^\phi + s_{t-m} + \epsilon_t \\ &l_t = l_{t-1} b_{t-1}^\phi + \alpha \epsilon_t \\ &b_t = b_{t-1}^\phi + \beta \frac{\epsilon_t}{l_{t-1}} \\ &s_t = s_{t-m} + \gamma \epsilon_t \end{aligned}yt=lt1bt1ϕstm+ϵtlt=lt1bt1ϕ+αϵtstmbt=bt1ϕ+βϵtlt1stmst=stm+γϵtlt1bt1\begin{aligned} &y_{t} = l_{t-1} b_{t-1}^\phi s_{t-m} + \epsilon_t \\ &l_t = l_{t-1} b_{t-1}^\phi + \alpha \frac{\epsilon_t}{s_{t-m}} \\ &b_t = b_{t-1}^\phi + \beta \frac{\epsilon_t}{l_{t-1}s_{t-m}} \\ &s_t = s_{t-m} + \gamma \frac{\epsilon_t}{l_{t-1} b_{t-1}} \end{aligned}

表2:乘法误差ETS模型

非季节性加法乘法
无趋势yt=lt1(1+ϵt)lt=lt1(1+αϵt)\begin{aligned} &y_{t} = l_{t-1}(1 + \epsilon_t) \\ &l_t = l_{t-1}(1 + \alpha \epsilon_t) \end{aligned}yt=(lt1+stm)(1+ϵt)lt=lt1+αμy,tϵtst=stm+γμy,tϵt\begin{aligned} &y_{t} = (l_{t-1} + s_{t-m})(1 + \epsilon_t) \\ &l_t = l_{t-1} + \alpha \mu_{y,t} \epsilon_t \\ &s_t = s_{t-m} + \gamma \mu_{y,t} \epsilon_t \end{aligned}yt=lt1stm(1+ϵt)lt=lt1(1+αϵt)st=stm(1+γϵt)\begin{aligned} &y_{t} = l_{t-1} s_{t-m}(1 + \epsilon_t) \\ &l_t = l_{t-1}(1 + \alpha \epsilon_t) \\ &s_t = s_{t-m}(1 + \gamma \epsilon_t) \end{aligned}
加法yt=(lt1+bt1)(1+ϵt)lt=(lt1+bt1)(1+αϵt)bt=bt1+βμy,tϵt\begin{aligned} &y_{t} = (l_{t-1} + b_{t-1})(1 + \epsilon_t) \\ &l_t = (l_{t-1} + b_{t-1})(1 + \alpha \epsilon_t) \\ &b_t = b_{t-1} + \beta \mu_{y,t} \epsilon_t \end{aligned}yt=(lt1+bt1+stm)(1+ϵt)lt=lt1+bt1+αμy,tϵtbt=bt1+βμy,tϵtst=stm+γμy,tϵt\begin{aligned} &y_{t} = (l_{t-1} + b_{t-1} + s_{t-m})(1 + \epsilon_t) \\ &l_t = l_{t-1} + b_{t-1} + \alpha \mu_{y,t} \epsilon_t \\ &b_t = b_{t-1} + \beta \mu_{y,t} \epsilon_t \\ &s_t = s_{t-m} + \gamma \mu_{y,t} \epsilon_t \end{aligned}yt=(lt1+bt1)stm(1+ϵt)lt=(lt1+bt1)(1+αϵt)bt=bt1+β(lt1+bt1)ϵtst=stm(1+γϵt)\begin{aligned} &y_{t} = (l_{t-1} + b_{t-1}) s_{t-m}(1 + \epsilon_t) \\ &l_t = (l_{t-1} + b_{t-1})(1 + \alpha \epsilon_t) \\ &b_t = b_{t-1} + \beta (l_{t-1} + b_{t-1}) \epsilon_t \\ &s_t = s_{t-m} (1 + \gamma \epsilon_t) \end{aligned}
加性阻尼yt=(lt1+ϕbt1)(1+ϵt)lt=(lt1+ϕbt1)(1+αϵt)bt=ϕbt1+βμy,tϵt\begin{aligned} &y_{t} = (l_{t-1} + \phi b_{t-1})(1 + \epsilon_t) \\ &l_t = (l_{t-1} + \phi b_{t-1})(1 + \alpha \epsilon_t) \\ &b_t = \phi b_{t-1} + \beta \mu_{y,t} \epsilon_t \end{aligned}yt=(lt1+ϕbt1+stm)(1+ϵt)lt=lt1+ϕbt1+αμy,tϵtbt=ϕbt1+βμy,tϵtst=stm+γμy,tϵt\begin{aligned} &y_{t} = (l_{t-1} + \phi b_{t-1} + s_{t-m})(1 + \epsilon_t) \\ &l_t = l_{t-1} + \phi b_{t-1} + \alpha \mu_{y,t} \epsilon_t \\ &b_t = \phi b_{t-1} + \beta \mu_{y,t} \epsilon_t \\ &s_t = s_{t-m} + \gamma \mu_{y,t} \epsilon_t \end{aligned}yt=(lt1+ϕbt1)stm(1+ϵt)lt=lt1+ϕbt1(1+αϵt)bt=ϕbt1+β(lt1+ϕbt1)ϵtst=stm(1+γϵt)\begin{aligned} &y_{t} = (l_{t-1} + \phi b_{t-1}) s_{t-m}(1 + \epsilon_t) \\ &l_t = l_{t-1} + \phi b_{t-1} (1 + \alpha \epsilon_t) \\ &b_t = \phi b_{t-1} + \beta (l_{t-1} + \phi b_{t-1}) \epsilon_t \\ &s_t = s_{t-m}(1 + \gamma \epsilon_t) \end{aligned}
乘法yt=lt1bt1(1+ϵt)lt=lt1bt1(1+αϵt)bt=bt1(1+βϵt)\begin{aligned} &y_{t} = l_{t-1} b_{t-1} (1 + \epsilon_t) \\ &l_t = l_{t-1} b_{t-1} (1 + \alpha \epsilon_t) \\ &b_t = b_{t-1} (1 + \beta \epsilon_t) \end{aligned}yt=(lt1bt1+stm)(1+ϵt)lt=lt1bt1+αμy,tϵtbt=bt1+βμy,tlt1ϵtst=stm+γμy,tϵt\begin{aligned} &y_{t} = (l_{t-1} b_{t-1} + s_{t-m})(1 + \epsilon_t) \\ &l_t = l_{t-1} b_{t-1} + \alpha \mu_{y,t} \epsilon_t \\ &b_t = b_{t-1} + \beta \frac{\mu_{y,t}}{l_{t-1}} \epsilon_t \\ &s_t = s_{t-m} + \gamma \mu_{y,t} \epsilon_t \end{aligned}yt=lt1bt1stm(1+ϵt)lt=lt1bt1(1+αϵt)bt=bt1(1+βϵt)st=stm(1+γϵt)\begin{aligned} &y_{t} = l_{t-1} b_{t-1} s_{t-m} (1 + \epsilon_t) \\ &l_t = l_{t-1} b_{t-1} (1 + \alpha \epsilon_t) \\ &b_t = b_{t-1} (1 + \beta \epsilon_t) \\ &s_t = s_{t-m} (1 + \gamma \epsilon_t) \end{aligned}
乘法阻尼yt=lt1bt1ϕ(1+ϵt)lt=lt1bt1ϕ(1+αϵt)bt=bt1ϕ(1+βϵt)\begin{aligned} &y_{t} = l_{t-1} b_{t-1}^\phi (1 + \epsilon_t) \\ &l_t = l_{t-1} b_{t-1}^\phi (1 + \alpha \epsilon_t) \\ &b_t = b_{t-1}^\phi (1 + \beta \epsilon_t) \end{aligned}yt=(lt1bt1ϕ+stm)(1+ϵt)lt=lt1bt1ϕ+αμy,tϵtbt=bt1ϕ+βμy,tlt1ϵtst=stm+γμy,tϵt\begin{aligned} &y_{t} = (l_{t-1} b_{t-1}^\phi + s_{t-m})(1 + \epsilon_t) \\ &l_t = l_{t-1} b_{t-1}^\phi + \alpha \mu_{y,t} \epsilon_t \\ &b_t = b_{t-1}^\phi + \beta \frac{\mu_{y,t}}{l_{t-1}} \epsilon_t \\ &s_t = s_{t-m} + \gamma \mu_{y,t} \epsilon_t \end{aligned}yt=lt1bt1ϕstm(1+ϵt)lt=lt1bt1ϕ(1+αϵt)bt=bt1ϕ(1+βϵt)st=stm(1+γϵt)\begin{aligned} &y_{t} = l_{t-1} b_{t-1}^\phi s_{t-m} (1 + \epsilon_t) \\ &l_t = l_{t-1} b_{t-1}^\phi \left(1 + \alpha \epsilon_t\right) \\ &b_t = b_{t-1}^\phi \left(1 + \beta \epsilon_t\right) \\ &s_t = s_{t-m} \left(1 + \gamma \epsilon_t\right) \end{aligned}

从统计学的角度来看,表1和表2中的公式对应于“真实模型”,它们解释了潜在数据基础上的模型。但当涉及到它们的构建和估计时,ϵt\epsilon_t 被估计的 ete_t(根据误差类型计算方式不同)所替代,时间序列分量和平滑参数也被它们的估计值所替代(例如,用 α^\hat \alpha 代替 α\alpha)。然而,如果这些模型参数的值已知,则可以从这些模型生成点预测和向前 h 步的条件期望。

模型选择

Holt Winters 统计框架的一个巨大优势是可以利用信息准则进行模型选择。AIC、AIC_cBIC 可以用于此处,以确定哪个 Holt Winters 模型最适合给定的时间序列。

对于 Holt Winters 模型,Akaike 信息准则 (AIC) 定义为

AIC=2log(L)+2k,\text{AIC} = -2\log(L) + 2k,

其中 LL 是模型的似然,kk 是估计的参数和初始状态的总数(包括残差方差)。

针对小样本偏差进行修正的 AIC (AIC_c) 定义为

AICc=AIC+2k(k+1)Tk1AIC_c = AIC + \frac{2k(k+1)}{T-k-1}

贝叶斯信息准则 (BIC)

BIC=AIC+k[log(T)2]\text{BIC} = \text{AIC} + k[\log(T)-2]

(误差、趋势、季节性)的三种组合可能导致数值困难。具体而言,可能导致此类不稳定性的模型是 ETS(A,N,M)、ETS(A,A,M)ETS(A,Ad,M),这是由于状态方程中可能出现除以接近零的值。我们通常在选择模型时不会考虑这些特定组合。

乘法误差模型在数据严格为正时很有用,但当数据包含零或负值时,数值上不稳定。因此,如果时间序列不是严格为正,则不会考虑乘法误差模型。在这种情况下,将只应用六种完全加法模型。

加载库和数据

提示

需要安装 Statsforecast。请参阅安装说明

接下来,我们导入绘图库并配置绘图样式。

import matplotlib.pyplot as plt
import seaborn as sns
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
plt.style.use('grayscale') # fivethirtyeight  grayscale  classic
plt.rcParams['lines.linewidth'] = 1.5
dark_style = {
    'figure.facecolor': '#008080',  # #212946
    'axes.facecolor': '#008080',
    'savefig.facecolor': '#008080',
    'axes.grid': True,
    'axes.grid.which': 'both',
    'axes.spines.left': False,
    'axes.spines.right': False,
    'axes.spines.top': False,
    'axes.spines.bottom': False,
    'grid.color': '#000000',  #2A3459
    'grid.linewidth': '1',
    'text.color': '0.9',
    'axes.labelcolor': '0.9',
    'xtick.color': '0.9',
    'ytick.color': '0.9',
    'font.size': 12 }
plt.rcParams.update(dark_style)


from pylab import rcParams
rcParams['figure.figsize'] = (18,7)

读取数据

df=pd.read_csv("https://raw.githubusercontent.com/Naren8520/Serie-de-tiempo-con-Machine-Learning/main/Data/ads.csv")
df.head()
时间广告
02017-09-13T00:00:0080115
12017-09-13T01:00:0079885
22017-09-13T02:00:0089325
32017-09-13T03:00:00101930
42017-09-13T04:00:00121630

StatsForecast 的输入始终是一个长格式的数据框,包含三列:unique_id、ds 和 y

  • unique_id(字符串、整数或类别)表示序列的标识符。

  • ds(日期戳)列应采用 Pandas 期望的格式,日期最好采用 YYYY-MM-DD 格式,时间戳最好采用 YYYY-MM-DD HH:MM:SS 格式。

  • y(数字)表示我们希望预测的测量值。

df["unique_id"]="1"
df.columns=["ds", "y", "unique_id"]
df.head()
dsyunique_id
02017-09-13T00:00:00801151
12017-09-13T01:00:00798851
22017-09-13T02:00:00893251
32017-09-13T03:00:001019301
42017-09-13T04:00:001216301
print(df.dtypes)
ds           object
y             int64
unique_id    object
dtype: object

我们可以看到我们的时间变量 (ds) 是对象格式的,我们需要将其转换为日期格式

df["ds"] = pd.to_datetime(df["ds"])

使用绘图方法探索数据

使用 StatsForecast 类中的 plot 方法绘制一些序列。此方法从数据集中打印随机序列,对于基本 EDA 非常有用。

from statsforecast import StatsForecast

StatsForecast.plot(df)

增广迪基-福勒检验

增强 Dickey-Fuller (ADF) 检验是一种统计检验,用于确定时间序列数据中是否存在单位根。单位根会导致时间序列分析中出现不可预测的结果。单位根检验中形成一个零假设,以确定时间序列数据受趋势影响的程度。通过接受零假设,我们接受时间序列数据非平稳的证据。通过拒绝零假设或接受备择假设,我们接受时间序列数据由平稳过程生成的证据。这个过程也称为平稳趋势。ADF 检验统计量的值为负。较低的 ADF 值表示对零假设的拒绝更强烈。

增强 Dickey-Fuller 检验是常用的统计检验,用于检验给定时间序列是否平稳。我们可以通过定义零假设和备择假设来实现这一点。

  • 零假设:时间序列非平稳。它呈现出时间依赖的趋势。

  • 备择假设:时间序列平稳。换句话说,序列不依赖于时间。

  • ADF 或 t 统计量 < 临界值:拒绝零假设,时间序列平稳。

  • ADF 或 t 统计量 > 临界值:未能拒绝零假设,时间序列非平稳。

from statsmodels.tsa.stattools import adfuller

def Augmented_Dickey_Fuller_Test_func(series , column_name):
    print (f'Dickey-Fuller test results for columns: {column_name}')
    dftest = adfuller(series, autolag='AIC')
    dfoutput = pd.Series(dftest[0:4], index=['Test Statistic','p-value','No Lags Used','Number of observations used'])
    for key,value in dftest[4].items():
        dfoutput['Critical Value (%s)'%key] = value
    print (dfoutput)
    if dftest[1] <= 0.05:
        print("Conclusion:====>")
        print("Reject the null hypothesis")
        print("The data is stationary")
    else:
        print("Conclusion:====>")
        print("The null hypothesis cannot be rejected")
        print("The data is not stationary")
Augmented_Dickey_Fuller_Test_func(df["y"],'Ads')
Dickey-Fuller test results for columns: Ads
Test Statistic         -7.089634e+00
p-value                 4.444804e-10
No Lags Used            9.000000e+00
                            ...     
Critical Value (1%)    -3.462499e+00
Critical Value (5%)    -2.875675e+00
Critical Value (10%)   -2.574304e+00
Length: 7, dtype: float64
Conclusion:====>
Reject the null hypothesis
The data is stationary

自相关图

自相关函数

定义 1。{xt;1tn}\{x_t;1 ≤ t ≤ n\} 是来自 {Xt}\{X_t\} 的大小为 n 的时间序列样本。1. xˉ=t=1nxtn\bar x = \sum_{t=1}^n \frac{x_t}{n} 称为 {Xt}\{X_t\} 的样本均值。2. ck=t=1nk(xt+kxˉ)(xtxˉ)/nc_k =\sum_{t=1}^{n−k} (x_{t+k}- \bar x)(x_t−\bar x)/n 称为 {Xt}\{X_t\} 的样本自协方差函数。3. rk=ck/c0r_k = c_k /c_0 称为 {Xt}\{X_t\} 的样本自相关函数。

请注意此定义的以下备注

  • 像大多数文献一样,本指南使用 ACF 表示样本自相关函数和自相关函数。根据上下文可以很容易地识别 ACF 所表示的内容。

  • 显然 c0 是 {Xt}\{X_t\} 的样本方差。此外,r0=c0/c0=1r_0 = c_0/c_0 = 1,并且对于任何整数 k,rk1k, |r_k| ≤ 1

  • 当我们计算任何固定长度 nn 的样本序列的 ACF 时,我们对较大 k 的 rkr_k 值不能抱有太大信心,因为随着 kk 增大,可用于计算 rkr_k(xt+k,xt)(x_{t +k }, x_t ) 对越来越少。一个经验法则是不要估计 rkr_kk>n/3k > n/3,另一个是 n50,kn/4n ≥ 50, k ≤ n/4。无论如何,谨慎总是明智的。

  • 我们还通过定义 1 计算非平稳时间序列样本的 ACF。然而在这种情况下,ACF 或 rkr_k 随着 kk 的增加,衰减非常缓慢甚至几乎不衰减。

  • 绘制 ACF (rk)(r_k) 对滞后 kk 的图很容易绘制,但对于分析时间序列样本非常有帮助。这种 ACF 图被称为相关图。

  • 如果 {Xt}\{X_t\} 是平稳的,且 E(Xt)=0E(X_t)=0,并且对于所有 k0k \neq 0,即它是一个白噪声序列,那么 rkr_k 的抽样分布是渐近正态分布,均值为 0,方差为 1/n1/n。因此,rkr_k 大约有 95% 的概率落在区间 [1.96/n,1.96/n][−1.96/\sqrt{n}, 1.96/\sqrt{n}] 内。

现在我们可以总结一下:(1) 如果时间序列图清楚地显示出趋势或/和季节性,那么它肯定是非平稳的;(2) 如果 ACF rkr_k 随着滞后 kk 的增加而衰减非常缓慢或几乎不衰减,那么该时间序列也应该是非平稳的。

fig, axs = plt.subplots(nrows=1, ncols=2)

plot_acf(df["y"],  lags=30, ax=axs[0],color="fuchsia")
axs[0].set_title("Autocorrelation");

plot_pacf(df["y"],  lags=30, ax=axs[1],color="lime")
axs[1].set_title('Partial Autocorrelation')

plt.show();

时间序列分解

如何以及为什么要分解时间序列?

在时间序列分析中,为了预测新值,了解过去数据非常重要。更正式地说,了解值随时间遵循的模式非常重要。有许多原因可能导致我们的预测值偏离正确方向。基本上,一个时间序列由四个组成部分构成。这些组成部分的变化导致时间序列模式的变化。这些组成部分是:

  • 水平 (Level):这是随时间平均的主要值。
  • 趋势 (Trend):趋势是导致时间序列中出现递增或递减模式的值。
  • 季节性 (Seasonality):这是在时间序列中短期出现的周期性事件,导致时间序列中短期递增或递减的模式。
  • 残差/噪声 (Residual/Noise):这是时间序列中的随机变化。

这些分量随时间组合形成时间序列。大多数时间序列包含水平和噪声/残差,而趋势或季节性是可选值。

如果时间序列中包含季节性和趋势,那么这将对预测值产生影响。因为预测时间序列的模式可能与先前的时间序列不同。

时间序列中分量的组合可以是两种类型:* 加法型 * 乘法型

加法时间序列

如果时间序列的分量相加构成时间序列。那么该时间序列称为加法时间序列。通过可视化,我们可以说如果时间序列的递增或递减模式在整个序列中相似,则该时间序列是加法型的。任何加法时间序列的数学函数可以表示为:y(t)=level+Trend+seasonality+noisey(t) = level + Trend + seasonality + noise

乘法时间序列

如果时间序列的分量相乘构成时间序列,那么该时间序列称为乘法时间序列。从可视化来看,如果时间序列随时间呈指数增长或下降,则可以认为该时间序列是乘法时间序列。乘法时间序列的数学函数可以表示为:

y(t)=LevelTrendseasonalityNoisey(t) = Level * Trend * seasonality * Noise

加法

from statsmodels.tsa.seasonal import seasonal_decompose 
a = seasonal_decompose(df["y"], model = "additive", period=24)
a.plot();

乘法

from statsmodels.tsa.seasonal import seasonal_decompose 
a = seasonal_decompose(df["y"], model = "Multiplicative", period=24)
a.plot();

将数据分割为训练集和测试集

我们将数据分割成数据集

  1. 用于训练我们的 Holt Winters 模型 的数据。
  2. 用于测试我们的模型的数据

对于测试数据,我们将使用最后 30 小时的数据来测试和评估模型的性能。

train = df[df.ds<='2017-09-20 17:00:00'] 
test = df[df.ds>'2017-09-20 17:00:00']
train.shape, test.shape
((186, 3), (30, 3))

现在我们绘制训练数据和测试数据。

sns.lineplot(train,x="ds", y="y", label="Train", linestyle="--",linewidth=2)
sns.lineplot(test, x="ds", y="y", label="Test", linewidth=2, color="yellow")
plt.title("Ads watched (hourly data)");
plt.show()

使用 StatsForecast 实现 Holt-Winters 方法

加载库

from statsforecast import StatsForecast
from statsforecast.models import HoltWinters

实例化模型

导入并实例化模型。设置参数有时很棘手。关于季节周期 的这篇文章由大师 Rob Hyndman 撰写,对 season_length 参数可能很有帮助。

在这种情况下,我们将测试模型的两种替代方案,一种是加法型,一种是乘法型。

season_length = 24 # Hourly data 
horizon = len(test) # number of predictions

models = [HoltWinters(season_length=season_length, error_type="A", alias="Add"),
          HoltWinters(season_length=season_length, error_type="M", alias="Multi")]

我们通过实例化一个新的 StatsForecast 对象并传入以下参数来拟合模型:

models: 模型列表。从 models 中选择您想要的模型并导入它们。

  • freq: 表示数据频率的字符串。(请参阅pandas 可用频率。)

  • n_jobs: int,并行处理中使用的作业数,使用 -1 表示所有核心。

  • fallback_model: 如果某个模型失败,则使用的备用模型。

所有设置都通过构造函数传入。然后调用其 fit 方法并传入历史数据框。

sf = StatsForecast(models=models, freq='h')

拟合模型

sf.fit(df=train)
StatsForecast(models=[Add,Multi])

让我们看看我们的 Holt Winters 模型 的结果。我们可以使用以下指令观察它:

result=sf.fitted_[0,0].model_
print(result.keys())
print(result['fit'])
dict_keys(['loglik', 'aic', 'bic', 'aicc', 'mse', 'amse', 'fit', 'residuals', 'components', 'm', 'nstate', 'fitted', 'states', 'par', 'sigma2', 'n_params', 'method', 'actual_residuals'])
results(x=array([ 2.60632491e-02,  1.53030002e-03,  3.22298668e-02,  9.00958233e-01,
        1.23628350e+05, -5.12405452e+01, -3.96677340e+04, -2.83800237e+04,
       -1.49514829e+04,  1.05413201e+04,  3.65409126e+04,  3.58433030e+04,
        2.93235036e+04,  2.66607410e+04,  2.55392078e+04,  2.60970444e+04,
        2.63155973e+04,  2.83192738e+04,  2.16640268e+04,  5.19120023e+03,
       -6.15595960e+03, -8.84863887e+03, -9.28320586e+03, -8.09549672e+03,
       -3.83755898e+03, -3.33456554e+03, -2.56333963e+04, -3.72181618e+04,
       -4.42497509e+04]), fn=4363.098387651742, nit=1001, simplex=None)

现在我们可视化模型的拟合值。

如我们所见,上面获得的结果是一个字典输出,为了从字典中提取每个元素,我们将使用 .get() 函数提取元素,然后将其保存在 pd.DataFrame() 中。

residual=pd.DataFrame(result.get("residuals"), columns=["residual Model"])
residual
残差模型
0-1087.029091
1623.989786
23054.101324
183-2783.032921
184-4618.147123
185-8194.063498
import scipy.stats as stats

fig, axs = plt.subplots(nrows=2, ncols=2)

residual.plot(ax=axs[0,0])
axs[0,0].set_title("Residuals");

sns.distplot(residual, ax=axs[0,1]);
axs[0,1].set_title("Density plot - Residual");

stats.probplot(residual["residual Model"], dist="norm", plot=axs[1,0])
axs[1,0].set_title('Plot Q-Q')

plot_acf(residual,  lags=35, ax=axs[1,1],color="fuchsia")
axs[1,1].set_title("Autocorrelation");

plt.show();

预测方法

如果您想在具有多个序列或模型的生产环境中提高速度,我们建议使用 StatsForecast.forecast 方法而不是 .fit.predict

主要区别在于 .forecast 不存储拟合值,并且在分布式环境中具有高度可伸缩性。

forecast 方法接受两个参数:预测接下来 h(预测范围)和 level

  • h (int): 表示未来 h 步的预测。在此例中,是未来 30 小时。

  • level (list of floats): 这个可选参数用于概率预测。设置预测区间的水平(或置信百分比)。例如,level=[90] 表示模型期望实际值有 90% 的时间落在这个区间内。

这里的 forecast 对象是一个新的数据框,包含模型名称和 y hat 值的列,以及不确定性区间的列。根据您的计算机,此步骤大约需要 1 分钟。

Y_hat = sf.forecast(df=train, h=horizon, fitted=True)
Y_hat
unique_idds加法型乘法型
012017-09-20 18:00:00154164.609375151414.984375
112017-09-20 19:00:00154547.171875152352.640625
212017-09-20 20:00:00128790.359375128274.789062
2712017-09-21 21:00:00103021.726562103086.851562
2812017-09-21 22:00:0089544.05468890028.406250
2912017-09-21 23:00:0078090.21093878823.953125

使用 forecast 方法,我们还可以从模型中提取拟合值并进行图形化可视化,可以使用以下指令完成。

values=sf.forecast_fitted_values()
values.head()
unique_iddsy加法型乘法型
012017-09-13 00:00:0080115.081202.03125079892.687500
112017-09-13 01:00:0079885.079261.00781278792.476562
212017-09-13 02:00:0089325.086270.89843885444.117188
312017-09-13 03:00:00101930.097905.27343897286.796875
412017-09-13 04:00:00121630.0120287.523438118195.570312
StatsForecast.plot(values)

使用 forecast 方法添加 95% 置信区间

sf.forecast(df=train, h=horizon, level=[95])
unique_idds加法型加法型-下限-95加法型-上限-95乘法型乘法型-下限-95乘法型-上限-95
012017-09-20 18:00:00154164.609375134594.859375173734.375000151414.984375125296.867188177533.109375
112017-09-20 19:00:00154547.171875134970.062500174124.265625152352.640625126234.515625178470.765625
212017-09-20 20:00:00128790.359375109205.242188148375.484375128274.789062102156.671875154392.906250
2712017-09-21 21:00:00103021.72656283118.632812122924.812500103086.85156276659.867188129513.835938
2812017-09-21 22:00:0089544.05468869626.210938109461.89062590028.40625063601.425781116455.390625
2912017-09-21 23:00:0078090.21093858157.57421998022.84375078823.95312552396.972656105250.937500
sf.plot(train, Y_hat)

带有置信区间的预测方法

要生成预测,请使用 predict 方法。

predict 方法接受两个参数:预测接下来 h(预测范围)和 level

  • h (int): 表示未来 h 步的预测。在此例中,是未来 30 小时。

  • level (list of floats): 这个可选参数用于概率预测。设置预测区间的水平(或置信百分比)。例如,level=[95] 表示模型期望实际值有 95% 的时间落在这个区间内。

这里的 forecast 对象是一个新的数据框,包含模型名称和 y hat 值的列,以及不确定性区间的列。

此步骤应少于 1 秒。

sf.predict(h=horizon)
unique_idds加法型乘法型
012017-09-20 18:00:00154164.609375151414.984375
112017-09-20 19:00:00154547.171875152352.640625
212017-09-20 20:00:00128790.359375128274.789062
2712017-09-21 21:00:00103021.726562103086.851562
2812017-09-21 22:00:0089544.05468890028.406250
2912017-09-21 23:00:0078090.21093878823.953125
forecast_df = sf.predict(h=horizon, level=[80,95]) 
forecast_df
unique_idds加法型加法型-下限-95加法型-下限-80加法型-上限-80加法型-上限-95乘法型乘法型-下限-95乘法型-下限-80乘法型-上限-80乘法型-上限-95
012017-09-20 18:00:00154164.609375134594.859375141368.640625166960.593750173734.375000151414.984375125296.867188134337.265625168492.703125177533.109375
112017-09-20 19:00:00154547.171875134970.062500141746.390625167347.953125174124.265625152352.640625126234.515625135274.921875169430.359375178470.765625
212017-09-20 20:00:00128790.359375109205.242188115984.335938141596.375000148375.484375128274.789062102156.671875111197.070312145352.515625154392.906250
2712017-09-21 21:00:00103021.72656283118.63281290007.796875116035.656250122924.812500103086.85156276659.86718885807.171875120366.523438129513.835938
2812017-09-21 22:00:0089544.05468869626.21093876520.476562102567.632812109461.89062590028.40625063601.42578172748.734375107308.085938116455.390625
2912017-09-21 23:00:0078090.21093858157.57421965056.96093891123.46093898022.84375078823.95312552396.97265661544.28125096103.632812105250.937500
sf.plot(train, forecast_df, level=[80, 95])

交叉验证

在前面的步骤中,我们使用历史数据预测未来。然而,为了评估其准确性,我们还想知道模型在过去会如何表现。为了评估模型在数据上的准确性和稳健性,请进行交叉验证。

对于时间序列数据,交叉验证是通过在历史数据上定义一个滑动窗口并预测其后的时间段来完成的。这种形式的交叉验证使我们能够更好地估计模型在更广泛的时间点上的预测能力,同时保持训练集中的数据连续,这是我们的模型所要求的。

下图描述了这种交叉验证策略:

执行时间序列交叉验证

时间序列模型的交叉验证被认为是最佳实践,但大多数实现非常慢。statsforecast 库将交叉验证实现为分布式操作,从而减少了执行过程所需的时间。如果您拥有大型数据集,您还可以使用 Ray、Dask 或 Spark 在分布式集群中执行交叉验证。

在此例中,我们希望评估每个模型在过去 5 个月 (n_windows=) 中的性能,每隔两个月 (step_size=12) 进行预测。根据您的计算机,此步骤大约需要 1 分钟。

StatsForecast 类中的 cross_validation 方法接受以下参数:

  • df: 训练数据框

  • h (int): 表示正在预测的未来 h 步。在此例中,是未来 12 个月。

  • step_size (int): 每个窗口之间的步长。换句话说:您希望多久运行一次预测过程。

  • n_windows(int): 用于交叉验证的窗口数。换句话说:您希望评估过去多少个预测过程。

crossvalidation_df = sf.cross_validation(df=df,
                                         h=horizon,
                                         step_size=30,
                                         n_windows=3)

crossvaldation_df 对象是一个新的数据框,包含以下列:

  • unique_id: 系列标识符。
  • ds: 日期戳或时间索引。
  • cutoff: n_windows 的最后一个日期戳或时间索引。
  • y: 真实值。
  • model: 包含模型名称和拟合值的列。
crossvalidation_df
unique_idds截止时间y加法型乘法型
012017-09-18 06:00:002017-09-18 05:00:0099440.0134578.328125133820.109375
112017-09-18 07:00:002017-09-18 05:00:0097655.0133548.781250133734.000000
212017-09-18 08:00:002017-09-18 05:00:0097655.0134798.656250135216.046875
8712017-09-21 21:00:002017-09-20 17:00:00103080.0103021.726562103086.851562
8812017-09-21 22:00:002017-09-20 17:00:0095155.089544.05468890028.406250
8912017-09-21 23:00:002017-09-20 17:00:0080285.078090.21093878823.953125

模型评估

现在我们将使用预测结果来评估我们的模型,我们将使用不同类型的指标 MAE、MAPE、MASE、RMSE、SMAPE 来评估准确性。

from functools import partial

import utilsforecast.losses as ufl
from utilsforecast.evaluation import evaluate
evaluate(
    test.merge(Y_hat),
    metrics=[ufl.mae, ufl.mape, partial(ufl.mase, seasonality=season_length), ufl.rmse, ufl.smape],
    train_df=train,
)
unique_id指标加法型乘法型
01平均绝对误差 (MAE)4306.2445314886.992188
11平均绝对百分比误差 (MAPE)0.0380870.043549
21平均绝对缩放误差 (MASE)0.5320450.603797
31均方根误差 (RMSE)5415.0155735862.473702
41对称平均绝对百分比误差 (SMAPE)0.0187080.021433

参考资料

  1. Changquan Huang • Alla Petukhina. Springer series (2022). 应用时间序列分析与 Python 预测。
  2. Ivan Svetunkov. 使用增强动态自适应模型 (ADAM) 进行预测和分析
  3. James D. Hamilton. 时间序列分析。普林斯顿大学出版社,新泽西州普林斯顿,第一版,1994 年。
  4. Nixtla 参数.
  5. Pandas 可用频率.
  6. Rob J. Hyndman 和 George Athanasopoulos (2018)。“预测原理与实践(第 3 版)”.
  7. 季节周期 - Rob J Hyndman.