Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
^.*\.Rproj$
^\.Rproj\.user$
^\.github$
^analysis$
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export(generate_scenario_library)
export(make_stan_init_fn)
export(plot_main_figure)
export(plot_main_figure_split)
export(plot_simulation_study_figure)
export(plot_simulation_study_sens_figure)
export(pre_inference_checks)
export(prepare_stan_data_from_datasets)
export(run_simulation_study_generalized)
Expand Down
127 changes: 127 additions & 0 deletions R/ploting_utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -1524,3 +1524,130 @@ plot_main_figure_split <- function(
)
)
}


# Internal core builder shared by the main and sensitivity simulation figures.
# x_var: column to put on the x-axis ("summary_type_label" or "dist_type").
# facet_var: column to facet by, or NULL for no faceting.
# color_var / shape_var: columns mapped to colour and shape aesthetics.
.sim_figure_core <- function(summary_res, base_size = 9,
x_var = "summary_type_label",
x_lab = "Summary type",
facet_var = "dist_type",
color_var = "n_datasets_bucket",
shape_var = "n_obs_bucket",
color_lab = "N datasets",
shape_lab = "N obs per study") {
dat <- .pred_buckets(summary_res)

.row <- function(data, y_col, ref_line = NULL, y_lab,
hide_x = FALSE, hide_strip = FALSE) {
p <- ggplot(data,
aes(x = .data[[x_var]],
y = .data[[y_col]],
color = .data[[color_var]],
shape = .data[[shape_var]]))

if (!is.null(ref_line))
p <- p + geom_hline(yintercept = ref_line, linetype = "dashed",
color = "red", linewidth = 0.4)

p <- p +
geom_point(alpha = 0.7,
size = 1,
position = position_jitterdodge(jitter.width = 0.1,
dodge.width = 0.4)) +
scale_color_aaas() +
labs(x = if (hide_x) NULL else x_lab,
y = y_lab,
color = color_lab, shape = shape_lab) +
theme_minimal(base_size = base_size) +
theme(
axis.text.x = if (hide_x) element_blank() else element_text(angle = 40, hjust = 1),
axis.ticks.x = if (hide_x) element_blank() else element_line(),
strip.text = if (hide_strip) element_blank() else element_text()
)

if (!is.null(facet_var))
p <- p + facet_wrap(reformulate(facet_var), nrow = 1,
labeller = label_value, scales = "free_x")

return(p)
}

p1 <- .row(dat, "mean_wis", ref_line = NULL, y_lab = "Mean WIS",
hide_x = TRUE, hide_strip = FALSE)
p2 <- .row(dat, "coverage_pred_median", ref_line = 0.95, y_lab = "Coverage - P50",
hide_x = TRUE, hide_strip = TRUE)
p3 <- .row(dat, "bias_pred_median", ref_line = 0, y_lab = "Bias - P50 (days)",
hide_x = TRUE, hide_strip = TRUE)
p4 <- .row(dat, "coverage_pred_q95", ref_line = 0.95, y_lab = "Coverage - P95",
hide_x = TRUE, hide_strip = TRUE)
p5 <- .row(dat, "bias_pred_q95", ref_line = 0, y_lab = "Bias - P95 (days)",
hide_x = FALSE, hide_strip = TRUE)

patchwork::wrap_plots(p1, p2, p3, p4, p5, ncol = 1) +
patchwork::plot_layout(guides = "collect") +
patchwork::plot_annotation(tag_levels = "A") &
theme(legend.position = "bottom",
plot.tag = element_text(face = "bold"))
}

#' Main-text simulation study figure
#'
#' Assembles a five-row composite figure from the simulation study summary,
#' excluding sensitivity scenarios (\code{TauSens} / \code{Mu0Sens}).
#'
#' @param summary_res A data frame returned by \code{create_results_summary()}.
#' @param base_size Base font size passed to \code{theme_minimal()}.
#' @return A \code{patchwork} object.
#' @export
plot_simulation_study_figure <- function(summary_res, base_size = 9) {
summary_res <- summary_res %>%
filter(!grepl("TauSens|Mu0Sens", scenario_name))
fig <- .sim_figure_core(summary_res, base_size = base_size)
fig & scale_shape_manual(values = c("5" = 4, "10" = 3, "20" = 8, "25+" = 5))
}

