This week’s discussion topic is comparing ETS & ARIMA models to a GARCH model. ARCH (autoregressive conditionally heteroscedastic) model is a model for the variance of a time series. ARCH models are used to describe a changing, possibly volatile variance. Although an ARCH model could possibly be used to describe a gradually increasing variance over time, most often it is used in situations in which there may be short periods of increased variation. A GARCH (generalized autoregressive conditionally heteroscedastic) model uses values of the past squared observations and past variances to model the variance at time t. (11.1 ARCH/GARCH Models) The models I will build will be for Electricity net generation measured in billions of kilowatt hours (kWh). The data is from 1973-01-01, to 2013-06-01. This data comes from the fpp2 package.
This season has an increasing trend, and elements of seasonality. Additionally, it does not appear so much that the variance is increasing over time, but the increasing trend is causing the peaks to be higher, and the troughs to be lower. Let’s decompose this series, and see the elects separate from one another.
autoplot(decompose(usmelec, "a")) +
ggtitle("Additive Decomposition")
autoplot(decompose(usmelec, "m")) +
ggtitle("Multiplicative Decomposition")
library(seasonal)
usmelec %>%
stl(t.window=13, s.window="periodic", robust=TRUE) %>%
autoplot() +
ggtitle("STL Decomposition")
usmelec %>%
seas(x11 = "") %>%
autoplot() +
ggtitle("X11 Decomposition")
This data is very seasonal. The X11 decomposition seems to do the best job removing all of the components from the remainder series. It is very stationary, and follows no patter, unlike the other 3.
The three models we will be building and comparing are ETS, ARIMA, and GARCH. First, let’s build out the ETS models, and select the best performing one. we’ll need to split the data into a training set and a test set. We will use until the end of 2004 to train, and 2005: 2013-06 to test.
The ETS models I will test are “ZZZ”, which is auto selected, “ANN” is simple exponential smoothing with additive errors, “MNN” is simple exponential smoothing with multiplicative errors, “MAM” is multiplicative Holt-Winters’ method with multiplicative errors, and “MAN” Holt’s linear method with multiplicative errors.
train <- window(usmelec, end = c(2004,12))
test<- window(usmelec, start = c(2005,1))
library(kableExtra)
ets_model_types <- c("ZZZ", "ANN", "MNN", "MAM", "MAN")
ts_model_list <- lapply(setNames(unique(ets_model_types),
unique(ets_model_types)),
function(x){
model <- ets(y = train, model = x)
fcsts <- forecast(model, h = 32)
eval_metrics <-
accuracy(object = fcsts,
x = test)
return(eval_metrics)
})
kable(ts_model_list[["ZZZ"]], caption = "ZZZ")
| ME | RMSE | MAE | MPE | MAPE | MASE | ACF1 | Theil’s U | |
|---|---|---|---|---|---|---|---|---|
| Training set | 0.5738632 | 6.694844 | 4.975248 | 0.1781215 | 2.059266 | 0.5794835 | 0.0159539 | NA |
| Test set | 4.8199138 | 14.973278 | 11.588235 | 1.0866991 | 3.228432 | 1.3497200 | 0.4636921 | 0.4532237 |
kable(ts_model_list[["ANN"]], caption = "ANN")
| ME | RMSE | MAE | MPE | MAPE | MASE | ACF1 | Theil’s U | |
|---|---|---|---|---|---|---|---|---|
| Training set | 0.5157664 | 21.16762 | 17.35242 | -0.1517985 | 7.159502 | 2.021094 | 0.1296537 | NA |
| Test set | 2.2053259 | 36.81947 | 29.98589 | -0.4386358 | 8.565380 | 3.492556 | 0.5647535 | 1.145587 |
kable(ts_model_list[["MNN"]], caption = "MNN")
| ME | RMSE | MAE | MPE | MAPE | MASE | ACF1 | Theil’s U | |
|---|---|---|---|---|---|---|---|---|
| Training set | 0.4761309 | 21.02734 | 17.14512 | -0.1458328 | 7.083829 | 1.996949 | 0.0791501 | NA |
| Test set | -0.4294072 | 36.75587 | 30.47991 | -1.2185408 | 8.777484 | 3.550095 | 0.5647535 | 1.153688 |
kable(ts_model_list[["MAM"]], caption = "MAM")
| ME | RMSE | MAE | MPE | MAPE | MASE | ACF1 | Theil’s U | |
|---|---|---|---|---|---|---|---|---|
| Training set | 0.5738632 | 6.694844 | 4.975248 | 0.1781215 | 2.059266 | 0.5794835 | 0.0159539 | NA |
| Test set | 4.8199138 | 14.973278 | 11.588235 | 1.0866991 | 3.228432 | 1.3497200 | 0.4636921 | 0.4532237 |
kable(ts_model_list[["MAN"]], caption = "MAN")
| ME | RMSE | MAE | MPE | MAPE | MASE | ACF1 | Theil’s U | |
|---|---|---|---|---|---|---|---|---|
| Training set | 0.4599866 | 21.03587 | 17.16474 | -0.1567924 | 7.096769 | 1.999234 | 0.0781813 | NA |
| Test set | -0.4390869 | 36.75533 | 30.48178 | -1.2213853 | 8.778278 | 3.550314 | 0.5647436 | 1.153708 |
It appears the auto selection found “MAM”. It significantly out performs the others, so we shall use it to test. Let’s fit the model.
my_ets <- ets(y = train, model = "MAM")
For the ARIMA models, I will derive two \(ARIMA(p,d,q)\) orders automatically. The first will use the functionality of forecast::auto.arima to account for seasonality directly by estimating the \((PDQ)\) component of the model is as such
$$
ARIMA(p,d,q)(P,Q,D)[m]
$$
The second will use forecast::stlf(method = "arima"). This function will forecast STL objects are obtained by applying a non-seasonal forecasting method to the seasonally adjusted data and re-seasonalizing using the last year of the seasonal component.
First, let’s look at some features of this series that will give use a sense of its structure from an “ARIMA” point of view. We will determine if our series has a unit root, and needs to be differenced. I will use the Kwiatkowski-Phillips-Schmidt-Shin (KPSS) test to stay consistent w/ the method used in the text. In this test, \(Ho\) is that the data are stationary, and we look for evidence that the null hypothesis is false. Consequently, rejections of \(Ho\) suggest that differencing is required.
library(urca)
diffs <- usmelec %>%
ndiffs()
print(paste("The number of differences this series requires is", diffs))
## [1] "The number of differences this series requires is 1"
sdiffs<- usmelec %>%
diff(lag=12) %>%
ndiffs()
print(paste("The number of seasonal differences this series requires is", diffs))
## [1] "The number of seasonal differences this series requires is 1"
The results of the tests indicate we should do both a seasonal difference and a first difference in our ARIMA models.
Now let’s take a look at the ACF and PACF plots, and fit the models.
train %>%
diff %>%
ggAcf()
train %>%
diff %>%
ggPacf()
my_auto_arima <- auto.arima(train, seasonal = TRUE)
my_stlf_arima <- stlf(train, method = "arima", h = length(test))
Here are the results using auto.arima()
my_auto_arima %>%
summary()
## Series: train
## ARIMA(1,0,2)(0,1,1)[12] with drift
##
## Coefficients:
## ar1 ma1 ma2 sma1 drift
## 0.9501 -0.4299 -0.2637 -0.6893 0.4796
## s.e. 0.0261 0.0588 0.0582 0.0362 0.0584
##
## sigma^2 estimated as 47.83: log likelihood=-1248.55
## AIC=2509.11 AICc=2509.34 BIC=2532.62
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE
## Training set -0.01886106 6.761253 5.066625 -0.1352361 2.067803 0.5901265
## ACF1
## Training set 0.006801918
And here is using STLF:
my_stlf_arima$model
## Series: x
## ARIMA(0,1,2) with drift
##
## Coefficients:
## ma1 ma2 drift
## -0.4890 -0.2396 0.4791
## s.e. 0.0507 0.0517 0.0798
##
## sigma^2 estimated as 32.76: log likelihood=-1210.44
## AIC=2428.87 AICc=2428.98 BIC=2444.66
accuracy(my_stlf_arima)
## ME RMSE MAE MPE MAPE MASE
## Training set 0.01574581 5.693439 4.327251 -0.04961887 1.786792 0.5040092
## ACF1
## Training set -0.001212224
The results from the STLF make quite a bit of sense, since the ACF is sinusoidal, and the PACF has about an order of 2. This model performs best in terms of AICc, RMSE, MAPE, and MASE. I will use this model to forecast test data.
Finally, we need to fit the GARCH model for our training set. I will fit the model to the order of \[GARCH(1,1)\]
library(fGarch)
## Loading required package: timeDate
## Loading required package: timeSeries
##
## Attaching package: 'timeSeries'
## The following objects are masked from 'package:seasonal':
##
## outlier, series
## The following object is masked from 'package:zoo':
##
## time<-
## Loading required package: fBasics
##
## Attaching package: 'fBasics'
## The following object is masked from 'package:TTR':
##
## volatility
garch_11=garchFit(data=train, formula = ~garch(1,1), cond.dist="QMLE", trace=FALSE)
summary(garch_11)
##
## Title:
## GARCH Modelling
##
## Call:
## garchFit(formula = ~garch(1, 1), data = train, cond.dist = "QMLE",
## trace = FALSE)
##
## Mean and Variance Equation:
## data ~ garch(1, 1)
## <environment: 0x000000001dad8f78>
## [data = train]
##
## Conditional Distribution:
## QMLE
##
## Coefficient(s):
## mu omega alpha1 beta1
## 197.72706 10.15699 0.15775 0.84475
##
## Std. Errors:
## robust
##
## Error Analysis:
## Estimate Std. Error t value Pr(>|t|)
## mu 197.72706 4.48756 44.061 < 2e-16 ***
## omega 10.15699 7.92538 1.282 0.2
## alpha1 0.15775 0.03275 4.817 1.46e-06 ***
## beta1 0.84475 0.03126 27.021 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Log Likelihood:
## -2021.1 normalized: -5.263281
##
## Description:
## Thu Jul 30 13:35:58 2020 by user: jawilliams
##
##
## Standardised Residuals Tests:
## Statistic p-Value
## Jarque-Bera Test R Chi^2 20.42772 3.665868e-05
## Shapiro-Wilk Test R W 0.9399884 2.447251e-11
## Ljung-Box Test R Q(10) 1516.179 0
## Ljung-Box Test R Q(15) 2479.345 0
## Ljung-Box Test R Q(20) 3211.581 0
## Ljung-Box Test R^2 Q(10) 212.9904 0
## Ljung-Box Test R^2 Q(15) 613.0244 0
## Ljung-Box Test R^2 Q(20) 682.6537 0
## LM Arch Test R TR^2 262.6909 0
##
## Information Criterion Statistics:
## AIC BIC SIC HQIC
## 10.54740 10.58855 10.54718 10.56372
Let’s evaluate our models performance on the test data.
fcst_ets <- forecast(my_ets, h = length(test))
fcst_arima <- my_stlf_arima$mean
fcst_garch <- fGarch::predict(garch_11, n.ahead = length(test))
fcst_garch <- ts(fcst_garch$meanForecast, start = c(2005,1), frequency = 12)
# MAM
accuracy(fcst_ets$mean, test)
## ME RMSE MAE MPE MAPE ACF1 Theil's U
## Test set 4.22075 13.79647 10.50331 0.9615199 2.967047 0.5847733 0.4153442
#ARIMA
accuracy(fcst_arima, test)
## ME RMSE MAE MPE MAPE ACF1 Theil's U
## Test set -19.95926 27.12156 22.22037 -6.194206 6.775985 0.7973904 0.8887439
#GARCH
accuracy(fcst_garch, test)
## ME RMSE MAE MPE MAPE ACF1 Theil's U
## Test set 141.3629 145.5129 141.3629 41.11859 41.11859 0.5583063 4.522462
Based on the test set evaluation metrics, the “MAM” model greatly outperforms the other two.
Let’s take a look at the forecasts versus the observed values in the test set.
autoplot(test)+
autolayer(fcst_ets$mean, series = "MAM")+
ggtitle("MAM Forecasts v Observed Values")+
ylab("kwH")
autoplot(test)+
autolayer(fcst_arima, series = "ARIMA")+
ggtitle("ARIMA(0,1,2) with drift Forecasts v Observed Values")+
ylab("kwH")
autoplot(test)+
autolayer(fcst_garch, series = "GARCH")+
ggtitle("GARCH(1,1)) Forecasts v Observed Values")+
ylab("kwH")
So it appears that I completely missed the mark w/ my Garch model. The ’MAM" model performed the best, just slightly under forecasting the values in the test set.