LSHTM
2026-09-26
Theory 1 Evaluating forecasts
Data 1 Hubs, set up to evaluate
Theory 2 Handling confounding
Data 2 Hubs are sparse
Theory 3 Use a regression
Data 3 Results
Theory 4 Conceptual limitations
Data 4 Sensitivity
Original title:
The influence of model structure and geographic specificity on predictive accuracy
Now:
Interpreting variation in infectious disease forecast performance with model-based evaluation
Theory 1 · Evaluating forecasts
Every score is a prediction meeting a target, with variation on both sides:
Data 1 · Hubs, set up to evaluate
48 models, 32 countries, 2 outcomes, 207,973 forecasts, scored with the weighted interval score on log counts per 100,000 (LWIS).

Relative WIS across models against a persistence baseline, 2 weeks ahead, 2021-2022. * = ensemble. Sherratt et al. 2023, eLife.
Data 1 · Hubs, set up to evaluate
Crude performance shows one consistent thing: the target and the horizon matter. Deaths score better than cases on the log scale, and every outcome gets worse further ahead.
Theory 2 · Handling confounding
| Threat to validity | Approach | Example in forecast evaluation | Limitation |
|---|---|---|---|
| None | Unadjusted comparison | Rank models over all forecasts as submitted | Mixes up the method with the difficulty of the targets each model chose |
| Confounding, selection | Inclusion criteria | Exclude forecasts without comparable uncertainty | Loses power; narrows the population |
| Confounding | Matching | Compare forecasters only on targets both submitted | Sparse overlap; holds only for the matched set |
| Confounding | Stratification | Scores by horizon, location or epidemic phase | Cells sparse with several factors; continuous covariates need binning |
| Differential population sizes | Indirect standardisation | Relative skill against a baseline available for all targets | Depends on the reference, which changes rankings |
| Confounding, clustering | Regression adjustment | Partial effect of structure, adjusting for the target | Specification-dependent; estimand informal |
Rows run from no control to full adjustment, each borrowed from observational study design.
Data 2 · Hubs are sparse
partT = transpose(participation)
Plot.plot({
width: 1000, height: 340, marginLeft: 40,
x: {label: "Week, March 2021 to March 2023", ticks: []},
y: {label: null},
color: {type: "sequential", scheme: "viridis", label: "Models forecasting 1 week ahead", legend: true},
marks: [
Plot.cell(partT.filter(d => d.Method === pMethod && d.epi_target === pOutcome),
{x: "forecast_date", y: "Location", fill: "n", inset: 0, tip: true})
]
})One week ahead shown. Every included model forecast all four horizons, so the other horizons look the same. Median participation: 3% of available targets per model.
Data 2 · Hubs are sparse
sparsityT = transpose(sparsity)
sRow = sparsityT.find(d => d.key === [...strataBy].sort().join("|"))
html`<div class="stats">
<div><span>${sRow.possible.toLocaleString()}</span>possible strata</div>
<div><span>${sRow.filled}%</span>contain any forecast</div>
<div><span>${sRow.median_n.toLocaleString()}</span>median forecasts per filled stratum</div>
<div><span>${sRow.pct_thin}%</span>of filled strata hold fewer than 5</div>
</div>`Untick covariates to see how fast the sample thins. Continuous covariates (incidence, horizon) would need binning on top.
Theory 3 · Use a regression
Generalised additive mixed model of each forecast’s LWIS (Tweedie, log link). Effects are ratios against the average score: 0.8 is 20% better than average.
| Target-generating | Enters as |
|---|---|
| Outcome (cases, deaths) | Fixed effect |
| Epidemic trend | Random intercept |
| Variant phase | Random intercept |
| Country | Random intercept |
| Incidence level | Smooth |
| Forecast-generating | Enters as |
|---|---|
| Structure × outcome | Random intercept per cell |
| Single or multi-country | Random intercept |
| Horizon | Smooth per model |
| Individual model | Random intercept |
Random effects shrink sparse groups towards the average. With 48 models, the effective sample for structure is 48, not 207,973.
Data 3 · Results
Unadjusted, judgement looks 26% better than average. Adjusted, every structure sits between 0.98 and 1.05, all intervals spanning 1.
Data 3 · Results

