diff --git a/.Rbuildignore b/.Rbuildignore index b6f3df67..12731618 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -15,3 +15,4 @@ ^data-raw$ ^CITATION\.cff$ ^_pkgdown\.yml$ +^CRAN-SUBMISSION$ diff --git a/.github/settings.yml b/.github/settings.yml new file mode 100644 index 00000000..38bde780 --- /dev/null +++ b/.github/settings.yml @@ -0,0 +1,22 @@ +repository: + # https://probot.github.io/apps/settings/ + allow_merge_commit: false + allow_rebase_merge: true + allow_squash_merge: true + default_branch: main + delete_branch_on_merge: true + # has_discussions: false + has_issues: true + # has_projects: false + # has_wiki: false + # private: false +branches: + - name: main + # https://docs.github.com/en/rest/reference/repos#update-branch-protection + protection: + required_pull_request_reviews: + required_approving_review_count: 0 # (1-6; optionally 0) + dismiss_stale_reviews: true + require_code_owner_reviews: false + required_status_checks: + strict: true diff --git a/.github/workflows/lint-changed-files.yaml b/.github/workflows/lint-changed-files.yaml index f9aa5c54..cf5c34b6 100644 --- a/.github/workflows/lint-changed-files.yaml +++ b/.github/workflows/lint-changed-files.yaml @@ -34,6 +34,7 @@ jobs: any::gh any::lintr any::purrr + any::cyclocomp epiverse-trace/etdev needs: check diff --git a/.lintr b/.lintr index c5c32d8c..9670d32b 100644 --- a/.lintr +++ b/.lintr @@ -1,5 +1,6 @@ linters: all_linters( packages = c("lintr", "etdev"), + pipe_consistency_linter(pipe = "%>%"), object_name_linter = NULL, implicit_integer_linter = NULL, extraction_operator_linter = NULL, diff --git a/CRAN-SUBMISSION b/CRAN-SUBMISSION new file mode 100644 index 00000000..38a0c860 --- /dev/null +++ b/CRAN-SUBMISSION @@ -0,0 +1,3 @@ +Version: 0.1.1 +Date: 2024-10-09 11:55:04 UTC +SHA: cefe3fcfbce1b012b86cae03cf55b3ec90fff852 diff --git a/DESCRIPTION b/DESCRIPTION index 969c6bbe..f7fdff6f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,11 +1,11 @@ Package: epichains Title: Simulating and Analysing Transmission Chain Statistics Using Branching Process Models -Version: 0.1.0.9000 +Version: 0.1.1 Authors@R: c( person("James M.", "Azam", , "james.azam@lshtm.ac.uk", role = c("aut", "cre", "cph"), comment = c(ORCID = "0000-0001-5782-7330")), - person("Sebastian", "Funk", , "sebastian.funk@lshtm.ac.uk", role = "aut", + person("Sebastian", "Funk", , "sebastian.funk@lshtm.ac.uk", role = c("aut", "cph"), comment = c(ORCID = "0000-0002-2842-3406")), person("Flavio", "Finger", , "flavio.finger@epicentre.msf.org", role = "aut", comment = c(ORCID = "0000-0002-8613-5170")), @@ -54,4 +54,4 @@ Encoding: UTF-8 Language: en-GB LazyData: true Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.1 +RoxygenNote: 7.3.2.9000 diff --git a/NEWS.md b/NEWS.md index b36fca2f..9f752bbf 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,6 @@ -# epichains (development version) +# epichains 0.1.1 + +This is the first CRAN release of `{epichains}`. ## Package @@ -8,7 +10,7 @@ We are excited to announce the first minor release of `{epichains}`. -`{epichains}` re-implements [{bpmodels}](https://github.com/epiverse-trace/bpmodels), focusing on a unified simulation framework using branching processes to simulate transmission chains data. The framework incorporates susceptible depletion and pre-existing immunity and provides dedicated data structures for handling and analysing transmission chains in both tabular and vector formats. The goal is to provide seamless interoperability with other packages within the Epiverse-TRACE Initiative and the broader epidemiological tool ecosystem. +`{epichains}` re-implements [{bpmodels}](https://github.com/epiforecasts/bpmodels/), focusing on a unified simulation framework using branching processes to simulate transmission chains data. The framework incorporates susceptible depletion and pre-existing immunity and provides dedicated data structures for handling and analysing transmission chains in both tabular and vector formats. The goal is to provide seamless interoperability with other packages within the Epiverse-TRACE Initiative and the broader epidemiological tool ecosystem. ## New Features diff --git a/R/borel.R b/R/borel.R index 3e73ba38..79991922 100644 --- a/R/borel.R +++ b/R/borel.R @@ -4,17 +4,22 @@ #' @param mu A non-negative number for the poisson mean. #' @param log Logical; if TRUE, probabilities p are given as log(p). #' @return A numeric vector of the borel probability density. -#' @author Sebastian Funk +#' @author Sebastian Funk James M. Azam #' @export #' @examples #' set.seed(32) #' dborel(1:5, 1) dborel <- function(x, mu, log = FALSE) { checkmate::assert_numeric( - x, lower = 1, upper = Inf + x, + lower = 1, + upper = Inf ) checkmate::assert_number( - mu, lower = 0, finite = TRUE, na.ok = FALSE + mu, + lower = 0, + finite = TRUE, + na.ok = FALSE ) ld <- -mu * x + (x - 1) * log(mu * x) - lgamma(x + 1) @@ -34,17 +39,23 @@ dborel <- function(x, mu, log = FALSE) { #' `mu >= 1`, the simulation could proceed unendingly. This parameter is used #' to prevent this. #' @return A numeric vector of random numbers. -#' @author Sebastian Funk +#' @author Sebastian Funk James M. Azam #' @export #' @examples #' set.seed(32) #' rborel(5, 1) rborel <- function(n, mu, censor_at = Inf) { checkmate::assert_number( - n, lower = 1, finite = TRUE, na.ok = FALSE + n, + lower = 1, + finite = TRUE, + na.ok = FALSE ) checkmate::assert_number( - mu, lower = 0, finite = TRUE, na.ok = FALSE + mu, + lower = 0, + finite = TRUE, + na.ok = FALSE ) # Run simulations out <- simulate_chain_stats( @@ -69,7 +80,7 @@ rborel <- function(n, mu, censor_at = Inf) { #' 0 and 1. #' @param mu Mean; A positive number. #' @return Numeric vector of random numbers -#' @author Sebastian Funk +#' @author Sebastian Funk James M. Azam #' @export #' @examples #' set.seed(32) @@ -81,16 +92,22 @@ rgborel <- function(n, size, prob, mu, censor_at = Inf) { # its "correct implementation" for documentation/clarity purposes, as well as # for simulations. checkmate::assert_number( - size, finite = TRUE, lower = 0 + size, + finite = TRUE, + lower = 0 ) if (!missing(prob)) { checkmate::assert_number( - prob, lower = 0, upper = 1 + prob, + lower = 0, + upper = 1 ) } if (!missing(mu)) { checkmate::assert_number( - mu, finite = TRUE, lower = 0 + mu, + finite = TRUE, + lower = 0 ) } if (!missing(prob)) { @@ -101,6 +118,7 @@ rgborel <- function(n, size, prob, mu, censor_at = Inf) { x <- rgamma(n, shape = size, rate = size / mu) # then, sample from borel return(vapply( - x, rborel, n = 1, censor_at = censor_at, FUN.VALUE = numeric(1) + x, rborel, + n = 1, censor_at = censor_at, FUN.VALUE = numeric(1) )) } diff --git a/R/checks.R b/R/checks.R index 2fb2a0dc..e67e292b 100644 --- a/R/checks.R +++ b/R/checks.R @@ -72,9 +72,9 @@ # check that stat_threshold is an integer or Inf. checkmate::assert( checkmate::anyInfinite(stat_threshold), - checkmate::check_integerish( - stat_threshold, - lower = 1 + checkmate::check_integerish( + stat_threshold, + lower = 1 ), combine = "or" ) @@ -102,7 +102,7 @@ if (!is.null(generation_time)) { .check_generation_time_valid(generation_time) } else if (tf_specified) { - stop("If `tf` is specified, `generation_time` must be specified too.") + stop("If `tf` is specified, `generation_time` must be specified too.") } checkmate::assert_number( tf, diff --git a/R/epichains.R b/R/epichains.R index 1eed2b6a..ee2b4dc3 100644 --- a/R/epichains.R +++ b/R/epichains.R @@ -16,11 +16,11 @@ #' @author James M. Azam #' @keywords internal .new_epichains <- function(sim_df, - n_chains, - statistic, - offspring_dist, - stat_threshold, - track_pop) { + n_chains, + statistic, + offspring_dist, + stat_threshold, + track_pop) { # Assemble the elements of the object obj <- sim_df class(obj) <- c("epichains", class(obj)) @@ -55,11 +55,11 @@ #' @author James M. Azam #' @keywords internal .epichains <- function(sim_df, - n_chains, - offspring_dist, - track_pop, - statistic = c("size", "length"), - stat_threshold = Inf) { + n_chains, + offspring_dist, + track_pop, + statistic = c("size", "length"), + stat_threshold = Inf) { # Check that inputs are well specified checkmate::assert_data_frame(sim_df, min.cols = 3, min.rows = n_chains) checkmate::assert_integerish( @@ -112,10 +112,10 @@ #' @author James M. Azam #' @keywords internal .new_epichains_summary <- function(chains_summary, - n_chains, - statistic, - offspring_dist, - stat_threshold) { + n_chains, + statistic, + offspring_dist, + stat_threshold) { # Assemble the elements of the object obj <- chains_summary class(obj) <- c("epichains_summary", class(chains_summary)) @@ -141,10 +141,10 @@ #' @author James M. Azam #' @keywords internal .epichains_summary <- function(chains_summary, - n_chains, - offspring_dist, - statistic = c("size", "length"), - stat_threshold = Inf) { + n_chains, + offspring_dist, + statistic = c("size", "length"), + stat_threshold = Inf) { # chain_summary can sometimes contain infinite values, so check # that finite elements are integerish. checkmate::check_integerish( @@ -194,12 +194,12 @@ #' # population up to chain size 10. #' set.seed(32) #' chains_pois_offspring <- simulate_chains( -#' n_chains = 10, -#' statistic = "size", -#' offspring_dist = rpois, -#' stat_threshold = 10, -#' generation_time = function(n) rep(3, n), -#' lambda = 2 +#' n_chains = 10, +#' statistic = "size", +#' offspring_dist = rpois, +#' stat_threshold = 10, +#' generation_time = function(n) rep(3, n), +#' lambda = 2 #' ) #' chains_pois_offspring # Print the object print.epichains <- function(x, ...) { @@ -226,11 +226,11 @@ print.epichains <- function(x, ...) { #' # population up to chain size 10. #' set.seed(32) #' chain_summary_print_eg <- simulate_chain_stats( -#' n_chains = 10, -#' statistic = "size", -#' offspring_dist = rpois, -#' stat_threshold = 10, -#' lambda = 2 +#' n_chains = 10, +#' statistic = "size", +#' offspring_dist = rpois, +#' stat_threshold = 10, +#' lambda = 2 #' ) #' chain_summary_print_eg # Print the object print.epichains_summary <- function(x, ...) { @@ -322,9 +322,9 @@ format.epichains_summary <- function(x, ...) { "Max: %s", ifelse( is.infinite( - statistics[["max_stat"]]), - paste0(">=", attr(x, "stat_threshold") + statistics[["max_stat"]] ), + paste0(">=", attr(x, "stat_threshold")), statistics[["max_stat"]] ) ), @@ -332,9 +332,9 @@ format.epichains_summary <- function(x, ...) { "Min: %s", ifelse( is.infinite( - statistics[["min_stat"]]), - paste0(">=", attr(x, "stat_threshold") + statistics[["min_stat"]] ), + paste0(">=", attr(x, "stat_threshold")), statistics[["min_stat"]] ) ) @@ -455,11 +455,11 @@ summary.epichains <- function(object, ...) { #' # population up to chain size 10. #' set.seed(32) #' chain_stats <- simulate_chain_stats( -#' n_chains = 10, -#' statistic = "size", -#' offspring_dist = rpois, -#' stat_threshold = 10, -#' lambda = 2 +#' n_chains = 10, +#' statistic = "size", +#' offspring_dist = rpois, +#' stat_threshold = 10, +#' lambda = 2 #' ) #' summary(chain_stats) summary.epichains_summary <- function(object, ...) { @@ -526,7 +526,7 @@ summary.epichains_summary <- function(object, ...) { stopifnot( "object does not contain the correct columns" = c("chain", "infector", "infectee", "generation") %in% - colnames(x), + colnames(x), "column `chain` must be a numeric" = is.numeric(x$chain), "column `infector` must be a numeric" = @@ -643,11 +643,11 @@ tail.epichains <- function(x, ...) { #' cases_per_gen <- aggregate(chains, by = "generation") #' head(cases_per_gen) aggregate.epichains <- function(x, - by = c( - "time", - "generation" - ), - ...) { + by = c( + "time", + "generation" + ), + ...) { .validate_epichains(x) checkmate::assert_string(by) # Get grouping variable diff --git a/R/helpers.R b/R/helpers.R index bc5e49ee..62763864 100644 --- a/R/helpers.R +++ b/R/helpers.R @@ -10,8 +10,7 @@ #' @keywords internal .update_chain_stat <- function(stat_type, stat_latest, n_offspring) { return( - switch( - stat_type, + switch(stat_type, size = stat_latest + n_offspring, length = stat_latest + pmin(1, n_offspring), stop("stat_type must be 'size' or 'length'") @@ -27,8 +26,7 @@ #' @keywords internal .get_statistic_func <- function(chain_statistic) { return( - switch( - chain_statistic, + switch(chain_statistic, size = .rbinom_size, length = .rgen_length, stop("chain_statistic must be 'size' or 'length'") @@ -73,7 +71,6 @@ offspring_func_pars, n_offspring, chains) { - possible_new_offspring <- do.call( offspring_func, c( @@ -104,8 +101,8 @@ #' given the current susceptible population size. #' @keywords internal .get_susceptible_offspring <- function(new_offspring, - susc_pop, - pop) { + susc_pop, + pop) { # We first adjust for the case where susceptible can be Inf but prob can only # be maximum 1. binom_prob <- min(1, susc_pop / pop, na.rm = TRUE) diff --git a/R/likelihood.R b/R/likelihood.R index 07497803..f52b101b 100644 --- a/R/likelihood.R +++ b/R/likelihood.R @@ -61,16 +61,16 @@ #' # Example using an object #' set.seed(32) #' epichains_obj_eg <- simulate_chains( -#' n_chains = 10, -#' pop = 100, -#' percent_immune = 0, -#' statistic = "size", -#' offspring_dist = rnbinom, -#' stat_threshold = 10, -#' generation_time = function(n) rep(3, n), -#' mu = 2, -#' size = 0.2 -#') +#' n_chains = 10, +#' pop = 100, +#' percent_immune = 0, +#' statistic = "size", +#' offspring_dist = rnbinom, +#' stat_threshold = 10, +#' generation_time = function(n) rep(3, n), +#' mu = 2, +#' size = 0.2 +#' ) #' #' epichains_obj_eg_lik <- likelihood( #' chains = epichains_obj_eg, @@ -103,7 +103,7 @@ #' ) #' epichains_summary_eg_lik #' @export -#nolint start: cyclocomp_linter +# nolint start: cyclocomp_linter likelihood <- function(chains, statistic = c("size", "length"), offspring_dist, nsim_obs, obs_prob = 1, stat_threshold = Inf, log = TRUE, exclude = NULL, individual = FALSE, ...) { @@ -112,7 +112,8 @@ likelihood <- function(chains, statistic = c("size", "length"), offspring_dist, ## Input checking checkmate::assert( checkmate::check_numeric( - chains, lower = 0, upper = Inf, any.missing = FALSE + chains, + lower = 0, upper = Inf, any.missing = FALSE ), checkmate::check_class( chains, "epichains" @@ -128,16 +129,20 @@ likelihood <- function(chains, statistic = c("size", "length"), offspring_dist, ) .check_offspring_func_valid(offspring_dist) checkmate::assert_number( - obs_prob, lower = 0, upper = 1, finite = TRUE, na.ok = FALSE + obs_prob, + lower = 0, upper = 1, finite = TRUE, na.ok = FALSE ) checkmate::assert_logical( - log, any.missing = FALSE, all.missing = FALSE, len = 1 + log, + any.missing = FALSE, all.missing = FALSE, len = 1 ) checkmate::assert_logical( - individual, any.missing = FALSE, all.missing = FALSE, len = 1 + individual, + any.missing = FALSE, all.missing = FALSE, len = 1 ) checkmate::assert_numeric( - exclude, null.ok = TRUE + exclude, + null.ok = TRUE ) # likelihood is designed to work with numeric objects to objects # need to be coerced to objects (numeric vector under @@ -167,16 +172,16 @@ likelihood <- function(chains, statistic = c("size", "length"), offspring_dist, # censor the chains to be at most stat_threshold chains <- pmin(chains, stat_threshold) } else if (any(is.infinite(chains)) && !.is_epichains_summary(chains)) { - # Developer note: objects also pass the`is.numeric()` - # check, so we need to check if the object is not an - # object. - # Numeric chains can only contain Inf values based on human error/decision - # to censor it with infinite values, so we ask the user to fix that. - stop( - "`chains` must be censored with a finite value. ", - "Replace the `Inf` values with a finite value.", - call. = FALSE - ) + # Developer note: objects also pass the`is.numeric()` + # check, so we need to check if the object is not an + # object. + # Numeric chains can only contain Inf values based on human error/decision + # to censor it with infinite values, so we ask the user to fix that. + stop( + "`chains` must be censored with a finite value. ", + "Replace the `Inf` values with a finite value.", + call. = FALSE + ) } # Apply the observation process @@ -185,7 +190,8 @@ likelihood <- function(chains, statistic = c("size", "length"), offspring_dist, stop("'nsim_obs' must be specified if 'obs_prob' is < 1") } else { checkmate::assert_integerish( - nsim_obs, lower = 1 + nsim_obs, + lower = 1 ) } @@ -228,8 +234,9 @@ likelihood <- function(chains, statistic = c("size", "length"), offspring_dist, ## calculate log-likelihoods possible_internal_func <- paste0(".", ll_func) if (exists(possible_internal_func, - where = asNamespace("epichains"), - mode = "function") + where = asNamespace("epichains"), + mode = "function" + ) ) { func <- get(possible_internal_func) likelihoods[calc_sizes] <- do.call(func, c(list(x = calc_sizes), pars)) @@ -259,8 +266,7 @@ likelihood <- function(chains, statistic = c("size", "length"), offspring_dist, likelihoods[exclude] <- -Inf stat_rep_list <- lapply(stat_rep_list, function(y) { y[!(y %in% exclude)] - } - ) + }) } ## assign likelihoods @@ -285,4 +291,4 @@ likelihood <- function(chains, statistic = c("size", "length"), offspring_dist, return(chains_likelihood) } -#nolint end +# nolint end diff --git a/R/simulate.R b/R/simulate.R index 22a2c017..7d9c9fca 100644 --- a/R/simulate.R +++ b/R/simulate.R @@ -364,9 +364,9 @@ simulate_chains <- function(n_chains, #' the `likelihood()` function and for sampling from the borel #' distribution (See ?epichains::rborel). It is used extensively in the #' vignette on -#nolint start +# nolint start #' [modelling disease control](https://epiverse-trace.github.io/epichains/articles/interventions.html), -#nolint end +# nolint end #' where only data on observed chain sizes and lengths are available. #' @author James M. Azam, Sebastian Funk #' @examples @@ -395,12 +395,12 @@ simulate_chains <- function(n_chains, #' ) #' @export simulate_chain_stats <- function(n_chains, - statistic = c("size", "length"), - offspring_dist, - ..., - stat_threshold = Inf, - pop = Inf, - percent_immune = 0) { + statistic = c("size", "length"), + offspring_dist, + ..., + stat_threshold = Inf, + pop = Inf, + percent_immune = 0) { # Check offspring and population-related arguments .check_sim_args( n_chains = n_chains, diff --git a/R/stat_likelihoods.R b/R/stat_likelihoods.R index 4ace67dc..aa7f5223 100644 --- a/R/stat_likelihoods.R +++ b/R/stat_likelihoods.R @@ -3,14 +3,18 @@ #' @param x A numeric vector of sizes #' @param lambda The rate of the Poisson distribution; A single numeric value. #' @return A numeric vector of log-likelihood values. -#' @author Sebastian Funk +#' @author Sebastian Funk James M. Azam #' @keywords internal .pois_size_ll <- function(x, lambda) { checkmate::assert_numeric( - x, lower = 0, any.missing = FALSE + x, + lower = 0, + any.missing = FALSE ) checkmate::assert_number( - lambda, finite = TRUE, lower = 0 + lambda, + finite = TRUE, + lower = 0 ) out <- (x - 1) * log(lambda) - lambda * x + (x - 2) * log(x) - lgamma(x) @@ -23,23 +27,31 @@ #' @param x A numeric vector of chain sizes. #' @inheritParams rgborel #' @return A numeric vector of log-likelihood values. -#' @author Sebastian Funk +#' @author Sebastian Funk James M. Azam #' @keywords internal .nbinom_size_ll <- function(x, size, prob, mu) { checkmate::assert_numeric( - x, lower = 0, any.missing = FALSE + x, + lower = 0, + any.missing = FALSE ) checkmate::assert_number( - size, finite = TRUE, lower = 0 + size, + finite = TRUE, + lower = 0 ) if (!missing(prob)) { checkmate::assert_number( - prob, lower = 0, upper = 1 + prob, + lower = 0, + upper = 1 ) } if (!missing(mu)) { checkmate::assert_number( - mu, finite = TRUE, lower = 0 + mu, + finite = TRUE, + lower = 0 ) } if (!missing(prob)) { @@ -57,23 +69,31 @@ #' @param x A numeric vector of chain sizes. #' @inheritParams rgborel #' @return A numeric vector of log-likelihood values. -#' @author Sebastian Funk +#' @author Sebastian Funk James M. Azam #' @keywords internal .gborel_size_ll <- function(x, size, prob, mu) { checkmate::assert_numeric( - x, lower = 0, any.missing = FALSE + x, + lower = 0, + any.missing = FALSE ) checkmate::assert_number( - size, finite = TRUE, lower = 0 + size, + finite = TRUE, + lower = 0 ) if (!missing(prob)) { checkmate::assert_number( - prob, lower = 0, upper = 1 + prob, + lower = 0, + upper = 1 ) } if (!missing(mu)) { checkmate::assert_number( - mu, finite = TRUE, lower = 0 + mu, + finite = TRUE, + lower = 0 ) } @@ -93,14 +113,18 @@ #' @param lambda The rate of the Poisson distribution. A single numeric value. #' Must be positive. #' @return A numeric vector of log-likelihood values. -#' @author Sebastian Funk +#' @author Sebastian Funk James M. Azam #' @keywords internal .pois_length_ll <- function(x, lambda) { checkmate::assert_numeric( - x, lower = 0, any.missing = FALSE + x, + lower = 0, + any.missing = FALSE ) checkmate::assert_number( - lambda, finite = TRUE, lower = 0 + lambda, + finite = TRUE, + lower = 0 ) ## iterated exponential function @@ -120,14 +144,17 @@ #' @param prob The probability of the geometric distribution with mean #' \code{1/prob}. A single numeric value between 0 and 1. #' @return A numeric vector of log-likelihood values. -#' @author Sebastian Funk +#' @author Sebastian Funk James M. Azam #' @keywords internal .geom_length_ll <- function(x, prob) { checkmate::assert_numeric( - x, lower = 0, any.missing = FALSE + x, + lower = 0, any.missing = FALSE ) checkmate::assert_number( - prob, lower = 0, upper = 1 + prob, + lower = 0, + upper = 1 ) lambda <- 1 / prob @@ -152,13 +179,15 @@ #' (size/length) #' @param ... any parameters to pass to \code{\link{simulate_chain_stats}} #' @return A numeric vector of log-likelihood values. -#' @author Sebastian Funk +#' @author Sebastian Funk James M. Azam #' @keywords internal .offspring_ll <- function(x, offspring_dist, statistic, - nsim_offspring = 100, ...) { + nsim_offspring = 100, ...) { # Input checking checkmate::assert_numeric( - x, lower = 0, any.missing = FALSE + x, + lower = 0, + any.missing = FALSE ) # Remaining checks are done in simulate_chain_stats() # Simulate the chains diff --git a/R/utils.R b/R/utils.R index 7981064c..40f1bf2c 100644 --- a/R/utils.R +++ b/R/utils.R @@ -7,7 +7,9 @@ #' @keywords internal .complementary_logprob <- function(x) { checkmate::assert_numeric( - x, lower = -Inf, upper = 0 + x, + lower = -Inf, + upper = 0 ) out <- tryCatch(log1p(-sum(exp(x))), error = function(e) -Inf) diff --git a/README.Rmd b/README.Rmd index 7fe3603c..d777336d 100644 --- a/README.Rmd +++ b/README.Rmd @@ -36,7 +36,7 @@ models are often used in infectious disease epidemiology, where the chains repre transmission, and the offspring distribution represents the distribution of secondary infections caused by an infected individual. -_{{ packagename }}_ re-implements [bpmodels](https://github.com/epiverse-trace/bpmodels/) +_{{ packagename }}_ re-implements [bpmodels](https://github.com/epiforecasts/bpmodels/) by providing bespoke functions and data structures that allow easy manipulation and interoperability with other Epiverse-TRACE packages, for example, [superspreading](https://github.com/epiverse-trace/superspreading/) and [epiparameter](https://github.com/epiverse-trace/epiparameter/), and potentially some existing packages for handling transmission chains, for example, [epicontacts](https://github.com/reconhub/epicontacts). @@ -44,6 +44,12 @@ _{{ packagename }}_ is developed at the [Centre for the Mathematical Modelling o ## Installation +Install the released version of the package: + +```{r install_cran, include=TRUE,eval=FALSE} +install.packages("{{ packagename }}") +``` + The latest development version of the _{{ packagename }}_ package can be installed via ```{r install_with_remotes, include=TRUE,eval=FALSE} @@ -65,7 +71,7 @@ If both of these options fail, please [file an issue](https://github.com/epivers To load the package, use ```{r load_pkg, eval=TRUE} -library("epichains") +library("{{ packagename }}") ``` ## Quick start @@ -118,7 +124,9 @@ sim_chains <- simulate_chains( statistic = "size", offspring_dist = rpois, stat_threshold = 25, - generation_time = function(n) {rep(3, n)}, # constant generation time of 3 + generation_time = function(n) { + rep(3, n) + }, # constant generation time of 3 lambda = 1 # mean of the Poisson distribution ) # View the head of the simulation @@ -178,9 +186,9 @@ processes and transmission chains. Click to expand -* [bpmodels](https://github.com/epiverse-trace/bpmodels): provides methods +* [bpmodels](https://github.com/epiforecasts/bpmodels/): provides methods for analysing the size and length of transmission chains from branching -process models. `{epichains}` is intended to supersede `{bpmodels}`. +process models. `{epichains}` supersedes `{bpmodels}`, which has been retired. * [ringbp](https://github.com/epiforecasts/ringbp): a branching process model, parameterised to the 2019-nCoV outbreak, and used to quantify @@ -225,10 +233,6 @@ stored as a `data.frame` with the class `outbreak`, including information on transmission chains; the output can be converted to `` objects for visualisation. -* [outbreakr](https://sites.google.com/site/therepiproject/r-pac/outbreaker): -implements a Bayesian approach for reconstructing outbreak data from -pathogen genome sequences. It also implements a tool for outbreak simulation. - * [outbreakr2](https://github.com/reconhub/outbreaker2): a Bayesian framework for integrating epidemiological and genetic data to reconstruct transmission trees of densely sampled outbreaks. It re-implements, diff --git a/README.md b/README.md index d32e6316..debc5815 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ distribution represents the distribution of secondary infections caused by an infected individual. *epichains* re-implements -[bpmodels](https://github.com/epiverse-trace/bpmodels/) by providing +[bpmodels](https://github.com/epiforecasts/bpmodels/) by providing bespoke functions and data structures that allow easy manipulation and interoperability with other Epiverse-TRACE packages, for example, [superspreading](https://github.com/epiverse-trace/superspreading/) and @@ -42,6 +42,12 @@ at the London School of Hygiene and Tropical Medicine as part of the ## Installation +Install the released version of the package: + +``` r +install.packages("epichains") +``` + The latest development version of the *epichains* package can be installed via @@ -125,7 +131,9 @@ sim_chains <- simulate_chains( statistic = "size", offspring_dist = rpois, stat_threshold = 25, - generation_time = function(n) {rep(3, n)}, # constant generation time of 3 + generation_time = function(n) { + rep(3, n) + }, # constant generation time of 3 lambda = 1 # mean of the Poisson distribution ) # View the head of the simulation @@ -137,9 +145,6 @@ head(sim_chains) #> 24 3 1 3 2 3 #> 25 4 1 2 2 3 #> 26 6 1 2 2 3 -``` - -``` r # Summarise the simulation summary(sim_chains) @@ -152,9 +157,6 @@ summary(sim_chains) #> #> Max: >=25 #> Min: 1 -``` - -``` r # Aggregate the simulation into cases per generation chains_agregegated <- aggregate(sim_chains, by = "generation") @@ -188,9 +190,6 @@ set.seed(32) chain_lengths <- sample(1:40, 20, replace = TRUE) chain_lengths #> [1] 6 11 20 9 40 33 39 27 6 12 39 35 9 25 6 15 12 6 37 35 -``` - -``` r # estimate loglikelihood of the observed chain sizes likelihood_eg <- likelihood( @@ -232,10 +231,10 @@ branching processes and transmission chains. Click to expand -- [bpmodels](https://github.com/epiverse-trace/bpmodels): provides +- [bpmodels](https://github.com/epiforecasts/bpmodels/): provides methods for analysing the size and length of transmission chains from - branching process models. `{epichains}` is intended to supersede - `{bpmodels}`. + branching process models. `{epichains}` supersedes `{bpmodels}`, which + has been retired. - [ringbp](https://github.com/epiforecasts/ringbp): a branching process model, parameterised to the 2019-nCoV outbreak, and used to quantify @@ -282,11 +281,6 @@ Click to expand information on transmission chains; the output can be converted to `` objects for visualisation. -- [outbreakr](https://sites.google.com/site/therepiproject/r-pac/outbreaker): - implements a Bayesian approach for reconstructing outbreak data from - pathogen genome sequences. It also implements a tool for outbreak - simulation. - - [outbreakr2](https://github.com/reconhub/outbreaker2): a Bayesian framework for integrating epidemiological and genetic data to reconstruct transmission trees of densely sampled outbreaks. It @@ -337,7 +331,7 @@ citation("epichains") #> #> Azam J, Funk S, Finger F (2024). _epichains: Simulating and Analysing #> Transmission Chain Statistics Using Branching Process Models_. R -#> package version 0.1.0, https://epiverse-trace.github.io/epichains/, +#> package version 0.1.1, https://epiverse-trace.github.io/epichains/, #> . #> #> A BibTeX entry for LaTeX users is @@ -347,7 +341,7 @@ citation("epichains") #> Branching Process Models}, #> author = {James M. Azam and Sebastian Funk and Flavio Finger}, #> year = {2024}, -#> note = {R package version 0.1.0, +#> note = {R package version 0.1.1, #> https://epiverse-trace.github.io/epichains/}, #> url = {https://github.com/epiverse-trace/epichains}, #> } diff --git a/cran-comments.md b/cran-comments.md new file mode 100644 index 00000000..858617db --- /dev/null +++ b/cran-comments.md @@ -0,0 +1,5 @@ +## R CMD check results + +0 errors | 0 warnings | 1 note + +* This is a new release. diff --git a/inst/WORDLIST b/inst/WORDLIST index 87574536..48f9485e 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -60,3 +60,6 @@ svg th truncdist unnormalised +Farrington +et +al diff --git a/man/dborel.Rd b/man/dborel.Rd index 3aada775..6b402060 100644 --- a/man/dborel.Rd +++ b/man/dborel.Rd @@ -24,5 +24,5 @@ set.seed(32) dborel(1:5, 1) } \author{ -Sebastian Funk +Sebastian Funk James M. Azam } diff --git a/man/dot-gborel_size_ll.Rd b/man/dot-gborel_size_ll.Rd index c02ff60b..0f82e066 100644 --- a/man/dot-gborel_size_ll.Rd +++ b/man/dot-gborel_size_ll.Rd @@ -25,6 +25,6 @@ A numeric vector of log-likelihood values. Log-likelihood of the size of chains with gamma-Borel offspring distribution } \author{ -Sebastian Funk +Sebastian Funk James M. Azam } \keyword{internal} diff --git a/man/dot-geom_length_ll.Rd b/man/dot-geom_length_ll.Rd index e893ee8a..cc288c50 100644 --- a/man/dot-geom_length_ll.Rd +++ b/man/dot-geom_length_ll.Rd @@ -19,6 +19,6 @@ A numeric vector of log-likelihood values. Log-likelihood of the length of chains with geometric offspring distribution } \author{ -Sebastian Funk +Sebastian Funk James M. Azam } \keyword{internal} diff --git a/man/dot-nbinom_size_ll.Rd b/man/dot-nbinom_size_ll.Rd index f758707a..7ea2b135 100644 --- a/man/dot-nbinom_size_ll.Rd +++ b/man/dot-nbinom_size_ll.Rd @@ -27,6 +27,6 @@ Log-likelihood of the size of chains with Negative-Binomial offspring distribution } \author{ -Sebastian Funk +Sebastian Funk James M. Azam } \keyword{internal} diff --git a/man/dot-offspring_ll.Rd b/man/dot-offspring_ll.Rd index f4bf949b..92210bd6 100644 --- a/man/dot-offspring_ll.Rd +++ b/man/dot-offspring_ll.Rd @@ -44,6 +44,6 @@ chain summaries by linearly approximating any missing values in the empirical cumulative distribution function (ecdf). } \author{ -Sebastian Funk +Sebastian Funk James M. Azam } \keyword{internal} diff --git a/man/dot-pois_length_ll.Rd b/man/dot-pois_length_ll.Rd index 0a433839..e06ebb11 100644 --- a/man/dot-pois_length_ll.Rd +++ b/man/dot-pois_length_ll.Rd @@ -19,6 +19,6 @@ A numeric vector of log-likelihood values. Log-likelihood of the length of chains with Poisson offspring distribution } \author{ -Sebastian Funk +Sebastian Funk James M. Azam } \keyword{internal} diff --git a/man/dot-pois_size_ll.Rd b/man/dot-pois_size_ll.Rd index c209035e..78f070d9 100644 --- a/man/dot-pois_size_ll.Rd +++ b/man/dot-pois_size_ll.Rd @@ -18,6 +18,6 @@ A numeric vector of log-likelihood values. Log-likelihood of the size of chains with Poisson offspring distribution } \author{ -Sebastian Funk +Sebastian Funk James M. Azam } \keyword{internal} diff --git a/man/epichains-package.Rd b/man/epichains-package.Rd index 01d0bab3..986765c8 100644 --- a/man/epichains-package.Rd +++ b/man/epichains-package.Rd @@ -22,7 +22,7 @@ Useful links: Authors: \itemize{ - \item Sebastian Funk \email{sebastian.funk@lshtm.ac.uk} (\href{https://orcid.org/0000-0002-2842-3406}{ORCID}) + \item Sebastian Funk \email{sebastian.funk@lshtm.ac.uk} (\href{https://orcid.org/0000-0002-2842-3406}{ORCID}) [copyright holder] \item Flavio Finger \email{flavio.finger@epicentre.msf.org} (\href{https://orcid.org/0000-0002-8613-5170}{ORCID}) } diff --git a/man/likelihood.Rd b/man/likelihood.Rd index 2e1ccb5b..244cd89a 100644 --- a/man/likelihood.Rd +++ b/man/likelihood.Rd @@ -109,15 +109,15 @@ likelihood( # Example using an object set.seed(32) epichains_obj_eg <- simulate_chains( - n_chains = 10, - pop = 100, - percent_immune = 0, - statistic = "size", - offspring_dist = rnbinom, - stat_threshold = 10, - generation_time = function(n) rep(3, n), - mu = 2, - size = 0.2 + n_chains = 10, + pop = 100, + percent_immune = 0, + statistic = "size", + offspring_dist = rnbinom, + stat_threshold = 10, + generation_time = function(n) rep(3, n), + mu = 2, + size = 0.2 ) epichains_obj_eg_lik <- likelihood( diff --git a/man/print.epichains.Rd b/man/print.epichains.Rd index 58bf795b..c1874a02 100644 --- a/man/print.epichains.Rd +++ b/man/print.epichains.Rd @@ -23,12 +23,12 @@ Print an \verb{} object # population up to chain size 10. set.seed(32) chains_pois_offspring <- simulate_chains( - n_chains = 10, - statistic = "size", - offspring_dist = rpois, - stat_threshold = 10, - generation_time = function(n) rep(3, n), - lambda = 2 + n_chains = 10, + statistic = "size", + offspring_dist = rpois, + stat_threshold = 10, + generation_time = function(n) rep(3, n), + lambda = 2 ) chains_pois_offspring # Print the object } diff --git a/man/print.epichains_summary.Rd b/man/print.epichains_summary.Rd index 7a66e409..1071b48e 100644 --- a/man/print.epichains_summary.Rd +++ b/man/print.epichains_summary.Rd @@ -28,11 +28,11 @@ limit. See \code{?epichains_summary()} for the definition of \code{stat_threshol # population up to chain size 10. set.seed(32) chain_summary_print_eg <- simulate_chain_stats( - n_chains = 10, - statistic = "size", - offspring_dist = rpois, - stat_threshold = 10, - lambda = 2 + n_chains = 10, + statistic = "size", + offspring_dist = rpois, + stat_threshold = 10, + lambda = 2 ) chain_summary_print_eg # Print the object } diff --git a/man/rborel.Rd b/man/rborel.Rd index ea0ab599..9efefd0f 100644 --- a/man/rborel.Rd +++ b/man/rborel.Rd @@ -29,5 +29,5 @@ set.seed(32) rborel(5, 1) } \author{ -Sebastian Funk +Sebastian Funk James M. Azam } diff --git a/man/rgborel.Rd b/man/rgborel.Rd index 1ef311c3..2fd974b6 100644 --- a/man/rgborel.Rd +++ b/man/rgborel.Rd @@ -36,5 +36,5 @@ set.seed(32) rgborel(n = 5, size = 0.3, mu = 1, censor_at = 5) } \author{ -Sebastian Funk +Sebastian Funk James M. Azam } diff --git a/man/summary.epichains_summary.Rd b/man/summary.epichains_summary.Rd index 417dc6bb..127d1423 100644 --- a/man/summary.epichains_summary.Rd +++ b/man/summary.epichains_summary.Rd @@ -30,11 +30,11 @@ Summary method for \verb{} class # population up to chain size 10. set.seed(32) chain_stats <- simulate_chain_stats( - n_chains = 10, - statistic = "size", - offspring_dist = rpois, - stat_threshold = 10, - lambda = 2 + n_chains = 10, + statistic = "size", + offspring_dist = rpois, + stat_threshold = 10, + lambda = 2 ) summary(chain_stats) } diff --git a/tests/spelling.R b/tests/spelling.R index fc341c34..647406c2 100644 --- a/tests/spelling.R +++ b/tests/spelling.R @@ -1,7 +1,7 @@ if (requireNamespace("spelling", quietly = TRUE)) { - spelling::spell_check_test( - vignettes = TRUE, - error = TRUE, - skip_on_cran = TRUE - ) + spelling::spell_check_test( + vignettes = TRUE, + error = TRUE, + skip_on_cran = TRUE + ) } diff --git a/tests/testthat/helper-state.R b/tests/testthat/helper-state.R index 29466710..4910faa6 100644 --- a/tests/testthat/helper-state.R +++ b/tests/testthat/helper-state.R @@ -6,21 +6,21 @@ # We add a test on R >= 4.0.0 because some functions such as # `globalCallingHandlers()` did not exist before. if (getRversion() >= "4.0.0") { - testthat::set_state_inspector(function() { - list( - attached = search(), - connections = getAllConnections(), - cwd = getwd(), - envvars = Sys.getenv(), - handlers = globalCallingHandlers(), - libpaths = .libPaths(), - locale = Sys.getlocale(), - options = options(), - par = par(), - packages = .packages(all.available = TRUE), - sink = sink.number(), - timezone = Sys.timezone(), - NULL - ) - }) + testthat::set_state_inspector(function() { + list( + attached = search(), + connections = getAllConnections(), + cwd = getwd(), + envvars = Sys.getenv(), + handlers = globalCallingHandlers(), + libpaths = .libPaths(), + locale = Sys.getlocale(), + options = options(), + par = par(), + packages = .packages(all.available = TRUE), + sink = sink.number(), + timezone = Sys.timezone(), + NULL + ) + }) } diff --git a/tests/testthat/setup-options.R b/tests/testthat/setup-options.R index 0a86b247..4ca3f1c0 100644 --- a/tests/testthat/setup-options.R +++ b/tests/testthat/setup-options.R @@ -1,7 +1,7 @@ # We want to flag partial matching as part of our testing & continuous # integration process because it makes code more brittle. options( - warnPartialMatchAttr = TRUE, - warnPartialMatchDollar = TRUE, - warnPartialMatchArgs = TRUE + warnPartialMatchAttr = TRUE, + warnPartialMatchDollar = TRUE, + warnPartialMatchArgs = TRUE ) diff --git a/tests/testthat/test-checks.R b/tests/testthat/test-checks.R index 38a9682a..2469a514 100644 --- a/tests/testthat/test-checks.R +++ b/tests/testthat/test-checks.R @@ -1,169 +1,169 @@ # A function that passes all checks in .check_sim_args but returns an error # message if the supplied arguments are invalid .check_sim_args_default <- function(...) { - default_args <- list( - n_chains = 10, - statistic = "size", - offspring_dist = rpois, - stat_threshold = 10, - pop = 10, - percent_immune = 0.1 - ) - # Modify the default arguments with the user's arguments - new_args <- modifyList( - default_args, - list(...) - ) - # Run the check_sim_args function and capture the output - out <- tryCatch( - expr = { - do.call( - .check_sim_args, - new_args - ) - }, - error = function(e) { - stop(e) - } - ) - # Return the output - return(out) + default_args <- list( + n_chains = 10, + statistic = "size", + offspring_dist = rpois, + stat_threshold = 10, + pop = 10, + percent_immune = 0.1 + ) + # Modify the default arguments with the user's arguments + new_args <- modifyList( + default_args, + list(...) + ) + # Run the check_sim_args function and capture the output + out <- tryCatch( + expr = { + do.call( + .check_sim_args, + new_args + ) + }, + error = function(e) { + stop(e) + } + ) + # Return the output + return(out) } # A function that passes all checks in `.check_time_args` but returns an error # message if the supplied arguments are invalid .check_time_args_default <- function(...) { - default_args <- list( - t0 = 0, - tf_specified = FALSE, # tf is not specified but default tf = Inf below - tf = Inf, - generation_time = NULL - ) - new_args <- modifyList( - default_args, - list(...) - ) - out <- tryCatch( - expr = { - do.call( - .check_time_args, - new_args - ) - }, - error = function(e) { - stop(e) - } - ) - return(out) + default_args <- list( + t0 = 0, + tf_specified = FALSE, # tf is not specified but default tf = Inf below + tf = Inf, + generation_time = NULL + ) + new_args <- modifyList( + default_args, + list(...) + ) + out <- tryCatch( + expr = { + do.call( + .check_time_args, + new_args + ) + }, + error = function(e) { + stop(e) + } + ) + return(out) } test_that("Smaller checker functions work", { - expect_error( - .check_offspring_func_valid(rrpois), - "not found" - ) - expect_error( - .check_generation_time_valid("a"), - "Must be a function" - ) - expect_error( - .check_generation_time_valid(function(x) rep("a", 10)), - "numeric" - ) - expect_error( - .check_generation_time_valid(function(x) 3), - "Must have length" - ) - expect_no_error( - .check_statistic_args( - statistic = "size", - stat_threshold = 10 - ) - ) + expect_error( + .check_offspring_func_valid(rrpois), + "not found" + ) + expect_error( + .check_generation_time_valid("a"), + "Must be a function" + ) + expect_error( + .check_generation_time_valid(function(x) rep("a", 10)), + "numeric" + ) + expect_error( + .check_generation_time_valid(function(x) 3), + "Must have length" + ) + expect_no_error( + .check_statistic_args( + statistic = "size", + stat_threshold = 10 + ) + ) }) test_that(".check_sim_args() returns errors", { - # Checks with .check_sim_args - expect_no_error( - .check_sim_args_default() - ) - # n_chains must be >= 1 - expect_error( - .check_sim_args_default( - n_chains = 0 - ), - "Must be >= 1." - ) - # statistic can only be "size" or "length" - expect_error( - .check_sim_args_default( - statistic = "duration" - ), - "Must be element of set \\{'size','length'\\}" - ) - # offspring_dist must be a function - expect_error( - .check_sim_args_default( - offspring_dist = "rpois" - ), - "Must be a function, not 'character'" - ) - # offspring_dist must be a known function (in the environment) - expect_error( - .check_sim_args_default( - offspring_dist = r - ), - "object 'r' not found" - ) - # stat_threshold must be >= 1 - expect_error( - .check_sim_args_default( - stat_threshold = 0 - ), - "Assertion failed." - ) - # pop cannot be negative - expect_error( - .check_sim_args_default( - pop = -1 - ), - "Element 1 is not >= 1." - ) - # percent_immune must be in between 0 and 1 - expect_error( - .check_sim_args_default( - percent_immune = 1.1 - ), - "Element 1 is not <= 1." - ) + # Checks with .check_sim_args + expect_no_error( + .check_sim_args_default() + ) + # n_chains must be >= 1 + expect_error( + .check_sim_args_default( + n_chains = 0 + ), + "Must be >= 1." + ) + # statistic can only be "size" or "length" + expect_error( + .check_sim_args_default( + statistic = "duration" + ), + "Must be element of set \\{'size','length'\\}" + ) + # offspring_dist must be a function + expect_error( + .check_sim_args_default( + offspring_dist = "rpois" + ), + "Must be a function, not 'character'" + ) + # offspring_dist must be a known function (in the environment) + expect_error( + .check_sim_args_default( + offspring_dist = r + ), + "object 'r' not found" + ) + # stat_threshold must be >= 1 + expect_error( + .check_sim_args_default( + stat_threshold = 0 + ), + "Assertion failed." + ) + # pop cannot be negative + expect_error( + .check_sim_args_default( + pop = -1 + ), + "Element 1 is not >= 1." + ) + # percent_immune must be in between 0 and 1 + expect_error( + .check_sim_args_default( + percent_immune = 1.1 + ), + "Element 1 is not <= 1." + ) }) test_that(".check_time_args() returns errors", { - # Checks with .check_time_args - expect_no_error( - .check_time_args_default() - ) - # t0 cannot be negative - expect_error( - .check_time_args_default( - t0 = -1 - ), - "Element 1 is not >= 0." - ) - # tf cannot be negative - expect_error( - .check_time_args_default( - tf = -1 - ), - "Element 1 is not >= 0." - ) - # If tf is specified, generation_time must be specified too - expect_error( - .check_time_args_default( - tf_specified = TRUE, - tf = 10 - ), - "If `tf` is specified, `generation_time` must be specified too." - ) + # Checks with .check_time_args + expect_no_error( + .check_time_args_default() + ) + # t0 cannot be negative + expect_error( + .check_time_args_default( + t0 = -1 + ), + "Element 1 is not >= 0." + ) + # tf cannot be negative + expect_error( + .check_time_args_default( + tf = -1 + ), + "Element 1 is not >= 0." + ) + # If tf is specified, generation_time must be specified too + expect_error( + .check_time_args_default( + tf_specified = TRUE, + tf = 10 + ), + "If `tf` is specified, `generation_time` must be specified too." + ) }) diff --git a/tests/testthat/test-epichains.R b/tests/testthat/test-epichains.R index 42a30a7b..68d863dc 100644 --- a/tests/testthat/test-epichains.R +++ b/tests/testthat/test-epichains.R @@ -11,7 +11,7 @@ shared_args <- list( lambda = 0.9 ) -simulate_chains_default <- function(...){ +simulate_chains_default <- function(...) { default_args <- c( shared_args, generation_time = generation_time_fn diff --git a/tests/testthat/test-likelihood.R b/tests/testthat/test-likelihood.R index d3addd89..9bcd9313 100644 --- a/tests/testthat/test-likelihood.R +++ b/tests/testthat/test-likelihood.R @@ -1,138 +1,137 @@ test_that("Likelihoods can be calculated", { chains <- c(1, 1, 4, 7) set.seed(12) - expect_lt( - likelihood( - chains = chains, - statistic = "size", - offspring_dist = rpois, - lambda = 0.5 - ), - 0 - ) - expect_lt( - likelihood( - chains = chains, - statistic = "size", - offspring_dist = rpois, - lambda = 0.5, - exclude = 1 - ), - 0 - ) - expect_lt( - likelihood( - chains = chains, - statistic = "size", - offspring_dist = rpois, - lambda = 0.5, - stat_threshold = 5 - ), - 0 - ) - expect_lt( - likelihood( - chains = chains, - statistic = "size", - offspring_dist = rpois, - lambda = 0.5, - obs_prob = 0.5, - nsim_obs = 1 - ), - 0 - ) - expect_lt( - likelihood( - chains = chains, - statistic = "size", - offspring_dist = rpois, - lambda = 0.5, - stat_threshold = 5, - obs_prob = 0.5, - nsim_obs = 1 - ), - 0 - ) - expect_lt( - likelihood( - chains = chains, - statistic = "length", - offspring_dist = rbinom, - size = 1, - prob = 0.5 - ), - 0 - ) - expect_gte( - likelihood( - chains = chains, - statistic = "length", - offspring_dist = rbinom, - size = 1, - prob = 0.5, - log = FALSE - ), - 0 - ) - expect_gte( - likelihood( - chains = chains, - statistic = "length", - offspring_dist = rbinom, - size = 1, - prob = 0.5, - individual = FALSE, - log = FALSE - ), - 0 - ) - } -) + expect_lt( + likelihood( + chains = chains, + statistic = "size", + offspring_dist = rpois, + lambda = 0.5 + ), + 0 + ) + expect_lt( + likelihood( + chains = chains, + statistic = "size", + offspring_dist = rpois, + lambda = 0.5, + exclude = 1 + ), + 0 + ) + expect_lt( + likelihood( + chains = chains, + statistic = "size", + offspring_dist = rpois, + lambda = 0.5, + stat_threshold = 5 + ), + 0 + ) + expect_lt( + likelihood( + chains = chains, + statistic = "size", + offspring_dist = rpois, + lambda = 0.5, + obs_prob = 0.5, + nsim_obs = 1 + ), + 0 + ) + expect_lt( + likelihood( + chains = chains, + statistic = "size", + offspring_dist = rpois, + lambda = 0.5, + stat_threshold = 5, + obs_prob = 0.5, + nsim_obs = 1 + ), + 0 + ) + expect_lt( + likelihood( + chains = chains, + statistic = "length", + offspring_dist = rbinom, + size = 1, + prob = 0.5 + ), + 0 + ) + expect_gte( + likelihood( + chains = chains, + statistic = "length", + offspring_dist = rbinom, + size = 1, + prob = 0.5, + log = FALSE + ), + 0 + ) + expect_gte( + likelihood( + chains = chains, + statistic = "length", + offspring_dist = rbinom, + size = 1, + prob = 0.5, + individual = FALSE, + log = FALSE + ), + 0 + ) +}) test_that("likelihood() works with epichains and epichains_summary objects", { - # Simulate an object - set.seed(32) - chains_tree_eg <- simulate_chains( - n_chains = 10, - pop = 100, - percent_immune = 0, + # Simulate an object + set.seed(32) + chains_tree_eg <- simulate_chains( + n_chains = 10, + pop = 100, + percent_immune = 0, + statistic = "size", + offspring_dist = rpois, + stat_threshold = 10, + generation_time = function(n) rep(3, n), + lambda = 0.9 + ) + # Simulate an object + set.seed(32) + chains_summary_eg <- simulate_chain_stats( + n_chains = 10, + pop = 100, + percent_immune = 0, + statistic = "size", + offspring_dist = rpois, + stat_threshold = 10, + lambda = 0.9 + ) + # Use the simulated object to calculate likelihood + expect_equal( + likelihood( + chains = chains_tree_eg, statistic = "size", offspring_dist = rpois, - stat_threshold = 10, - generation_time = function(n) rep(3, n), lambda = 0.9 - ) - # Simulate an object - set.seed(32) - chains_summary_eg <- simulate_chain_stats( - n_chains = 10, - pop = 100, - percent_immune = 0, + ), + -23.538996774 + ) + # Use the simulated object to calculate likelihood + expect_equal( + likelihood( + chains = chains_summary_eg, statistic = "size", offspring_dist = rpois, - stat_threshold = 10, lambda = 0.9 - ) - # Use the simulated object to calculate likelihood - expect_equal( - likelihood( - chains = chains_tree_eg, - statistic = "size", - offspring_dist = rpois, - lambda = 0.9 - ), - -23.538996774 - ) - # Use the simulated object to calculate likelihood - expect_equal( - likelihood( - chains = chains_summary_eg, - statistic = "size", - offspring_dist = rpois, - lambda = 0.9 - ), - -23.538997 - ) + ), + -23.538997 + ) }) test_that("Likelihoods are numerically correct", { diff --git a/tests/testthat/test-simulate.R b/tests/testthat/test-simulate.R index 72940556..3eacb2cb 100644 --- a/tests/testthat/test-simulate.R +++ b/tests/testthat/test-simulate.R @@ -240,7 +240,7 @@ test_that("simulate_chain_stats throws errors", { ) }) -test_that("simulate_chain_stats is numerically correct",{ +test_that("simulate_chain_stats is numerically correct", { # Run a simulation in a small population so that # we encounter the case where we have more potential offspring than # susceptible individuals diff --git a/vignettes/design-principles.Rmd b/vignettes/design-principles.Rmd index 4c6f52e1..f569de94 100644 --- a/vignettes/design-principles.Rmd +++ b/vignettes/design-principles.Rmd @@ -108,8 +108,8 @@ The package uses the following naming conventions: ## Development journey -{epichains} is a successor to the [bpmodels](https://github.com/epiverse-trace/bpmodels) package, which will be retired when {epichains} is released. +{epichains} is a successor to the [bpmodels](https://github.com/epiforecasts/bpmodels/) package, which was retired after the release of {epichains} v0.1.0. {epichains} was born out of a need to refactor {bpmodels}, which led to a name change and subsequent changes in design that would have required significant disruptive changes to {bpmodels}. {epichains} is a major refactoring of {bpmodels} to provide a simulation function that accounts for susceptible depletion and population immunity without restrictions on the offspring distributions, better function and long form documentation, and an object-oriented approach to simulating and handling transmission chains in R. -Future plans include simulation of contacts of infectees, the incorporation of network effects, an object-oriented approach to estimating chain size and length likelihoods, and interoperability with the [{epiparameter}](https://epiverse-trace.github.io/epiparameter/) package for ease of setting up various epidemiological delays. +Future plans include simulation of contacts of infectors, the incorporation of network effects, an object-oriented approach to estimating chain size and length likelihoods, and interoperability with the [{epiparameter}](https://epiverse-trace.github.io/epiparameter/) package for ease of setting up various epidemiological delays. diff --git a/vignettes/interventions.Rmd b/vignettes/interventions.Rmd index b73d797d..cb8ce7cd 100644 --- a/vignettes/interventions.Rmd +++ b/vignettes/interventions.Rmd @@ -62,8 +62,10 @@ We then plot the resulting distribution of chain sizes sims[is.infinite(sims)] <- 100 # Replace infections > 99 with 100 for plotting. ggplot(data.frame(x = sims), aes(x = x)) + geom_histogram(breaks = seq(0, 100, by = 5), closed = "left") + - scale_x_continuous(breaks = c(0, 25, 50, 75, 100), - labels = c(0, 25, 50, 75, ">99")) + + scale_x_continuous( + breaks = c(0, 25, 50, 75, 100), + labels = c(0, 25, 50, 75, ">99") + ) + theme_bw() ``` @@ -84,8 +86,10 @@ sims <- simulate_chain_stats( sims[is.infinite(sims)] <- 100 # Replace infections > 99 with 100 for plotting. ggplot(data.frame(x = sims), aes(x = x)) + geom_histogram(breaks = seq(0, 100, by = 5), closed = "left") + - scale_x_continuous(breaks = c(0, 25, 50, 75, 100), - labels = c(0, 25, 50, 75, ">99")) + + scale_x_continuous( + breaks = c(0, 25, 50, 75, 100), + labels = c(0, 25, 50, 75, ">99") + ) + theme_bw() ``` @@ -122,8 +126,10 @@ sims <- simulate_chain_stats( sims[is.infinite(sims)] <- 100 # Replace infections > 99 with 100 for plotting. ggplot(data.frame(x = sims), aes(x = x)) + geom_histogram(breaks = seq(0, 100, by = 5), closed = "left") + - scale_x_continuous(breaks = c(0, 25, 50, 75, 100), - labels = c(0, 25, 50, 75, ">99")) + + scale_x_continuous( + breaks = c(0, 25, 50, 75, 100), + labels = c(0, 25, 50, 75, ">99") + ) + theme_bw() ``` @@ -147,13 +153,15 @@ This can be likened to a disease control strategy where gatherings are limited t ```{r simulate_chains_truncated} sims <- simulate_chain_stats( n_chains = 200, offspring_dist = rnbinom_truncated, stat_threshold = 99, - mu = 1.2, size = 0.5, max = 10, statistic = "size" + mu = 1.2, size = 0.5, max = 10, statistic = "size" ) sims[is.infinite(sims)] <- 100 # Replace infections > 99 with 100 for plotting. ggplot(data.frame(x = sims), aes(x = x)) + geom_histogram(breaks = seq(0, 100, by = 5), closed = "left") + - scale_x_continuous(breaks = c(0, 25, 50, 75, 100), - labels = c(0, 25, 50, 75, ">99")) + + scale_x_continuous( + breaks = c(0, 25, 50, 75, 100), + labels = c(0, 25, 50, 75, ">99") + ) + theme_bw() ``` diff --git a/vignettes/projecting_incidence.Rmd b/vignettes/projecting_incidence.Rmd index b03ad1d6..e4236343 100644 --- a/vignettes/projecting_incidence.Rmd +++ b/vignettes/projecting_incidence.Rmd @@ -23,7 +23,6 @@ knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) - ``` ## Overview @@ -137,7 +136,7 @@ first argument to determine the number of observations to sample mu <- 4.7 sgma <- 2.9 -log_mean <- log((mu^2) / (sqrt(sgma^2 + mu^2))) # log mean +log_mean <- log((mu^2) / (sqrt(sgma^2 + mu^2))) # log mean log_sd <- sqrt(log(1 + (sgma / mu)^2)) # log sd #' serial interval function @@ -308,7 +307,6 @@ head(median_daily_cases) We will now plot the individual simulation results alongside the median of the aggregated results. ```{r viz, fig.cap ="COVID-19 incidence in South Africa projected over a two week window in 2020. The light gray lines represent the individual simulations, the red line represents the median daily cases across all simulations, the black connected dots represent the observed data, and the dashed vertical line marks the beginning of the projection.", fig.width=6.0, fig.height=6} - # since all simulations may end at a different date, we will find the minimum # final date for all simulations for the purposes of visualisation. final_date <- incidence_ts_by_date %>%