Chapter 7 tsforeign package
7.1 Introduction
The tsforeign package provides custom wrappers for the auto.arima
function from the forecast package of R. Hyndman et al. (2022) package and the bsts
method from bsts package of Steven L. Scott (2022), with methods for estimation, some diagnostics and prediction. This package may be extended in future to provide wrappers for other interesting models. For the bsts
model, we have also made use of the tsconvert
method from the tsmethods package to convert the output of the model to one conforming to the required inputs of the dlm package, for which we provide an example in the demonstration section.
Since both of those packages have their own vignettes and documentation, we proceed here directly into a demonstration of the functionality.
7.2 ARIMA model
The entry point for the ARIMA model is the arima_modelspec
function
suppressPackageStartupMessages(library(tsforeign))
library(xts)
library(tsaux)
args(arima_modelspec)
## function (y, xreg = NULL, frequency = NULL, seasonal = FALSE,
## seasonal_type = "regular", lambda = NULL, seasonal_harmonics = NULL,
## lambda_lower = 0, lambda_upper = 1.5, ...)
## NULL
which allows for both regular and trigonometric seasonality.
For the demonstration example, we’ll use the AirPassengers
dataset after first converting it into an xts
object:
<- AirPassengers
air <- future_dates(as.Date("1948-12-31"), "months", length(air))
dt <- xts(as.numeric(air), dt)
air <- arima_modelspec(y = air, frequency = 12, seasonal_type = "regular",
spec seasonal = TRUE, lambda = NA)
<- estimate(spec)
mod summary(mod)
## ARIMA(0,1,1)(0,1,1)[12]
## ------------------------
## Estimate Std.Error t value Pr(>|t|)
## ma1 -0.4018 0.08964 -4.482 7.381e-06
## sma1 -0.5569 0.07310 -7.619 2.554e-14
##
## sigma : 0.037
##
## AIC BIC AICc
## -483.31 -474.69 -483.13
##
## MAPE MASE MSLRE BIAS
## 0.0262 0.2297 0.0012 0
tsmetrics(mod)
## n no.pars LogLik AIC BIC AICc MAPE MASE
## 1 131 2 244.6574 -483.3147 -474.6891 -483.1258 0.02623633 0.2297063
## MSLRE BIAS
## 1 0.001228422 4.059721e-05
plot(mod)
The predict function generates a predictive distribution via simulation and returns an object of class tsmodel.predict
:
<- predict(mod, h = 12)
p plot(p)
Additionally, there is also a backtest method:
<- tsbacktest(spec, h = 12, alpha = c(0.02, 0.1), cores = 5, trace = F)
b $metrics b
## horizon variable MAPE MSLRE BIAS n MIS[0.02]
## <int> <char> <num> <num> <num> <int> <num>
## 1: 1 y 0.02357239 0.0009512022 0.003713807 72 76.78371
## 2: 2 y 0.02837170 0.0013727862 0.006297666 71 79.22509
## 3: 3 y 0.03296483 0.0018393442 0.008375600 70 89.95706
## 4: 4 y 0.03694681 0.0022937517 0.009861276 69 116.03158
## 5: 5 y 0.03845403 0.0024637211 0.011796591 68 123.39160
## 6: 6 y 0.04150696 0.0027772296 0.013512230 67 130.95317
## 7: 7 y 0.04380133 0.0030903056 0.015835961 66 135.72532
## 8: 8 y 0.04533363 0.0031925974 0.018676736 65 138.92727
## 9: 9 y 0.04703576 0.0034092951 0.020971360 64 142.82301
## 10: 10 y 0.04911560 0.0037578974 0.024080807 63 153.80218
## 11: 11 y 0.05020916 0.0039095064 0.026176728 62 162.43629
## 12: 12 y 0.05267677 0.0042013412 0.027876521 61 169.94190
## MIS[0.1]
## <num>
## 1: 58.30995
## 2: 63.93438
## 3: 75.58588
## 4: 86.38726
## 5: 90.69070
## 6: 96.63358
## 7: 100.87521
## 8: 104.42741
## 9: 112.38600
## 10: 118.11243
## 11: 123.67679
## 12: 130.53549
7.3 bsts model
The bsts package of (Steven L. Scott 2022) provides functions for fitting Bayesian structural time series models. The entry point in our wrapper is the bsts_modelspec
function:
args(bsts_modelspec)
## function (y, xreg = NULL, frequency = NULL, differences = 0,
## level = TRUE, slope = TRUE, damped = FALSE, seasonal = FALSE,
## seasonal_frequency = 4, ar = FALSE, ar_max = 1, cycle = FALSE,
## cycle_frequency = NULL, cycle_names = NULL, seasonal_type = "regular",
## lambda = NULL, lambda_lower = 0, lambda_upper = 1, seasonal_harmonics = NULL,
## distribution = "gaussian", ...)
## NULL
which provides a rich set of options such as degree of differencing,17 a sparse AR component, sparse regressors, regular or trigonometric seasonal components (including multiple seasonal), cyclical components and the Box Cox transformation. Once an object is estimated, and all required components have been generated, any additional methods on the estimated component are performed directly by custom written functions in the tsforeign package.
For the demonstration we’ll use the priceunits
dataset from the tsdatasets package. Once a series is estimated, the resulting MCMC draws are converted to an mcmc
object from the coda package of Plummer et al. (2020) in order to provide a nice summary report.
data("priceunits", package = "tsdatasets")
<- bsts_modelspec(y = priceunits[1:80,1], frequency = 12, differences = 0,
spec level = T, slope = T, damped = T, seasonal = T,
seasonal_frequency = 12, ar = T, ar_max = 3, lambda = 0)
<- estimate(spec, n_iter = 2000, trace = FALSE)
mod summary(mod)
##
## Iterations = 1:2000
## Thinning interval = 1
## Number of chains = 1
## Sample size per chain = 2000
##
## 1. Empirical mean and standard deviation for each variable,
## plus standard error of the mean:
##
## Mean SD Naive SE Time-series SE Prob[Include]
## obs.sigma 0.0132905 0.006549 1.464e-04 0.0006929 1.0000
## level.sigma 0.0046552 0.003710 8.295e-05 0.0008889 1.0000
## slope.sigma 0.0321532 0.006358 1.422e-04 0.0004551 1.0000
## slope.ar -0.3336629 0.267831 5.989e-03 0.0248040 1.0000
## seasonal12.sigma 0.0023919 0.002541 5.682e-05 0.0008274 1.0000
## ar1 0.0300258 0.235424 5.264e-03 0.0208192 0.6905
## ar2 0.0389725 0.127629 2.854e-03 0.0115642 0.4260
## ar3 0.0009242 0.051035 1.141e-03 0.0022904 0.2050
## ar3.sigma 0.0285918 0.006131 1.371e-04 0.0003575 1.0000
##
## 2. Quantiles for each variable:
##
## 2.5% 25% 50% 75% 97.5%
## obs.sigma 5.301e-03 0.009145 0.012391 0.016388 0.026054
## level.sigma 4.049e-04 0.001536 0.003908 0.006786 0.013702
## slope.sigma 1.953e-02 0.027971 0.032119 0.036327 0.044569
## slope.ar -7.402e-01 -0.529793 -0.369327 -0.179222 0.300217
## seasonal12.sigma 7.983e-05 0.000488 0.001482 0.003395 0.009623
## ar1 -4.120e-01 -0.069753 0.000000 0.123664 0.597288
## ar2 -1.580e-01 0.000000 0.000000 0.027540 0.399110
## ar3 -1.072e-01 0.000000 0.000000 0.000000 0.139456
## ar3.sigma 2.054e-02 0.025165 0.028213 0.031091 0.038241
##
##
## Harvey's Goodness of Fit Statistic: 0.2161046
##
## MAPE MASE MSLRE BIAS
## 0.0371 0.4694 0.0023 -2e-04
tsmetrics(mod)
## n no.pars MAPE MASE MSLRE BIAS
## 1 80 9 0.03708086 0.4694221 0.002281904 -0.0001531455
plot(mod)
The decomposition of the model into it’s fitted components is done via the tsdecompose
method. However, note that the bsts routine returns the smoothed component states, not the filtered ones.
<- tsdecompose(mod)
tde # distribution objects of state components
str(tde)
## List of 5
## $ Level : 'tsmodel.distribution' num [1:1948, 1:80] 1.6 1.6 1.55 1.58 1.55 ...
## ..- attr(*, "dimnames")=List of 2
## .. ..$ : NULL
## .. ..$ : chr [1:80] "1992-08-31" "1992-09-30" "1992-10-31" "1992-11-30" ...
## ..- attr(*, "date_class")= chr "Date"
## $ Slope : 'tsmodel.distribution' num [1:1948, 1:80] 0.03346 0.02428 -0.00824 0.01225 0.08149 ...
## ..- attr(*, "dimnames")=List of 2
## .. ..$ : NULL
## .. ..$ : chr [1:80] "1992-08-31" "1992-09-30" "1992-10-31" "1992-11-30" ...
## ..- attr(*, "date_class")= chr "Date"
## $ AR : 'tsmodel.distribution' num [1:1948, 1:80] 0.02726 0.00632 0.09124 0.03447 0.06217 ...
## ..- attr(*, "dimnames")=List of 2
## .. ..$ : NULL
## .. ..$ : chr [1:80] "1992-08-31" "1992-09-30" "1992-10-31" "1992-11-30" ...
## ..- attr(*, "date_class")= chr "Date"
## $ X : NULL
## $ Seasonal12: 'tsmodel.distribution' num [1:1948, 1:80] -0.0466 -0.0506 -0.0523 -0.0414 -0.0393 ...
## ..- attr(*, "dimnames")=List of 2
## .. ..$ : NULL
## .. ..$ : chr [1:80] "1992-08-31" "1992-09-30" "1992-10-31" "1992-11-30" ...
## ..- attr(*, "date_class")= chr "Date"
Once bsts model is estimated, we can convert it to a dlm
object18 using either the mean values of the parameters and initial states, or a specific draw using the tsconvert
method.
library(dlm)
<- tsconvert(mod, to = "dlm", draw = "mean",
dlm_model burn = bsts::SuggestBurn(0.1, mod$model))
str(dlm_model)
## List of 6
## $ m0: num [1:17] 1.56851 0.01552 -0.00441 -0.04117 -0.03039 ...
## $ C0: num [1:17, 1:17] 1e+07 0e+00 0e+00 0e+00 0e+00 0e+00 0e+00 0e+00 0e+00 0e+00 ...
## $ FF: num [1, 1:17] 1 0 0 1 0 0 0 0 0 0 ...
## $ V : num [1, 1] 0.0129
## $ GG: num [1:17, 1:17] 1 0 0 0 0 0 0 0 0 0 ...
## $ W : num [1:17, 1:17] 0.00476 0 0 0 0 ...
## - attr(*, "class")= chr "dlm"
<- dlmFilter(log(priceunits[1:80,1]), dlm_model)
filtered_dlm <- dlmSmooth(filtered_dlm)
smoothed_dlm <- xts((dlm_model$FF %*% t(smoothed_dlm$s))[1,-1], index(priceunits)[1:80])
smoothed_series par(mfrow = c(2,1), mar = c(3,3,3,3))
plot(as.zoo(filtered_dlm$f), type = "l", main = "Filtered Series", ylab = "")
lines(as.zoo(fitted(mod, raw = TRUE)), col = 2)
grid()
legend("topright", c("DLM","BSTS"), col = 1:2, lty = 1, bty = "n")
plot(as.zoo(smoothed_series), type = "l", main = "Smoothed Series", ylab = "")
lines(as.zoo(fitted(mod, raw = TRUE, type = "smoothed")), col = 2)
grid()
legend("topright", c("DLM","BSTS"), col = 1:2, lty = 1, bty = "n")
We compare the predictive distribution of the predicted object from calling the predict method of the tsforeign package and that of the bsts package. Note that there are a lot more arguments which can be passed to the predict
routine, including user overrides for the last state means and the posterior means of the parameters (see the documentation for more details).
<- predict(mod, h = 12)
p1 # BSTS method
<- bsts::predict.bsts(mod$model, horizon = 12)
p2 # convert predictive distribution to tsmodel.distribution for comparison
<- p2$distribution
p2d colnames(p2d) <- colnames(p1$distribution)
class(p2d) <- "tsmodel.distribution"
plot(log(p1$distribution), main = "tsforeign native predict c++ routine vs bsts routine")
plot(p2d, add = TRUE, median_color = 2, interval_color = 2, median_type = 2)
A predicted object can also be decomposed into it’s structural components, something we are able to do because of the custom predict routine since bsts
does not return this information.
<- tsdecompose(p1)
td par(mfrow = c(2,2), mar = c(3,3,3,3))
plot(td$Level, main = "Level")
plot(td$Slope, main = "Slope")
plot(td$Seasonal12, main = "Seasonal")
plot(td$AR, main = "AR")