Derived outputs in stratified models¶
In a previous example, we saw how to track derived outputs for a simple, unstratified model. In this example we’ll look into tracking derived outputs for a stratified model.
We’ll be looking at:
Let’s start by defining a reasonably complicated compartmental SEIR model stratified by age and clinical status. This model will have:
An age stratification with strata:
young
old
A “clinical status”” stratification with strata:
asymptomatic: has disease, doesn’t know it, no symptoms
symptomatic: has disease, doesn’t know it (or not tested), symptoms
isolated: has disease, knows it (tested), is isolated at home, symptoms
hospital: has disease, knows it (tested), severse symptoms and in hospital
We’ll use this model as a basis for defining our derived outputs.
[1]
from summer2 import CompartmentalModel, Stratification, Multiply, Overwrite
import pandas as pd
pd.options.plotting.backend = "plotly"
def build_model():
"""Returns a new SIR model"""
# Create basic SEIR model.
model = CompartmentalModel(
times=[0, 20],
compartments=["S", "E", "I", "R"],
infectious_compartments=["I"],
timestep=0.1,
)
model.set_initial_population(distribution={"S": 990, "I": 10})
model.add_infection_frequency_flow(name="infection", contact_rate=2, source="S", dest="E")
model.add_transition_flow(name="incidence", fractional_rate=0.5, source="E", dest="I")
model.add_transition_flow(name="recovery", fractional_rate=0.3, source="I", dest="R")
model.add_death_flow(name="infection_death", death_rate=0.05, source="I")
# Stratify by age.
age_strat = Stratification('age', ['young', 'old'], ['S', 'E', 'I', 'R'])
age_strat.set_population_split({'young': 0.5, 'old': 0.5})
model.stratify_with(age_strat)
# Stratify by clinical status for infected people.
strata = ['asymptomatic', 'symptomatic', 'isolated', 'hospital']
clinical_strat = Stratification('clinical', strata, ['I'])
clinical_strat.set_population_split({'asymptomatic': 1, 'symptomatic': 0, 'isolated': 0, 'hospital': 0})
# Half of young people become asymptomatic.
young_incidence_adjustments = {
"asymptomatic": Multiply(0.4),
"symptomatic": Multiply(0.3),
"isolated": Multiply(0.2),
"hospital": Multiply(0.1),
}
clinical_strat.set_flow_adjustments(
"incidence",
young_incidence_adjustments,
source_strata={'age': 'young'}
)
# A higher proporiton of old people go to hospital
old_incidence_adjustments = {
"asymptomatic": Multiply(0.3),
"symptomatic": Multiply(0.3),
"isolated": Multiply(0.2),
"hospital": Multiply(0.3),
}
clinical_strat.set_flow_adjustments(
"incidence",
old_incidence_adjustments,
source_strata={'age': 'old'}
)
# Adjust risk of dying given clinical status
clinical_strat.set_flow_adjustments("infection_death", {
"asymptomatic": Overwrite(0), # Can't die if no symptoms
"symptomatic": None,
"isolated": None,
"hospital": Multiply(2), # Severe cases go to hospital
})
# Adjust infectiousness given clinical status
clinical_strat.add_infectiousness_adjustments("I", {
"asymptomatic": Multiply(0.5),
"symptomatic": None,
"isolated": Multiply(0.2),
"hospital": Multiply(0.1),
})
model.stratify_with(clinical_strat)
return model
Now that we have a model that we can inspect, let’s use the derived output requests from our previous example to calculate some quantities of interest.
Tracking cumulative deaths by age group¶
We can use a flow output plus a cumulative output to track the cumulative number of people who have died from the disease, segmented by age group:
[2]
model = build_model()
age_strata = ["young", "old"]
for age_stratum in age_strata:
output_name = f"death_{age_stratum}"
model.request_output_for_flow(
output_name,
flow_name="infection_death",
source_strata={"age": age_stratum},
save_results=False
)
model.request_cumulative_output(f"accum_{output_name}", output_name)
model.run()
model.get_derived_outputs_df().plot()
Tracking disease incidence¶
We can use a flow output to track the number of people who progress from exposed to infected per day (‘incidence’) and break it down by either age group, severity, or both.
To start, let’s look at incidence (daily flow from E to I), by age group:
[3]
model = build_model()
age_strata = ["young", "old"]
for age_stratum in age_strata:
model.request_output_for_flow(
f"incidence_{age_stratum}",
flow_name="incidence",
source_strata={"age": age_stratum}
)
model.run()
model.get_derived_outputs_df().plot()
We can also inspect incidence by clinical status
[4]
model = build_model()
clinical_strata = ["asymptomatic", "symptomatic", "isolated", "hospital"]
output_names = [f"incidence_{s}" for s in clinical_strata]
for clinical_stratum, output_name in zip(clinical_strata, output_names):
model.request_output_for_flow(
output_name,
flow_name="incidence",
dest_strata={"clinical": clinical_stratum}
)
model.run()
model.get_derived_outputs_df().plot()
Finally, we can get a break down of incidence by both age AND clinical status.
[5]
from itertools import product
model = build_model()
age_strata = ["young", "old"]
clinical_strata = ["asymptomatic", "symptomatic", "isolated", "hospital"]
strata = list(product(age_strata, clinical_strata))
output_names = [f"incidence_{a}_{c}" for a, c in strata]
for (age, clinical), output_name in zip(strata, output_names):
model.request_output_for_flow(
output_name,
flow_name="incidence",
dest_strata={"clinical": clinical, "age": age}
)
model.run()
model.get_derived_outputs_df().plot()
Tracking daily notifications¶
We could use the same approach to track “notifications”: disease incidence that is detected via testing. In our (simplified) clinical status strata, we could define notifications as:
asymptomatic: no notification
symptomatic: no notification
isolated: notification
hospital: notification
Given this definition, notifications can be calculated as follows:
[6]
model = build_model()
# Step 1: Get model to track the incidence flows for the notification strata
notify_strata = ["isolated", "hospital"]
output_names = [f"incidence_{s}" for s in notify_strata]
for notify_strata, output_name in zip(notify_strata, output_names):
model.request_output_for_flow(
output_name,
flow_name="incidence",
dest_strata={"clinical": notify_strata}
)
# Step 2: Aggregate the notification strata
model.request_aggregate_output(
name="notifications",
sources=output_names,
)
model.run()
model.get_derived_outputs_df().plot()
Tracking hospital occupancy¶
We can use a compartmental output to track the number of infected people who are in the hospital strata per timestep.
[7]
model = build_model()
model.request_output_for_compartments(
'hospital_occupancy',
compartments=["I"],
strata={"clinical": "hospital"}
)
model.run()
model.get_derived_outputs_df().plot()
Using DerivedOutput GraphObjects¶
Previously requested derived outputs are available as GraphObjects via the DerivedOutput constructor. These can be operated on in exactly the same way as all other GraphObjects
[8]
from summer2.parameters import DerivedOutput
[9]
model = build_model()
model.request_output_for_compartments(
'hospital_occupancy',
compartments=["I"],
strata={"clinical": "hospital"},
save_results=False
)
model.request_output_for_compartments(
'all_infected',
compartments=["I"],
save_results=False
)
model.request_function_output("prop_infected_hosp", DerivedOutput("hospital_occupancy") / DerivedOutput("all_infected") )
model.run()
model.get_derived_outputs_df().plot()
Summary¶
Now you know how to request derived ouputs that target specific strata in a stratified model.
[ ]