Skip to contents

An implementation of the FASSTER (Forecasting with Additive Switching of Seasonality, Trend and Exogenous Regressors) model in R. This model is designed to capture patterns of multiple seasonality in a state space framework by using state switching. The fasster package prioritizes flexibility, computational speed and accuracy to provide convenient tools for modelling, predicting and understanding high frequency time-series.

Model information

This model was developed in 2017 for an honours thesis, and the clearest description with examples of using fasster can be found in my useR! 2018 talk: slides, video, source.

The model uses a forward filtering backward smoothing heuristic for estimating the initial states in the model. It is essentially an opinionated wrapper of the {dlm} package aimed at making it easier to specify a sophisticated model suitable for forecasting complex seasonal patterns.

Installation

The stable version can be installed from CRAN:

install.packages("fasster")

The development version can be installed from GitHub using:

# install.packages("devtools")
devtools::install_github("tidyverts/fasster")

Usage

Model specification

fasster allows flexible model specification by allowing the user to specify the model structure with standard formula conventions.

library(fasster)
library(tidyverse)
library(lubridate)
library(tsibble)
library(fable)

lung_deaths <- as_tsibble(cbind(mdeaths, fdeaths), pivot_longer = FALSE)
fit <- lung_deaths |>
  model(fasster = FASSTER(fdeaths ~ mdeaths))
fit |> report()
#> Series: fdeaths 
#> Model: FASSTER 
#> 
#> Estimated variances:
#>  State noise variances (W):
#>   mdeaths
#>    1.7119e-34
#> 
#>  Observation noise variance (V):
#>   1.6631e+03
#> 
#> Initial states (m0):
#>   mdeaths: 3.7711e-01

Commonly used state space components can be added using the following convenience functions:

  • trend(n) to include an n-th order polynomial
  • season(s) to include a seasonal factor of frequency s
  • fourier(s, q) to include seasonal fourier terms of frequency s with q harmonics
  • arma(ar, ma) to include an ARMA term (where ar and ma are vectors of coefficients)
  • Exogenous regressors can be added by referring to their name

For example, to create a model with trend and monthly seasonality, you can use:

fit <- as_tsibble(USAccDeaths) |> 
  model(fasster = FASSTER(value ~ trend(1) + fourier(12)))
fit |> report()
#> Series: value 
#> Model: FASSTER 
#> 
#> Estimated variances:
#>  State noise variances (W):
#>   fourier(12)
#>    3.5590e-13 2.3103e-13 3.5905e-13 4.3745e-13 3.6531e-13 8.4136e-13 2.6433e-13 3.4998e-13 4.1144e-13 3.6736e-13 1.3359e-13
#>   trend(1)
#>    5.9382e+03
#> 
#>  Observation noise variance (V):
#>   2.0543e+04
#> 
#> Initial states (m0):
#>   fourier(12): -7.2510e+02
#>   fourier(12): -7.4524e+02
#>   fourier(12):  4.1713e+02
#>   fourier(12):  8.1375e+01
#>   fourier(12):  1.5506e+02
#>   fourier(12): -1.9470e+02
#>   fourier(12): -1.0038e+01
#>   fourier(12):  1.5899e+02
#>   fourier(12):  1.5997e+02
#>   fourier(12):  2.0428e+02
#>   fourier(12): -1.5165e+01
#>   trend(1):  9.7398e+03

The interface for creating a FASSTER model introduces a new formula construct, %S%, known as the switch operator. This allows modelling of more complex patterns such as multiple seasonality by modelling the components for each group separately and switching between them.

elec_tr <- tsibbledata::vic_elec |>
  filter(
    Time < lubridate::ymd("2012-03-01")
  ) |> 
  mutate(WorkDay = wday(Time) %in% 2:6 & !Holiday)

elec_fit <- elec_tr |>
  model(
    fasster = fasster(log(Demand) ~ 
      WorkDay %S% (fourier(48, 16) + trend(1)) + Temperature + I(Temperature^2)
    )
  )

