文章目录
- 前言
 - 使用前提
 - 平稳性检验
 - 白噪声检验
 
- 用法
 - 代码实例
 - 第一步——平稳性分析
 - 方法一
 - 方法二
 - 方法三
 
- 第二步——白噪声分析
 - 第三步——确定参数
 - 第四步——模型构建与检验
 - 检验模型效果
 - 预测未来数据
 
前言
  常见的时间序列分析方法有很多,之前介绍了一个稍微新颖的 Prophet 时间序列分析法,这个方法的好处是可以综合考虑节假日的影响(节假日可能导致异常值的出现),并站在不同的时间跨度上考量周际  
     
      
       
       
         ( 
        
       
         T 
        
       
         = 
        
       
         7 
        
       
         d 
        
       
         ) 
        
       
      
        (T=7\mathrm d) 
       
      
    (T=7d)、月际  
     
      
       
       
         ( 
        
       
         T 
        
       
         = 
        
       
         30 
        
       
         d 
        
       
         ) 
        
       
      
        (T=30\mathrm d) 
       
      
    (T=30d)、年际  
     
      
       
       
         ( 
        
       
         T 
        
       
         = 
        
       
         365 
        
       
         d 
        
       
         ) 
        
       
      
        (T=365\mathrm d) 
       
      
    (T=365d) 的周期性及其影响。Prophet 还能考虑外生变量的影响,这是它的突出特点。详情请看博客:Python 数学建模——Prophet 时间序列预测_多变量prophet-CSDN博客。
   但是 Prophet 的缺点也很多,例如当时间序列不是“日期”的序列时,Prophet 将稍显逊色。本篇博客我们将介绍一个新的时间序列分析方法——ARMA 时间序列分析的使用方法。
ARMA 的具体原理可以参考其他博客,本篇博客主要介绍 ARMA 的使用方法和 Python 实现。
使用前提
  ARMA 模型进行时间序列分析,适合用于平稳非白噪声序列。对于非平稳序列应该使用 ARIMA 模型(即使用差分等方法,后面会稍作介绍),或者取对数等方式,转化为平稳序列进行分析。
   基于这个适用前提,任何一个时间序列在使用 ARMA 之前,都必须进行平稳性检验和白噪声检验。
平稳性检验
检验一个时间序列的平稳性,有 3 3 3 种可行的检验方式。下面将逐一介绍。其中第三种基于假设检验的定量方法很好地摈弃了主观性,因此应该是平稳性检验的最佳选择。前两种方式可以用于数据可视化,带来有关平稳性的直接视觉冲击。
- 从时间序列上来看,有明显上升、明显下降趋势的为非平稳序列,稳定在一定范围附近的为平稳序列。
 - 从自相关图、偏自相关图上看,具有较强长期相关性(即大部分自相关系数的绝对值都较大,或者说远离0)的是非平稳序列,比如下面最左边的那张图。相对而言自相关系数的绝对值没那么大的,就具有短期相关性,是平稳序列,如下图右边两张图。
 
 
 
 
 
 
图片来自于参考文献。可以看到最左边图,阴影部分达到了 ± 0.75 \pm0.75 ±0.75,自相关系数的绝对值已经很大、严重偏离 0 0 0 了,说明后期的数据严重依赖于前期的数据,具有比较长期的相关性,不适合使用 ARMA。然而中间这张图,阴影部分在 ± 0.4 \pm0.4 ±0.4 范围左右,则可以认为没有长期相关性。右边这张图也是没有长期相关性。
- 基于假设检验的平稳性检验方法,或者说是单位根检验。Python 库提供的基于假设检验的方法
statsmodels.tsa.stattools.adfuller,对一个时间序列给出 p p p 值,若 p < 0.05 p<0.05 p<0.05,则接受“序列是平稳序列”的原假设。 
白噪声检验
  白噪声检验也是直接调用 Python 库提供的statsmodels.stats.diagnostic.acorr_ljungbox方法。这个方法基于假设检验给出一个  
     
      
       
       
         p 
        
       
      
        p 
       
      
    p 值,当  
     
      
       
       
         p 
        
       
         < 
        
       
         0.05 
        
       
      
        p<0.05 
       
      
    p<0.05 时接受“序列是非白噪声数据”的原假设。
