Registers an addition term (a brms "aterm": the trials(n) in
y | trials(n) ~ x) that frmtmb will accept on the left-hand side of
a formula. A family that lives outside frmtmb uses this to give its
per-row data the spelling its literature uses, instead of asking
users for vint(). Register from the contributing package's
.onLoad().
Arguments
- name
The term's name, as it is written in a formula, without parentheses:
"dec"acceptsy | dec(response) ~ x.- arity
How many arguments the term takes. With
arity = 1the value arrives asaterms[[name]]; above one it arrives asaterms[[paste0(name, i)]]for each argument, which is thevint()convention.- coerce
Function applied to each evaluated argument, once, at frame assembly. It must return a numeric vector, because the value is baked into the tape as data. The default is
as.numeric(); a term whose natural spelling is a factor gives a function that maps the factor to the numbers the density wants. A factor, character or logical value is coerced on the fitted data only. Newdata and refits are coded through a table of what each value became there, so that a coercion that builds levels from its input cannot code new rows by their own levels, and a value the fit did not see is refused.- option
Optional function
(family, call, env)that makes the term an option of the family rather than data (see the section "An option of the family"). The default,NULL, is a data term.
Details
The registered term carries DATA and nothing else. Its evaluated,
coerced value reaches the family's lpdf, lcdf, post$mean_fn and
sim in the aterms list under name (or under name1, name2,
... when arity is above one, following vint()), it is required on
newdata, and it is what frmtmb_family(required_aterms =) names.
It cannot reshape the likelihood: censoring, truncation and case
weights are core terms because the core acts on them, and a
contributed term is not a route to that.
Registering a name twice replaces the earlier entry, so that reloading the contributing package is not an error. The eight core terms cannot be replaced.
The term joins the compatibility vocabulary at the same time, as the
feature "<name>()" of kind "aterm", so that frm_compat() can be
asked about it and frmtmb_register_compat() rules may name it. A
term frm() accepts and the table cannot describe would be a gap by
construction, so the registrant is not asked to declare it twice;
declaring it in frmtmb_register_compat(features =) anyway is a
no-op rather than a duplicate. Register the term BEFORE the rules
that name it.
An option of the family
Some terms carry no data. brms's bhaz(df = 6) in
time | bhaz(df = 6) ~ x sets the basis of the baseline hazard of
cox(): it is an argument of the family, written on the response
side. Give such a term an option function. Then the term is not an
addition-term value: it does not reach aterms, it is not required
on newdata, and arity and coerce are not used. When the formula
is parsed and the response's family is known, frmtmb calls
option(family, call, env) once, with the family, the term's call as
written (quote(bhaz(df = 6))), and the formula's environment, in
which the function evaluates the call's arguments. The function
returns the family that the fit uses, or stops with a message that
says why it refuses the call. Build that family again from its
constructor, as the example does: a density is a closure over the
constructor's arguments, so a slot set on the family afterwards
changes nothing that the likelihood reads.
The family must accept the term. A family whose
frmtmb_family(accepts_aterms =) does not name the term is refused
before option() runs. A family that declares no allow-list reaches
option(), which must refuse a family it cannot change.
update(), predict() on new data and refits parse the formula
again, each time from the family as it was written, so option()
must return the same family for the same family and call.
A name the vocabulary already holds under another kind is refused,
and nothing is registered on either side: s() is a smooth, so
frmtmb_register_aterm("s") would give one spelling two meanings in
one formula. Thirteen names are taken this way: s, t2, mo,
mi_pred, gp_pred, cs_pred, ar, ma, arma, cosy,
unstr, mm and mmc.
See also
frmtmb_family() for required_aterms, which is how a
family says the term is not optional, frmtmb_register_frame_check()
for the seam that refuses a data problem, and
frmtmb-extension-api for the accessors an extension may use
Examples
# a decision indicator, given as a factor and delivered as 0/1
if (FALSE) { # \dontrun{
frmtmb_register_aterm("dec", arity = 1, coerce = function(x) {
as.integer(factor(x)) - 1L
})
} # }
# an option of a family: `y | knownsd(2) ~ x` is a normal model whose
# standard deviation is known to be 2. The family declares the term,
# and the option builds the family again with the new value.
normal_sd <- function(sd = 1) {
force(sd)
frmtmb_family(
"normal_sd", dpars = "mu", links = list(mu = "identity"),
accepts_aterms = "knownsd",
lpdf = function(y, dpars, aterms, extra) {
-0.5 * ((y - dpars$mu) / sd)^2 - log(sd) - 0.5 * log(2 * pi)
})
}
set.seed(1)
d <- data.frame(x = rnorm(50))
d$y <- 1 + d$x + rnorm(50, sd = 2)
# frmtmb_local_registries() undoes the registration when the function
# returns, so the example leaves the registry as it found it
fit_knownsd <- function() {
frmtmb_local_registries()
frmtmb_register_aterm("knownsd", option = function(family, call, env) {
normal_sd(eval(call[[2L]], env))
})
frm(bf(y | knownsd(2) ~ x), family = normal_sd(), data = d)
}
if (requireNamespace("withr", quietly = TRUE)) {
fit <- fit_knownsd()
# the log likelihood of the least-squares line with sd = 2
print(logLik(fit))
print(sum(dnorm(d$y, fitted(lm(y ~ x, d)), 2, log = TRUE)))
}
#> 'log Lik.' -103.5655 (df=2)
#> [1] -103.5655