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:

air <- AirPassengers
dt <- future_dates(as.Date("1948-12-31"), "months", length(air))
air <- xts(as.numeric(air), dt)
spec <- arima_modelspec(y = air, frequency = 12, seasonal_type = "regular", 
                        seasonal = TRUE, lambda = NA)
mod <- estimate(spec)
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:

p <- predict(mod, h = 12)
plot(p)

Additionally, there is also a backtest method:

b <- tsbacktest(spec, h = 12, alpha = c(0.02, 0.1), cores = 5, trace = F)
b$metrics
##     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")
spec <- bsts_modelspec(y = priceunits[1:80,1], frequency = 12, differences = 0, 
                       level = T, slope = T, damped = T, seasonal = T, 
                       seasonal_frequency = 12, ar = T, ar_max = 3, lambda = 0)
mod <- estimate(spec, n_iter = 2000, trace = FALSE)
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.

tde <- tsdecompose(mod)
# 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)
dlm_model <- tsconvert(mod, to = "dlm", draw = "mean", 
                       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"
filtered_dlm <- dlmFilter(log(priceunits[1:80,1]), dlm_model)
smoothed_dlm <- dlmSmooth(filtered_dlm)
smoothed_series <- xts((dlm_model$FF %*% t(smoothed_dlm$s))[1,-1], index(priceunits)[1:80])
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).

p1 <- predict(mod, h = 12)
# BSTS method
p2 <- bsts::predict.bsts(mod$model, horizon = 12)
# convert predictive distribution to tsmodel.distribution for comparison
p2d <- p2$distribution
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.

td <- tsdecompose(p1)
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")

References

Hyndman, Rob, George Athanasopoulos, Christoph Bergmeir, Gabriel Caceres, Leanne Chhay, Kirill Kuroptev, Mitchell O’Hara-Wild, et al. 2022. Forecast: Forecasting Functions for Time Series and Linear Models. https://CRAN.R-project.org/package=forecast.
Petris, Giovanni. 2018. Dlm: Bayesian and Likelihood Analysis of Dynamic Linear Models. https://CRAN.R-project.org/package=dlm.
Plummer, Martyn, Nicky Best, Kate Cowles, Karen Vines, Deepayan Sarkar, Douglas Bates, Russell Almond, and Arni Magnusson. 2020. Coda: Output Analysis and Diagnostics for MCMC. https://CRAN.R-project.org/package=coda.
Scott, Steven L. 2022. Bsts: Bayesian Structural Time Series. https://CRAN.R-project.org/package=bsts.

  1. Fitted and predicted distributions are always returned in their original state by inverting any differencing used.↩︎

  2. See the the dlm package of Petris (2018).↩︎