Line lists and contacts
Simulated epidemiological data can be generated from any branching process simulation. Output generation is model-agnostic and works with single-type, multi-type, with or without interventions.
Line list
A simulation state is converted to a DataFrame with one row per case using linelist:
julia
using EpiBranch
using Distributions
using DataFrames
using Dates
using StableRNGs
model = BranchingProcess(NegBin(1.5, 0.5), LogNormal(1.6, 0.5))
rng = StableRNG(42)
state = simulate(model;
condition = 50:200,
attributes = clinical_presentation(incubation_period = LogNormal(1.5, 0.5)),
sim_opts = SimOpts(max_cases = 200),
rng = rng,
)
ll = linelist(state;
reference_date = Date(2024, 1, 1),
delays = DelayOpts(
onset_to_reporting = Exponential(3.0),
onset_to_admission = Exponential(5.0),
onset_to_outcome = Exponential(14.0),
),
outcomes = OutcomeOpts(prob_hospitalisation = 0.2, prob_death = 0.05),
rng = StableRNG(99),
)
first(ll, 5)5×12 DataFrame
| Row | id | parent_id | generation | chain_id | case_type | date_infection | date_onset | date_reporting | date_admission | outcome | date_outcome | asymptomatic |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Int64 | Int64 | Int64 | Int64 | String | Date | Date? | Date? | Date? | String | Date? | Bool | |
| 1 | 1 | 0 | 0 | 1 | index | 2024-01-01 | 2024-01-12 | 2024-01-24 | missing | recovered | 2024-01-13 | false |
| 2 | 2 | 1 | 1 | 1 | secondary | 2024-01-03 | 2024-01-07 | 2024-01-08 | missing | recovered | 2024-01-13 | false |
| 3 | 3 | 1 | 1 | 1 | secondary | 2024-01-04 | 2024-01-08 | 2024-01-08 | 2024-01-09 | recovered | 2024-01-18 | false |
| 4 | 4 | 1 | 1 | 1 | secondary | 2024-01-03 | 2024-01-07 | 2024-01-15 | missing | recovered | 2024-01-13 | false |
| 5 | 5 | 1 | 1 | 1 | secondary | 2024-01-04 | 2024-01-09 | 2024-01-16 | missing | recovered | 2024-01-16 | false |
Demographics
Control age distribution, age range, and sex ratio:
julia
ll = linelist(state;
reference_date = Date(2024, 1, 1),
demographics = DemographicOpts(
age_distribution = Normal(40, 15),
age_range = (0, 90),
prob_female = 0.55,
),
rng = StableRNG(99),
)
println("Age range: $(minimum(ll.age)) - $(maximum(ll.age))")
println("Female: $(round(count(==("female"), ll.sex) / nrow(ll) * 100, digits=1))%")Age range: 0 - 80
Female: 50.0%Age-stratified risks
Provide age-specific case fatality risk as a dictionary mapping (lower, upper) age bounds to risk values:
julia
age_cfr = Dict((0, 14) => 0.001, (15, 64) => 0.01, (65, 90) => 0.15)
ll = linelist(state;
reference_date = Date(2024, 1, 1),
outcomes = OutcomeOpts(age_specific_cfr = age_cfr),
demographics = DemographicOpts(age_range = (0, 90)),
rng = StableRNG(99),
)
for (lo, hi) in [(0, 14), (15, 64), (65, 90)]
group = filter(r -> lo <= r.age <= hi, ll)
n_died = count(==("died"), group.outcome)
pct = nrow(group) > 0 ? round(n_died / nrow(group) * 100, digits=1) : 0.0
println("Age $lo-$hi: $(nrow(group)) cases, $n_died deaths ($pct%)")
endAge 0-14: 11 cases, 0 deaths (0.0%)
Age 15-64: 116 cases, 0 deaths (0.0%)
Age 65-90: 5 cases, 1 deaths (20.0%)Contacts table
All contacts (infected and non-infected) are returned by contacts, with a was_case flag matching the simulist R package output format:
julia
ct = contacts(state; reference_date = Date(2024, 1, 1))
println("Total: $(nrow(ct)), Infected: $(count(ct.was_case)), Not infected: $(count(.!ct.was_case))")
first(ct, 5)5×6 DataFrame
| Row | from | to | was_case | generation | infection_time | date_infection |
|---|---|---|---|---|---|---|
| Int64 | Int64 | Bool | Int64 | Float64 | Date | |
| 1 | 1 | 2 | true | 1 | 2.56999 | 2024-01-03 |
| 2 | 1 | 3 | true | 1 | 3.77089 | 2024-01-04 |
| 3 | 1 | 4 | true | 1 | 2.61245 | 2024-01-03 |
| 4 | 1 | 5 | true | 1 | 3.35486 | 2024-01-04 |
| 5 | 1 | 6 | true | 1 | 7.87624 | 2024-01-08 |
Conditioned simulation
Generate outbreaks of a specific size range:
julia
rng = StableRNG(42)
state = simulate(model;
condition = 100:150,
attributes = clinical_presentation(incubation_period = LogNormal(1.5, 0.5)),
sim_opts = SimOpts(max_cases = 200),
rng = rng,
)
println("Outbreak size: $(state.cumulative_cases) (target: 100-150)")Outbreak size: 132 (target: 100-150)