Analysis walkthrough
This page fits the BDBV delay + CFR model in BdbvLinelist.jl to the Rosello et al. 2015 Isiro deposit (n = 52) and renders the headline outputs. Three parametric families (LogNormal, Gamma, Weibull) are compared by WAIC; the Gamma fit is the canonical run. The marginal onset → death and onset → discharge distributions are derived in post-processing as Monte Carlo convolutions of the atomic components, enforcing the per-case natural-history identity at the population level. A logistic regression stratifies case-fatality by HCW status, case definition (Probable vs Confirmed), and standardised age.
Priors, the doubly-censored likelihood, and post-processing are detailed on the Model page. Caveats are on the Limitations page. Per-draw posterior CSV and figure bundle from this build is in the rolling main-latest release.
using BdbvLinelist
using DataFrames
using DataFramesMeta
using Chain
using FlexiChains
using Statistics
using Printf
using Random
using CairoMakie
using Turing: @varnameLoad the line list
load_linelist parses the bundled CSV from data/linelist.csv and scrubs the five admission-date encoding outliers and the one notification-date outlier described in Limitations. build_data re-encodes the per-pair delays into the vectors used by the doubly-censored likelihood.
ll = load_linelist()
d = build_data(ll)
@chain ll begin
@rsubset !ismissing(:Date_of_onset_symp)
@select :Person_ID :Date_of_onset_symp :Date_of_Hospitalisation :Date_of_Death :Date_hospital_discharge :Date_of_notification :is_hcw :Case_definition
first(8)
end| Row | Person_ID | Date_of_onset_symp | Date_of_Hospitalisation | Date_of_Death | Date_hospital_discharge | Date_of_notification | is_hcw | Case_definition |
|---|---|---|---|---|---|---|---|---|
| String15 | Date | Date? | Date? | Date? | Date? | Bool | String15 | |
| 1 | Isi_2012_72 | 2012-08-27 | 2012-09-02 | missing | 2012-09-10 | 2012-09-01 | false | Confirmed |
| 2 | Isi_2012_17 | 2012-08-09 | 2012-08-13 | missing | 2012-08-29 | 2012-08-13 | false | Confirmed |
| 3 | Isi_2012_76 | 2012-07-19 | missing | 2012-07-29 | missing | 2012-09-10 | false | Probable |
| 4 | Isi_2012_44 | 2012-08-05 | 2012-08-10 | 2012-08-18 | missing | 2012-09-03 | false | Probable |
| 5 | Isi_2012_137 | 2012-09-21 | missing | missing | missing | 2012-09-22 | true | Confirmed |
| 6 | Isi_2012_106 | 2012-07-25 | 2012-07-25 | missing | 2012-07-28 | 2012-09-10 | false | Probable |
| 7 | Isi_2012_10 | 2012-07-27 | 2012-07-29 | 2012-07-31 | missing | missing | false | Probable |
| 8 | Isi_2012_108 | 2012-08-19 | 2012-08-24 | 2012-08-28 | missing | 2012-09-11 | false | Probable |
Outbreak context
Per the Charniga et al. 2024 reporting checklist (item 12: provide the contextual information needed to interpret the delays). Sample size, observation window, demographics, case-definition mix and care-setting are summarised here; epidemic curve and control measures are below (Epidemic curve) and on the Limitations page (outbreak setting and ETC support).
let onsets = collect(skipmissing(ll.Date_of_onset_symp)),
ages = collect(skipmissing(ll.Age)),
sexes = collect(skipmissing(ll.Sex))
DataFrame(
"Quantity" => [
"Total cases", "Cases with onset date",
"Onset window",
"Median age (IQR), years",
"Sex (Female / Male)",
"HCW (yes / no)",
"Case definition (Confirmed / Probable)",
"Outcome (Dead / Alive / Unknown)",
],
"Value" => [
string(nrow(ll)),
string(length(onsets)),
@sprintf("%s to %s", minimum(onsets), maximum(onsets)),
@sprintf("%.0f (%.0f – %.0f)",
quantile(ages, 0.5),
quantile(ages, 0.25),
quantile(ages, 0.75)),
@sprintf("%d / %d",
count(==("Female"), sexes), count(==("Male"), sexes)),
@sprintf("%d / %d", sum(ll.is_hcw), nrow(ll) - sum(ll.is_hcw)),
@sprintf("%d / %d",
count(==("Confirmed"), skipmissing(ll.Case_definition)),
count(==("Probable"), skipmissing(ll.Case_definition))),
@sprintf("%d / %d / %d",
count(==("Dead"), skipmissing(ll.Outcome)),
count(==("Alive"), skipmissing(ll.Outcome)),
count(o -> !(o in ("Dead", "Alive")),
skipmissing(ll.Outcome))),
],
)
end| Row | Quantity | Value |
|---|---|---|
| String | String | |
| 1 | Total cases | 52 |
| 2 | Cases with onset date | 52 |
| 3 | Onset window | 2012-06-01 to 2012-11-08 |
| 4 | Median age (IQR), years | 40 (28 – 46) |
| 5 | Sex (Female / Male) | 40 / 12 |
| 6 | HCW (yes / no) | 14 / 38 |
| 7 | Case definition (Confirmed / Probable) | 36 / 16 |
| 8 | Outcome (Dead / Alive / Unknown) | 28 / 24 / 0 |
Headline Gamma estimates
Fit all three families (Gamma wins on WAIC; comparison table and plot are in the Family comparison section below) and tabulate the four atomic delay components alongside Rosello et al. 2015 Table 5 empirical means. Rosello capped the raw delays at 30 days before computing those summary statistics — this is binding on onset → notification (where 9 of 38 cases exceed 30 d) and accounts for most of the difference there.
# Suppress the package's stdout printing — we build clean DataFrame
# tables from the returned posterior vectors below. The fit itself
# emits a fair amount of Turing diagnostics (initial step size,
# sampling progress, the occasional divergent-transition warning)
# that aren't part of the narrative, so the call and its captured
# output are tucked into the dropdown below.Fit: compare_families() — Turing diagnostics
results = redirect_stdout(devnull) do
compare_families()
end┌ Info: Loaded line list
└ n_cases = 52
┌ Info: Fitting family
└ family = :lognormal
┌ Info: Found initial step size
└ ϵ = 0.4
┌ Info: Found initial step size
└ ϵ = 0.8
┌ Info: Found initial step size
└ ϵ = 0.4
┌ Info: Found initial step size
└ ϵ = 0.4
┌ Warning: compute_waic: dropped non-finite log-likelihood draws
│ family = :lognormal
│ n_dropped = 66686
│ n_obs = 167
└ @ BdbvLinelist ~/work/bdbv-linelist-analysis/bdbv-linelist-analysis/src/postprocess.jl:448
┌ Info: Fit done
│ family = :lognormal
│ rhat = 1.008
│ ess = 3496
│ ndiv = 0
│ waic = 652838.5
└ p_waic = 325843.9
┌ Info: Fitting family
└ family = :gamma
┌ Info: Found initial step size
└ ϵ = 0.05
┌ Info: Found initial step size
└ ϵ = 0.2
┌ Info: Found initial step size
└ ϵ = 0.4
┌ Info: Found initial step size
└ ϵ = 0.2
┌ Warning: There were 1 divergent transitions. Consider reparameterising your model or using a smaller step size. For adaptive samplers such as NUTS and HMCDA, consider increasing `target_accept`.
└ @ Turing.Inference ~/.julia/packages/Turing/4hMHm/src/mcmc/hmc.jl:483
┌ Warning: There were 1 divergent transitions. Consider reparameterising your model or using a smaller step size. For adaptive samplers such as NUTS and HMCDA, consider increasing `target_accept`.
└ @ Turing.Inference ~/.julia/packages/Turing/4hMHm/src/mcmc/hmc.jl:483
┌ Warning: There were 2 divergent transitions. Consider reparameterising your model or using a smaller step size. For adaptive samplers such as NUTS and HMCDA, consider increasing `target_accept`.
└ @ Turing.Inference ~/.julia/packages/Turing/4hMHm/src/mcmc/hmc.jl:483
┌ Info: Fit done
│ family = :gamma
│ rhat = 1.007
│ ess = 3933
│ ndiv = 2
│ waic = 792.4
└ p_waic = 9.2
┌ Info: Fitting family
└ family = :weibull
┌ Info: Found initial step size
└ ϵ = 0.2
┌ Info: Found initial step size
└ ϵ = 0.2
┌ Info: Found initial step size
└ ϵ = 0.003125
┌ Info: Found initial step size
└ ϵ = 0.025
┌ Info: Fit done
│ family = :weibull
│ rhat = 1.006
│ ess = 3762
│ ndiv = 0
│ waic = 793.6
└ p_waic = 9.2Post-processing: summarise() on the Gamma fit
chn_gamma = results[:gamma].chain
post = redirect_stdout(devnull) do
summarise(chn_gamma, :gamma; d = d)
endLocal helper: format a vector as median (2.5% – 97.5%).
qci(x) = (quantile(x, 0.025), quantile(x, 0.5), quantile(x, 0.975))
function fmt(x)
lo, med, hi = qci(x)
return @sprintf("%.2f (%.2f – %.2f)", med, lo, hi)
end
headline_estimates = DataFrame(
"Delay" => [
"Onset → admission",
"Admission → death",
"Admission → discharge",
"Onset → notification",
],
"n" => [
length(d.onset_to_admit),
length(d.admit_to_death),
length(d.admit_to_discharge),
length(d.onset_to_notif),
],
"Gamma median (95% CrI), days" =>
[fmt(post.median_oa), fmt(post.median_ad),
fmt(post.median_ac), fmt(post.median_on)],
"Gamma mean (95% CrI), days" =>
[fmt(post.mean_oa), fmt(post.mean_ad),
fmt(post.mean_ac), fmt(post.mean_on)],
"Rosello mean" => [4.00, 7.59, 8.00, 8.83],
)| Row | Delay | n | Gamma median (95% CrI), days | Gamma mean (95% CrI), days | Rosello mean |
|---|---|---|---|---|---|
| String | Int64 | String | String | Float64 | |
| 1 | Onset → admission | 40 | 2.96 (2.06 – 4.01) | 4.03 (3.08 – 5.46) | 4.0 |
| 2 | Admission → death | 22 | 6.42 (4.47 – 8.70) | 7.60 (5.62 – 10.42) | 7.59 |
| 3 | Admission → discharge | 15 | 5.33 (2.82 – 8.87) | 7.70 (4.81 – 13.81) | 8.0 |
| 4 | Onset → notification | 38 | 11.07 (6.43 – 17.39) | 19.82 (13.64 – 29.94) | 8.83 |
Per-draw posterior shape, scale and SD for each atomic Gamma delay are reported in Gamma shape, scale and SD per atomic delay further down the page, and as columns in posterior_gamma.csv.
Posterior predictive check (Gamma)
Four panels — one per atomic delay — overlaying the observed integer day histogram with the simulated double-interval-censored posterior predictive (median + 95% band). The dashed black line in each panel marks the Rosello et al. 2015 Table 5 mean (4.00, 7.59, 8.00, 8.83 d) for visual comparison.
plot_ppc(chn_gamma, d, :gamma)Prior versus posterior
Pairwise distributions of the four atomic Gamma log_mean parameters under the prior used in fitting and the resulting posterior. Diagonal panels show the marginals; off-diagonal panels show the pairwise contours. The shrinkage from prior (grey) to posterior (red) is the visual analogue of the prior-sensitivity table immediately below.
using PairPlots
prior_vs_posterior = let
posterior_log_means = (;
oa = vec(collect(chn_gamma[@varname(dist_oa.log_mean)])),
ad = vec(collect(chn_gamma[@varname(dist_ad.log_mean)])),
ac = vec(collect(chn_gamma[@varname(dist_ac.log_mean)])),
on = vec(collect(chn_gamma[@varname(dist_on.log_mean)])),
)
# Sample the prior directly: each delay's log_mean is
# Normal(log(plausible_median_d), 1.0) at the default prior scale —
# see `bdbv_model` in `src/model.jl`.
S = length(posterior_log_means.oa)
rng = Random.MersenneTwister(20260519)
prior_log_means = (;
oa = log(3.0) .+ randn(rng, S),
ad = log(6.0) .+ randn(rng, S),
ac = log(13.0) .+ randn(rng, S),
on = log(7.0) .+ randn(rng, S),
)
pairplot(
PairPlots.Series(prior_log_means; label = "prior", color = :grey),
PairPlots.Series(posterior_log_means; label = "posterior", color = :firebrick),
)
endPrior sensitivity
Refit the Gamma model under three prior-scale settings (0.5, 1.0 default, 2.0). The default 1.0 prior gives ≈ ×3-fold prior latitude on the central tendency of each delay; a 4× span across the sweep shifts posterior means by < 5%.
Fit: sensitivity() — three prior-scale refits
sens = redirect_stdout(devnull) do
sensitivity()
end┌ Info: Prior sensitivity
└ prior_scale = 0.5
┌ Info: Found initial step size
└ ϵ = 0.2
┌ Info: Found initial step size
└ ϵ = 0.2
┌ Info: Found initial step size
└ ϵ = 0.4
┌ Info: Found initial step size
└ ϵ = 0.4
┌ Warning: There were 1 divergent transitions. Consider reparameterising your model or using a smaller step size. For adaptive samplers such as NUTS and HMCDA, consider increasing `target_accept`.
└ @ Turing.Inference ~/.julia/packages/Turing/4hMHm/src/mcmc/hmc.jl:483
┌ Warning: There were 1 divergent transitions. Consider reparameterising your model or using a smaller step size. For adaptive samplers such as NUTS and HMCDA, consider increasing `target_accept`.
└ @ Turing.Inference ~/.julia/packages/Turing/4hMHm/src/mcmc/hmc.jl:483
┌ Info: Prior sensitivity
└ prior_scale = 1.0
┌ Info: Found initial step size
└ ϵ = 0.05
┌ Info: Found initial step size
└ ϵ = 0.2
┌ Info: Found initial step size
└ ϵ = 0.2
┌ Info: Found initial step size
└ ϵ = 0.4
┌ Warning: There were 1 divergent transitions. Consider reparameterising your model or using a smaller step size. For adaptive samplers such as NUTS and HMCDA, consider increasing `target_accept`.
└ @ Turing.Inference ~/.julia/packages/Turing/4hMHm/src/mcmc/hmc.jl:483
┌ Warning: There were 1 divergent transitions. Consider reparameterising your model or using a smaller step size. For adaptive samplers such as NUTS and HMCDA, consider increasing `target_accept`.
└ @ Turing.Inference ~/.julia/packages/Turing/4hMHm/src/mcmc/hmc.jl:483
┌ Warning: There were 2 divergent transitions. Consider reparameterising your model or using a smaller step size. For adaptive samplers such as NUTS and HMCDA, consider increasing `target_accept`.
└ @ Turing.Inference ~/.julia/packages/Turing/4hMHm/src/mcmc/hmc.jl:483
┌ Info: Prior sensitivity
└ prior_scale = 2.0
┌ Info: Found initial step size
└ ϵ = 0.025
┌ Info: Found initial step size
└ ϵ = 0.2
┌ Info: Found initial step size
└ ϵ = 0.05
┌ Info: Found initial step size
└ ϵ = 0.025
┌ Warning: There were 1 divergent transitions. Consider reparameterising your model or using a smaller step size. For adaptive samplers such as NUTS and HMCDA, consider increasing `target_accept`.
└ @ Turing.Inference ~/.julia/packages/Turing/4hMHm/src/mcmc/hmc.jl:483
┌ Warning: There were 1 divergent transitions. Consider reparameterising your model or using a smaller step size. For adaptive samplers such as NUTS and HMCDA, consider increasing `target_accept`.
└ @ Turing.Inference ~/.julia/packages/Turing/4hMHm/src/mcmc/hmc.jl:483Pull the per-draw posterior mean for each atomic delay out of each sensitivity chain. The Gamma submodel parametrises each delay as (log_mean, log_shape), so exp(log_mean) is the population mean.
const DELAY_LM_VARS = (
onset_to_admit = @varname(dist_oa.log_mean),
admit_to_death = @varname(dist_ad.log_mean),
admit_to_discharge = @varname(dist_ac.log_mean),
onset_to_notif = @varname(dist_on.log_mean),
)
chain_mean(chn, var) = exp.(vec(collect(chn[var])))
prior_sensitivity = DataFrame(
delay = ["Onset → admission", "Admission → death",
"Admission → discharge", "Onset → notification"],
scale_05 = [fmt(chain_mean(sens[0.5].chain, v))
for v in DELAY_LM_VARS],
scale_10 = [fmt(chain_mean(sens[1.0].chain, v))
for v in DELAY_LM_VARS],
scale_20 = [fmt(chain_mean(sens[2.0].chain, v))
for v in DELAY_LM_VARS],
)| Row | delay | scale_05 | scale_10 | scale_20 |
|---|---|---|---|---|
| String | String | String | String | |
| 1 | Onset → admission | 3.94 (2.97 – 5.34) | 4.03 (3.08 – 5.46) | 4.04 (3.06 – 5.54) |
| 2 | Admission → death | 7.47 (5.64 – 10.24) | 7.60 (5.62 – 10.42) | 7.64 (5.81 – 10.52) |
| 3 | Admission → discharge | 8.39 (5.34 – 14.60) | 7.70 (4.81 – 13.81) | 7.52 (4.64 – 13.82) |
| 4 | Onset → notification | 17.99 (13.04 – 25.70) | 19.82 (13.64 – 29.94) | 20.43 (13.95 – 31.31) |
Length of stay in hospital
Time from admission to leaving hospital (admission → departure). The overall length of stay is a mixture of the fatal pathway (admission → death) and the survivor pathway (admission → discharge), weighted per posterior draw by the in-hospital fatality among admitted cases — Beta(1 + n_died, 1 + n_discharged) with 22 deaths and 15 discharges. The fatal and survivor rows are the corresponding atomic components; the overall row is the bed-occupancy-relevant marginal across both outcomes.
length_of_stay = DataFrame(
pathway = [
"Fatal (admission → death)",
"Survivor (admission → discharge)",
"Overall (mixture)",
],
median = [fmt(post.median_ad), fmt(post.median_ac), fmt(post.los_median)],
mean = [fmt(post.mean_ad), fmt(post.mean_ac), fmt(post.los_mean)],
sd = ["—", "—", fmt(post.los_sd)],
P95 = ["—", "—", fmt(post.los_p95)],
)| Row | pathway | median | mean | sd | P95 |
|---|---|---|---|---|---|
| String | String | String | String | String | |
| 1 | Fatal (admission → death) | 6.42 (4.47 – 8.70) | 7.60 (5.62 – 10.42) | — | — |
| 2 | Survivor (admission → discharge) | 5.33 (2.82 – 8.87) | 7.70 (4.81 – 13.81) | — | — |
| 3 | Overall (mixture) | 6.05 (4.40 – 7.99) | 7.73 (5.84 – 10.73) | 6.57 (4.54 – 12.32) | 20.15 (14.70 – 33.28) |
Epidemic curve
Weekly onset counts with HCW subcounts stacked.
plot_epi_curve(ll)Early-phase growth rate
An exploratory exponential-growth fit to the rising phase of the weekly onset curve — week 1 (2012-05-28) through the peak week (2012-09-10), inclusive. The model is a Poisson regression log(λ_t) = α + r·t with weakly-informative Normal(0, 5) and Normal(0, 1) priors on α and r_week. Intended as a prior source for downstream re-applications (e.g. the outbreak-size work in epiforecasts/BVDOutbreakSize) rather than as a primary headline of this analysis. The CrI on r covers zero — Isiro was a slow, noisy rise — so use the posterior as a weakly-informative prior, not a tight constraint.
growth = redirect_stdout(devnull) do
fit_growth_rate(ll)
end┌ Info: Found initial step size
└ ϵ = 6.103515625e-6
┌ Info: Found initial step size
└ ϵ = 0.0125Doubling time log(2)/r is reported as a median only; its distribution is heavy-tailed because the posterior on r includes values close to zero. The credibility statement worth quoting is the CrI on r itself.
growth_estimates = DataFrame(
"Quantity" => [
"Growth rate r (per week)",
"Growth rate r (per day)",
"Doubling time (days, median)",
"P(r > 0)",
],
"Posterior summary" => [
fmt(growth.r_week),
fmt(growth.r_day),
@sprintf("%.1f", quantile(growth.doubling_time, 0.5)),
@sprintf("%.2f", mean(growth.r_day .> 0)),
],
)| Row | Quantity | Posterior summary |
|---|---|---|
| String | String | |
| 1 | Growth rate r (per week) | 0.05 (-0.00 – 0.12) |
| 2 | Growth rate r (per day) | 0.01 (-0.00 – 0.02) |
| 3 | Doubling time (days, median) | 86.3 |
| 4 | P(r > 0) | 0.96 |
Recommended downstream prior on r (per day): Normal(mean(r_day), sd(r_day)) from the posterior above. A single-line summary intended for epiforecasts/BVDOutbreakSize and any other downstream model that needs an Isiro-anchored growth prior. See Limitations for caveats.
prior_summary = @sprintf("Normal(%.4f, %.4f)",
mean(growth.r_day),
std(growth.r_day))"Normal(0.0077, 0.0046)"Family comparison
WAIC ranking and convergence diagnostics for each fit. Lower WAIC is better; ΔWAIC is relative to the best family.
families = (:lognormal, :gamma, :weibull)
waics = [results[f].waic.waic for f in families]
best = minimum(waics)
family_comparison = DataFrame(
family = collect(families),
WAIC = round.(waics, digits = 1),
ΔWAIC = round.(waics .- best, digits = 1),
p_waic = round.([results[f].waic.p_waic for f in families], digits = 1),
max_Rhat = round.([results[f].diag.rhat for f in families], digits = 3),
min_ESS = [round(Int, results[f].diag.ess) for f in families],
n_divergent = [results[f].diag.ndiv for f in families],
)| Row | family | WAIC | ΔWAIC | p_waic | max_Rhat | min_ESS | n_divergent |
|---|---|---|---|---|---|---|---|
| Symbol | Float64 | Float64 | Float64 | Float64 | Int64 | Int64 | |
| 1 | lognormal | 6.52838e5 | 6.52046e5 | 3.25844e5 | 1.008 | 3496 | 0 |
| 2 | gamma | 792.4 | 0.0 | 9.2 | 1.007 | 3933 | 2 |
| 3 | weibull | 793.6 | 1.1 | 9.2 | 1.006 | 3762 | 0 |
Side-by-side posterior-predictive comparison across families. One column per family, four rows (one per delay). The LogNormal tail-over-fit on the onset → notification panel is what drives the WAIC penalty. The black dashed line in each panel marks the Rosello et al. 2015 Table 5 empirical mean for that delay (computed after capping the raw delays at 30 days) — the onset → notification gap is the most visible consequence of that cap.
chains_by_family = Dict(f => results[f].chain for f in families)
plot_family_comparison(chains_by_family, d)Derived (convolved) marginals
Onset → death = (Onset → admission) ⊛ (Admission → death); Onset → discharge = (Onset → admission) ⊛ (Admission → discharge). Sampled per posterior draw (500 realisations per draw).
convolved_marginals = DataFrame(
marginal = [
"Onset → death (oa ⊛ ad)",
"Onset → discharge (oa ⊛ ac)",
],
median = [fmt(post.od_median), fmt(post.oc_median)],
mean = [fmt(post.od_mean), fmt(post.oc_mean)],
sd = [fmt(post.od_sd), fmt(post.oc_sd)],
P95 = [fmt(post.od_p95), fmt(post.oc_p95)],
)| Row | marginal | median | mean | sd | P95 |
|---|---|---|---|---|---|
| String | String | String | String | String | |
| 1 | Onset → death (oa ⊛ ad) | 10.48 (8.21 – 13.19) | 11.71 (9.41 – 14.87) | 6.54 (4.92 – 9.33) | 24.10 (19.07 – 32.45) |
| 2 | Onset → discharge (oa ⊛ ac) | 9.69 (6.94 – 13.65) | 11.83 (8.62 – 18.01) | 8.53 (5.65 – 16.84) | 28.25 (19.71 – 51.85) |
Gamma shape, scale and SD per atomic delay
Underlying-distribution parameters for downstream consumers that need to reconstruct each atomic Gamma delay rather than just its central tendency.
gamma_parameters = DataFrame(
"delay" => [
"Onset → admission",
"Admission → death",
"Admission → discharge",
"Onset → notification",
],
"shape (95% CrI)" =>
[fmt(post.shape_oa), fmt(post.shape_ad),
fmt(post.shape_ac), fmt(post.shape_on)],
"scale (95% CrI)" =>
[fmt(post.scale_oa), fmt(post.scale_ad),
fmt(post.scale_ac), fmt(post.scale_on)],
"sd (95% CrI)" =>
[fmt(post.sd_oa), fmt(post.sd_ad),
fmt(post.sd_ac), fmt(post.sd_on)],
)| Row | delay | shape (95% CrI) | scale (95% CrI) | sd (95% CrI) |
|---|---|---|---|---|
| String | String | String | String | |
| 1 | Onset → admission | 1.18 (0.73 – 1.82) | 3.41 (2.08 – 6.22) | 3.70 (2.66 – 5.58) |
| 2 | Admission → death | 2.07 (1.12 – 3.50) | 3.66 (2.06 – 7.32) | 5.26 (3.68 – 8.31) |
| 3 | Admission → discharge | 1.00 (0.50 – 1.91) | 7.63 (3.56 – 21.46) | 7.61 (4.46 – 16.81) |
| 4 | Onset → notification | 0.66 (0.43 – 0.95) | 29.97 (18.13 – 55.77) | 24.36 (16.42 – 39.31) |
Stratified case-fatality
cfr_table = DataFrame(
stratum = [
"Non-HCW, Confirmed (baseline)",
"HCW, Confirmed",
"Non-HCW, Probable",
"HCW, Probable",
],
CFR = [
fmt(post.cfr_baseline),
fmt(post.cfr_hcw_conf),
fmt(post.cfr_nonhcw_prob),
fmt(post.cfr_hcw_prob),
],
)| Row | stratum | CFR |
|---|---|---|
| String | String | |
| 1 | Non-HCW, Confirmed (baseline) | 0.48 (0.30 – 0.65) |
| 2 | HCW, Confirmed | 0.23 (0.09 – 0.46) |
| 3 | Non-HCW, Probable | 0.88 (0.69 – 0.96) |
| 4 | HCW, Probable | 0.70 (0.38 – 0.91) |
Logit-scale coefficients and odds ratios.
function fmt_or(β)
lo, med, hi = qci(β)
log_or = @sprintf("%+.2f (%+.2f, %+.2f)", med, lo, hi)
or = @sprintf("%.2f (%.2f, %.2f)", exp(med), exp(lo), exp(hi))
return log_or, or
end
logit_coefficients = DataFrame(
map(((label, β),) -> begin
log_or, or = fmt_or(β)
(; coefficient = label, log_OR = log_or, OR = or)
end,
[("HCW status", post.β_hcw),
("Probable case definition", post.β_def),
("Standardised age", post.β_age)]),
)| Row | coefficient | log_OR | OR |
|---|---|---|---|
| String | String | String | |
| 1 | HCW status | -1.13 (-2.27, +0.00) | 0.32 (0.10, 1.00) |
| 2 | Probable case definition | +2.06 (+0.91, +3.35) | 7.88 (2.48, 28.50) |
| 3 | Standardised age | -0.29 (-0.91, +0.29) | 0.75 (0.40, 1.33) |
See also
Model — likelihood, priors and post-processing details.
Limitations — known caveats.
API Reference — exported functions.