Skip to content

Overhaul severe and mortality outcomes#8

Merged
pwinskill merged 18 commits into
mainfrom
severe_inc
Jun 29, 2026
Merged

Overhaul severe and mortality outcomes#8
pwinskill merged 18 commits into
mainfrom
severe_inc

Conversation

@pwinskill

@pwinskill pwinskill commented May 7, 2025

Copy link
Copy Markdown
Member

Aim: to make assumptions about severe treatment (hospitalisation) coverage and resulting probability of death more transparent and flexible.

The refactor replaces the hard‑wired Griffin‑2016 shortcut, where all malaria deaths were simply taken as 0.215 times the modelled incidence of hospital‑treated severe cases, with an explicit two‑stream calculation that first separates severe episodes into hospital and community compartments, then applies setting‑specific case‑fatality ratios to each stream. A new column, ft_sev, lets users supply (and vary over time) the proportion of severe cases that reach hospital. A warning‑backed default of 0.8 preserves backward compatibility - when this default is used the original 0.215 deaths‑to‑hospitalised‑cases ratio is recovered.

The flow is as follows:

  1. Estimate total severe inc based on original parameterisation from Griffen et al (2016) outputting hospitalised severe malaria inc, with an assummed 80% of severe cases hospitalised.
  2. Split total severe inc into hospitalised and community severe inc based on user-specified (or default) ft_sev parameter that defines the proportion of severe cases that are hospitalised.
  3. Rescale severe incidence metrics to account for the impact of first-line treatment reducing progression to severe.
  4. Use hospital and community-specific severe case fatality rates to convert to mortality.

The same treatment‑coverage multiplier that used to be applied is still applied to both severe streams before mortality is derived, making the algebra transparent and allowing referral improvements to be explored independently of first‑line treatment scale‑up.

image

Adding derivation of the 80% for reference

Let
mH = severe case fatality hospital: 0.065
mC= severe case fatality community: 0.6
λH= severe incidence hospital
λC= severe incidence community
ν = scaling factor linking hospitalised severe incidence to total malaria deaths: 0.215

Total deaths (predicted two ways) are:

image

Solving for the ratio of non‑hospitalised to hospitalised severe cases gives:
image

Coverage (ρ): the share of severe episodes treated in hospital, is therefore:
image

Which = 80%

Comment thread R/epi.R Outdated
Comment thread R/epi.R Outdated
Comment thread R/rates.R Outdated
Comment thread R/epi.R Outdated
Comment thread R/rates.R

rates <- x |>
dplyr::select(dplyr::all_of(c("timestep", "ft", clinical_cols, severe_cols, denominator_cols))) |>
dplyr::select(dplyr::all_of(c("timestep", "ft", "ft_sev", clinical_cols, severe_cols, denominator_cols))) |>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How will the ft_sev be defined? I can see it gets set to 0.8 in the absence of other inputs, but would it be anticipated to be an output of malaria simulation? Or is it possible to input it as a parameter in get_rates (I may have misunderstood!)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose it means you can assign the proportion quite flexibly and through time, but it just means that it needs to be done actively before the get_rates function is called, right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly, yes, the user can specify the ft_sev column prior to running get rates manual if they so wish, if not, it will default to the 0.8.

pwinskill and others added 4 commits June 13, 2025 14:05
…n-downscaled) severe inc was lost, and simplified algebra
Co-authored-by: RJSheppard <73181123+RJSheppard@users.noreply.github.com>
Co-authored-by: RJSheppard <73181123+RJSheppard@users.noreply.github.com>
@codecov

codecov Bot commented Jul 22, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (50c7756) to head (b4a38b3).
⚠️ Report is 17 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff             @@
##             main        #8      +/-   ##
===========================================
+ Coverage   98.37%   100.00%   +1.62%     
===========================================
  Files           5         5              
  Lines         123       201      +78     
===========================================
+ Hits          121       201      +80     
+ Misses          2         0       -2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

RJSheppard and others added 8 commits September 15, 2025 11:58
…ionality-demonstration

Expand rates vignette
Resolve DESCRIPTION conflict: keep R (>= 3.5) and add VignetteBuilder: knitr.

Make the new severe-mortality vignette and the existing rates vignette
(analysing_malaria_output) work together:
- Add ggplot2 to Suggests. The PR enables VignetteBuilder, so the rates
  vignette is now actually built and needs ggplot2 (which it uses).
- Cross-reference the two vignettes: the rates tutorial points to
  severe-mortality for the methodology, and severe-mortality points to the
  rates tutorial for a runnable end-to-end example.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Also update the matching test expectation so the ft warning assertion
still passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Build-ignore .claude and AGENTS.md (non-package files).
- Use the .data pronoun for `severe` in severe_incidence_mortality()
  to remove the "no visible binding for global variable" NOTE.

R CMD check now passes with Status: OK (0 errors, 0 warnings, 0 notes),
including re-building both vignettes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
P. vivax model output has no `n_inc_severe_...` columns, which previously
caused get_rates() to error. Instead of erroring, when severe incidence
columns are absent we now create them to match the clinical incidence age
groups and fill with NA, emitting a warning that the severe, mortality and
DALY (yld/yll/dalys) output columns will be NA as a result.

- Replace the hard stop with a create-and-warn path.
- Document the behaviour in get_rates().
- Replace the obsolete error test with a test asserting the warning and
  that severe/mortality/DALY columns come back NA while clinical is kept.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The severe-to-death scaler (0.215) in Griffin et al (2016) was estimated
with the model run at an assumed baseline treatment coverage f0. Per the
Griffin SI, severe incidence and mortality at coverage f1 are expressed
relative to f0 by multiplying by
  ((1-chi)*f1 + (1-f1)) / ((1-chi)*f0 + (1-f0)),
where chi is the treatment_scaler. The raw model severe output therefore
represents severe incidence at f0, so dropping the denominator (as the
branch previously did) left results inconsistent with the 0.215 calibration.

- Re-introduce the f0 normaliser in severe_incidence_mortality(), hard-coded
  to a representative f0 = 0.1 (not exposed as an argument), with a comment
  citing the Griffin SI. This makes severe_hospital/mortality/yll match the
  main branch exactly under defaults.
- Add a regression test locking in the f0 = 0.1 normalisation.
- Fix the severe-mortality vignette: the treatment term inverted averted vs
  not-averted (using nu = 0.42 as the retained fraction rather than the
  averted proportion), giving s_m*(1-0.58*tx) instead of the correct
  s_m*(1-0.42*tx). Rewrite the formula to match the code and the Griffin SI,
  including the f0 baseline term and the 1/rho0 hospitalised->total gross-up.

Note: yld/dalys still differ from main because YLD now weights total
(hospital + community) severe rather than hospitalised-only - a separate,
intentional DALY change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@pwinskill

Copy link
Copy Markdown
Member Author

@

Latest changes on severe_inc

Pushed up to 3a7e302. This brings in the severe/mortality vignette (PR #10, now merged into this branch) and a set of corrections to keep the severe/mortality calculations faithful to the original Griffin (2016) inference.

Vignettes

  • Merged the severe-mortality vignette and made it work alongside the existing "rates" vignette (analysing_malaria_output): added ggplot2 to Suggests, declared VignetteBuilder: knitr, and cross-referenced the two (methodology <-> runnable example).

Severe / mortality

  • Restored the Griffin (2016) baseline-treatment normalisation (f0 = 0.1). Per the Griffin SI, severe incidence and mortality at coverage f1 are expressed relative to the baseline coverage f0 the model was fitted at, via ((1-chi)*f1 + (1-f1)) / ((1-chi)*f0 + (1-f0)). It had been dropped, leaving results inconsistent with the 0.215 scaler. Now hard-coded (not user-exposed) with a citation comment.
  • Fixed the severe-incidence formula in the vignette, which had inverted averted vs not-averted (s_m*(1-0.58*tx) instead of the correct s_m*(1-0.42*tx)); rewritten to match the code and SI, including the f0 baseline and 1/rho0 hospitalised->total gross-up.
  • P. vivax support: get_rates() no longer errors when n_inc_severe_... columns are absent. It creates them to match the clinical age groups, fills with NA, and warns that severe/mortality/DALY outputs will be NA.
  • Fixed an "assumming" -> "assuming" typo in the warnings.

Verification

  • Compared against main (identical simulation input, all defaults): clinical, severe_hospital, mortality and yll are numerically identical (differences at machine epsilon). Defaults reproduce the original behaviour, with extra disaggregation (_hospital/_community, ft_sev, CFR args) available when needed.
  • yld/dalys intentionally differ from main: YLD now weights total (hospital + community) severe rather than hospitalised-only.
  • devtools::test(): 56 passed, 0 failures (added regression tests for the f0 normalisation and the missing-severe-column path).
  • R CMD check: Status OK (0 errors / 0 warnings / 0 notes), both vignettes rebuild.
    @

@pwinskill

Copy link
Copy Markdown
Member Author

closes #11

Coverage dropped from 98.4% to 88.2% on this branch, failing the
codecov/project and codecov/patch checks. Restore it to 100%:

- Delete R/age.R. Its fexp/cexp/ea/expected_age are dead duplicates of
  the integrand/ea/expected_age defined in R/dalys.R; since files load
  alphabetically, dalys.R overwrites ea/expected_age and age.R's
  fexp/cexp are never reachable. Regenerate docs to drop the orphaned
  fexp.Rd/cexp.Rd (ea/expected_age remain, documented from dalys.R).
- Add tests for rates_aggregate() and prevalence_aggregate().
- Add get_rates() tests for the inferred-ft_sev warning and the
  mismatched clinical/severe age-group error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@pwinskill pwinskill merged commit b3dbb06 into main Jun 29, 2026
17 of 19 checks passed
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.

2 participants