From 61028c15cbc73c9b84f7ae2ea8eb40199cfc8189 Mon Sep 17 00:00:00 2001 From: sbfnk-bot <242615673+sbfnk-bot@users.noreply.github.com> Date: Fri, 17 Jul 2026 21:20:20 +0100 Subject: [PATCH 1/4] Add Weibull delay support and adopt distspec natural_params (0.2.0) Co-authored-by: sbfnk --- DESCRIPTION | 2 +- NEWS.md | 8 +++- R/cure_model.R | 6 +-- R/fit_cfr.R | 9 ++-- R/simulate_linelist.R | 20 +++++++-- R/summarise_cfr.R | 7 ++- R/translate.R | 79 ++++++++++++++++++++++++++-------- R/utils.R | 20 --------- man/cfrnow-cure-model.Rd | 2 +- man/fit_cfr.Rd | 11 ++--- man/simulate_linelist.Rd | 3 +- tests/testthat/test-families.R | 5 ++- tests/testthat/test-fit_cfr.R | 18 ++++++++ 13 files changed, 130 insertions(+), 60 deletions(-) delete mode 100644 R/utils.R diff --git a/DESCRIPTION b/DESCRIPTION index 84862d2..595ec41 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: cfrnow Title: Real-Time Case Fatality Ratio via a Mixture-Cure Survival Model -Version: 0.1.0.9000 +Version: 0.2.0 Authors@R: person("Sebastian", "Funk", , "sebastian.funk@lshtm.ac.uk", role = c("aut", "cre")) diff --git a/NEWS.md b/NEWS.md index c0e4b7a..3b0bcc9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,11 @@ -# cfrnow (development version) +# cfrnow 0.2.0 +* `fit_cfr()` and `simulate_linelist()` support a `Weibull()` onset-to-death (and + recovery) delay, alongside `LogNormal()` and `Gamma()`. +* Delay parameterisation now uses distspec's exported `natural_params()` in place + of an internal helper, tracking the distspec API. +* Added a "Stratified and partially-pooled CFR" vignette covering no-, complete- + and partial-pooling CFR fits and per-group `summary()` output. * `pp_check_cfr()` runs a posterior-predictive check on a fit: it draws replicate line-list outcomes from the posterior, replays the real-time truncation, and compares the observed death counts (plus recoveries in a two-outcome fit) and diff --git a/R/cure_model.R b/R/cure_model.R index c13517a..47f1f05 100644 --- a/R/cure_model.R +++ b/R/cure_model.R @@ -16,7 +16,7 @@ #' #' The delay distribution's location is `mu` (as `epidist` expects); the cure #' probability `cfr` is an additional dpar with a logit link. Supported delay -#' families are `lognormal()` and `Gamma()`. +#' families are `lognormal()`, `Gamma()` and `Weibull()`. #' #' @name cfrnow-cure-model #' @keywords internal @@ -81,8 +81,8 @@ assert_epidist.epidist_cure_model <- function(data, ...) { } .assert_delay_family <- function(family, what = "delay") { - if (!family$family %in% c("lognormal", "gamma")) { - stop("cfrnow supports lognormal() and Gamma() ", what, "s only.", + if (!family$family %in% c("lognormal", "gamma", "weibull")) { + stop("cfrnow supports lognormal(), Gamma() and Weibull() ", what, "s only.", call. = FALSE ) } diff --git a/R/fit_cfr.R b/R/fit_cfr.R index 5822b2f..b5ec01e 100644 --- a/R/fit_cfr.R +++ b/R/fit_cfr.R @@ -8,8 +8,9 @@ #' #' The delay's native parameters may each be a fixed number (held fixed; fixing #' the whole delay gives the Ghani/Nishiura estimator) or a `Normal()` prior -#' (co-estimated). The family (`LogNormal()` or `Gamma()`) sets the delay -#' distribution. `cfr_prior` is a `Beta()`; it matters because the CFR is weakly +#' (co-estimated). The family (`LogNormal()`, `Gamma()` or `Weibull()`) sets the +#' delay distribution. `cfr_prior` is a `Beta()`; it matters because the CFR is +#' weakly #' identified early on (`Beta(1, 1)` is uniform, `Beta(1, 9)` favours a low CFR, #' `Beta(6.6, 13.4)` suits a high-fatality pathogen). #' @@ -22,8 +23,8 @@ #' @param data Output of [prepare_cfr_data()], or an `epidist_cure_model` / #' data frame with `y`, `outcome`, `pwindow`, `swindow`. #' @param delay Onset-to-death delay as a \pkg{distspec} distribution -#' ([distspec::LogNormal()] or [distspec::Gamma()]) whose native parameters -#' are fixed numbers or `Normal()` priors. +#' ([distspec::LogNormal()], [distspec::Gamma()] or [distspec::Weibull()]) +#' whose native parameters are fixed numbers or `Normal()` priors. #' @param cfr_prior CFR prior as a [distspec::Beta()]. Defaults to `Beta(1, 1)`. #' @param recovery_delay Optional onset-to-recovery delay (same form as `delay`) #' for the two-outcome fit; may use a different family from `delay`. diff --git a/R/simulate_linelist.R b/R/simulate_linelist.R index bd1490c..d064c1d 100644 --- a/R/simulate_linelist.R +++ b/R/simulate_linelist.R @@ -20,7 +20,8 @@ #' @param n Number of cases. #' @param cfr True case fatality ratio. #' @param delay Onset-to-death delay: a distspec distribution -#' ([distspec::LogNormal()] or [distspec::Gamma()]) with fixed parameters. +#' ([distspec::LogNormal()], [distspec::Gamma()] or [distspec::Weibull()]) +#' with fixed parameters. #' @param recovery Optional onset-to-recovery delay (same form as `delay`); when #' given, non-fatal cases get a `recovery_date`. #' @param onset_start First possible onset date. @@ -74,15 +75,26 @@ simulate_linelist <- function(n = 200, cfr = 0.5, delay, recovery = NULL, #' @noRd sample_delay <- function(n, delay) { fam <- get_distribution(delay) - pars <- get_parameters(delay)[delay_native_order(fam)] + pars <- get_parameters(delay)[natural_params(delay)] if (!all(vapply(pars, is.numeric, logical(1)))) { stop("simulate_linelist() needs a delay with fixed parameters (numbers), ", "not priors.", call. = FALSE ) } - switch(fam, + out <- switch(fam, lognormal = stats::rlnorm(n, pars[["meanlog"]], pars[["sdlog"]]), - gamma = stats::rgamma(n, shape = pars[["shape"]], rate = pars[["rate"]]) + gamma = stats::rgamma(n, shape = pars[["shape"]], rate = pars[["rate"]]), + weibull = stats::rweibull( + n, + shape = pars[["shape"]], scale = pars[["scale"]] + ) ) + if (is.null(out)) { + stop("simulate_linelist() supports LogNormal(), Gamma() and Weibull() ", + "delays only.", + call. = FALSE + ) + } + out } diff --git a/R/summarise_cfr.R b/R/summarise_cfr.R index f03431b..9e5d662 100644 --- a/R/summarise_cfr.R +++ b/R/summarise_cfr.R @@ -11,7 +11,7 @@ naive_cfr <- function(n_deaths, n_cases) { #' #' `loc_var` is the location intercept and `scale_var` the second parameter's #' intercept; the second parameter is log-linked. Handles lognormal (loc is -#' meanlog) and gamma (loc is log-mean, second parameter is shape). +#' meanlog), gamma and weibull (loc is log-mean, second parameter is the shape). #' @noRd .delay_moments <- function(dr, loc_var, scale_var, family) { loc <- dr[[loc_var]] @@ -19,6 +19,11 @@ naive_cfr <- function(n_deaths, n_cases) { if (family == "lognormal") { dmean <- exp(loc + sc^2 / 2) list(mean = dmean, sd = sqrt(exp(sc^2) - 1) * dmean) + } else if (family == "weibull") { + # brms weibull: mu is the mean (log link), sc is the shape k + dmean <- exp(loc) + cv <- sqrt(gamma(1 + 2 / sc) / gamma(1 + 1 / sc)^2 - 1) + list(mean = dmean, sd = dmean * cv) } else { dmean <- exp(loc) list(mean = dmean, sd = dmean / sqrt(sc)) diff --git a/R/translate.R b/R/translate.R index cc3620f..6bdf01f 100644 --- a/R/translate.R +++ b/R/translate.R @@ -66,6 +66,37 @@ ) } +# distspec Weibull() uses (shape, scale); brms weibull uses (mu = mean, shape), +# both log-linked. Build the mu prior from mean = scale * gamma(1 + 1 / shape), +# propagating shape/scale uncertainty to log(mu) by the delta method. +.weibull_mu_prior <- function(shape_spec, scale_spec, dpar) { + mu_of <- function(k, lambda) lambda * gamma(1 + 1 / k) + if (!is.null(shape_spec$fixed) && !is.null(scale_spec$fixed)) { + return(.prior_row( + list(fixed = mu_of(shape_spec$fixed, scale_spec$fixed)), dpar, TRUE + )) + } + kc <- shape_spec$fixed %||% shape_spec$mean + lc <- scale_spec$fixed %||% scale_spec$mean + # d log(mu)/d shape uses digamma(1 + 1/k); sign drops on squaring + d_shape <- digamma(1 + 1 / kc) / kc^2 + vlog <- sqrt( + ((scale_spec$sd %||% 0) / lc)^2 + (d_shape * (shape_spec$sd %||% 0))^2 + ) + prior_args <- if (nzchar(dpar)) { + list(class = "Intercept", dpar = dpar) + } else { + list(class = "Intercept") + } + do.call( + brms::set_prior, + c( + list(sprintf("normal(%.6f, %.6f)", log(lc) + lgamma(1 + 1 / kc), vlog)), + prior_args + ) + ) +} + # A distspec delay -> list(family, prior). `main = TRUE` targets the death delay # (mu is the main dpar); `main = FALSE` the recovery delay (r-prefixed dpars). .delay_family_prior <- function(delay, main = TRUE) { @@ -78,25 +109,39 @@ fam <- get_distribution(delay) pars <- get_parameters(delay) loc_dpar <- if (main) "" else "rmu" - if (fam == "lognormal") { - scale_dpar <- if (main) "sigma" else "rsigma" - prior <- c( - .prior_row(.param_spec(pars$meanlog), loc_dpar, FALSE), - .prior_row(.param_spec(pars$sdlog), scale_dpar, TRUE) - ) - list(family = brms::lognormal(), prior = prior) - } else if (fam == "gamma") { - scale_dpar <- if (main) "shape" else "rshape" - sh <- .param_spec(pars$shape) - rate_spec <- .param_spec(pars$rate) - prior <- c( - .gamma_mu_prior(sh, rate_spec, loc_dpar), - .prior_row(sh, scale_dpar, TRUE) + scale_dpar <- if (main) "sigma" else "rsigma" + shape_dpar <- if (main) "shape" else "rshape" + out <- switch(fam, + lognormal = { + prior <- c( + .prior_row(.param_spec(pars$meanlog), loc_dpar, FALSE), + .prior_row(.param_spec(pars$sdlog), scale_dpar, TRUE) + ) + list(family = brms::lognormal(), prior = prior) + }, + gamma = { + sh <- .param_spec(pars$shape) + prior <- c( + .gamma_mu_prior(sh, .param_spec(pars$rate), loc_dpar), + .prior_row(sh, shape_dpar, TRUE) + ) + list(family = stats::Gamma(link = "log"), prior = prior) + }, + weibull = { + sh <- .param_spec(pars$shape) + prior <- c( + .weibull_mu_prior(sh, .param_spec(pars$scale), loc_dpar), + .prior_row(sh, shape_dpar, TRUE) + ) + list(family = brms::weibull(), prior = prior) + } + ) + if (is.null(out)) { + stop("cfrnow supports LogNormal(), Gamma() and Weibull() delays only.", + call. = FALSE ) - list(family = stats::Gamma(link = "log"), prior = prior) - } else { - stop("cfrnow supports LogNormal() and Gamma() delays only.", call. = FALSE) } + out } # A Normal(m, s) on logit(cfr) whose induced mean/sd on the [0, 1] CFR scale diff --git a/R/utils.R b/R/utils.R deleted file mode 100644 index d7a2fb4..0000000 --- a/R/utils.R +++ /dev/null @@ -1,20 +0,0 @@ -#' Native parameter order for a delay family -#' -#' The two native parameters in the positional order used by the delay -#' distribution. Doubles as cfrnow's supported-family guard (lognormal, gamma). -#' @param family Delay family name. -#' @return Character vector of the two native parameter names. -#' @noRd -delay_native_order <- function(family) { - native <- switch(family, - lognormal = c("meanlog", "sdlog"), - gamma = c("shape", "rate") - ) - if (is.null(native)) { - stop("unsupported delay family '", family, - "'; cfrnow supports lognormal and gamma.", - call. = FALSE - ) - } - native -} diff --git a/man/cfrnow-cure-model.Rd b/man/cfrnow-cure-model.Rd index 4e0f415..0e08972 100644 --- a/man/cfrnow-cure-model.Rd +++ b/man/cfrnow-cure-model.Rd @@ -21,6 +21,6 @@ different family from the death delay. The delay distribution's location is \code{mu} (as \code{epidist} expects); the cure probability \code{cfr} is an additional dpar with a logit link. Supported delay -families are \code{lognormal()} and \code{Gamma()}. +families are \code{lognormal()}, \code{Gamma()} and \code{Weibull()}. } \keyword{internal} diff --git a/man/fit_cfr.Rd b/man/fit_cfr.Rd index 5c9ffa3..c829b25 100644 --- a/man/fit_cfr.Rd +++ b/man/fit_cfr.Rd @@ -18,10 +18,10 @@ fit_cfr( data frame with \code{y}, \code{outcome}, \code{pwindow}, \code{swindow}.} \item{delay}{Onset-to-death delay as a \pkg{distspec} distribution -(\code{\link[distspec:Distributions]{distspec::LogNormal()}} or \code{\link[distspec:Distributions]{distspec::Gamma()}}) whose native parameters -are fixed numbers or \code{Normal()} priors.} +(\code{\link[distspec:LogNormal]{distspec::LogNormal()}}, \code{\link[distspec:Gamma]{distspec::Gamma()}} or \code{\link[distspec:Weibull]{distspec::Weibull()}}) +whose native parameters are fixed numbers or \code{Normal()} priors.} -\item{cfr_prior}{CFR prior as a \code{\link[distspec:Distributions]{distspec::Beta()}}. Defaults to \code{Beta(1, 1)}.} +\item{cfr_prior}{CFR prior as a \code{\link[distspec:Beta]{distspec::Beta()}}. Defaults to \code{Beta(1, 1)}.} \item{recovery_delay}{Optional onset-to-recovery delay (same form as \code{delay}) for the two-outcome fit; may use a different family from \code{delay}.} @@ -49,8 +49,9 @@ are given as \pkg{distspec} distributions. \details{ The delay's native parameters may each be a fixed number (held fixed; fixing the whole delay gives the Ghani/Nishiura estimator) or a \code{Normal()} prior -(co-estimated). The family (\code{LogNormal()} or \code{Gamma()}) sets the delay -distribution. \code{cfr_prior} is a \code{Beta()}; it matters because the CFR is weakly +(co-estimated). The family (\code{LogNormal()}, \code{Gamma()} or \code{Weibull()}) sets the +delay distribution. \code{cfr_prior} is a \code{Beta()}; it matters because the CFR is +weakly identified early on (\code{Beta(1, 1)} is uniform, \code{Beta(1, 9)} favours a low CFR, \code{Beta(6.6, 13.4)} suits a high-fatality pathogen). diff --git a/man/simulate_linelist.Rd b/man/simulate_linelist.Rd index 96dacc4..40263d0 100644 --- a/man/simulate_linelist.Rd +++ b/man/simulate_linelist.Rd @@ -19,7 +19,8 @@ simulate_linelist( \item{cfr}{True case fatality ratio.} \item{delay}{Onset-to-death delay: a distspec distribution -(\code{\link[distspec:Distributions]{distspec::LogNormal()}} or \code{\link[distspec:Distributions]{distspec::Gamma()}}) with fixed parameters.} +(\code{\link[distspec:LogNormal]{distspec::LogNormal()}}, \code{\link[distspec:Gamma]{distspec::Gamma()}} or \code{\link[distspec:Weibull]{distspec::Weibull()}}) +with fixed parameters.} \item{recovery}{Optional onset-to-recovery delay (same form as \code{delay}); when given, non-fatal cases get a \code{recovery_date}.} diff --git a/tests/testthat/test-families.R b/tests/testthat/test-families.R index ad6acaf..2ec6fd1 100644 --- a/tests/testthat/test-families.R +++ b/tests/testthat/test-families.R @@ -2,7 +2,8 @@ test_that("simulated delays match the requested mean and sd", { set.seed(1) specs <- list( LogNormal(mean = 12.75, sd = 7), - Gamma(mean = 12.75, sd = 7) + Gamma(mean = 12.75, sd = 7), + Weibull(mean = 12.75, sd = 7) ) for (delay in specs) { ll <- simulate_linelist(n = 20000, cfr = 1, delay = delay) @@ -28,7 +29,7 @@ test_that("a recovery delay adds recoveries for non-fatal cases only", { }) test_that("unsupported families and prior-parameter delays are rejected", { - expect_error(delay_native_order("weibull"), "unsupported") + expect_error(sample_delay(5, Exp(rate = 1)), "supports") # exp not supported expect_error(sample_delay(5, LogNormal( meanlog = Normal(2, 0.1), sdlog = Normal(0.5, 0.1) )), "fixed") diff --git a/tests/testthat/test-fit_cfr.R b/tests/testthat/test-fit_cfr.R index 6bcf5a7..7c086a1 100644 --- a/tests/testthat/test-fit_cfr.R +++ b/tests/testthat/test-fit_cfr.R @@ -60,6 +60,24 @@ test_that("a gamma delay recovers the CFR and delay moments", { expect_equal(s[s$quantity == "delay_mean", "q50"], 8, tolerance = 0.8) }) +test_that("a weibull delay recovers the CFR and delay moments", { + skip_if_no_cmdstan() + set.seed(3) + ll <- simulate_linelist( + n = 2000, cfr = 0.4, onset_days = 40, + delay = Weibull(shape = 1.5, scale = 13) + ) + d <- prepare_cfr_data(ll, obs_time = max(ll$onset_date) - 2) + s <- summary(fit_quick(d, + delay = Weibull(shape = Normal(1.5, 0.4), scale = Normal(13, 3)), + cfr_prior = Beta(1, 1) + )) + true_mean <- 13 * gamma(1 + 1 / 1.5) # Weibull mean = scale * Gamma(1 + 1/shape) + expect_lt(s[s$quantity == "cfr", "q2.5"], 0.4) + expect_gt(s[s$quantity == "cfr", "q97.5"], 0.4) + expect_equal(s[s$quantity == "delay_mean", "q50"], true_mean, tolerance = 1.5) +}) + test_that("a fixed delay runs the Ghani/Nishiura estimator (delay held constant)", { skip_if_no_cmdstan() set.seed(4) From c92bc31b76328f08875606896d9ebf74d4e5bf5a Mon Sep 17 00:00:00 2001 From: sbfnk-bot <242615673+sbfnk-bot@users.noreply.github.com> Date: Fri, 17 Jul 2026 23:04:51 +0100 Subject: [PATCH 2/4] address review: fix Weibull recovery Stan generation, add recovery test Co-authored-by: sbfnk --- R/cure_model.R | 15 +++++++-------- tests/testthat/test-fit_cfr.R | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/R/cure_model.R b/R/cure_model.R index 47f1f05..ddd14fd 100644 --- a/R/cure_model.R +++ b/R/cure_model.R @@ -130,19 +130,18 @@ epidist_model_prior.epidist_cure_model <- function(data, formula, ...) NULL # The native Stan parameterisation brms uses for a family's mu-form, read out of # a dummy model (as epidist does), so primarycensored gets the right arguments. +# The mu-form arguments may themselves contain parentheses (e.g. weibull's +# `mu ./ tgamma(1 + 1 ./ shape)`), so the args run to the statement's closing +# `);` rather than the first `)`. .family_stan_param <- function(family_name) { code <- brms::make_stancode(y ~ 1, data = data.frame(y = c(1, 2)), family = family_name ) - pat <- sprintf("target \\+= %s_(lpdf|lpmf)\\(Y \\| ([^)]+)\\)", family_name) - hits <- grep("mu", regmatches(code, gregexpr(pat, code))[[1]], - fixed = TRUE, value = TRUE - ) - sub(")", "", sub( - sprintf("target \\+= %s_(lpdf|lpmf)\\(Y \\| ", family_name), - "", hits[1] - ), fixed = TRUE) + pat <- sprintf("%s_l[a-z]+\\(Y \\| (.+?)\\);", family_name) + stmts <- regmatches(code, gregexpr(pat, code, perl = TRUE))[[1]] + stmt <- grep("mu", stmts, fixed = TRUE, value = TRUE)[1] + sub(pat, "\\1", stmt, perl = TRUE) } # Fill a `<>` template from inst/stan with a named list of replacements. diff --git a/tests/testthat/test-fit_cfr.R b/tests/testthat/test-fit_cfr.R index 7c086a1..fdb6d2a 100644 --- a/tests/testthat/test-fit_cfr.R +++ b/tests/testthat/test-fit_cfr.R @@ -78,6 +78,27 @@ test_that("a weibull delay recovers the CFR and delay moments", { expect_equal(s[s$quantity == "delay_mean", "q50"], true_mean, tolerance = 1.5) }) +test_that("a Weibull recovery delay compiles and gives sensible moments", { + skip_if_no_cmdstan() + set.seed(5) + ll <- simulate_linelist( + n = 2000, cfr = 0.4, onset_days = 40, + delay = Gamma(mean = 12.75, sd = 7), + recovery = Weibull(mean = 21, sd = 9) + ) + d <- prepare_cfr_data(ll, obs_time = max(ll$onset_date) - 2) + fit <- fit_quick(d, + delay = Gamma(shape = Normal(3.3, 1), rate = Normal(0.26, 0.08)), + recovery_delay = Weibull(shape = Normal(2.4, 0.6), scale = Normal(23.7, 4)), + cfr_prior = Beta(1, 1) + ) + expect_equal(fit$cfrnow$recovery_family, "weibull") # differs from gamma death + s <- summary(fit) + rm <- s[s$quantity == "recovery_mean", ] # onset-to-recovery ~21 (heavy censoring) + expect_gt(rm[["q50"]], 17) + expect_lt(rm[["q50"]], 26) +}) + test_that("a fixed delay runs the Ghani/Nishiura estimator (delay held constant)", { skip_if_no_cmdstan() set.seed(4) From c13b069c6ba3753db6ba1d3a4c461e0d70794285 Mon Sep 17 00:00:00 2001 From: sbfnk-bot <242615673+sbfnk-bot@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:33:04 +0100 Subject: [PATCH 3/4] Use Exponential() over deprecated Exp() in delay-family test Co-authored-by: sbfnk --- tests/testthat/test-families.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-families.R b/tests/testthat/test-families.R index 2ec6fd1..b3e993d 100644 --- a/tests/testthat/test-families.R +++ b/tests/testthat/test-families.R @@ -29,7 +29,7 @@ test_that("a recovery delay adds recoveries for non-fatal cases only", { }) test_that("unsupported families and prior-parameter delays are rejected", { - expect_error(sample_delay(5, Exp(rate = 1)), "supports") # exp not supported + expect_error(sample_delay(5, Exponential(rate = 1)), "supports") # exp not supported expect_error(sample_delay(5, LogNormal( meanlog = Normal(2, 0.1), sdlog = Normal(0.5, 0.1) )), "fixed") From 951d0ce11d977dfa246ff4fdbb7ecdafa44fe884 Mon Sep 17 00:00:00 2001 From: sbfnk-bot <242615673+sbfnk-bot@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:33:56 +0100 Subject: [PATCH 4/4] Require distspec (>= 0.1.0) Co-authored-by: sbfnk --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 595ec41..d2ed844 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -18,7 +18,7 @@ BugReports: https://github.com/sbfnk/cfrnow/issues Encoding: UTF-8 Roxygen: list(markdown = TRUE) Depends: - distspec, + distspec (>= 0.1.0), R (>= 4.1.0) Imports: brms,