A list of scenarios

A list of scenarios runs several campaigns against one shared setting in a single call. The five below all start from the same district, at under-5 prevalence 0.45, pyrethroid resistance 0.30, and pyrethroid-only nets at 0.70 coverage.

Every scenario carries the same EirTarget, so the baseline EIR is inverted from the same prevalence under the same current net mix. Any difference is caused by the campaign alone.

Building the list

The shared fields go in a dictionary and are splatted into each Scenario, an idiom R writes as do.call(Scenario, c(district, list(name = "PBO", ...))).

from estimint import run_scenarios
from estimint.scenarios import Scenario, EirTarget

district = dict(
    res_use=0.30, Q0=0.85, phi=0.80, seasonal=0.0, irs=0.0,
    py_only=0.70,     # the nets in place today
    routine=0.0,
    eir_target=EirTarget(0.45, "prevalence"),
)

scenarios = [
    Scenario(name="Withdraw", **district),
    Scenario(name="Pyrethroid-only", itn_future=0.70,
             net_type_future="pyrethroid_only", **district),
    Scenario(name="PBO", itn_future=0.70,
             net_type_future="pyrethroid_pbo", **district),
    Scenario(name="Pyrrole", itn_future=0.70,
             net_type_future="pyrethroid_pyrrole", **district),
    Scenario(name="PBO + IRS + LSM", itn_future=0.70,
             net_type_future="pyrethroid_pbo", irs_future=0.60, lsm=0.30, **district),
]

len(scenarios)
5

One call

run_scenarios takes the list, loads the models once, and puts the five scenarios through the emulator as a single batch. Five scenarios cost barely more than one.

res = run_scenarios(scenarios)
res.shape
(5, 23)

The columns are the same twenty-three that a single scenario returns.

The shared baseline

Check the baseline columns first.

res[["name", "dn0_use", "itn_use", "eir_baseline", "prev_y9"]]
name dn0_use itn_use eir_baseline prev_y9
0 Withdraw 0.311 0.7 31.960129 0.456028
1 Pyrethroid-only 0.311 0.7 31.960129 0.456028
2 PBO 0.311 0.7 31.960129 0.456028
3 Pyrrole 0.311 0.7 31.960129 0.456028
4 PBO + IRS + LSM 0.311 0.7 31.960129 0.456028

All five share the same dn0_use, the same itn_use, an eir_baseline of about 32 infectious bites per person per year, and a prev_y9 of about 0.456 against the 0.45 that went in. They diverge only after the campaign.

The summary table

res[[
    "name", "net_future", "dn0_future", "itn_future",
    "irs_future", "lsm", "prev_endline", "cases_endline",
]]
name net_future dn0_future itn_future irs_future lsm prev_endline cases_endline
0 Withdraw none 0.00000 0.0 0.0 0.0 0.498149 2.127164
1 Pyrethroid-only pyrethroid_only 0.31100 0.7 0.0 0.0 0.483688 2.433113
2 PBO pyrethroid_pbo 0.48495 0.7 0.0 0.0 0.470118 2.412444
3 Pyrrole pyrethroid_pyrrole 0.55015 0.7 0.0 0.0 0.464200 2.389937
4 PBO + IRS + LSM pyrethroid_pbo 0.48495 0.7 0.6 0.3 0.038096 0.165287

Read the dn0_future column first, because at resistance 0.30 a pyrethroid-only net kills about 0.31 of the mosquitoes that touch it, a PBO net about 0.48, and a pyrrole net about 0.55. The ordering of the three net scenarios follows from that column, every other covariate being held fixed.

The combination scenario carries the PBO dn0_future (the nets are the same nets) and adds spraying at 0.60 and larviciding at 0.30 on top. Its endline prevalence comes out an order of magnitude below any net-only scenario.

Withdrawal

The first row was built by passing the district dictionary and nothing else, with no itn_future and no net_type_future.

res.loc[res["name"] == "Withdraw", ["net_future", "dn0_future", "itn_future", "irs_future"]]
net_future dn0_future itn_future irs_future
0 none 0.0 0.0 0.0

net_future is "none", dn0_future is 0, itn_future is 0, and the district has been modelled as stopping net distribution altogether. No warning is raised. The call succeeds, the frame comes back, and the trajectory is plausible. The only indication is the rebound.

import numpy as np

withdraw = res.loc[res["name"] == "Withdraw"].iloc[0]
like_for_like = res.loc[res["name"] == "Pyrethroid-only"].iloc[0]

float(withdraw["prev_endline"]), float(like_for_like["prev_endline"])
(0.49814853072166443, 0.4836880564689636)

Withdrawal ends about one and a half prevalence points above like-for-like replacement, a gap small enough to be mistaken for noise. The difference in cumulative case burden between the two is considerably larger, as Trajectories and cases works out.

See also

Trajectories and cases plots the five scenarios and integrates the burden each campaign averts, and Exporting results gets the scenarios out of Python. Please see Nets and dn0 for the resistance curves behind dn0_future.