用法
ARMA 模型有两个参数 p , q p,q p,q,要确定参数,考虑下面的两个指标:
- AIC:赤池信息量准则(Akaike information criterion),定义为 A I C = n ln  σ ^ ε 2 + 2 ( p + q + 1 ) \mathrm{AIC}=n{{\ln{\hat{\sigma }}}_{\varepsilon }^{2}}+2(p+q+1) AIC=nlnσ^ε2+2(p+q+1)
 - BIC:贝叶斯信息量准则(Bayesian information criterion),定义为 B I C = n ln  σ ^ ε 2 + ( p + q ) ln  n \mathrm{BIC}=n{{\ln{\hat{\sigma }}}_{\varepsilon }^{2}}+(p+q)\ln{n} BIC=nlnσ^ε2+(p+q)lnn
 
  这两个指标都是关于  
     
      
       
       
         p 
        
       
         , 
        
       
         q 
        
       
      
        p,q 
       
      
    p,q 的函数,它们是研究者针对 ARMA 模型复杂度设计的惩罚项,用于避免过拟合问题。好的参数  
     
      
       
       
         p 
        
       
         , 
        
       
         q 
        
       
      
        p,q 
       
      
    p,q 应该要使得上面两个指标尽可能小。
   确定参数是可以只用 AIC/BIC 其中一个,也可以混合起来用,实际操作中通过穷举一些  
     
      
       
       
         ( 
        
       
         p 
        
       
         , 
        
       
         q 
        
       
         ) 
        
       
      
        (p,q) 
       
      
    (p,q) 值,从中挑选最佳参数。即  
     
      
       
       
         ( 
        
       
         p 
        
       
         , 
        
       
         q 
        
       
         ) 
        
       
         = 
        
        
         
         
           argmin 
          
         
            
          
         
         
         
           p 
          
         
           , 
          
         
           q 
          
         
        
        
        
          A 
         
        
          I 
         
        
          C 
         
        
       
         ( 
        
       
         p 
        
       
         , 
        
       
         q 
        
       
         ) 
        
       
      
        (p,q)=\operatorname{argmin}\limits_{p,q}\mathrm{AIC}(p,q) 
       
      
    (p,q)=argminp,qAIC(p,q),或者  
     
      
       
       
         ( 
        
       
         p 
        
       
         , 
        
       
         q 
        
       
         ) 
        
       
         = 
        
        
         
         
           argmin 
          
         
            
          
         
         
         
           p 
          
         
           , 
          
         
           q 
          
         
        
        
        
          B 
         
        
          I 
         
        
          C 
         
        
       
         ( 
        
       
         p 
        
       
         , 
        
       
         q 
        
       
         ) 
        
       
      
        (p,q)=\operatorname{argmin}\limits_{p,q}\mathrm{BIC}(p,q) 
       
      
    (p,q)=argminp,qBIC(p,q)
代码实例
有一份太阳黑子数据,展示了从 1700 1700 1700 到 1988 1988 1988 年每年太阳黑子的观测数量。部分如下所示:
| year | count | 
|---|---|
| 1700 1700 1700 | 5 5 5 | 
| 1701 1701 1701 | 11 11 11 | 
| 1702 1702 1702 | 16 16 16 | 
| 1703 1703 1703 | 23 23 23 | 
| 1704 1704 1704 | 36 36 36 | 
| 1705 1705 1705 | 58 58 58 | 
点击下载路径,
Ctrl+S下载上表所示sunspots.csv表单。
第一步——平稳性分析
这是一个“年份”的时间序列,不适合使用 Prophet 时间预测模型。考虑使用 ARMA,先进行平稳性分析。
方法一
基于方法一,可以画出时间序列,代码如下:
import pandas as pd, numpy as np
import statsmodels.api as sm
import matplotlib.pylab as plt
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
plt.rc('axes',unicode_minus=False)
plt.rc('font',family='SimHei'); plt.rc('font',size=16)
d=pd.read_csv('sunspots.csv'); dd=d['counts']
years=d['year'].values.astype(int)
plt.plot(years,dd.values,'-*');
plt.savefig("原始数据折线图.pdf")
 
画出时间序列图如下图所示。肉眼观察图像,认为太阳黑子数量稳定在一定范围附近,没有明显上升或者明显下降的趋势,原序列基本平稳。
 
