Skip to content

Model structure by epidemiological outcome (#158), DAG update (#162) - #167

Open
kathsherratt wants to merge 6 commits into
model-error-familyfrom
method-target-interaction
Open

Model structure by epidemiological outcome (#158), DAG update (#162)#167
kathsherratt wants to merge 6 commits into
model-error-familyfrom
method-target-interaction

Conversation

@kathsherratt

Copy link
Copy Markdown
Contributor

Draft. Stacked on #166, which is stacked on #164 — review those first, and this diff will shrink once they merge.

Closes #158 and #162.

What changed

The model assumed each structure predicted cases and deaths equally well. Replaced s(Method, bs = "re") with s(Method, Epi_target, bs = "re"), so a structure may predict one outcome relatively better than the other.

There is deliberately no structure main effect alongside it. mgcv's bs = "re" interaction is an unconstrained zero-mean prior over all cells, so its average across outcomes is exactly what a main effect represents. With both terms penalised, the split between them follows the relative variance estimates rather than the data — and fitted together, mgcv gave the main effect 0.001 effective degrees of freedom against 4.9 for the crossed term. Dropping it changed AIC, deviance explained and residuals by nothing on either scale.

The pooled per-structure effect is instead recovered as a contrast averaging a structure's two cells, evaluated against the full covariance matrix.

Why this matters beyond answering the issue

The old main effect was shrunk flat, so Table 2 was reporting a term carrying no information:

Structure before after
Agent-based 1.000 (0.994–1.007) 0.982 (0.881–1.094)
Judgement 1.000 (0.994–1.007) 0.977 (0.878–1.088)
Mechanistic 1.000 (0.994–1.007) 1.001 (0.912–1.100)
Semi-mechanistic 1.000 (0.994–1.008) 1.046 (0.946–1.155)
Statistical 1.000 (0.994–1.007) 0.995 (0.904–1.095)

Adding the crossed term improves AIC by 93 on the log scale and by 16,724 on the natural scale, where it also cuts residual skew from 4.60 to 4.38.

Findings

Human judgement models perform relatively better on cases than deaths (0.96 against 0.99), the same direction as Bosse et al. (2022). The widest separation is among agent-based models (cases 1.09, deaths 0.88) — from three models, flagged as such in text. Every interval spans the grand mean.

The direction of these contrasts is stable across error families; the magnitude is not. Under a Gaussian family the judgement separation widens and the agent-based separation disappears, because a Gaussian fit is dominated by the largest scores, which fall disproportionately among case forecasts. Recorded as a supplementary sensitivity with a pointer from the main text. Including or excluding the Hub baseline makes no material difference.

Required detour: replacing the effect extraction

gammit::extract_ranef() cannot handle a factor-by-factor random effect. It reads only the last variable name of the interaction and looks up that factor's levels, so it collects 5 labels for a 10-coefficient term and fails — taking down extraction for every term in the fit, not just the interaction. #158 could not be delivered without replacing this layer.

New R/utils-effects.R rebuilds each smooth's design matrix from the formula mgcv stores on the smooth object ($form), so labels map to coefficients exactly without assuming how mgcv orders interactions. Validated against gammit on a no-interaction fit: identical on every column to the last decimal, 48 rows. A stopifnot guards the mapping in case mgcv's construction changes.

DAG (#162)

The epidemiological outcome is added as a confounder, not merely a covariate: forecasters chose which outcomes to submit for, and that choice is associated with structure, opening a backdoor through modeller strategy.

This earned more than expected. Querying the updated diagram returns our exact covariate set as a minimal sufficient adjustment set for the direct effect, and returns no valid set for the total effect, because latent modeller strategy cannot be blocked. That turns the paper's partial-effect framing from an assertion into a derivation. The crossed term is effect modification, which a DAG does not encode, so it does not alter the adjustment set.

Prose

Corrected the discussion claim that adjusted estimates "were no different from the overall average" — that described the collapsed term; they are imprecise, not identical to the mean.

Added a discussion paragraph identifying this interaction as the one specification choice that materially changed the results, against an approach otherwise robust to covariate selection, link function and error family.

Review notes

  • Model outputs under output/ are regenerated; both scales refit with no convergence warnings.
  • output/diagnostics/fit-summary.csv keeps the both-terms fit alongside the final one, so the 0.001-edf claim is checkable.
  • Site renders clean, both pages.
  • Still open, unrelated: R/sensitivity/check-autocorrelation.R sources the deleted model-logresp.R and does not run.

🤖 Generated with Claude Code

kathsherratt and others added 4 commits July 28, 2026 17:21
Code and prose only. Model outputs under output/ are deliberately not
included: a refit is still running and will regenerate them, so they are
left for the follow-up commit once it lands.

Specification: replace s(Method) with s(Method, Epi_target). mgcv's
bs = "re" interaction is an unconstrained zero-mean prior over all cells,
so its target-average is exactly what a main effect represents; fitted
together mgcv gave the main effect 0.001 edf against 4.9 for the
interaction. Dropping it removes the aliasing. The pooled per-structure
effect is recovered as a contrast across cells.

New R/utils-effects.R replaces gammit::extract_ranef(), which cannot
handle a factor-by-factor random effect: it reads only the last variable
name of the interaction, collects 5 labels for a 10-coefficient term and
fails, taking down extraction for every term in the fit. The replacement
rebuilds each smooth's design matrix from the formula mgcv stores on it,
so labels map to coefficients exactly. Validated against gammit on a
no-interaction fit: identical to the last decimal on all columns.

DAG (#162): add the epidemiological outcome as a confounder, not merely a
covariate -- forecasters chose which outcomes to submit for, opening a
backdoor through modeller strategy. Querying the updated diagram returns
our exact covariate set as a minimal sufficient adjustment set for the
DIRECT effect, and returns no valid set for the total effect, since
latent modeller strategy cannot be blocked. This formally supports the
paper's partial-effect framing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Completes the specification change committed as WIP: regenerated model
outputs for both scales, and the reporting layer for the new term.

Dropping the aliased s(Method) main effect changed the fit by nothing --
identical AIC, deviance explained and residuals on both scales,
confirming it carried no information. It changed materially what can be
reported. The old main effect was shrunk flat to 1.000 (0.994-1.007) for
every structure; the pooled contrast across cells gives real estimates
with honest intervals, from 0.977 (0.878-1.088) for judgement models to
1.046 (0.946-1.155) for semi-mechanistic. Table 2 therefore now reports a
quantity that carries information.

Adding the crossed term improves AIC by 93 on the log scale and by 16,724
on the natural scale, where it also cuts residual skew from 4.60 to 4.38.

New reporting: plot_method_target() and print_table_method_target() for
the per-outcome effects, added to the results as Figure 4 and Table 3,
with the raw per-cell effects added to the supplement.

Findings are reported conservatively. Human judgement models perform
relatively better on cases than deaths (0.96 against 0.99), the same
direction as Bosse et al. (2022). The widest separation is among
agent-based models (cases 1.09, deaths 0.88), which rests on three models
and is flagged as such in text; every interval spans the grand mean. The
direction of these contrasts is stable across error families but the
magnitude is not -- under a Gaussian family the judgement separation is
larger and the agent-based separation disappears -- so that is recorded
as a supplementary sensitivity.

Uses a plain chunk label for the new table rather than a tbl- prefix: a
tbl- label combined with a kableExtra caption left an unresolved
cross-reference and leaked the div as literal text. This matches the
convention the existing tables already use.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Discussion: the claim that adjusted estimates "were no different from
the overall average" described a term since shown to be shrunk to
nothing. They are imprecise rather than identical to the mean, so the
wording now says so, and covers both the pooled and per-outcome
estimates. Checked for consistency against the abstract and results,
which state the conclusion in terms that remain accurate.

Adds a discussion paragraph identifying the structure-by-outcome
interaction as the single specification choice that materially changed
the results. A model-based evaluation of this kind is highly flexible,
and the substantive conclusion proved robust to covariate selection,
link function and error family; assuming a shared effect across cases
and deaths was the one assumption that was not neutral. Draws out the
implication for other evaluations pooling across distinct targets.

Adds a main-text pointer to the Gaussian sensitivity so it is visible
without reading the supplement, with the detail kept in the supplement.
The direction of every structure-by-outcome contrast is stable across
error families while the magnitudes are not, so the ordering of
structures within an outcome is described as indicative.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The central interpretive point was implied by the numbers but never
stated: structural differences pointed in opposite directions for cases
and deaths, so any term averaging over outcomes recovers close to zero.
This explains both why the shared structure effect was always flat and
why the crossed term carries signal, and it reframes an existing null
literature.

Six places updated:

- Discussion, prior work: offers a mechanism for null findings in earlier
  structure comparisons. A pooled null is consistent with either an
  absence of differences or with differences that offset across targets,
  and the two cannot be separated without letting the effect vary by
  target. Closes with an explicit limit: we demonstrate the mechanism, we
  do not establish any particular contrast.
- Discussion, specification: states why assuming a shared effect is not
  neutral. The average is near zero by arithmetic, and penalisation then
  takes it to zero exactly.
- Results, pooled: notes an estimate near the grand mean has two possible
  causes, motivating the per-outcome split.
- Results, per-outcome: leads with the offsetting, illustrated by the
  widest-separated structure, then human judgement against Bosse et al.
- Methods: gives the substantive cause alongside the technical one, so
  the 0.001 edf does not read as a fitting artefact.
- Abstract: one sentence noting the overall null concealed differences
  that cancelled.

Keeps the widest-separation claim data-driven rather than naming a
structure, so a refit cannot silently falsify it. Consolidates a
duplicate description of agent-based models and gathers the three
caveats -- intervals spanning the mean, three contributing models,
family sensitivity -- into one place.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kathsherratt

Copy link
Copy Markdown
Contributor Author

Added: the offsetting result, brought out through the text

Follow-up commits 9c08409 and f3da10d.

While reviewing why the shared structure effect was always flat, the underlying reason turned out to be worth stating explicitly, and it had never been written down:

Structural differences pointed in opposite directions for cases and deaths, so any term averaging over outcomes recovers close to zero.

Structure cases deaths mean
Agent-based +0.086 −0.123 −0.018
Mechanistic −0.043 +0.046 +0.001
Statistical −0.034 +0.024 −0.005
Judgement −0.037 −0.008 −0.023
Semi-mechanistic +0.028 +0.061 +0.045

Cell effects of ±0.04–0.12 average to ±0.001–0.045. A shared effect is that average, so it was near zero before any penalty applied — and a penalised term near zero is shrunk to zero exactly. This explains both why the main effect was always flat (it was flat in every earlier specification too, before the interaction existed) and why the crossed term carries 4.9 edf.

Worth noting what this is not: the signal did not migrate out of s(Model). Individual-model effects are essentially unchanged by adding the interaction (correlation 0.995, sd 0.199 → 0.194, largest change 0.049), and s(Model) is one number per model, so it cannot represent a within-model case/death difference for the 34 of 48 models that forecast both. The variance was simply unexplained before.

Where the text changed

  • Discussion, prior work — the most substantive addition. Offers a mechanism for null findings in earlier structure comparisons: a pooled null is consistent with either no differences or offsetting ones, and they cannot be distinguished without letting the effect vary by target.
  • Discussion, specification — why assuming a shared effect is not neutral.
  • Results, pooled and per-outcome — the two possible causes of a near-mean pooled estimate; then the offsetting, illustrated by the widest-separated structure.
  • Methods — the substantive cause alongside the technical one.
  • Abstract — one sentence.

Deliberate limits

Every per-outcome interval still spans the grand mean, so the text says we demonstrate the mechanism rather than establish any particular contrast. The widest separation rests on three models and does not survive a Gaussian family; all three caveats are stated together rather than scattered.

The widest-separation claim is computed rather than hardcoded, so a refit cannot silently falsify it.

The same aliasing that ruled out a structure main effect also applies to
the epidemiological target: the within-target average of the crossed
cells is what a target main effect represents. The difference is that
only one of the two terms is penalised, so the unpenalised fixed effect
takes the component common to all structures and the crossed term keeps
only departures from it.

That separation holds in the fitted model. The crossed effects average to
zero within each target to ~1e-13, so the entire deaths-versus-cases
difference sits in the fixed coefficient (-1.03) and none leaks into the
structure estimates (largest cell 0.12). This is emergent rather than
imposed: the smooth retains all ten coefficients, so mgcv applied no
centring constraint.

Retaining the fixed effect is therefore what licenses reading the crossed
term as structure-specific. Dropping it would force the single largest
effect in the model through a penalised term and bias it toward zero;
making the target random would attempt to estimate a variance from two
levels.

Recorded in the Methods and alongside the formula in analysis-model.R.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kathsherratt

kathsherratt commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

Above messages generated by LLM. This sequence of changes was developed iteratively based on LLM and detailed human review.

My TLDR from a long sequence of working out what means what where:

  • In this version, we set up structure to interact with the epidemiological outcome, with a fixed effect of epi outcome and the other terms remaining as they were. This specification is better at bringing out any real underlying signal from structure, which was previously getting left in the residuals.

  • Because the epi outcome is specified as a fixed effect, it's not penalised, while the interaction cells are. So the fixed term absorbs the common difference between cases and deaths (the heftiest effect of any of the terms we're interested in). This leaves the interaction cells with only the structure-specific variation.

  • The structure-specific interpretation is justified because the cells average to zero within each epi outcome. So they carry no component shared across structures. mgcv left all 10 coefficients (5 structures * 2 outcomes) unconstrained, so it never imposed centring, which I take as showing this is a meaningful property of the fit.

  • At the same time, we drop the model-structure-only term. This was providing no distinct information (0.001 edf), and only made it more annoying to extract results from the interaction. Removing it changed no results. We now recover the pooled per-structure effect as a contrast across cells, which is now an informative pooled estimate rather than an uninformative one.

Covers the two preceding commits, which changed the manuscript text but
had not been logged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kathsherratt
kathsherratt marked this pull request as ready for review July 28, 2026 17:59
@kathsherratt

Copy link
Copy Markdown
Contributor Author

Notes for review:

PRs are stacked on top of each other - so should be merged in order - but I've left them open hoping this helps to review the three major changes one at a time.

#164, Include the Hub baseline model

  • We'd been excluding EuroCOVIDhub-baseline alongside the ensemble, but I wanted to add it back in; I think it's fine to include it as a statistical model (which the raters all classified it as).

#166, Change the error family

  • Compared Gaussian, Gamma and Tweedie. Tweedie fits decently, Gamma fits just as well but doesn't converge. Detail in the PR body.

#167, Let effect of model structure differ by outcome

  • Add interaction term between model structure and epi outcome. Also updates the DAG. See PR comments for detail of what changed here, but main impact shown in manuscript Table 2. Where the main estimate used to be a flat 1.00 for all five model types, specifying the interaction (and dropping the isolated term for model structure) shows a more interpretable result. Still overlapping 1 for all, but I think this actually means something now rather than just a term that shrunk to nothing.
  • The manuscript is updated to reflect this, just currently rather peppered with LLM speak. Still worth looking at as the single source of truth across all these changes (but I'll also take another pass over the text).

@kathsherratt
kathsherratt marked this pull request as draft July 28, 2026 18:17
@kathsherratt
kathsherratt marked this pull request as ready for review July 28, 2026 18:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant