
Rolling-Window Forecast Validation for the MOSAIC Transmission Model
Source:R/run_rolling_cv.R
run_rolling_cv.RdRuns an expanding-window (fixed-anchor) rolling-origin backtest of the MOSAIC
transmission model. For each cutoff date T it (1) trains the
environmental-suitability (psi) LSTM on data up to T, (2) injects that
psi into the config, (3) calibrates the transmission model on observed cases
up to T, and (4) projects forward over the out-of-sample (OOS) window.
Usage
run_rolling_cv(
PATHS,
iso,
n_cutoffs = 12L,
latest_cutoff = NULL,
step_months = 1L,
horizons_months = c(1, 3, 5),
embargo_weeks = 1L,
base_config = MOSAIC::config_default,
priors = MOSAIC::priors_default,
control = NULL,
optimize_subset = TRUE,
models = c("ensemble", "ensemble_opt", "medoid"),
n_reps_best_medoid = 50L,
central_method = c(cases = "median", deaths = "mean"),
est_suitability_spec = list(),
psi_cache = NULL,
dir_output,
verbose = TRUE
)Arguments
- PATHS
Path list from
get_paths.- iso
Character; one ISO3 code or a vector (coupled metapopulation).
- n_cutoffs
Integer; number of monthly cutoffs (default 12).
- latest_cutoff
Date/character or NULL; most-recent cutoff. If NULL, computed as
(last scorable observed date) - embargo - max(horizons).- step_months
Integer; months between cutoffs (default 1).
- horizons_months
Numeric vector of forecast horizons in months (default
c(1,3,5)); used to set the projection length and to label OOS points. The largest horizon bounds the latest cutoff.- embargo_weeks
Integer; gap between IS stop (T) and OOS start (default 1).
- base_config
MOSAIC config (default
MOSAIC::config_default); its window defines the anchor and projection span.- priors
Priors list (default
MOSAIC::priors_default).- control
run_MOSAICcontrol list, or NULL for an experiment-grade cheap default (fixedn_simulations, plots off). Seemosaic_control_defaults. The harness forcespaths$clean_output = TRUE, so each cutoff'sruns/cutoff_<T>/directory is wiped before its calibration and simulation shards left by an earlier interrupted attempt cannot enter the new posterior.- optimize_subset
Logical (default
TRUE); enablerun_MOSAIC's post-ensemble best-subset optimizer (control$predictions$optimize_subset). WhenTRUEthe harness sets this on the resolved control for every cutoff, so the ensemble is re-scored against the training-window observed series and the posterior is driven by the optimizer-selected subset. SetFALSEto use the raw candidate ensemble.- models
Character vector of model types to score and carry in
predictions.parquet(defaultc("ensemble","ensemble_opt","medoid"))."ensemble"(posterior-weighted candidate) is always included."ensemble_opt"is the optimizer-selected subset, emitted only for cutoffs where the optimizer actually ran and selected a subset (subset_opt.rdspresent, or a finiten_ensemble_params_tierinsummary.json); withoptimize_subset = FALSEit is dropped frommodelsup front, and a cutoff whose optimizer selected nothing is skipped with a warning rather than duplicating the candidate ensemble, whichrun_MOSAIC()saves as a fallbackensemble_optimized.rds."medoid"is re-simulated from its saved config (seen_reps_best_medoid)."best"is accepted for back-compat but is no longer produced byrun_MOSAIC()(noconfig_best.json); it is skipped with a warning unless an older run dir still carries that file. Each model appears as a value of themodelcolumn.- n_reps_best_medoid
Integer (default 50); number of stochastic reruns used to build the predictive median + intervals for the
bestandmedoidconfigs. They run throughcalc_model_ensemble()with the cutoff run's observation model (the weekly cases dispersion its candidate ensemble recorded) and deaths integration (2_calibration/deaths_integration.rds), seeded 1001, 1002, ... asrun_MOSAIC()seeds its medoid ensemble. These reruns execute locally in the calling R process, so cost scales with this value times the number of cutoffs and locations.- central_method
Ensemble central tendency used for the compiled predictions and the in-sample calibration metrics/medoid:
"mean"(the expected count, which never collapses to zero on sparse deaths) or"median"(the typical trajectory). Scalar or per-channelc(cases=, deaths=); defaultc(cases = "median", deaths = "mean")(both mean from v0.98.0 to v0.100.x, both median from v0.46.1 to v0.97.x). The predictions table carriespred_central(this choice) pluspred_mean/pred_medianfor cross-walk; WIS/coverage remain quantile-based and are unaffected.- est_suitability_spec
Named list of modeling arguments passed through to
est_suitability(e.g.architecture,feature_set,response_var,bias_correct, and the lstm_v2arch_controllist). Date arguments are ignored (harness-owned). Deprecated v0.33 keys (n_splits,exclude_covariates) are accepted but ignored with a per-cutoff deprecation message – preferarch_controlfor lstm_v2 knobs. Whenpsi_cacheis supplied this spec is used only to recompute the cache spec-hash for validation; the per-cutoffest_suitability()fit is skipped entirely.- psi_cache
NULL (default) or a directory produced by
prefit_rolling_cv_psi. When NULL the per-cutoff psi is re-fit in-place (original behavior). When set, the per-cutoffest_suitability()call is skipped and the frozenpsi_<T>.csvis loaded from this cache directory instead. The run hard-errors if a requested cutoff is absent from the cache manifest, if the run'sest_suitability_spechash does not match the manifestspec_hashrecorded for that cutoff, or if the cache's prediction window does not coverbase_config's start through the cutoff's last OOS date. Whenpsi_cacheis NULL the per-cutoff psi is fitted into a scratchMODEL_INPUT(the canonicalpred_psi_suitability_day.csvis never overwritten) and kept asruns/psi_cutoff_<T>.csv.- dir_output
Directory for the experiment artifact (created if needed).
- verbose
Logical (default TRUE).
Value
Invisibly, the manifest list. Side effects: writes
manifest.json, predictions.parquet, and runs/ under
dir_output. manifest.json is rewritten atomically after every
cutoff (status = "running", then "complete"), so the cutoffs
finished before an interruption can still be compiled with
compile_rolling_cv_predictions.
Details
This function is a fit-and-forecast engine only: it produces
calibrations, projections, and one organized predictions artifact. It does
not compute evaluation metrics, baselines, or skill scores – those are
done post-hoc by reading predictions.parquet.
Window. The in-sample (IS) start is fixed at config$date_start
(the anchor); the simulation runs over the full config window
(config$date_start .. config$date_stop). For each cutoff T
the calibration likelihood only scores weeks \(\le T\) (observations after
T are masked to NA); the post-T portion of the simulation
is the forecast. A embargo_weeks gap separates the IS stop (T)
from the OOS start: dates in (T, T + embargo] are labeled
"embargo" (neither trained nor scored) and the first OOS date is
T + embargo + 1. weeks_ahead counts whole weeks from that first
OOS date (week 1 = its first seven days).
Leakage discipline. What is rebuilt as of each cutoff:
psi is re-fit per cutoff with
fit_date_stop = T; the harness owns the leakage-critical date arguments toest_suitabilityand overrides any date keys passed viaest_suitability_spec(with a warning), so the spec controls only modeling choices (target, features, architecture).The reported CFR
mu_jtand its prior are rebuilt from a WHO-annual GAM fitted only to years up toyear(T) - 1, carried flat past that year's 1 July. The annual totals used are the final revised ones, not the vintage that had been published atT.Observed cases and deaths after
Tare masked, and epidemic peaks whose 14-day peak-shape scoring window reaches pastTare dropped from the cutoff config (an empty set is kept as a 0-row table so the likelihood never falls back to the fullepidemic_peaksdataset). The remaining peaks were still detected on the full record.
What is not as of the cutoff, so the hindcast is only approximately leak-free:
psi fitted in place (
psi_cache = NULL), or from a cache built with any feature set other than"v7.4", is trained on the canonical suitability panel. Its per-country target anchors and its flood-probability GAM were fitted on the whole panel, including rows afterT. The run warns when this is the case; build the cache withprefit_rolling_cv_psi(est_suitability_spec = list(feature_set = "v7.4", ...))for per-cutoff leak-free panels.Every prior other than
mu_jtcomes frompriorsunchanged.priors_defaultcarries centres derived from surveillance through its build date (e.g. the per-countrybeta_j0_totcentres, recentred on posterior medians of fits to the full record); supply as-of priors for a strictly leak-free hindcast.
Coupled metapopulation. iso may be a single country or a vector;
a vector runs as the coupled metapopulation (one calibration per cutoff covering
all listed locations). Thus there is one run_MOSAIC calibration per cutoff
(not per country).
Outputs. Under dir_output: manifest.json (settings +
per-run index with status), predictions.parquet (the compiled long
table), and runs/cutoff_<T>/ (the native run_MOSAIC directory for
each cutoff). predictions.parquet is a derived view – it can be rebuilt
from the run directories with compile_rolling_cv_predictions.
The predictions table has one row per (cutoff x location x date x metric) with
columns: run_id, iso_code, anchor_date, cutoff_date, date, metric,
segment (IS/embargo/OOS), weeks_ahead, horizon_bucket, observed,
observed_source, pred_central (the scored series), pred_mean,
pred_median, central_method, CI columns (pi*_lo/pi*_hi) and
pred_median_obs, the median of the same draws as the CI columns (the
observation-level predictive median since v0.101.0), which WIS pairs with them.
Every model's rows carry the cutoff run's interval semantics: the
medoid (and legacy best) configs are re-simulated through
calc_model_ensemble with the run's observation model and deaths
integration, as its own ensembles were built.
observed is the held-out (unmasked) trusted surveillance value, so OOS
rows carry the real target for post-hoc scoring.