Skip to content

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
Rowidparent_idgenerationchain_idcase_typedate_infectiondate_onsetdate_reportingdate_admissionoutcomedate_outcomeasymptomatic
Int64Int64Int64Int64StringDateDate?Date?Date?StringDate?Bool
11001index2024-01-012024-01-122024-01-24missingrecovered2024-01-13false
22111secondary2024-01-032024-01-072024-01-08missingrecovered2024-01-13false
33111secondary2024-01-042024-01-082024-01-082024-01-09recovered2024-01-18false
44111secondary2024-01-032024-01-072024-01-15missingrecovered2024-01-13false
55111secondary2024-01-042024-01-092024-01-16missingrecovered2024-01-16false

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%)")
end
Age 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
Rowfromtowas_casegenerationinfection_timedate_infection
Int64Int64BoolInt64Float64Date
112true12.569992024-01-03
213true13.770892024-01-04
314true12.612452024-01-03
415true13.354862024-01-04
516true17.876242024-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)