#' Supplementary simulation study figure — sensitivity scenarios
#'
#' Same layout as \code{plot_simulation_study_figure()} but restricted to
#' \code{TauSens} and \code{Mu0Sens} scenarios.
#'
#' @param summary_res A data frame returned by \code{create_results_summary()}.
#' @param base_size Base font size passed to \code{theme_minimal()}.
#' @return A \code{patchwork} object.
#' @export
plot_simulation_study_sens_figure <- function(summary_res, base_size = 9) {
Comment thread
cm401 marked this conversation as resolved.
summary_res <- summary_res %>%
filter(grepl("TauSens|Mu0Sens", scenario_name)) %>%
mutate(
sens_type = if_else(grepl("TauSens", scenario_name),
"τ sensitivity", "μ₀ sensitivity"),
sens_value = case_when(
grepl("TauSens", scenario_name) ~
paste0("τ = ", stringr::str_extract(scenario_name, "(?<=tau)[0-9.]+")),
TRUE ~
stringr::str_replace(
scenario_name,
"^Mu0Sens_(?:lognormal|gamma|weibull|burr12|gengamma)_(.+)_D\\d+.*",
"μ₀ = \\1"
)
)
) %>%
mutate(
sens_value = forcats::fct_reorder(sens_value, grepl("μ", sens_value))
)

fig <- .sim_figure_core(summary_res, base_size = base_size,
x_var = "dist_type",
x_lab = "Distribution",
facet_var = NULL,
color_var = "sens_value",
shape_var = "sens_type",
color_lab = "Parameter value",
shape_lab = "Sensitivity")

fig & guides(color = guide_legend(nrow = 2, byrow = FALSE),
shape = guide_legend(nrow = 2, byrow = FALSE))
}
2 changes: 1 addition & 1 deletion R/table_utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,7 @@ generate_data_table <- function(
) {
stopifnot(is.list(datasets), length(datasets) > 0L, !is.null(names(datasets)))

col_spec <- "|C{4cm}|C{2.0cm}|C{2.5cm}|R{0.7cm}|C{5.5cm}|C{6cm}|"
col_spec <- "|C{4cm}|C{2.0cm}|C{2.5cm}|R{0.7cm}|C{7.3cm}|C{6cm}|"

header_cells <- paste(
"\\textbf{Reference}",
Expand Down
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,31 @@
[![lifecycle](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental)
<!--[ ![DOI](https://zenodo.org/badge/DOI/XYZ)](https://doi.org/XYZ) -->

Methods to compute delay distributions from summary statistics
Bayesian hierarchical synthesis of incubation period distributions from individual-level data and published summary statistics.

<br clear="right"/>

## Overview

The incubation period -- the interval between pathogen exposure and symptom onset -- is a critical parameter for quarantine policy and outbreak response, yet individual-level exposure data remain scarce in the published literature. For most pathogens, only summary statistics are available, and restricting inference to individual-level data alone would leave too few datasets for reliable between-study heterogeneity estimation.

**ddsynth** introduces a Bayesian hierarchical framework that jointly models individual-level observations and published summary statistics under a unified federated analysis approach. Key features:

- Accurately recovers incubation period distributions across a range of data availability scenarios
- Outperforms approaches that use summary data alone
- Quantifies between-study heterogeneity, including by outbreak country, pathogen variant, and exposure pathway
- Covers 18 pathogens with outbreak potential, spanning six pathogen groups

## Dataset coverage

The package includes a curated dataset of incubation period studies spanning outbreaks worldwide:

<img src="man/figures/map_figure.png" width="100%" alt="Geographic distribution of incubation period datasets across 18 pathogens" />

Datasets cover six pathogen groups:

<img src="man/figures/pathogen_grouping.png" width="100%" alt="Pathogen grouping diagram showing six disease categories with WHO R&D Blueprint priority pathogens highlighted" />

## Prerequisites

### R
Expand Down
Loading
Loading