Multi-Model Time Series Forecasting Toolkit and Local App
Fits, compares and visualizes univariate time series forecasts
through an extensible model registry built largely on the 'forecast'
package: AutoRegressive Integrated Moving Average (ARIMA) and Seasonal
ARIMA (SARIMA) models (Hyndman and Khandakar (2008)
), Error, Trend, Seasonal (ETS) exponential
smoothing (Hyndman et al. (2002) ),
Trigonometric seasonality, Box-Cox transformation, AutoRegressive Moving
Average (ARMA) errors, Trend and Seasonal components (TBATS) models
(De Livera, Hyndman and Snyder (2011) ),
Neural Network AutoRegression (NNETAR) and Holt-Winters exponential
smoothing (Winters (1960) ), plus optional
'Prophet' models via the 'prophet' package (Taylor and Letham (2018)
) and Long Short-Term Memory (LSTM)
networks via the 'torch' package (Hochreiter and Schmidhuber (1997)
). Also provides grouped series with
bottom-up and top-down reconciliation, rolling-origin cross-validation,
ensembles and residual diagnostics, following Hyndman and Athanasopoulos
(2021) < https://otexts.com/fpp3/>, and a bundled local 'shiny'
application exposing every model.
forecastsuite
A local, full-featured companion to the hosted Forecast Dashboard Shiny app. Where the
hosted app deliberately sticks to Prophet + ARIMA to fit a ~1GB-RAM free hosting tier,
this package has no such constraint and adds:
- A pluggable model registry (
register_model()/get_model()/list_models()) — every
model implements a fit/forecast/to_tibble contract, so adding a new model later
is one file + one register_model() call.
- Built-in models: Prophet (optional, requires
prophet), ARIMA, SARIMA (both with auto
and manual p,d,q/P,D,Q order entry), ETS, TBATS, NNETAR, Holt-Winters, and LSTM
(optional, requires torch).
- A bundled local Shiny app (
run_app()) with all models exposed, Plotly always on, and
a dataset-driven model recommendation step.
- Import from any of four sources: a file upload (CSV, TSV or Excel -- multi-sheet
workbooks let you pick the worksheet), a data frame already in your R global
environment (or, esquisse-style, a built-in dataset from any installed package --
e.g.
datasets, ggplot2 -- picked by package then dataset name, no library() or
data() call needed first), a link-shared Google Sheet, a CSV URL, or pasted
delimited text.
- Dates split across columns (Year + Quarter, Year + Month, optionally + Day) are
assembled automatically, and aggregation is set to the finest part supplied.
- An N-way model comparison: overlay several models' forecasts on one Plotly chart
alongside the side-by-side metrics table.
- Population normalization: divide by a population table to forecast incidence (e.g.
cases per 100,000) rather than absolute counts.
- A full holiday system: Sundays, a fixed-date catalog, movable holidays from a file,
manual entries, relabel/remove, per-holiday windows, and a consistency check against
your data.
- Multi-window evaluation (Train / Last 6 Months / Last 2 Years / held-out Test) and
suggested starting hyperparameters (including ARIMA/SARIMA's p, d, q, P, D, Q, all
data-derived) alongside the model recommendation.
- Individual Observations as an alternative to Aggregated data: import one row per
event (e.g. one case, one visit) and it's counted into periods for you, no value
column needed. Population normalization works with either format.
- A Model Guide tab explaining every model, the parameters, the metrics, and how the
recommendation heuristic works.
- A "Show Code" panel that prints runnable R reproducing whatever you just fit or
compared, syntax-highlighted, downloadable as a
.R file, or copyable to the
clipboard in one click.
- An Analysis panel (Model tab): seasonal decomposition (trend/seasonal/remainder),
anomaly detection on the raw series (IQR or z-score, downloadable as CSV), residual
diagnostics after a fit (Ljung-Box, Shapiro-Wilk, residual ACF, and prediction
interval coverage -- is the error actually unpredictable noise, or is there
structure left over, and are the uncertainty bounds honest?), bias/drift tracking
(is the model's error systematically one-sided, and is it growing across the test
window?), and, with a grouping column active, a cross-group correlation
heatmap/table. Decomposition, anomaly detection, and bias/drift need only a
finalized dataset or a fit, no extra setup.
- Forecast ensembling: combine two or more compared models' forecasts (simple average
or inverse-error-weighted) into one more entry on the same comparison plot/table --
"Build Ensemble from Selected" on the Model tab.
- A multi-model backtest leaderboard: the same rolling-origin folds as
Cross-Validation, but across every model selected for Compare Models at once,
ranked by mean test MASE.
- Plot appearance controls (trend/uncertainty/holiday/changepoint toggles, colour
pickers for every line) and one-click downloads: the processed dataset and the
holiday list as CSV, and the forecast plot -- single-model or the multi-model
comparison -- as PNG.
- A bundled 5-year demo dataset (simulated daily clinic visits, with missing and
partially-open Sundays) so you can explore the whole app with no data of your own --
pick it from the same icon row as the other import sources.
- An optional grouping column (District, Sex, Product, ...): split your dataset into
one time series per distinct value instead of only ever seeing the aggregate, fit
one model across every included group, browse each group's own plot/metrics, and
compare them on one overlay plot (actuals + forecasts, one color per group).
Population normalization, the recommendation table, and the holiday consistency
check are all group-aware when it's on. Messy raw values ("female"/"Female"/
"FEMALE") are auto-merged by casing/spacing, plus a manual relabel table for
anything else (abbreviations, typos) -- the same select-and-relabel pattern used
for holidays.
- Bottom-up hierarchical reconciliation: once you've fit two or more groups, view a
"Reconciled (bottom-up)" forecast -- the sum of the already-fit group forecasts,
always coherent by construction, labeled as partial if not every configured group
was fit this run.
- Rolling-origin cross-validation: refit the currently-viewed series at several
walk-forward cutoffs instead of relying on one train/test split, for a more robust
read on accuracy (Model tab, "Run Cross-Validation").
- Save your dataset(s), holidays, and every setting as one project file, and reload
it in a fresh session with no re-import needed -- fitted results aren't saved, so
you re-click Fit & Forecast after loading.
Install
install.packages("remotes")
remotes::install_local("forecastsuite")
Run the app
forecastsuite::run_app()
In RStudio, the app is also available from the Addins menu ("Run forecastsuite") --
no need to type the command.
Learn more
vignette("forecastsuite") walks through the bundled app tab-by-tab and the model
registry API for scripting forecasts directly in R, without the app.
Prophet support (optional)
install.packages("prophet")
Everything else works without prophet — list_models() simply omits Prophet until
it's installed. prophet pulls in a compiled Stan backend (rstan/StanHeaders) that
has a real history of install/check fragility on some platforms, so it's kept optional
(Suggests, not Imports) rather than force-loaded by library(forecastsuite). Once
installed, Prophet's changepoint/seasonality/holiday priors are exposed on the Model tab
like any other model's parameters, saved/restored with a project file, and included in
the "Show Code" panel.
LSTM support (optional)
install.packages("torch")
torch::install_torch()
Everything else works without torch — list_models() simply omits LSTM until it's
installed. Once available, its epochs / hidden units / lookback window / learning rate
are exposed on the Model tab like any other model's parameters, saved/restored with a
project file, and included in the "Show Code" panel.
Documentation (contributors)
Every exported function has real roxygen2 comments in its R/ source, but
NAMESPACE/man/*.Rd are regenerated from them, not hand-edited. After changing a
function's documentation or @export status:
Rscript document.R
(installs roxygen2 if needed, then runs roxygen2::roxygenise()). Commit the
updated NAMESPACE/man/.
Relationship to the hosted app
R/data_utils.R, R/metric_utils.R, R/holidays_helpers.R, and R/horizon_utils.R are
adapted from the parent repo's helpers/ files (same behavior, package-namespaced). They
are intentionally copied, not shared, so this package and the hosted app can evolve
independently without one destabilizing the other. If you fix a bug in one copy, check
whether the other needs the same fix.