Validation guide
This guide explains how to choose and configure validation strategies in
meridian-tools. Validation is the process of evaluating a candidate model
specification on held-out data before committing to a final production fit.
Why validation matters for MMM
Marketing Mix Models are fitted to time series data. Unlike standard supervised
learning, the temporal structure of the data means that naive IID
cross-validation (random train/test splits) is statistically inappropriate.
meridian-tools does not implement random shuffling or naive k-fold splits.
Instead, it provides two time-respecting validation strategies and a clear
separation between validation runs and the final production fit.
Validation strategies
none — No validation
The model is fitted on the full dataset with no holdout. Use this when you do not need candidate evaluation — for example, when rerunning a previously validated specification.
blocked_tail — Single contiguous tail holdout
Reserves the last holdout_size time periods as a test block. The model is
fitted on all preceding periods. This is the recommended default for short
MMM time series where you want one simple candidate evaluation.
When to use: Most standard MMM projects with fewer than 150 weekly observations.
How it works:
The holdout mask is generated automatically and injected into Meridian’s
holdout_id parameter. For geo-panel models, the mask is broadcast across
all geos.
rolling_origin — Expanding-window validation
Creates multiple expanding-window splits where each successive split adds more training data. This provides a more robust evaluation signal than a single blocked tail, but requires enough history to support multiple splits.
When to use: Projects with longer time series (typically 100+ weekly observations) where you want multiple evaluation windows.
How it works:
Constraints:
step_sizemust equaltest_size(non-overlapping test windows).max_splitsmust be at least 2.initial_train_size + test_sizemust not exceed the number of observations.- The plan must yield at least two splits.
authored_holdout — User-provided holdout mask
This is not a YAML strategy setting. Instead, you provide holdout_id
directly in model_spec.kwargs:
When the runner detects an authored holdout_id in the YAML, it treats the
run as an authored_holdout validation run. The mask is passed through to
Meridian verbatim and recorded in the validation spec artefact.
When to use: When you need a specific holdout pattern that does not follow blocked-tail or rolling-origin conventions.
CLI vs Python API
Blocked tail from the CLI
blocked_tail runs directly from the CLI because they produce one run:
Rolling origin requires the Python API
rolling_origin is a Python-first planning surface because it produces
multiple runs — one per split plus a final fit. The CLI will reject direct
rolling_origin execution:
Instead, use the Python API:
Separating validation from the final fit
Validation runs and the final production fit are different jobs. First you evaluate candidate specifications on held-out splits. Then, once you have chosen the specification, you run a separate full-sample fit with no holdout.
Do not reuse a validation fit as the production artefact. The validation fit was trained on a subset of the data and its posterior reflects that subset.
Final fit after blocked tail
For blocked_tail, build_validation_plan provides a final_fit_run spec:
Final fit after rolling origin
The same pattern works for rolling origin:
The final_fit_run spec has mode="final_fit", strategy="none", and
holdout_id=None. It trains on the full time axis with no holdout.
Run directory naming
The runner automatically appends a validation-aware suffix to the run name:
| Scenario | Run name pattern |
|---|---|
| No validation | <project_name>_<timestamp> |
| Blocked tail | <project_name>_blocked_tail_<timestamp> |
| Rolling origin split 1 | <project_name>_split_01_<timestamp> |
| Final fit | <project_name>_final_fit_<timestamp> |
| Authored holdout | <project_name>_authored_holdout_<timestamp> |
Override the name with --run-name or PipelineRunConfig(run_name=...).
Validation spec artefact
Every validation-aware run writes a validation_spec.json artefact in the
10_validation/ stage directory. This JSON records:
mode—"validation"or"final_fit"strategy— the validation strategy usedsplit_label— human-readable split identifierholdout_source—"generated_validation","authored_model_spec", or"none"generated_holdout— whether the holdout mask was auto-generatedholdout_shape— shape of the holdout mask (without the actual data)train_indices/test_indices— integer indices into the time axistrain_dates/test_dates— corresponding date values
The actual holdout mask is not stored in the JSON artefact (it can be large). It is injected into the model at runtime.
Interaction with model selection
Bayesian model selection (LOO/WAIC) is only available for runs where
holdout_id is None — meaning full-sample fitted models and final-fit runs.
Validation fits and authored-holdout runs write a
model_selection_status.json artefact instead of LOO/WAIC outputs. See the
model selection guide for details.