Baseline forecasting models for the VIEWS pipeline.
This package provides simple, transparent baseline models that can be used for benchmarking more complex forecasting models and sanity checks. The baselines are intentionally minimal and deterministic (or reproducibly stochastic for distributional models).
The package implements several common baseline strategies for panel time-series data with a multi-index of:
- time (e.g.
month_id) - entity (e.g.
priogrid_id,country_id)
All models follow a common interface:
model.fit(df) # df: a pandas DataFrame OR a views_frames FeatureFrame
predictions = model.predict(df, sequence_number)fit() / predict() accept either a pandas DataFrame or a views_frames.FeatureFrame (ADR-019): the input is normalized to a FeatureFrame once at the boundary (to_feature_frame in model/frames/input.py, the only place pandas is read), and all windowing/aggregation runs on its numpy arrays. Models are fitted automatically via fit() before generating predictions. Fitted model artifacts are pickled for ensemble compatibility.
All baselines operate on panel data indexed by (time t, unit i) — month_id × spatial unit (priogrid_id at pgm, country_id at cm) — and forecast one or more target columns over a horizon of output_length steps starting at test_start + sequence_number. ("unit" is the term used in a PredictionFrame's SpatioTemporalIndex; the input index calls the same axis the entity.)
Causal split (no leakage). Every model is fit using only observations strictly before the test period. The last training month is train_end = test_start − 1; no value at or after test_start enters any fitted quantity. (The point models and MixtureBaseline filter t < test_start; ConflictologyModel filters t ≤ train_end — the same boundary, written two ways.)
Two output families:
- Point forecasts — one deterministic value per (unit, time, target). Returned as
dict[str, PredictionFrame]withy_predof shape(N, 1). - Distributional forecasts —
n_samplesMonte-Carlo draws per (unit, time, target). Returned asdict[str, PredictionFrame]withy_predof shape(N, n_samples).
In all cases N = (number of units) × output_length, and each PredictionFrame carries a SpatioTemporalIndex with the time, unit, and spatial level (CM/PGM) of every row. All frames are built through a single construction seam, to_prediction_frames in model/frames/output.py (ADR-020), and the level is derived from the declared loa and validated against the input index (ADR-003).
Predicts exactly 0 for every target, unit, and forecast step. The lower-bound reference; performs no fitting.
ZeroModel(targets, partition_dict, loa)For each unit and target, carries the last observed value at train_end forward unchanged across the entire horizon. A persistence baseline ("the most recent observation is the best guess"), strong for highly autocorrelated targets.
LocfModel(targets, partition_dict, loa)For each unit and target, forecasts the arithmetic mean of that unit's last window_months observations before test_start, held constant across the horizon. A smoothed-persistence baseline, more robust than LOCF when individual months are noisy.
AverageModel(targets, window_months, partition_dict, loa)- Means are computed per unit; units with no history before
test_startare skipped (logged).
Both draw n_samples i.i.d. samples per cell from a fresh, seeded generator (numpy.random.default_rng(seed)), so a given (data, configuration, seed) reproduces bit-for-bit. The RNG is consumed in a fixed unit → time → target order to guarantee reproducibility.
For each unit i, collects that unit's last window_months observed values up to train_end, then draws n_samples samples with replacement from that per-unit history for every forecast cell. The predictive distribution for a cell is the recent empirical distribution of that same unit — a conflict "climatology." It uses only the unit's own recent history: no pooling across units, no older history.
ConflictologyModel(targets, window_months, partition_dict, loa, n_samples, seed=42)Combines two empirical sources to avoid the zero-probability trap (a unit whose recent history is entirely zero being structurally unable to predict a nonzero outcome):
- Local pool — the unit's last
window_monthsobserved values (as inConflictologyModel). - Global pool — all strictly-positive observed values, pooled across every unit and the entire training span.
Each of the n_samples draws is taken from the global pool with probability lambda_mix, otherwise from the local pool (probability 1 − lambda_mix). At lambda_mix = 0 it reduces to a local-only empirical baseline (same source as ConflictologyModel); larger lambda_mix injects more cross-unit, full-history positive mass.
MixtureBaseline(targets, window_months, lambda_mix, n_samples, partition_dict, loa, seed=42)The parametric counterpart of ConflictologyModel: instead of resampling each unit's window empirically, it fits a single native-zero distribution (family, e.g. nb) to that same window and draws n_samples per cell from the fitted law. family, transform, and seed are required, audited genome keys (ADR-021/ADR-022); transform="log1p" is illegal for count families and fails loud. In the closeness study (see reports/closeness_experiment/), nb is the family closest to conflictology on the C2ST indistinguishability metric.
ParametricConflictology(targets, window_months, partition_dict, loa, n_samples, family, transform="none", seed=42)A two-part law per unit: a zero-spike (empirical zero-rate, Bernoulli) plus a continuous positive-part family (lognormal/gumbel/gamma) fit to the positive window values (mirrors Vesco et al. 2026's RVI mixture). transform (none/log1p) applies to the positive part and is inverted per sample; a non-negativity floor (EMIT_FLOOR) guarantees no negative magnitudes even for gumbel. Closeness study: gamma/none is closest on magnitude fidelity (Wasserstein/energy); log1p is worse on active cells. (Tweedie was evaluated and excluded — ADR-022.)
ParametricHurdleConflictology(targets, window_months, partition_dict, loa, n_samples, family, transform="none", seed=42)The BaselineModelCatalog provides a factory for instantiating models based on config:
from views_baseline.model.catalog import BaselineModelCatalog
catalog = BaselineModelCatalog(config, partition_dict, loa)
model = catalog.get_model("LocfModel")Available models:
catalog.list_models()
# ['ZeroModel', 'LocfModel', 'AverageModel', 'ConflictologyModel', 'MixtureBaseline',
# 'ParametricConflictology', 'ParametricHurdleConflictology']The package integrates with the core pipeline via:
BaselineForecastingModelManagerKey characteristics:
- Models are fitted automatically via
fit()and artifacts are pickled - Predictions are generated per evaluation sequence
- Supports both evaluation and forecasting modes
- Distributional models are dispatched automatically via the
DistributionalBaselineModelprotocol
Input DataFrame:
- Must be indexed by
(time, entity)as a MultiIndex - Must contain target columns specified in
config['regression_targets'](thetargetskey was retired upstream in views-pipeline-core #380 and is now rejected — do not add it back)
Example index:
MultiIndex(levels=[month_id, priogrid_id])
- Entities without sufficient history are skipped (with a warning)
- No imputation beyond what the baseline logic implies
- No clipping or post-processing is applied by default
Clone the repository and install with pip:
pip install -e .Internal VIEWS package. Intended for research and forecasting pipelines, not as a general-purpose forecasting library.