使用R实现ARIMA模型, 拟合时间序列, 评估拟合效果。并结合bootstrap进行预测和区间估计
ARIMA(AutoRegressive Integrated Moving Average,自动回归积分滑动平均)模型是一种常用于时间序列预测的统计模型。ARIMA模型通过三个主要参数来描述时间序列数据的特征:AR(自回归)、I(差分)和MA(滑动平均)。该模型假设时间序列数据是平稳的或可以通过差分使其平稳,并通过自回归和滑动平均成分来捕捉数据中的依赖关系。ARIMA模型广泛应用于金融、经济、气候等领域的时间序列预测。
为了提高ARIMA模型的预测精度,结合了Bootstrap方法,通过重复抽样生成样本的分布,进行区间估计,从而获得更准确的置信区间。
下载讲解视频AR(自回归):通过历史数据的线性组合来预测当前值,即当前时间点的数据依赖于其前几个时间点的值。
I(差分):用于处理非平稳时间序列,使其变得平稳,从而便于建模。差分操作是通过计算序列当前值与前一个值的差异来实现的。
MA(滑动平均):考虑到预测误差的影响,MA部分通过历史误差的线性组合来预测当前值。
ARIMA模型适用于平稳时间序列,若数据非平稳,通过差分(I部分)将其转换为平稳序列。该模型的核心思想是结合自回归(AR)和滑动平均(MA)过程来捕捉数据中的依赖性,并通过差分来解决非平稳问题。
更详细内容以及模型公式可见https://blog.csdn.net/weixin_62586597/article/details/120626460
Bootstrap方法是一种非参数统计方法,主要用于通过重复抽样来估计样本统计量的分布,特别适用于数据分布未知或不满足常规统计假设的情况。在ARIMA模型中,Bootstrap方法常用于以下目的:
估计预测区间:通过生成多组样本的预测值,从而得到预测值的置信区间。
提高模型的稳健性:当ARIMA模型的误差项不服从正态分布时,Bootstrap方法可以帮助获得更可靠的参数估计。
在时间序列预测中,ARIMA模型能够有效地捕捉数据中的趋势和季节性变化。但ARIMA模型的参数估计往往依赖于假设模型误差服从正态分布,这可能在某些情况下不成立。为了解决这个问题,我们可以使用Bootstrap方法来进行区间估计,通过对历史数据进行重复抽样,模拟出多组时间序列的样本分布,从而估计预测值的置信区间。
具体而言,首先使用ARIMA模型对数据进行拟合,得到残差。然后,从残差中随机抽取样本,生成新的时间序列,并对每个样本进行ARIMA模型拟合,反复进行多次(例如1000次)。通过这种方法,我们得到多个预测值,从而形成预测值的分布。根据这些分布,我们可以计算预测值的置信区间,通常取2.5%和97.5%分位数作为95%置信区间。这一过程能够为ARIMA模型的预测提供更为可靠和精准的区间估计,避免了误差项非正态分布对预测结果的影响,增强了模型的稳健性和精确性。
#加载包(运行前记得先安装)
library(tseries)
library(forecast)
library(ggplot2)
library(dplyr)
data <- data.frame(
t = c(1:96),
cases = c(
1468, 1267, 4922, 12190, 13328, 11939, 7578, 3152, 3307, 2676, 2666, 2331, 1417, 470, 1991, 5156, 10683, 11478, 7413, 4230, 4118, 4619, 7046, 6938, 3827, 3742, 7623, 15232, 19672, 10486, 4848, 1778, 2030, 2552, 5281, 6737,
3739, 3839, 8506, 10209,8982, 9265, 6752, 3427, 3991, 3347, 3890, 5776,
3366, 3439, 11286, 21903, 19796, 14231, 8298, 4955, 7596, 8878, 6825, 5214,
3707, 2967, 5131, 12216, 15203, 10605, 5455, 2985, 2782, 3050, 4152, 5170,
5383, 3089, 6770, 12533, 16241, 16154, 12090, 7863,6859, 8564, 8886, 5730,
4301, 5057, 5889, 8954, 9947, 9032, 6614,4834, 6172, 7658, 6813, 4970)
) #这里使用处理过的示例数据,通常要导入数据文件
#将数据被分为两部分:训练和验证
training_data<-subset(data,t >= 1 & t <= 84) #假设是2000-2006
validation_data<-subset(data,t >= 85 & t <= 96) #假设是2007
# 将数据转化为时间序列,使用ts()函数
ts_data <- ts(training_data$cases, frequency =12, start = c(2000, 1))
test_data <- ts(validation_data$cases, frequency = 12, start = c(2007, 1))
# 拆分季节性、趋势、随机性(时间序列分解,看看各种特性)
decomposition_data <- decompose(ts_data)
plot(decomposition_data)
plot(decomposition_data)可以对数据的流行曲线和特征有一个初步的观察
#稳定性检验,P值小于0.01说明数据稳定,可以建模
adf.test(ts_data)
#白噪声检验,P值小于0.05,序列不是白噪声,可以建模
Box.test(ts_data,type = "Ljung-Box")
adf.test p-value <0.01
Box.test p-value = 3.13e-11
平稳、非白噪声,可建模
#使用auto.arima,自动选取最优模型
auto.arima(ts_data,
stepwise = TRUE, # 是否启用逐步搜索算法,默认启用。逐步搜索会自动选择最优的p、d、q值
trace = TRUE, # 是否输出每一步的模型选择过程。启用后会显示每一步选择的模型
stationary = TRUE, # 指定数据是否为平稳数据。TRUE表示将自动对数据进行平稳性检验并进行差分(如有必要)
ic = c("aic"), # 使用AIC准则选择最佳模型,AIC即赤池信息量准则。可以设置为AIC或BIC,这里选择了AIC
max.p = 5, # 设置p的最大值。默认最大值是5,表示AR部分的最大阶数为5。你可以根据数据复杂性调整
max.q = 5, # 设置q的最大值。默认最大值是5,表示MA部分的最大阶数为5。你可以根据数据复杂性调整
max.d = 2, # 设置d的最大值。最大差分次数,通常为1或2
seasonal = TRUE, # 是否进行季节性差分
)
#运行上方代码会得到不同公式下,模型的AIC值。AIC值较小表示模型更合适,一般选择最小的AIC所代表的模型
#找到上方代码结果中显示的best model(这个数据的结果显示,pdq、PDQ取值为0、0、2以及2、0、0)
fit <- arima(ts_data,
order = c(0, 0, 2), # ARIMA模型的p=0, d=0, q=2,表示0阶自回归和2阶移动平均项,无差分
seasonal = list(order = c(2, 0, 0), period = 12) # 季节性部分的阶数为2,0,0,表示有2阶季节性AR项,period=12表示周期为12(月度数据时,周期为12)
)
除了使用代码自动选择,也可以通过画出数据的ACF、PACF图来进行手动选择 非季节性部分: p(AR阶数):通过PACF图来确定。若PACF在滞后p处显著衰减且在后续滞后消失,选择p为此滞后值。 d(差分阶数):若数据非平稳,通常进行差分,差分次数即为d。 q(MA阶数):通过ACF图来确定。若ACF在滞后q处显著衰减且后续滞后消失,选择q为此滞后值。 季节性部分(可以使用decompose将季节性部分拆解出来): P(季节性AR阶数):通过季节性PACF图来确定。若季节性PACF在滞后s(季节周期)时显著衰减,选择P=1或P=2。 D(季节性差分阶数):若数据非平稳,通常进行差分,差分次数即为D。 Q(季节性MA阶数):通过季节性ACF图来确定,若季节性滞后期的ACF显著,选择Q=1或Q=2。 更详细的讲解可见:https://blog.csdn.net/weixin_62586597/article/details/120626460
#正态性检验
shapiro.test(fit$residuals)#残差是否服从正态(0假设残差正态)
qqnorm(fit$residuals, main = "Q-Q Plot of Residuals")#QQ图
#自相关性检验(0假设残差是白噪声)
Box.test(fit$residuals,lag=24,type="Ljung-Box") #非季节性=10,季节性=period*2
tsdisplay(residuals(fit),lag.max=20,main='残差')#残差相关性
shapiro.test p-value = 0.06676
Box.test p-value = 0.8147
残差正态且独立,模型效果良好
#预测测试集2007年
prediction<-forecast(fit,12)#向后预测12个月,也就是测试集的年份
# 提取预测值和实际值
predicted_values <- prediction$mean # 对验证集的预测值
actual_values <- test_data # 验证集的实际值
# 计算残差(实际-预测)
errors <- actual_values - predicted_values
# 计算指标
mean(abs(errors)) # 平均绝对误差(通常情况下越小越好)
mean(abs(errors / actual_values)) * 100
MAE: 1203.096
MAPE: 17.3117 %
mape<20%,精确度不错
#预测2008年
prediction2<-forecast(fit,24)
plot(prediction2)
将步长延长,就可以获得预测结果。可以看到其实预测部分已经有区间。但值得注意的是,预测的偏保守而不够集中,此时有请重抽样。
# 获取训练集的拟合值和残差序列
fitted_values <- fitted(fit)
residuals_values <- residuals(fit)
# 定义重抽样次数和预测长度
n_bootstrap <- 1000 # 生成1000个Bootstrap样本
forecast_horizon <- 12 # 预测步长12个月
# 建立存储每个样本预测值的矩阵
bootstrap_predictions <- matrix(NA, nrow = n_bootstrap, ncol = forecast_horizon)
set.seed(123) # 复现性,种子
#循环1000次
for (i in 1:n_bootstrap) {
# 从残差中进行有放回抽样
bootstrap_residuals <- sample(residuals_values, size = length(residuals_values), replace = TRUE)
# 将抽样的残差与拟合值相加,得到新的原始序列
new_ts_data <- fitted_values + bootstrap_residuals
# 重新拟合ARIMA模型
new_fit <- arima(new_ts_data, order = c(0, 0, 2), seasonal = list(order = c(2, 0, 0), period = 12))
# 对新的序列进行预测
bootstrap_prediction <- forecast(new_fit, h = forecast_horizon)
# 存储每次的预测值
bootstrap_predictions[i, ] <- bootstrap_prediction$mean
}
#现在已经获得了1000个预测路径
# 计算预测的95%置信区间(使用百分位数)
lower_bound <- apply(bootstrap_predictions, 2, function(x) quantile(x, 0.025))
upper_bound <- apply(bootstrap_predictions, 2, function(x) quantile(x, 0.975))
# 计算预测的中位数(避免极端值)
median_prediction <- apply(bootstrap_predictions, 2, median)
#看看重抽样的结果效果如何
errors2 <- validation_data$cases - median_prediction
# 计算指标
mean(abs(errors2)) # 平均绝对误差
mean(abs(errors2 / validation_data$cases)) * 100 # # 输出结果
MAE2: 1063.391
MAPE2: 17.21197 %
# 把重抽样结果转为数据框
prediction_data <- data.frame(
t = 85:96, # 预测的时间段
lower = lower_bound,
upper = upper_bound,
mean = median_prediction
)
ggplot() +
# 绘制实际值
geom_line(data = validation_data, aes(x = t, y = cases, color = "Actual Data"), linewidth = 1) +
# 绘制预测均值
geom_line(data = prediction_data, aes(x = t, y = mean, color = "Predicted Mean"), linetype = "dashed", linewidth = 1) +
# 绘制95%置信区间
geom_ribbon(data = prediction_data, aes(x = t, ymin = lower, ymax = upper, fill = "95% CI"), alpha = 0.3) +
labs(title = "Report vs Simulate",
x = "Time (t)",
y = "Cases",
color = " ", # 图例标题
fill = " ") + # 图例标题
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5)) + # 居中标题
scale_color_manual(values = c("Actual Data" = "blue", "Predicted Mean" = "red")) + # 设置线条颜色
scale_fill_manual(values = c("95% CI" = "lightblue")) # 设置填充颜色
在数据平稳、合适的情况下,ARIMA作为经典的时间序列模型,预测的效果并不会差(实际情况中,很多数据使用arima是不够的。这个模型不够复杂,抓特征的能力并没有那么强,特别是非线性部分)。R包自带的预测区间,是基于单次的残差序列来运算的,随着预测步长的前进,误差会越来越大,就导致预测区间的宽度比较宽,而且会变大,这意味着预测的非常保守。而使用重抽样的方法,通过生成多个预测路径,就能够得到更为精准的预测区间。
然而,ARIMA+Bootstrap方法仍然有其局限性。首先,ARIMA模型自身无法捕捉非线性特征和长期依赖,尽管Bootstrap通过重抽样弥补了ARIMA的短期预测能力,但如果残差存在系统性误差,重抽样的效果也会受到限制。通俗来讲,arima本身预测的很差,那bootstrap再怎么弥补也没太大意义。此外,Bootstrap方法本身的计算复杂度较高,尤其在大数据集上,其抽样和计算过程需要较长时间,会影响了效率和实时性。
如何改进?首先,针对真实数据,在拟合的时候选择最合适的模型。尽可能的捕捉数据本身的复杂趋势,在初步拟合效果不错的情况下,重抽样可以达到锦上添花的效果;其次,可以优化Bootstrap方法,采用动态抽样策略以适应时间序列的季节性波动和变化。同时,为了提升计算效率,可以借助GPU加速等技术提升在大规模数据上的实时预测能力;最后,可以使用残差建模的方法,运用深度学习在捕捉复杂关系的优势,进一步拟合残差,使用组合模型来提升预测精度。这些改进将帮助模型在处理复杂、非线性、高噪声数据时,提供更精准、可靠和高效的预测。