forecastsuite is a local, full-featured companion to the hosted
Forecast Dashboard Shiny app. This vignette covers two ways to use it:
the bundled app (run_app()), for interactive, no-code
forecasting, and the underlying model registry, for scripting forecasts
directly in R.
run_app() launches a local Shiny app with four tabs,
worked in order:
torch is
installed), Fit & Forecast, and browse the plot, metrics,
rolling-origin cross-validation, and an Analysis panel:
seasonal decomposition, anomaly detection, residual diagnostics, and
(with a grouping column active) cross-group correlation. A “Show Code”
panel prints the R equivalent of whatever you just ran, with a
copy-to-clipboard button and a download.Saving your setup (dataset, holidays, every Model-tab setting) to one file and reloading it later is available from any tab, via “Save Project (.rds)” / “Load Project (.rds)”.
Every model – built-in or your own – implements the same three-function contract:
fit(train_df, date_agg, ...) # -> model_obj
forecast(model_obj, h, ...) # -> raw_forecast_obj
to_tibble(raw_forecast_obj, test_df) # -> tibble(ds, yhat[, yhat_lower, yhat_upper])train_df/test_df are
ds/y tibbles (date, value).
list_models() lists what’s registered;
get_model(key) returns one entry.
library(forecastsuite)
vapply(list_models(available_only = TRUE), function(m) m$key, character(1))
#> [1] "arima" "ets" "holtwinters" "lstm" "nnetar"
#> [6] "prophet" "sarima" "tbats"set.seed(1)
n <- 120
ds <- as.Date("2023-01-01") + 0:(n - 1)
y <- 100 + 15 * sin(2 * pi * seq_len(n) / 7) + seq_len(n) * 0.2 + rnorm(n, sd = 3)
train_df <- tibble::tibble(ds = ds[1:100], y = y[1:100])
test_df <- tibble::tibble(ds = ds[101:120], y = y[101:120])
model <- get_model("arima")
fit_obj <- model$fit(train_df, date_agg = "day", auto = TRUE)
fc_raw <- model$forecast(fit_obj, h = 20)
fc_tib <- model$to_tibble(fc_raw, test_df)
head(fc_tib)
#> # A tibble: 6 × 4
#> ds yhat yhat_lower yhat_upper
#> <date> <dbl> <dbl> <dbl>
#> 1 2023-04-11 125. 118. 132.
#> 2 2023-04-12 116. 104. 128.
#> 3 2023-04-13 109. 93.7 125.
#> 4 2023-04-14 113. 96.6 130.
#> 5 2023-04-15 121. 105. 138.
#> 6 2023-04-16 129. 112. 145.The same functions behind the app’s Analysis panel work standalone on
any ds/y tibble – no fitted model needed for
decomposition or anomaly detection, and no Shiny session either.
decomp <- decompose_series(train_df, date_agg = "day")
head(decomp)
#> # A tibble: 6 × 5
#> ds observed trend seasonal remainder
#> <date> <dbl> <dbl> <dbl> <dbl>
#> 1 2023-01-01 110. 99.3 12.2 -1.44
#> 2 2023-01-02 116. 99.9 14.5 1.20
#> 3 2023-01-03 105. 100. 5.95 -1.77
#> 4 2023-01-04 99.1 101. -6.25 4.33
#> 5 2023-01-05 87.4 102. -13.9 -0.311
#> 6 2023-01-06 87.0 102. -11.7 -3.22anomalies <- detect_anomalies(train_df, date_agg = "day", method = "iqr", threshold = 1.5)
anomalies[anomalies$is_anomaly, ]
#> # A tibble: 3 × 4
#> ds y score is_anomaly
#> <date> <dbl> <dbl> <lgl>
#> 1 2023-02-25 117. 5.82 TRUE
#> 2 2023-03-08 101. -5.92 TRUE
#> 3 2023-03-11 121. 6.05 TRUEresid_diag <- compute_residual_diagnostics(fc_tib, test_df)
resid_diag[c("ljung_box_p", "shapiro_p", "n")]
#> $ljung_box_p
#> [1] 1.569801e-05
#>
#> $shapiro_p
#> [1] 0.06514511
#>
#> $n
#> [1] 20group_correlation_matrix() takes a named list of
ds/y tibbles (one per group, e.g.
grouped_series() from the app) and returns pairwise
correlations:
Adding a model is one register_model() call away from
working everywhere the built-in ones do (the app’s Model tab, Compare
Models, Cross-Validation, the Show Code panel):
register_model(
key = "naive_last",
label = "Naive (last value)",
fit = function(train_df, date_agg = "day", ...) utils::tail(train_df$y, 1),
forecast = function(model_obj, h, ...) list(mean = rep(model_obj, h)),
to_tibble = function(fc_obj, test_df) {
tibble::tibble(ds = test_df$ds[seq_along(fc_obj$mean)], yhat = fc_obj$mean)
}
)