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.
Write a method that turns your class into one epidist
understands.
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.
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.
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().
If you are building an extension and something in this interface is awkward, please open an issue.