Decomposing

Fitted FASSTER models can be decomposed to provide a description of how the underlying states function. Decomposing a FASSTER model provides aggregates of its components such as trends and seasonalities.

These components can accessed from a fitted model using the components() function:

fit |> 
  components()
#> # A dable: 72 x 5 [1M]
#> # Key:     .model [1]
#> # :        value = `fourier(12)` + `trend(1)`
#>    .model     index value `fourier(12)` `trend(1)`
#>    <chr>      <mth> <dbl>         <dbl>      <dbl>
#>  1 fasster 1973 Jan  9007        -795.       9740.
#>  2 fasster 1973 Feb  8106       -1546.       9754.
#>  3 fasster 1973 Mar  8928        -758.       9719.
#>  4 fasster 1973 Apr  9137        -536.       9706.
#>  5 fasster 1973 May 10017         322.       9693.
#>  6 fasster 1973 Jun 10826         802.       9694.
#>  7 fasster 1973 Jul 11317        1669.       9830.
#>  8 fasster 1973 Aug 10744         974.       9755.
#>  9 fasster 1973 Sep  9713         -65.7      9761.
#> 10 fasster 1973 Oct  9938         233.       9768.
#> # ℹ 62 more rows
elec_fit |>
  components()
#> # A dable: 2,880 x 9 [30m] <Australia/Melbourne>
#> # Key:     .model [1]
#> # :        log(Demand) = `WorkDay_FALSE/fourier(48, 16)` +
#> #   `WorkDay_FALSE/trend(1)` + `WorkDay_TRUE/fourier(48, 16)` +
#> #   `WorkDay_TRUE/trend(1)` + Temperature + `I(Temperature^2)`
#>    .model  Time                `log(Demand)` `WorkDay_FALSE/fourier(48, 16)`
#>    <chr>   <dttm>                      <dbl>                           <dbl>
#>  1 fasster 2012-01-01 00:00:00          8.39                        -0.00345
#>  2 fasster 2012-01-01 00:30:00          8.36                        -0.0234 
#>  3 fasster 2012-01-01 01:00:00          8.31                        -0.0971 
#>  4 fasster 2012-01-01 01:30:00          8.26                        -0.105  
#>  5 fasster 2012-01-01 02:00:00          8.30                        -0.117  
#>  6 fasster 2012-01-01 02:30:00          8.26                        -0.0812 
#>  7 fasster 2012-01-01 03:00:00          8.21                        -0.251  
#>  8 fasster 2012-01-01 03:30:00          8.18                        -0.144  
#>  9 fasster 2012-01-01 04:00:00          8.14                        -0.374  
#> 10 fasster 2012-01-01 04:30:00          8.12                        -0.202  
#> # ℹ 2,870 more rows
#> # ℹ 5 more variables: `WorkDay_FALSE/trend(1)` <dbl>,
#> #   `WorkDay_TRUE/fourier(48, 16)` <dbl>, `WorkDay_TRUE/trend(1)` <dbl>,
#> #   Temperature <dbl>, `I(Temperature^2)` <dbl>

The tools made available by fasster are designed to integrate seamlessly with the tidyverse of packages, enabling familiar data manipulation and visualisation capabilities.

Forecasting

fasster conforms to the object structure from the fable package, allowing common visualisation and analysis tools to be applied on FASSTER models.

fit |> 
  forecast(h=24) |>
  autoplot(as_tsibble(USAccDeaths))

Future index values are automatically produced and used where necessary in the model specification. If additional information is required by the model (such as WorkDay and Temperature) they must be included in a tsibble of future values passed to new_data.

elec_ts <- tsibbledata::vic_elec |>
  filter(
    yearmonth(Time) == yearmonth("2012 Mar")
  ) |> 
  mutate(WorkDay = wday(Time) %in% 2:6 & !Holiday) |> 
  select(-Demand)
elec_fit |> 
  forecast(new_data = elec_ts) |> 
  autoplot(elec_tr)