--- title: "Intro to rollcast" author: "Giancarlo Vercellino" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Intro to rollcast} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = FALSE ) ``` > "All models are wrong, but some are useful." > -- George E. P. Box, statistician and Box-Jenkins time-series coauthor > "Be approximately right rather than exactly wrong." > -- John W. Tukey, statistician and pioneer of exploratory data analysis > "The purpose of computing is insight, not numbers." > -- Richard W. Hamming, numerical analyst and Turing Award winner ## What you can do with rollcast `rollcast` builds probabilistic forecasts for one numeric time series by turning rolling statistics into forecast anchors, learning which anchors are useful in the current state, and simulating future paths from the resulting mixture. Instead of asking one model to explain everything, `rollcast` keeps a small bench of candidate locations: - rolling mean; - rolling median; - rolling minimum and maximum; - rolling linear-regression endpoint; - user-chosen rolling quantiles. Then a proper-score gate assigns state-dependent weights, optional conditional residual sampling adds local dispersion, and recursive simulation turns the one-step mixture into marginal and joint predictive distributions. The `rollcast` package works this way: - **anchor dictionary**: rolling summaries define plausible forecast locations. - **state features**: anchors are compared with the latest level after robust scaling. - **soft responsibilities**: recent outcomes tell the model which anchors were close, without forcing a winner-takes-all label. - **proper-score gate**: a softmax model is trained by predictive log score, so the target is probabilistic calibration. - **conditional residuals**: nearby historical states can donate residual shapes when `error_scale > 0`. - **adaptive persistence**: anchor weights can move slowly in stable regimes and faster when the state shifts. - **recursive mixture simulation**: every particle and every anchor contributes before resampling, so uncertainty can widen naturally with horizon. Five anchors walk into a forecast. The gate checks the room, turns the lights up on the useful ones, and lets the simulation take it from there. ## 1) Fit rollcast, aka anchor first, argue later We will use a synthetic series with a gentle drift and enough noise to keep things honest. Scalar hyperparameters mean "use this value"; vectors mean "try these candidates with causal validation." ```{r} library(rollcast) set.seed(1) y <- 100 + cumsum(rnorm(300, mean = 0.03, sd = 0.8)) fit <- rollcast( y, window = 60, tau = 0.25, lambda = 0.01, conditional_k = 40, state_bw = 1, residual_bw = 0.35, error_scale = 0.25, residual_smoothing = 0.03, rho_min = 0.05, rho_max = 0.90, rho_decay = 1 ) fit ``` The printed model shows the selected anchors, gate settings, adaptive persistence range, and current anchor probabilities. That last part is the quick read: which rolling summaries are currently getting the most probability. ## 2) Peek at the anchor mix The current probabilities live on the fitted object. A simple sort gives a small dashboard for the latest state. ```{r} sort(fit$current$probabilities, decreasing = TRUE) ``` The anchor names are deliberately plain. If `regression_forecast` is high, the recent direction is doing work. If `median` or inner quantiles dominate, the series is behaving more like a stable level. If extremes get weight, the model is seeing a state where boundary anchors helped historically. ## 3) Forecast paths, not just a line `predict()` recursively simulates a predictive mixture. The summary gives means, medians, standard deviations, and requested quantiles by horizon. ```{r} pred <- predict( fit, horizon = 20, nsim = 3000, seed = 123 ) head(pred$summary) ``` Forecasts should come with distribution handles, so the prediction object also returns four small functions: ```{r} pred$dfun(100, h = 1) pred$pfun(100, h = 1) pred$qfun(c(0.05, 0.50, 0.95), h = 1) pred$rfun(5, h = 1) ``` Calling `rfun()` without `h` returns coherent recursive paths rather than independent marginal draws: ```{r} paths <- pred$rfun(100) dim(paths) ``` That is the difference between "twenty separate one-step stories" and "one hundred possible futures that know their own history." ## 4) Try fixed mode versus search mode The interface has one main rule: **scalar = fixed; vector = search.** For a fast fixed run, give scalar values throughout: ```{r} fixed <- rollcast( y, window = 60, tau = 0.25, lambda = 0.01, conditional_k = 40, state_bw = 1, residual_bw = 0.35, error_scale = 0.25, residual_smoothing = 0.03, rho_min = 0.05, rho_max = 0.90, rho_decay = 1 ) ``` To let the model choose among a few sensible options, pass vectors: ```{r} tuned <- rollcast( y, window = c(40, 60, 90), tau = c(0.15, 0.25, 0.40), lambda = c(0.001, 0.01, 0.05), conditional_k = 40, state_bw = 1, residual_bw = c(0.20, 0.35, 0.55), error_scale = c(0, 0.25, 0.50), residual_smoothing = 0.03, rho_min = 0.05, rho_max = 0.90, rho_decay = 1, verbose = TRUE ) tuned$hyperparameter_search ``` The search is a cached coordinate search on common causal validation origins, not a full Cartesian grid. Translation: it tries to be useful without turning your check run into a long lunch. ## 5) Residuals: pure anchors or local texture Set `error_scale = 0` for a pure weighted-anchor forecast: ```{r} anchor_only <- rollcast( y, window = 60, tau = 0.25, lambda = 0.01, conditional_k = 40, state_bw = 1, residual_bw = 0.35, error_scale = 0, residual_smoothing = 0.03, rho_min = 0.05, rho_max = 0.90, rho_decay = 1 ) ``` Use `error_scale` between zero and one when the anchors should keep their location role but local historical residuals should add shape around them. Zero is crisp. One is full residual correction. The middle is often where the forecast behaves like it has both a steering wheel and suspension. ## 6) Minimal plots, tiny but telling The default plot is a compact diagnostic dashboard: ```{r} plot(pred) ``` Use the fan-only view when all you need is the forecast envelope: ```{r} plot(pred, type = "fan") ``` The diagnostic view combines four checks: 1. predictive fan; 2. absolute uncertainty width and transition horizon; 3. adaptive gating persistence; 4. median forecast drift from the last observation. The transition calculation fits the simple segmented shape \[ W_h = a + b_1 h + \Delta b (h-h^*)_+, \] where \(W_h\) is a predictive interval width and \(h^*\) is the horizon where the rate of uncertainty expansion changes. ## Conclusion `rollcast` is a small forecasting control room. Rolling anchors suggest where the next value might land, the gate decides who deserves attention, residuals add local texture when requested, and recursive simulation turns the result into full predictive paths. Fit, inspect, simulate, plot. Then let the distribution do the talking. Enzoi!