We noted that differences between outcomes largely offset when averaged. An averaged estimate therefore gives close to zero for most structures, whether or not outcome-specific differences exist.
Agent-based, the widest split, rests on 3 models for cases and 2 for deaths. A Gaussian refit reverses the direction for two of five structures.
Data 3 · Results
Adjusted performance ratio (95% CI) against the average LWIS, for every categorical term except the 48 individual models. Highlighted: epidemic trend and variant phase. Deaths vs cases is a fixed contrast, ratio 0.35, not shown.
Data 3 · Results
ranksT = transpose(ranks)
ranksLong = ranksT.flatMap(d => [
{...d, stage: "Unadjusted", rank: d.rank_unadjusted},
{...d, stage: "Adjusted", rank: d.rank_adjusted}
])
Plot.plot({
width: 520, height: 340,
x: {type: "point", domain: ["Unadjusted", "Adjusted"], label: null, inset: 40},
y: {reverse: true, label: "Rank (1 = best)", domain: [1, ranksT.length]},
color: {domain: methodLevels, range: methodColours},
marks: [
Plot.line(ranksLong, {x: "stage", y: "rank", z: "label", stroke: "classification",
strokeWidth: 2,
strokeOpacity: d => rankHighlight === "All structures" || d.classification === rankHighlight ? 0.9 : 0.08}),
Plot.dot(ranksLong, {x: "stage", y: "rank", fill: "classification", r: 4,
fillOpacity: d => rankHighlight === "All structures" || d.classification === rankHighlight ? 1 : 0.1}),
Plot.tip(ranksLong, Plot.pointer({x: "stage", y: "rank",
title: d => `${d.label}\n${d.rank_unadjusted} → ${d.rank_adjusted}`}))
]
})
Spearman 0.44. 23 of 48 models move at least 10 places; the furthest moves 37.
Theory 4 · Conceptual limitations
Five labels from optional metadata. Raters disagreed on 35%, mostly semi-mechanistic. Methods changed over time and we did not track it.
Impact: misclassification pulls any structure effect towards the null.
48 models is the effective sample; judgement and agent-based are 6.
Impact: wide intervals for the rare structures; no claim beyond this Hub.
Structure is a property of a model. Horizon is also fitted per model, so it can absorb structure × horizon.
Impact: we estimate a direct effect of structure, after the model’s own quirks.
Team capacity, experience and time plausibly shape both the choice of structure and the score. We scored against data as of March 2023, not what forecasters saw.
Impact: residual confounding; the estimand stays exploratory.
Theory 4 · Conceptual limitations
To answer “does model structure matter?” from a Hub, we would need:
Hubs are built to produce a good ensemble, but not to explain individual performance.
Data 4 · Sensitivity
| Check | Structure effect |
|---|---|
| Natural-scale WIS instead of log | No material change |
| Raw counts instead of per 100,000 | Near-zero impact on scores or fit |
| Covariate sets (with and without variant phase, outcome) | No material change |
| Link function | No material change |
| Gaussian or Gamma instead of Tweedie | By-outcome estimates shift; under Gaussian every interval still spans 1 |
No specification made any model structure clearly different from the average.
Theory 1 A score comes from a forecast and a target; unadjusted comparisons mix the two
Data 1 Crude scores mostly track the target and horizon
Theory 2 Designs run from unadjusted to formal; choose by the question
Data 2 Teams pick targets; full stratification leaves 30% of strata filled
Theory 3 Regression uses every forecast, with target and forecaster terms side by side
Data 3 No structure beats average; trend and variant shift scores about 20%; ranks move (ρ 0.44)
Theory 4 Hubs lack the metadata, sampling and estimand this question needs
Data 4 Nothing we tried changed the answer
Built in Quarto from modular section files (report/quarto/_*.qmd), with every number computed from saved fits. This deck reuses the same code.