python一阶差分_[Python][pmdarima] 季节性ARIMA模型学习

背景

前段时间参与了一个快消行业需求预测的项目。其中,用到了移动平均法、ARIMA、Xgboost等方法进行预测,现在打算总结一下ARIMA。

因为项目的销售数据属于私密数据,这里用网上找的一份案例数据用于展示。

构建ARIMA模型可以用到statsmodels库和pmdarima库。我这里用的是pmdarima库,这个库有一个优点是能够自动地对ARIMA模型的参数进行自动确定。

1、建模步骤

2、季节性ARIMA模型

A R I M A ( p , d , q ) ( P , D , Q ) m ARIMA (p,d,q) (P,D,Q)_mARIMA(p,d,q)(P,D,Q)m​ 该模型需要提供的参数有如下两类:

(1)趋势参数

p:趋势自回归阶数。

d:趋势差分阶数。

q:趋势移动平均阶数。

(2)季节性参数

P:季节性自回归阶数。

D:季节性差分阶数。

Q:季节性移动平均阶数。

m:单个季节期间的时间步数。

一、模块导入及数据读取

1、模块及数据

导入相关模块

from model.arimaModel import * # 导入自定义的方法

import pandas as pd

import matplotlib.pyplot as plt

import pmdarima as pm

from statsmodels.graphics.tsaplots import plot_acf, plot_pacf # 画图定阶

from statsmodels.tsa.stattools import adfuller # ADF检验

from statsmodels.stats.diagnostic import acorr_ljungbox # 白噪声检验

import warnings

warnings.filterwarnings("ignore") # 选择过滤警告

读取本地数据,需要数据可以留言。这里我用的数据是月份数据,如果有的数据集是日的数据,当发生太大波动性性很难发现趋势时,可以使用resample()进行每月重采样。

io = 'D:/PythonProject/ARIMA/AirPassengers.csv'

time_series = pd.DataFrame(pd.read_csv(io, header=0, index_col='Month'))

# 将字符串索引转换成时间格式

time_series.index = pd.to_datetime(time_series.index)

# 输出前5个数据,此时Date为时间索引

print("样本量为{}个".format(len(time_series)))

print(time_series.head())

2、季节性分析

# 时间序列图生成函数

def sequencePlot(data, sequencePlot_name):

data.plot(marker='o')

# 设置坐标轴标签

plt.xlabel("时间", fontsize=15)

plt.ylabel("销量", fontsize=15)

# 设置图表标题

plt.title("{}时间序列图".format(sequencePlot_name), fontsize=15)

# 图例位置

plt.legend(loc='best')

# 生成网格

plt.grid(linestyle='--')

plt.show()

orign_seqname = "原始"

sequencePlot(time_series, orign_seqname)

用matplotlib输出原始时间序列图如下。明显地,我们可以看到数据有上升的趋势,可以定性地判断该序列非平稳。另外,该时间序列有比较明显的季节性趋势,即基本上每一年数据都呈现先上升再下降。

同时,可以使用seasonal_decompose()进行分析,将时间序列分解成长期趋势项(Trend)、季节性周期项(Seansonal)和残差项(Resid)这三部分。该方法的原理见这篇文章 。

from statsmodels.tsa.seasonal import seasonal_decompose

# 分解数据查看季节性 freq为周期

ts_decomposition = seasonal_decompose(time_series['Passengers'], freq=12)

ts_decomposition.plot()

plt.show()

由下子图,确实每年的数据呈现先升后降的特征。

二、平稳性检验

1、Augmented Dickey-Fuller Test(ADF检验)

ARIMA模型要求时间序列是平稳的。所谓平稳性,其基本思想是:决定过程特性的统计规律不随着时间的变化而变化。由平稳性的定义:对于一切t , s t,st,s,Y t Y_tYt​和Y s Y_sYs​的协方差对于时间的依赖之和时间间隔∣ t − s ∣ |t-s|∣t−s∣有关,而与实际的时刻t和s无关。因此,平稳过程可以简化符号,其中C o v CovCov为自协方差函数,C o r r CorrCorr为自相关函数,记为:γ t = C o v ( Y t , Y t − k ) , ρ k = C o r r ( Y t , Y t − k ) \gamma_t=Cov(Y_t,Y_{t-k}) ,\rho_k=Corr(Y_t,Y_{t-k})γt​=Cov(Yt​,Yt−k​),ρk​=Corr(Yt​,Yt−k​)另外,平稳分为严平稳和宽平稳。定义如下:

(1)严平稳:特别地,如果对一切时滞k kk和时点点t 1 , t 2 , . . . , t k t_1,t_2,...,t_kt1​,t2​,...,tk​,都有Y t 1 , Y t 2 , . . . , Y t n Y_{t_1},Y_{t_2},...,Y_{t_n}Yt1​​,Yt2​​,...,Ytn​​与Y t 1 − k , Y t 2 − k , . . . , Y t n − k Y_{t_1-k},Y_{t_2-k},...,Y_{t_n-k}Yt1​−k​,Yt2​−k​,...,Ytn​−k​的联合分布相同,则称过程{Y t Y_tYt​}为严平稳的。

(2)宽平稳:满足1)均值函数在所有时间上恒为常数;2)γ t , t − k = γ 0 , k \gamma_{t,t-k}=\gamma_{0,k}γt,t−k​=γ0,k​,对所有的时间t tt和滞后k kk。

如果通过时间序列图来用肉眼观看的话可能会存在一些主观性。ADF检验(又称单位根检验)是一种比较常用的严格的统计检验方法。

ADF检验主要是通过判断时间序列中是否含有单位根:如果序列平稳,就不存在单位根;否则,就会存在单位根。

详细的介绍可以看这篇博客

https://blog.csdn.net/FrankieHello/article/details/86766625/

2、Python实现

from statsmodels.tsa.stattools import adfuller # ADF检验

# 该函数参考了其他文章

def stableCheck(timeseries):

# 移动12期的均值和方差

rol_mean = timeseries.rolling(window=12).mean()

rol_std = timeseries.rolling(window=12).std()

# 绘图

fig = plt.figure(figsize=(12, 8))

orig = plt.plot(timeseries, color='blue', label='Original')

mean = plt.plot(rol_mean, color='red', label='Rolling Mean')

std = plt.plot(rol_std, color='black', label='Rolling Std')

plt.legend(loc='best')

plt.title('Rolling Mean & Standard Deviation')

plt.show()

# 进行ADF检验

print('Results of Dickey-Fuller Test:')

dftest = adfuller(timeseries, autolag='AIC')

# 对检验结果进行语义描述

dfoutput = pd.Series(dftest[0:4], index=['Test Statistic', 'p-value', '#Lags Used', 'Number of Observations Used'])

for key, value in dftest[4].items():

dfoutput['Critical Value (%s)' % key] = value

print('ADF检验结果:')

print(dfoutput)

stableCheck_result1 = stableCheck(time_series)

检验结果如下。ADF检验的H 0 H_0H0​假设就是存在单位根,如果得到的显著性检验统计量小于三个置信度(10%,5%,1%),则对应有(90%,95,99%)的把握来拒绝原假设。有以下两种方式来判断:

(1)这里将Test Statistic与1%、%5、%10不同程度拒绝原假设的统计值进行比较。当ADF Test result 同时小于 1%、5%、10%即说明非常好地拒绝该假设。本数据中,0.815369并没有都小于三个level的统计值,所以判断为该时间序列time series非平稳。

(2)观察p-value,是否非常接近于0。这里p-value约为0.99>0.05,因此不能拒绝原假设。

三、序列平稳化

差分法

对于非平稳时间序列,可以通过d阶差分运算使变成平稳序列。从统计学角度来讲,只有平稳性的时间序列才能避免“伪回归”的存在,才有经济意义。

在这里,我先进行了一阶差分,再进行了一次季节性差分,才通过ADF检验。

# 差分处理非平稳序列,先进行一阶差分

time_series_diff1 = time_series.diff(1).dropna()

# 在一阶差分基础上进行季节性差分差分

time_series_diff2 = time_series_diff1.diff(12).dropna()

stableCheck_result2 = stableCheck(time_series_diff2)

结果如下,p-value小于0.05,且Test Statistic远小于Critical Value (5%)的值,可以有95%的概率认为时间序列平稳。

因此,我们可以确定季节性ARIMA模型的d = 1 , D = 1 , m = 12 d=1,D=1,m=12d=1,D=1,m=12。

四、白噪声检验

接下来,对d阶差分后的的平稳序列进行白噪声检验,若该平稳序列为非白噪声序列,则可以进行第五步。

1、白噪声定义

