Calculating growth rates from fable forecasts

Intro

This is an attempt to calculate growth rates from fable forecasts of a volume variable. Data was the tourism dataset from tsibble package.

Libraries loaded

Libs needed for this:

library(tsibble)
library(dplyr)
library(fable)
library(fabletools)
library(vctrs)
library(purrr)
library(distributional)

Base fit and forecast

 tsb_test <- tourism |>
    filter(Region %in% c('Adelaide') )  |>
    filter(Purpose %in% c('Business', 'Holiday')  )

fit_test <- tsb_test        |>
    model( ets = ETS(log(Trips) ) )

fc_test <-  fit_test |>
    forecast(h=3)

Base autoplot

autoplot(fc_test)

Experimental: growth rate calculation from forecast

na_distribution <- dist_transformed( dist_normal(NA,NA),
                              transform = function(Trips)  exp(Trips),
                              inverse =   function(Trips)  log(Trips))

Trips_lagged  <- fc_test |>
    group_by_key() |>
    mutate ( dplyr::lag(Trips, default = na_distribution), .keep = "none")  |>
    pull()

pull_first_element <- function(x)  {x[[1]]}

y_fc <- fc_test$Trips |> 
  vec_data() |> 
  lapply (pull_first_element) |> 
  vec_restore(fc_test$Trips )

y_lagged <- Trips_lagged  |> 
  vec_data()  |> 
  lapply (pull_first_element)  |> 
  vec_restore(fc_test$Trips )

y_diff <- map2_vec(y_fc, y_lagged, `-` )

y_diff_transformed <- dist_transformed( y_diff,
                         transform = function(Trips)  exp(Trips),
                         inverse =   function(Trips)  log(Trips)  )

fc_growth <- fc_test  |>
  mutate(Trips =   y_diff_transformed )  |>
  mutate(.mean = mean(Trips))

The echo: false option disables the printing of code (only output is displayed).

Autoplot of growth rates of the forecast

autoplot(fc_growth)
Warning in stats::rnorm(times, x[["mu"]], x[["sigma"]]): wyprodukowano wartości
NA
Warning in stats::rnorm(times, x[["mu"]], x[["sigma"]]): wyprodukowano wartości
NA
Warning in stats::rnorm(times, x[["mu"]], x[["sigma"]]): wyprodukowano wartości
NA
Warning in stats::rnorm(times, x[["mu"]], x[["sigma"]]): wyprodukowano wartości
NA
Warning in stats::rnorm(times, x[["mu"]], x[["sigma"]]): wyprodukowano wartości
NA
Warning in stats::rnorm(times, x[["mu"]], x[["sigma"]]): wyprodukowano wartości
NA
Warning in stats::rnorm(times, x[["mu"]], x[["sigma"]]): wyprodukowano wartości
NA
Warning in stats::rnorm(times, x[["mu"]], x[["sigma"]]): wyprodukowano wartości
NA
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_line()`).

Autoplot of growth rates of forecasts excluding NAs

fc_growth_not_na <- fc_growth |>
  filter(!is.na(.mean))

autoplot(fc_growth_not_na)

Forecast data table

fc_test |>  knitr::kable()
Region State Purpose .model Quarter Trips .mean
Adelaide South Australia Business ets 2018 Q1 t(N(5, 0.05)) 147.6086
Adelaide South Australia Business ets 2018 Q2 t(N(5.1, 0.054)) 175.5875
Adelaide South Australia Business ets 2018 Q3 t(N(5.2, 0.057)) 191.2471
Adelaide South Australia Holiday ets 2018 Q1 t(N(5.3, 0.019)) 212.5453
Adelaide South Australia Holiday ets 2018 Q2 t(N(5.1, 0.02)) 169.4951
Adelaide South Australia Holiday ets 2018 Q3 t(N(5.1, 0.021)) 166.4275
fc_growth |>  knitr::kable()
Region State Purpose .model Quarter Trips .mean
Adelaide South Australia Business ets 2018 Q1 t(N(NA, NA)) NA
Adelaide South Australia Business ets 2018 Q2 t(N(0.17, 0.1)) 1.2492463
Adelaide South Australia Business ets 2018 Q3 t(N(0.084, 0.11)) 1.1484114
Adelaide South Australia Holiday ets 2018 Q1 t(N(NA, NA)) NA
Adelaide South Australia Holiday ets 2018 Q2 t(N(-0.23, 0.04)) 0.8129706
Adelaide South Australia Holiday ets 2018 Q3 t(N(-0.019, 0.041)) 1.0016028

Lognormal distribution

y_diff_exp <-  y_diff |> exp()  


fc_growth_exp <- fc_test  |>
  mutate(Trips =   y_diff_exp )  |>
  mutate(.mean = mean(Trips))


autoplot(fc_growth_exp)
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_line()`).