方法二
基于方法二,我们还可以画自相关图和偏自相关图。代码如下所示:
# 接着上面的代码
ax1=plt.subplot(121); plot_acf(dd,ax=ax1,title='自相关',lags = len(dd) - 1)
ax2=plt.subplot(122); plot_pacf(dd,ax=ax2,title='偏自相关',lags = len(dd) - 1)
plt.savefig("原序列的自相关与偏自相关图.pdf")
# lags 参数是我自己加的,原代码里面没有,其含义应该是相关图横坐标的范围
# 如果不加 lags 参数,自相关、偏自相关图的横坐标就只到 25 左右,很不能说明问题
 
画出结果如下图所示。自相关图蓝色阴影在 ± 0.4 \pm0.4 ±0.4 之内,偏自相关图几乎没有阴影。从而认为原序列没有很长的长期相关性,从而是平稳序列。
 
方法三
  基于方法三,我们可以调库,使用ADF方法得到置信因子  
     
      
       
       
         p 
        
       
      
        p 
       
      
    p 的值:
# 接着上面的代码
from statsmodels.tsa.stattools import adfuller as ADF
print(ADF(dd))
"""
结果如下,返回值依次为adf、pvalue、usedlag、nobs、critical values、icbest、regresults、resstore
(-2.3842262328920083, 0.1462380194095098, 8, 280, {'1%': -3.453922368485787, '5%': -2.871918329081633, '10%': -2.5723001147959184}, 2267.166855421211)
看出 pvalue = 0.1462380194095098
"""
 
  遗憾的是,这个最严谨的方法认为太阳黑子是非平稳序列,理论上原数据不适合使用 ARMA 预测。对于非平稳序列我们的处理手段是:差分,或者取对数。取对数就是将原数据  
     
      
       
        
        
          x 
         
        
          i 
         
        
       
      
        x_i 
       
      
    xi 转换为  
     
      
       
        
         
         
           x 
          
         
           ^ 
          
         
        
          i 
         
        
       
         = 
        
       
         log 
        
       
          
        
        
        
          x 
         
        
          i 
         
        
       
      
        \hat x_i=\log x_i 
       
      
    x^i=logxi,而一阶差分则是将原数据  
     
      
       
        
        
          x 
         
        
          i 
         
        
       
      
        x_i 
       
      
    xi 转换为  
     
      
       
        
         
         
           x 
          
         
           ^ 
          
         
        
          i 
         
        
       
         = 
        
        
        
          x 
         
        
          i 
         
        
       
         − 
        
        
        
          x 
         
         
         
           i 
          
         
           − 
          
         
           1 
          
         
        
       
      
        \hat x_i = x_i-x_{i-1} 
       
      
    x^i=xi−xi−1。
   ARIMA 相比于 ARMA 的区别,就是引入了  
     
      
       
       
         n 
        
       
      
        n 
       
      
    n 阶差分,使得原来的非平稳序列  
     
      
       
       
         { 
        
        
        
          x 
         
        
          i 
         
        
       
         } 
        
       
      
        \{x_i\} 
       
      
    {xi} 转变为平稳序列  
     
      
       
       
         { 
        
        
         
         
           x 
          
         
           ^ 
          
         
        
          i 
         
        
       
         } 
        
       
      
        \{\hat x_i\} 
       
      
    {x^i},从而继续使用 ARMA 进行时间序列分析。我们看一下太阳黑子一阶差分数据的平稳性:
# 接着上面的代码
print(ADF(dd.diff().dropna()))
"""
(-14.076125927559811, 2.8827295545409215e-26, 7, 280, {'1%': -3.453922368485787, '5%': -2.871918329081633, '10%': -2.5723001147959184}, 2263.2365299490502)
可以看出 pvalue = 2.8827295545409215e-26,相当地平稳
"""
 
  结果相当平稳,因此可以使用太阳黑子的一阶差分数据进行时间序列分析,分析结果求前缀和得到原来的太阳黑子数据。下面我将分别使用原数据dd(虽然不适合用 ARMA,但还是使用着看看)以及它的一阶差分数据dd.diff().dropna()进行时间序列分析,然后对比它们的结果。
第二步——白噪声分析
使用下面的代码,分别对原数据、一阶差分数据进行白噪声分析。结果显示,它们都不是白噪声数据。光看着一点,它们都符合 ARMA 使用条件。
# 接着上面的代码
from statsmodels.stats.diagnostic import acorr_ljungbox
print(acorr_ljungbox(dd,lags=1)) # 原数据
# 结果 (array([193.5490947]), array([5.34166945e-44]))
# 其中 pvalue = 5.34166945e-44 < 0.05,确定为非白噪声数据
print(acorr_ljungbox(dd.diff().dropna(),lags=1)) # 一阶差分数据
# 结果 (array([80.51235627]), array([2.88892777e-19]))
# 其中 pvalue = 2.88892777e-19 < 0.05,确定为非白噪声数据
 
