Why extend epidist?
epidist handles the parts of delay estimation that are tedious and easy to get wrong.
Censoring, truncation, the brms formula interface, priors, and the Stan code that ties them together.
If you are building a model that shares those problems but has a different likelihood, you might want to do so by extending epidist.
Two packages already do this, in two different ways.
-
cfrnowregisters a new model type. -
tbl.nowadds a new data source, by writingas_epidist_linelist_data()andas_epidist_aggregate_data()methods for its own class.
Adding a data source
Write a method that turns your class into one epidist understands.
Code
#' @importFrom epidist as_epidist_linelist_data
#' @export
as_epidist_linelist_data.my_class <- function(data, ...) {
return(as_epidist_linelist_data(
as.data.frame(data),
pdate_lwr = "onset_date",
sdate_lwr = "report_date"
))
}Code
my_data |>
as_epidist_linelist_data() |>
as_epidist_marginal_model() |>
epidist(formula = mu ~ 1)Adding a model type
A model type is an S3 class plus methods for six generics.
Three you have to supply.
| Generic | What you supply | If you skip it |
|---|---|---|
assert_epidist() |
What columns your data must have and what must be true of them | Errors |
epidist_family_model() |
The brms custom family, including the likelihood name and its vars
|
Returns the family unchanged |
epidist_stancode() |
The Stan code injected through brms stanvars
|
NULL, so no likelihood |
Three are optional.
| Generic | What you supply | If you skip it |
|---|---|---|
epidist_formula_model() |
How the user’s formula is rewritten, usually adding vreal() terms |
Returns the formula unchanged |
epidist_transform_data_model() |
Reshaping before fitting | Returns the data unchanged |
epidist_model_prior() |
Model specific priors | NULL |
You also write a constructor that sets your class, by convention as_epidist_<name>_model().
The skeleton looks like this.
Code
as_epidist_my_model <- function(data) {
class(data) <- c("epidist_my_model", class(data))
assert_epidist(data)
return(data)
}
#' @importFrom epidist assert_epidist
#' @export
assert_epidist.epidist_my_model <- function(data, ...) {
checkmate::assert_names(names(data), must.include = c("delay", "n"))
return(invisible(NULL))
}
#' @importFrom epidist epidist_family_model
#' @export
epidist_family_model.epidist_my_model <- function(data, family, ...) {
return(brms::custom_family(
paste0("my_model_", family$family),
dpars = family$dpars,
vars = "vreal1",
loop = FALSE
))
}
#' @importFrom epidist epidist_formula_model
#' @export
epidist_formula_model.epidist_my_model <- function(data, formula, ...) {
return(stats::update(formula, delay | vreal(n) ~ .))
}
#' @importFrom epidist epidist_stancode
#' @export
epidist_stancode.epidist_my_model <- function(data, family, formula, ...) {
return(brms::stanvar(block = "functions", scode = "// your lpdf here"))
}Register the S3 methods in your NAMESPACE and import the generics from epidist, as cfrnow does:
importFrom(epidist,assert_epidist)
importFrom(epidist,epidist_family_model)
importFrom(epidist,epidist_formula_model)
importFrom(epidist,epidist_stancode)
S3method(assert_epidist,epidist_my_model)
S3method(epidist_family_model,epidist_my_model)
S3method(epidist_formula_model,epidist_my_model)
S3method(epidist_stancode,epidist_my_model)
Users then call epidist() exactly as they would for a built-in model.
Code
data |>
as_epidist_my_model() |>
epidist(formula = mu ~ 1)Exploring how cfrnow extends epidist
cfrnow fits a mixture-cure survival model to estimate a real-time case fatality ratio.
Every method it needs is in R/cure_model.R.
| Method | |
|---|---|
as_epidist_cure_model() |
Sets the class |
assert_epidist() |
Checks the columns its likelihood needs |
epidist_family_model() |
Declares the custom family and its vars
|
epidist_formula_model() |
Adds the vreal() terms |
epidist_transform_data_model() |
Reshapes before fitting |
epidist_model_prior() |
Returns NULL
|
epidist_stancode() |
Injects the mixture-cure likelihood |
cfrnow also exports its own frontend.
Its users call prepare_cfr_data(), fit_cfr() and pp_check_cfr() rather than epidist().
