Hey guys, 第七篇就来实作一遍,「以传统统计方法」预测多变量时间序列吧
虽然 VAR 的准确度和复杂度不像我们後面天数要介绍的神经网络一样,但这个实作流程的重点,我觉得在於学习「更了解时间序列的特性和预测的关系」,比方说,平稳性、从时间序列中找自回归系数等。
对基本的东西更熟悉可以避免说,只是会使用神经网络,但不了解时间序列的特性。
像我一开始写这个系列也在挣扎说,要不要直接从机器学习回归开始讲,或是直接介绍各种神经网络、Attention is all you need? 不是吗XD
後来还是觉得应该从基础开始,即便多半是统计和数学,也至少确认自己是理解基本理论的。
好了,不废话了,开始我们的 Python 实作吧
今天的大纲如下:
我们使用 R 语言的一份范例资料集 Raotbl6,来自於套件库 urca
(Unit Root and Cointegration Tests for Time Series Data,一个计量经济学的常用R套件)。这个资料集也是 Yash P. Mehra 曾在其 1994 年论文中使用的资料。
dataset = 'https://raw.githubusercontent.com/selva86/datasets/master/Raotbl6.csv'
df = pd.read_csv(dataset, parse_dates=['date'], index_col='date')
print(df.shape)
df.head()
rgnp
: 实际国民生产总值(real GNP)pgnp
: 潜在国民生产总值(potential GNP)ulc
: 单位劳动成本(unit labor cost)gdfco
: 核心通膨率(core PCE)(剔除季节性食品及能源价格後的个人消费支出物价指数):一个国家在不同时期内,个人消费支出总水平变动程度的经济指数gdf
: 国民生产总值(GNP) 缩减指数gdfim
: 进口缩减指数gdfcf
: 食品价格通膨率gdfce
: 能源价格通膨率了解这 8 种经济指标之间的关系,以及纳入指标间的相互影响,预测未来数值
- 资料分布不会随着时间改变而改变,平均数与变异数维持固定,例如白噪音
- 大部分时间序列需要去掉趋势性、做 differencing 等动作,平稳性才会显现
- 自回归模型中,仅以历史变量和当前变量预测未来;因此需要时间序列变量的基本特性能长时间维持不变,否则以过去预测未来的思路就不成立了。
EX: Unit Root Tests
以下我们使用 Augmented Dickey-Fuller Test 进行平稳性检测:
def adf_test(series, title=''):
print(f'Augmented Dickey-Fuller Test: {title}')
result = adfuller(series.dropna(), autolag='AIC') # .dropna() handles differenced data
labels = ['ADF test statistic', 'p-value', 'Number of lags used', 'Number of observations used']
out = pd.Series(result[0:4], index=labels)
for k, v in result[4].items():
out[f'critical value ({k})'] = v
print(out)
if result[1] <= 0.05: # 有显着性,推翻虚无假设
print("Data has no unit root and is stationary")
else:
print("Data has a unit root and is non-stationary")
for col in df.columns:
adf_test(df[col], title=col)
print()
几乎所有变量都是不平稳序列,differencing!
# differencing(1)
df_differenced = df.diff().dropna()
# do ADF test on differencing(1) dataframe
for col in df_differenced.columns:
adf_test(df_differenced[col], title=col)
print()
还是有部分时间序列变量不平稳,再做一次 differencing
(自动一点的方法可以写成当所有时间序列变量的 p-value 都 ≤ 0.05 时停止 differencing,保存做过差分的次数;这边为了快速示范,就没有特别写~)
# Second Differencing
df_differenced = df_differenced.diff().dropna()
# do ADF test on differencing(2) dataframe
for col in df_differenced.columns:
adf_test(df_differenced[col], title=col)
print()
所有时间序列变量都符合平稳性了!
接着我们把要用来 forecasting 的部分切出来保存
steps = 4
train, test = df_differenced[0:-steps], df_differenced[-steps:]
print(train.shape)
print(test.shape)
接着搜寻自回归系数 p:
for i in range(1, 11):
model = VAR(train)
results = model.fit(i)
print('Number of Lag = ', i)
print('AIC: ', results.aic)
print('BIC: ', results.bic, '\n')
根据数据,选择 AIC 及 BIC 最小值所在的 Lag = 1
P.S. 这边不一定要用回圈找,也可以使用 VAR model 的 model.select_order(maxlags)
搜寻最佳 order(p)
:
model.select_order(maxlags=12).summary()
建立 order(p=1) 的自回归模型:
model_fitted = model.fit(1)
model_fitted.summary()
这边呈现了模型的拟合情况和各项系数
预测
lag_num = model_fitted.k_ar
test_x = df_differenced.values[-lag_num:]
# forecasting
pred = model_fitted.forecast(y=test_x, steps=steps)
# 加 "_2d" 是为了提醒,预测出的结果是经过 2 次 differencing 的
df_pred = pd.DataFrame(pred, index=df.index[-steps:], columns=df.columns + '_2d')
# 将预测结果还原
for col in df.columns:
df_pred[f'{col}_1d'] = (df[col].iloc[-steps-1]-df[col].iloc[-steps-2]) + df_pred[f'{col}_2d'].cumsum()
df_pred[f'{col}_forecast'] = df[col].iloc[-steps-1] + df_pred[f'{col}_1d'].cumsum()
test_original = df[-steps:]
test_original.index = pd.to_datetime(test_original.index)
将预测结果和实际画出比较:
fig, axes = plt.subplots(nrows=4, ncols=2, figsize=(15,10))
for i, ax in enumerate(axes.flatten()):
ax.plot(test_original[df.columns[i]], color='blue', linewidth=1)
ax.plot(df_forecast[f'{df.columns[i]}_Forecast'], color='red', linewidth=1)
ax.set_title(df.columns[i])
ax.spines['top'].set_alpha(0)
ax.tick_params(labelsize=10)
ax.legend(['actual', 'forecast'])
fig.tight_layout();
根据图表,我们发现在
rgnp
和pgnp
的预测效果较好
你也可以引入 R2, RMSE, MSE 等指标来呈现成效好坏~
好的,总结本篇教学重点:
感谢你的收看!明天见!
<<: [DAY07] 开始用 Designer 在 Azure Machine Learning 做 AI
URL : https://app.hackthebox.eu/machines/51 IP : ...
一起来延伸视野,迎接更大的画面吧! 今天要介绍的 FullScreen API 会被忽略的原因可能...
前言 铁人倒数十天!利用最後时间来分享浏览器,这里才是真正的战场。 在 ECMAScript 上并没...
fetch 改善了 XHR 又长又麻烦的写法,简化了程序码使阅读容易许多,而 fetch retur...
Cache and TLB Flushing Under Linux 的最後一部份,一样文件! 文件...