第三步——确定参数
  需要确定参数  
     
      
       
       
         p 
        
       
         , 
        
       
         q 
        
       
      
        p,q 
       
      
    p,q。按照参考文献中的说法,我们需要把满足  
     
      
       
       
         0 
        
       
         ≤ 
        
       
         p 
        
       
         , 
        
       
         q 
        
       
         ≤ 
        
       
         ⌊ 
        
       
         n 
        
       
         / 
        
       
         10 
        
       
         ⌋ 
        
       
      
        0\le p,q\le ⌊n/10⌋ 
       
      
    0≤p,q≤⌊n/10⌋ 的参数全部尝试一遍,在这之中找出最好的参数对  
     
      
       
       
         ( 
        
       
         p 
        
       
         , 
        
       
         q 
        
       
         ) 
        
       
      
        (p,q) 
       
      
    (p,q) 。但是我们的数据横跨  
     
      
       
       
         289 
        
       
      
        289 
       
      
    289 年, 
     
      
       
       
         ⌊ 
        
       
         n 
        
       
         / 
        
       
         10 
        
       
         ⌋ 
        
       
         = 
        
       
         28 
        
       
      
        ⌊n/10⌋ = 28 
       
      
    ⌊n/10⌋=28,这样时间开销太大了,程序会跑得很慢。
   实际上, 
     
      
       
       
         ( 
        
       
         p 
        
       
         , 
        
       
         q 
        
       
         ) 
        
       
      
        (p,q) 
       
      
    (p,q) 太大就没必要尝试下去了。为了更快地跑出结果,我这里尝试  
     
      
       
       
         0 
        
       
         ≤ 
        
       
         p 
        
       
         , 
        
       
         q 
        
       
         < 
        
       
         6 
        
       
      
        0\le p,q<6 
       
      
    0≤p,q<6 的所有参数(也跑了很久),并且只使用 BIC 指标。
   先确定原数据的参数:
# 接着上面的代码
bic_matrix = []  # BIC矩阵
for p in range(6):
    tmp = []
    for q in range(6):
        try:  # 存在部分报错,所以用try来跳过报错。
            tmp.append(sm.tsa.ARMA(dd, (p, q)).fit().bic)
        except:
            tmp.append(float("inf"))
    bic_matrix.append(tmp)
print(np.argmin(np.array(bic_matrix)))
# 结果为 26,即 p = 4,q = 2
 
再试一试一阶差分数据的参数:
# 接着上面的代码
bic_matrix = []  # BIC矩阵
for p in range(6):
    tmp = []
    for q in range(6):
        try:  # 存在部分报错,所以用try来跳过报错。
            tmp.append(sm.tsa.ARMA(dd, (p, q)).fit().bic)
        except:
            tmp.append(float("inf"))
    bic_matrix.append(tmp)
print(np.argmin(np.array(bic_matrix)))
# 结果为 14,即 p = 2,q = 2
 
至此,确定原数据 ARMA 的参数 p = 4 , q = 2 p=4,q=2 p=4,q=2,一阶差分数据的参数 p = 2 , q = 2 p=2,q=2 p=2,q=2。
第四步——模型构建与检验
检验模型效果
按照下面的代码训练模型:
# 接着上面的代码
################ 获取原数据的预测值 ################
zmd=sm.tsa.ARMA(dd,(4,2)).fit()
dhat=list(zmd.predict())
#################################################
############## 获取一阶差分数据的预测值 ##############
zmd2=sm.tsa.ARMA(dd.diff.dropna(),(2,2)).fit()
dhat2=list(zmd2.predict())
# 进行前缀和操作,恢复为原数据
dhat2.insert(0,dhat[0])
from itertools import accumulate
dhat2 = list(accumulate(dhat2))
##################################################
 
  上面获取的数据,是对已知的  
     
      
       
       
         1700 
        
       
         − 
        
       
         1988 
        
       
      
        1700-1988 
       
      
    1700−1988 年数据的拟合,用于检验模型的正确性的。实际上,在获取zmd对象后,可以调用zmd.summary()对此次 ARMA 模型训练结果进行总结。比如print(zmd.summary())的结果就是:
                              ARMA Model Results                              