fc_growth_exp |>  knitr::kable()
Region State Purpose .model Quarter Trips .mean
Adelaide South Australia Business ets 2018 Q1 lN(NA, NA) NA
Adelaide South Australia Business ets 2018 Q2 lN(0.17, 0.1) 1.2509001
Adelaide South Australia Business ets 2018 Q3 lN(0.084, 0.11) 1.1501272
Adelaide South Australia Holiday ets 2018 Q1 lN(NA, NA) NA
Adelaide South Australia Holiday ets 2018 Q2 lN(-0.23, 0.04) 0.8131273
Adelaide South Australia Holiday ets 2018 Q3 lN(-0.019, 0.041) 1.0018079

Check growth rates by hand

Mean is calculated from formula:

\[ mean = \exp \left(\mu +{\frac {\sigma ^{2}}{2}}\right) \]

mus    <-  sapply(  y_diff  |> vec_data()  ,   \(x) x[["mu"]]  )  
sigmas <-  sapply(  y_diff  |> vec_data()  ,   \(x) x[["sigma"]]  )  

growth_by_hand  <- mus + (sigmas ^2)/2
growth_by_hand
[1]           NA  0.223863368  0.139872576           NA -0.206867547
[6]  0.001806264
exp_growth_by_hand  <- exp(growth_by_hand)
exp_growth_by_hand
[1]        NA 1.2509001 1.1501272        NA 0.8131273 1.0018079
fc_augment <- fit_test |> 
  augment()  |>
  select(-any_of(c(".innov", ".fitted", ".resid"))) |>
  mutate(Trips =  dist_lognormal(  log(Trips), 0))  |>
  mutate(.mean = mean(Trips))

dimnames(fc_augment$Trips) <- "Trips"
  
fc_augment <- fc_augment |>
    as_fable(response = "Trips", distribution = "Trips")

y_as_lnorm <- fc_test |> pull(Trips) |> vec_data()  |> lapply (\(x)  x[[1]] )   |>  vec_restore( fc_test$Trips )    |> exp()

fc_as_lnorm <- fc_test  |>
  mutate( Trips = y_as_lnorm ) |>
  mutate( .mean = mean(Trips) ) 

fc_all <- dplyr::bind_rows (fc_augment, fc_as_lnorm)
autoplot(fc_all, level=90)

fc_augment |> tail() |>  knitr::kable()
Region State Purpose .model Quarter Trips .mean
Adelaide South Australia Holiday ets 2016 Q3 lN(5, 0) 154.1169
Adelaide South Australia Holiday ets 2016 Q4 lN(5, 0) 147.1020
Adelaide South Australia Holiday ets 2017 Q1 lN(5.4, 0) 215.9403
Adelaide South Australia Holiday ets 2017 Q2 lN(5.3, 0) 201.7341
Adelaide South Australia Holiday ets 2017 Q3 lN(5.1, 0) 163.7119
Adelaide South Australia Holiday ets 2017 Q4 lN(5.4, 0) 214.0531
Trips_lagged  <- fc_all |>
    group_by_key() |>
    mutate ( dplyr::lag(Trips, default =  dist_lognormal(NA,NA)), .keep = "none")    |>
    pull()

fc_diff <- fc_all |>
  mutate(Trips =  exp( log(Trips) - log(Trips_lagged) )) |>
  mutate(.mean = mean(Trips))
autoplot(fc_diff, level=90)
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_line()`).

fc_diff |> tsibble::group_by_key() |> slice_tail(n=6) |>  knitr::kable()
Region State Purpose .model Quarter Trips .mean
Adelaide South Australia Business ets 2017 Q2 lN(0.3, 0) 1.3464928
Adelaide South Australia Business ets 2017 Q3 lN(0.064, 0) 1.0665732
Adelaide South Australia Business ets 2017 Q4 lN(0.063, 0) 1.0646371
Adelaide South Australia Business ets 2018 Q1 lN(-0.31, 0.05) 0.7484511
Adelaide South Australia Business ets 2018 Q2 lN(0.17, 0.1) 1.2509001
Adelaide South Australia Business ets 2018 Q3 lN(0.084, 0.11) 1.1501272
Adelaide South Australia Holiday ets 2017 Q2 lN(-0.068, 0) 0.9342122
Adelaide South Australia Holiday ets 2017 Q3 lN(-0.21, 0) 0.8115231
Adelaide South Australia Holiday ets 2017 Q4 lN(0.27, 0) 1.3074985
Adelaide South Australia Holiday ets 2018 Q1 lN(-0.017, 0.019) 0.9930028
Adelaide South Australia Holiday ets 2018 Q2 lN(-0.23, 0.04) 0.8131273
Adelaide South Australia Holiday ets 2018 Q3 lN(-0.019, 0.041) 1.0018079