对于一序列e 1 , e 2 , e 3 , . . . , e t e_1,e_2,e_3,...,e_te1​,e2​,e3​,...,et​,若满足:E ( e t ) = 0 E(e_t)=0E(et​)=0V a r ( e t ) = σ 2 Var(e_t)=σ^2Var(et​)=σ2C o v ( e t , e t + k ) = 0 ( k ≠ 0 ) Cov(e_t,e_{t+k})=0 (k≠0)Cov(et​,et+k​)=0(k​=0) 那么该序列是一个白噪声序列。

详细的介绍可以看这篇博客

https://www.cnblogs.com/travelcat/p/11400307.html

2、Ljung-Box检验

Ljung-Box检验即LB检验,是时间序列分析中检验序列自相关性的方法。LB检验的Q统计量为:

Q ( m ) = T ( T + 2 ) ∑ l = 1 m ρ ^ l 2 T − l Q(m)=T(T+2) \sum_{l=1}^m \frac{\hat{ρ}_l^2}{T-l} \quadQ(m)=T(T+2)l=1∑m​T−lρ^​l2​​      用来检验m mm阶滞后范围内序列的自相关性是否显著,或序列是否为白噪声,Q统计量服从自由度为m mm的卡方分布。

3、Python实现

def whiteNoiseCheck(data):

result = acorr_ljungbox(data, lags=1)

temp = result[1]

print('白噪声检验结果:', result)

# 如果temp小于0.05,则可以以95%的概率拒绝原假设,认为该序列为非白噪声序列;否则,为白噪声序列,认为没有分析意义

print(temp)

return result

ifwhiteNoise = whiteNoiseCheck(time_series_diff2)

检验结果如下:返回的是统计量和p-value,其中,延迟1阶的p-value约为0.00033<0.05,可以以95%的概率拒绝原假设,认为该序列为非白噪声序列。

五、时间序列定阶

def draw_acf(data):

# 利用ACF判断模型阶数

plot_acf(data)

plt.title("序列自相关图(ACF)")

plt.show()

def draw_pacf(data):

# 利用PACF判断模型阶数

plot_pacf(data)

plt.title("序列偏自相关图(PACF)")

plt.show()

def draw_acf_pacf(data):

f = plt.figure(facecolor='white')

# 构建第一个图

ax1 = f.add_subplot(211)

# 把x轴的刻度间隔设置为1,并存在变量里

x_major_locator = MultipleLocator(1)

plot_acf(data, ax=ax1)

# 构建第二个图

ax2 = f.add_subplot(212)

plot_pacf(data, ax=ax2)

plt.subplots_adjust(hspace=0.5)

# 把x轴的主刻度设置为1的倍数

ax1.xaxis.set_major_locator(x_major_locator)

ax2.xaxis.set_major_locator(x_major_locator)

plt.show()

draw_acf_pacf(time_series_diff2)

具体怎么对季节性ARIMA模型进行定阶数可以看这里。

(1)确定d dd和D DD的阶数:当对原序列进行了d dd阶差分和l a g laglag为m mm的D DD阶差分后序列为平稳序列,则可以确定d dd,D DD,m mm的值。

(2)确定p pp,q qq和P PP,Q QQ的阶数:1)首先对平稳化后的时间序列绘制ACF和PACF图;2)然后,可以通过观察季节性l a g laglag处的拖尾/截尾情况来确定P , Q P,QP,Q的值;3)观察短期非季节性l a g laglag处的拖尾/截尾情况来确定p , q p,qp,q的值。

之前已经在步骤三处确定了d dd,D DD,m mm。对于剩下的参数,将依据如下的ACF和PACF图确定。下图的横坐标l a g laglag是以月份为单位。

非季节性部分:对于p pp,在l a g = 1 lag=1lag=1后,ACF图拖尾,PACF图截尾。同样地,对于q qq,在l a g = 1 lag=1lag=1后,ACF图截尾,PACF图拖尾。

季节性部分:P , Q P,QP,Q的确定和非季节性一样,不过需要记得滞后的间隔为12。

【补充参考】

AR模型:自相关系数拖尾,偏自相关系数截尾;

MA模型:自相关系数截尾,偏自相关函数拖尾;

ARMA模型:自相关函数和偏自相关函数均拖尾。

六、构建ARIMA模型及预测