==============================================================================
Dep. Variable:                 counts   No. Observations:                  289
Model:                     ARMA(4, 2)   Log Likelihood               -1197.676
Method:                       css-mle   S.D. of innovations             15.159
Date:                Sat, 14 Sep 2024   AIC                           2411.353
Time:                        15:20:12   BIC                           2440.684
Sample:                             0   HQIC                          2423.106
                                                                              
================================================================================
                   coef    std err          z      P>|z|      [0.025      0.975]
--------------------------------------------------------------------------------
const           49.7380      6.211      8.009      0.000      37.566      61.910
ar.L1.counts     2.8101      0.086     32.694      0.000       2.642       2.979
ar.L2.counts    -3.1179      0.218    -14.294      0.000      -3.545      -2.690
ar.L3.counts     1.5248      0.213      7.165      0.000       1.108       1.942
ar.L4.counts    -0.2366      0.080     -2.954      0.003      -0.394      -0.080
ma.L1.counts    -1.6480      0.057    -29.016      0.000      -1.759      -1.537
ma.L2.counts     0.7885      0.055     14.396      0.000       0.681       0.896
                                    Roots                                    
=============================================================================
                  Real          Imaginary           Modulus         Frequency
-----------------------------------------------------------------------------
AR.1            0.8639           -0.5664j            1.0330           -0.0924
AR.2            0.8639           +0.5664j            1.0330            0.0924
AR.3            1.0928           -0.0000j            1.0928           -0.0000
AR.4            3.6248           -0.0000j            3.6248           -0.0000
MA.1            1.0450           -0.4197j            1.1262           -0.0608
MA.2            1.0450           +0.4197j            1.1262            0.0608
-----------------------------------------------------------------------------
 
这里面可能有一些比较重要的信息。接下来我们就原数据,原数据的预测数据、一阶差分数据的预测数据进行作图:
# 接着上面的代码
plt.figure(figsize=(10, 6))
plt.plot(years,dd,'-')
plt.plot(years,dhat,'--')
plt.plot(years,dhat2,'--')
plt.rcParams['font.family'] = 'KaiTi'
plt.legend(('原始观测值','原数据预测值','一阶差分数据预测值'))
plt.grid()
plt.show()
 
  作图结果如下所示。本不适合 ARMA 预测的原数据,预测后与原始观测值非常符合;而适合 ARMA 预测的一阶差分数据,进行前缀和操作后,预测结果反而非常糟糕。这并不是因为一阶差分数据拟合效果太差,而是它在通过前缀和操作变回原数据的过程中,误差不断积累变大,最终导致结果产生严重的偏离。
   手动差分数据不可取,要想对非平稳数据进行时间序列分析,还应用 ARIMA 模型合适。
 
经过尝试,对于原数据 { x i } \{x_i\} {xi},使用 x ^ i = ln  ( x + 0.02 ) \hat x_i=\ln(x+0.02) x^i=ln(x+0.02) (因为原数据中含有 0 0 0 值,不可直接取对数,故加 0.02 0.02 0.02)会通过平稳性分析( p = 0.03817 < 0.05 p=0.03817<0.05 p=0.03817<0.05)和白噪声分析,获取最佳参数 p = 2 , q = 4 p=2,q=4 p=2,q=4 且获取比较良好的拟合效果,有兴趣的读者可以尝试一下。使用这样的 { x ^ i } \{\hat x_i\} {x^i} 进行预测的结果如下图所示:
![]()
预测未来数据
若要预测接下来的数据,可以用下面的代码(二选一,都行):
# 从已知数据的后一天开始预测
begin = d.shape[0]
# 预测 5 天
end = begin + 5
dnext = zmd.predict(begin, end)
print(dnext)  #显示预测值
"""结果如下:
289    139.440487
290    153.467594
291    143.350740
292    114.224154
293     76.024077
294     40.746472
"""
 
或者可以这样:
# 预测后面 5 天的数据
dnext = zmd.forecast(5)
print(dnext[0])  #显示预测值
"""结果如下:
[139.44048728 153.46759385 143.35074018 114.22415414  76.02407734]
"""
 
  这里的zmd.forecast得到的数据分别是预测结果、标准误差、置信区间。dnext[0]只打印预测结果。
 原理和代码参考文献:python之时间序列算法(ARMA)_python arma-CSDN博客
 
 

















