首先引入相关的 statsmodels,包含统计模型函数(时间序列)。
# 引入相关的统计包
import warnings # 忽略警告
warnings.filterwarnings('ignore')
import numpy as np # 矢量和矩阵
import pandas as pd # 表格和数据操作
import matplotlib.pyplot as plt
import seaborn as sns
from dateutil.relativedelta import relativedelta # 有风格地处理日期
from scipy.optimize import minimize # 函数优化
import statsmodels.formula.api as smf # 统计与经济计量
import statsmodels.tsa.api as smt
import scipy.stats as scs
from itertools import product
from tqdm import tqdm_notebook
import statsmodels.api as sm
用真实的手机游戏数据作为样例,研究每小时观看的广告数和每日所花的游戏币。
# 1 如真实的手机游戏数据,将调查每小时观看的广告和每天花费的游戏币
ads = pd.read_csv(r'./test/ads.csv', index_col=['Time'], parse_dates=['Time'])
currency = pd.read_csv(r'./test/currency.csv', index_col=['Time'], parse_dates=['Time'])
建模前,先来了解一下稳定性(stationarity)。
如果一个过程是平稳的,这意味着它不会随时间改变其统计特性,如均值和方差等等。
方差的恒常性称为同方差,协方差函数不依赖于时间,它只取决于观测值之间的距离。
非平稳过程是指分布参数或者分布规律随时间发生变化。也就是说,非平稳过程的统计特征是时间的函数(随时间变化)。
下面的红色图表不是平稳的:
为什么平稳性如此重要呢? 通过假设未来的统计性质与目前观测到的统计性质不会有什么不同,可以很容易对平稳序列进行预测。
大多数的时间序列模型,以这样或那样的方式,试图预测那些属性(例如均值或方差)。
如果原始序列不是平稳的,那么未来的预测将是错误的。
大多数时间序列都是非平稳的,但可以(也应该)改变这一点。
平稳时间序列的类型:
若时序中有明显的趋势和季节性,则对这些成分建模,将它们从观察中剔除,然后用残差训练建模。
平稳性检查方法(可以检查观测值和残差):
若时序的均值和方差相差过大,则有可能是非平稳序列,此时可以对观测值取对数,将指数变化转变为线性变化。然后再次查看取对数后的观测值的均值和方差以及频率图。
上面前两种方法常常会欺骗使用者,因此更好的方法是用统计测试 sm.tsa.stattools.adfuller()。
接下来将学习如何检测稳定性,从白噪声开始。
# 5经济计量方法(Econometric approach)
# ARIMA 属于经济计量方法
# 创建平稳序列
white_noise = np.random.normal(size=1000)
with plt.style.context('bmh'):
plt.figure(figsize=(15,5))
plt.plot(white_noise)
标准正态分布产生的过程是平稳的,在0附近振荡,偏差为1。现在,基于这个过程将生成一个新的过程,其中每个后续值都依赖于前一个值。
def plotProcess(n_samples=1000,rho=0):
x=w=np.random.normal(size=n_samples)
for t in range(n_samples):
x[t] = rho*x[t-1]+w[t]
with plt.style.context('bmh'):
plt.figure(figsize=(10,5))
plt.plot(x)
plt.title('Rho {}\\n Dickey-Fuller p-value: {}'.format(rho,round(sm.tsa.stattools.adfuller(x)[1],3)))
#-------------------------------------------------------------------------------------
for rho in [0,0.6,0.9,1]:
plotProcess(rho=rho)
第一张图与静止白噪声一样。
第二张图,ρ增加至0.6,大的周期出现,但整体是静止的。
第三张图,偏离0均值,但仍然在均值附近震荡。
第四张图,ρ= 1,有一个随机游走过程即非平稳时间序列。当达到临界值时, 不返回其均值。从两边减去 ,得到 ,左边的表达式被称为 一阶差分 。
如果 ρ= 1,那么一阶差分等于平稳白噪声 。这是 Dickey-Fuller时间序列平稳性测试 (测试单位根的存在)背后的主要思想。
如果 可以用一阶差分从非平稳序列中得到平稳序列,称这些序列为1阶积分 。 该检验的零假设是时间序列是非平稳的 ,在前三个图中被拒绝,在最后一个图中被接受。
1阶差分并不总是得到一个平稳的序列,因为这个过程可能是 d 阶的积分,d > 1阶的积分(有多个单位根)。在这种情况下,使用增广的Dickey-Fuller检验,它一次检查多个滞后时间。
可以使用不同的方法来去除非平稳性:各种顺序差分、趋势和季节性去除、平滑以及转换如Box-Cox或对数转换。
接下来开始建立ARIMA模型,在建模之前需要将非平稳时序转换为平稳时序。
摆脱非平稳性,建立SARIMA(Getting rid of non-stationarity and building SARIMA)
def tsplot(y,lags=None,figsize=(12,7),style='bmh'):
"""
Plot time series, its ACF and PACF, calculate Dickey-Fuller test
y:timeseries
lags:how many lags to include in ACF,PACF calculation
"""
ifnot isinstance(y, pd.Series):
y = pd.Series(y)
with plt.style.context(style):
fig = plt.figure(figsize=figsize)
layout=(2,2)
ts_ax = plt.subplot2grid(layout, (0,0), colspan=2)
acf_ax = plt.subplot2grid(layout, (1,0))
pacf_ax = plt.subplot2grid(layout, (1,1))
y.plot(ax=ts_ax)
p_value = sm.tsa.stattools.adfuller(y)[1]
ts_ax.set_title('Time Series Analysis Plots\\n Dickey-Fuller: p={0:.5f}'.format(p_value))
smt.graphics.plot_acf(y,lags=lags, ax=acf_ax)
smt.graphics.plot_pacf(y,lags=lags, ax=pacf_ax)
plt.tight_layout()
#-------------------------------------------------------------------------------------
tsplot(ads.Ads, lags=60)
从图中可以看出,Dickey-Fuller检验拒绝了单位根存在的原假设(p=0);序列是平稳的,没有明显的趋势,所以均值是常数,方差很稳定。
唯一剩下的是季节性,必须在建模之前处理它。可以通过季节差分去除季节性,即序列本身减去一个滞后等于季节周期的序列。
ads_diff = ads.Ads-ads.Ads.shift(24) # 去除季节性
tsplot(ads_diff[24:], lags=60)
ads_diff = ads_diff - ads_diff.shift(1) # 去除趋势
tsplot(ads_diff[24+1:], lags=60) # 最终图
第一张图中,随着季节性的消失,自回归好多了,但是仍存在太多显著的滞后,需要删除。首先使用一阶差分,用滞后1从自身中减去时序。
第二张图中,通过季节差分和一阶差分得到的序列在0周围震荡。Dickey-Fuller试验表明,ACF是平稳的,显著峰值的数量已经下降,可以开始建模了。
SARIMA:Seasonal Autoregression Integrated Moving Average model。
是简单自回归移动平均的推广,并增加了积分的概念。
每个成分都对应着相应的参数。
SARIMA(p,d,q)(P,D,Q,s) 模型需要选择趋势和季节的超参数。
趋势参数 ,趋势有三个参数,与ARIMA模型的参数一样:
季节参数 :
可以通过分析ACF和PACF图来选择趋势参数,查看最近时间步长的相关性(如1,2,3)。
同样,可以分析ACF和PACF图,查看季节滞后时间步长的相关性来指定季节模型参数的值。
现在知道了如何设置初始参数,查看最终图并设置参数。
上面倒数第一张图就是最终图:
下面看不同参数的模型表现如何:
# 建模 SARIMA
# setting initial values and some bounds for them
ps = range(2,5)
d=1
qs=range(2,5)
Ps=range(0,2)
D=1
Qs=range(0,2)
s=24#season length
# creating list with all the possible combinations of parameters
parameters=product(ps,qs,Ps,Qs)
parameters_list = list(parameters)
print(parameters)
print(parameters_list)
print(len(parameters_list))
# 36
def optimizeSARIMA(parameters_list, d,D,s):
"""
Return dataframe with parameters and corresponding AIC
parameters_list:list with (p,q,P,Q) tuples
d:integration order in ARIMA model
D:seasonal integration order
s:length of season
"""
results = []
best_aic = float('inf')
for param in tqdm_notebook(parameters_list):
# we need try-exccept because on some combinations model fails to converge
try:
model = sm.tsa.statespace.SARIMAX(ads.Ads, order=(param[0], d,param[1]),
seasonal_order=(param[2], D,param[3], s)).fit(disp=-1)
except:
continue
aic = model.aic
# saving best model, AIC and parameters
if aic< best_aic:
best_model = model
best_aic = aic
best_param = param
results.append([param, model.aic])
result_table = pd.DataFrame(results)
result_table.columns = ['parameters', 'aic']
# sorting in ascending order, the lower AIC is - the better
result_table = result_table.sort_values(by='aic',ascending=True).reset_index(drop=True)
return result_table
%%time
result_table = optimizeSARIMA(parameters_list, d,D,s)
result_table.head()
# set the parameters that give the lowerst AIC
p,q,P,Q = result_table.parameters[0]
best_model = sm.tsa.statespace.SARIMAX(ads.Ads, order=(p,d,q),seasonal_order=(P,D,Q,s)).fit(disp=-1)
print(best_model.summary()) # 打印拟合模型的摘要。这总结了所使用的系数值以及对样本内观测值的拟合技巧。
# inspect the residuals of the model
tsplot(best_model.resid[24+1:], lags=60)
parameters aic
0 (2, 3, 1, 1) 3888.642174
1 (3, 2, 1, 1) 3888.763568
2 (4, 2, 1, 1) 3890.279740
3 (3, 3, 1, 1) 3890.513196
4 (2, 4, 1, 1) 3892.302849
Statespace Model Results
==========================================================================================
Dep. Variable: Ads No. Observations: 216
Model: SARIMAX(2, 1, 3)x(1, 1, 1, 24) Log Likelihood -1936.321
Date: Sun, 08 Mar 2020 AIC 3888.642
Time: 23:06:23 BIC 3914.660
Sample: 09-13-2017 HQIC 3899.181
- 09-21-2017
Covariance Type: opg
==============================================================================
coef std err z P >|z| [0.0250.975]
------------------------------------------------------------------------------
ar.L1 0.79130.2702.9280.0030.2621.321
ar.L2 -0.55030.306-1.7990.072-1.1500.049
ma.L1 -0.73160.262-2.7930.005-1.245-0.218
ma.L2