这里使用了pmdarima.auto_arima()方法。这个方法可以帮助我们自动确定 A R I M A ( p , d , q ) ( P , D , Q ) m ARIMA(p,d,q)(P,D,Q)_mARIMA(p,d,q)(P,D,Q)m​的参数,也就是可以直接跳过上述的步骤2~5,直接输入数据,设置auto_arima()中的参数则可。

之前我们是通过观察ACF、PACF图的拖尾截尾现象来定阶,但是这样可能不准确。实际上,往往需要结合图像拟合多个模型,通过模型的AIC、BIC值以及残差分析结果来选择合适的模型。

1、构建模型

import pmdarima as pm

将数据分为训练集data_train和测试集data_test 。

split_point = int(len(time_series) * 0.85)

# 确定训练集/测试集

data_train, data_test = time_series[0:split_point], time_series[split_point:len(time_series)]

# 使用训练集的数据来拟合模型

built_arimamodel = pm.auto_arima(data_train,

start_p=0, # p最小值

start_q=0, # q最小值

test='adf', # ADF检验确认差分阶数d

max_p=5, # p最大值

max_q=5, # q最大值

m=12, # 季节性周期长度,当m=1时则不考虑季节性

d=None, # 通过函数来计算d

seasonal=True, start_P=0, D=1, trace=True,

error_action='ignore', suppress_warnings=True,

stepwise=False # stepwise为False则不进行完全组合遍历

)

print(built_arimamodel.summary())

相关结果如下,从Model可以看出确定的参数与之前确定的参数大致相同。

2、模型识别

built_arimamodel.plot_diagnostics()

plt.show()

3、需求预测

# 进行多步预测,再与测试集的数据进行比较

pred_list = []

for index, row in data_test.iterrows():

# 输出索引,值

# print(index, row)

pred_list += [built_arimamodel.predict(n_periods=1)]

# 更新模型,model.update()函数,不断用新观测到的 value 更新模型

built_arimamodel.update(row)

# 预测时间序列以外未来的一次

predict_f1 = built_arimamodel.predict(n_periods=1)

print('未来一期的预测需求为:', predict_f1[0])

3、结果评估

对预测结果进行评价,指标解释看这。

# ARIMA模型评价

def forecast_accuracy(forecast, actual):

mape = np.mean(np.abs(forecast - actual)/np.abs(actual))

me = np.mean(forecast - actual)

mae = np.mean(np.abs(forecast - actual))

mpe = np.mean((forecast - actual)/actual)

# rmse = np.mean((forecast - actual)**2)**0.5 # RMSE

rmse_1 = np.sqrt(sum((forecast - actual) ** 2) / actual.size)

corr = np.corrcoef(forecast, actual)[0, 1]

mins = np.amin(np.hstack([forecast[:, None], actual[:, None]]), axis=1)

maxs = np.amax(np.hstack([forecast[:, None], actual[:, None]]), axis=1)

minmax = 1 - np.mean(mins/maxs)

return ({'mape': mape,

'me': me,

'mae': mae,

'mpe': mpe,

'rmse': rmse_1,

'corr': corr,

'minmax': minmax

})

# 画图观测实际与测试的对比

test_predict = data_test.copy()

for x in range(len(test_predict)):

test_predict.iloc[x] = pred_list[x]

# 模型评价

eval_result = forecast_accuracy(test_predict.values, data_test.values)

print('模型评价结果\n', eval_result)

# 画图显示

plt.plot(origin_timeseries, 'b-', lw=1, label='True Data', marker='*')

plt.plot(test_predict, 'r-', lw=2, label='Prediction', marker='o')

plt.title('RMSE:{}'.format(eval_result['rmse']))

plt.legend(loc='best')

plt.grid() # 生成网格

plt.show()

红色的点为预测结果,蓝色的点为原来的数据。

RMSE约为16.4,均方根误差(Root Mean Square Error,RMSE)是预测值与真实值偏差的平方与观测次数n比值的平方根指标,衡量观测值与真实值之间的偏差。一般是越小越好,但是实际上需要进行结果对比才能判断。

七、小结

在做项目时候,发现ARIMA模型对数据的要求还是挺高的。其中,数据量太少不行,当时做的时候发现一些门店的数据量太少,而且只有一年左右的跨度,很难发现一些周期规律。另外就是对平稳性的要求。

目前对时间序列分析还是初学阶段,很多地方的解释不够深入,后续还需要继续学习、完善。

你可能感兴趣的:(python一阶差分)