Skip to contents

A mixture likelihood is invariant to permuting its components, so the location coefficients enter a multimodal objective. Both REML and profile = TRUE integrate those coefficients out with a Laplace approximation about a single inner mode, which is not defined here: the inner Newton solve walks between the component modes and the fit either dies at "NA/NaN gradient evaluation" or reports an optimum with a gradient near 1e9. Quadrature is unaffected, because it marginalizes the random effects, not the coefficients.

The conservative all-FALSE default is right for a family whose likelihood does not factorize: it starts fully refused and opts in. It is exactly wrong for a family whose likelihood DOES factorize and which carries a structure only to hold the two or three refusals that belong to it, because there every other capability already works and a flag would only take it away. This is the starting point for that second kind, so such a family names what it refuses and nothing else.

The read-only surface a frmtmb_structure() slot may use. A structure's fitted_mean, fitted_var, latent_probs and sim_ctx run at the estimates and outside the AD tape, and they are handed a frmtmb_fit; these are how such a slot reads what it needs out of one without reaching into the fit's internal layout. The rest of a family's needs are already public: frm(), bf(), fixef(), set_prior(), posterior_predict(), conditional_effects() and hypothesis() among them.

Usage

response_mean(fam, dpars, aterms)

mixture_multimodal_refusals(what)

mixture_posterior(fit)

as_frmtmb_family(x)

eval_dpars(fit, b = fit$estimates[["b"]])

single_response(fit, what)

fit_extras(fit, resp = NULL)

dpar_linpred(frame, params, resp, dpar)

structure_supports_all(...)

frame_block_of(frame, resp)

Arguments

fam

A frmtmb_family.

dpars

Named list of numeric dpar values, as one element of eval_dpars().

aterms

Evaluated addition terms for the response, fit$frame$aterm_values[[resp]].

what

For single_response(), the calling function as it should open a refusal: "latent_probs()". For mixture_multimodal_refusals(), the family as the refusal should name it: "an lca() family".

fit

A frmtmb_fit.

x

A family: a frmtmb_family, a stats::family() object, a family constructor, or a family name.

b

Random-effect vector to evaluate at, defaulting to the fit's own estimates. NULL drops the random-effect contribution.

resp, dpar

The response name and the distributional parameter name, as eval_dpars() returns them.

frame

A fit$frame, or the frame a check_fit slot is given.

params

A parameter list in the frame's layout: a fit's estimates, or the starting template a check_fit slot is given.

...

Capability flags to override, named as in the supports table of frmtmb_structure().

Value

single_response() a response spec; eval_dpars() a nested list of numeric vectors; fit_extras() a named list or NULL; dpar_linpred() a numeric vector or NULL; response_mean() a numeric vector; as_frmtmb_family() a frmtmb_family; frame_block_of() a list or NULL; structure_supports_all() a named list of logicals; mixture_posterior() a matrix of class probabilities; mixture_multimodal_refusals() a list of two refusal strings.

Details

what is the family as the sentence should name it. These used to be one message listing every mixture-type family, raised from one gate in fit.R that knew all three by name; each family states its own now.

They are documented together because they are one contract with one promise: what they return is stable, so a family in another package can be written against them. Everything else in a fit, a frame or a family object is internal and may be renamed without notice.

single_response()

The one response spec of a univariate fit, or the refusal a method owes a multivariate one. what names the caller and opens the message. Almost every structured family is univariate, so this is the first line of most slots. It was named uni_resp() while it was internal.

eval_dpars()

The distributional parameters of every response at the estimates, on their natural (response) scale, evaluated over the training rows: out[[response]][[dpar]] is a numeric vector of length n or 1. b supplies a different random-effect vector, which is how a family evaluates at the modes of a resampled fit. This is the same evaluation predict(), fitted() and residuals() run.

fit_extras()

The family's extra (non-dpar) parameters at the estimates, as a named list in the order extra_pars declared them, or NULL when the family declared none. Item profiles, ordinal thresholds and class covariances arrive here. In a multivariate model each response's block is stored under a name that carries the response; resp adds that response's block back under the names its family declared, which is the list its density and simulator read.

dpar_linpred()

The FIXED-effect part of one dpar's linear predictor, on the link scale, at a parameter list in the frame's own layout, or NULL when that response and dpar have no linear predictor with columns. Random effects are excluded on purpose: this exists for a check_fit slot, which runs before the inner modes are solved and cannot see them. Not to be confused with the dpar_* accessors of frmtmb-robust-dpars, which take a dpars list rather than a frame and run inside a density while the objective is taped.

response_mean()

The expected response of a family at given dpar values, with trunc() bounds applied when the response carries them. A structured family composing per-state or per-class means calls this once per state rather than reaching into the wrapped family's post slot.

as_frmtmb_family()

The internal representation of a family given as a frmtmb_family, a stats::family(), a bare family constructor or a name. A family that WRAPS another one calls this on its argument, so that hmm(2, gaussian), hmm(2, gaussian()) and hmm(2, "gaussian") all mean the same thing.

frame_block_of()

The block a frame_block slot built for one response, or NULL when it built none. This is the read half of that slot: the block is written once at frame assembly and read back by every slot that runs at the estimates, and where a frame keeps its blocks is not a layout an extension should spell for itself.

structure_supports_all()

A supports list with every capability TRUE and the named ones overridden. The starting point for the second kind of structure: a family whose likelihood DOES factorize over rows and which declares a structure only to carry the two or three refusals that belong to it. Writing the TRUEs out by hand instead would silently take away each new capability frmtmb adds.

mixture_posterior()

The posterior class probabilities of any family that implements the fam$mix component interface, which is what mixture(), a multivariate normal mixture and a latent-class family all do. A family implementing that interface uses this as its whole latent_probs slot rather than deriving the same quantity a second time. See "The mixture component interface" below for the fields it reads.

mixture_multimodal_refusals()

The refusals entries for reml and profile owed by a family whose likelihood is invariant to permuting its components. what is the family as the sentence should name it, for example "an lca() family". The fact refused is the same one in every such family, so the sentence is issued from one place rather than copied.

The mixture component interface

A family is a finite mixture to frmtmb when its family object has a mix element (fam[["mix"]] <- list(...), set after frmtmb_family() returns). frmtmb then treats it as one: mixture_probs() and pp_mixture() accept its fits, a prior must name the dpar it means (several locations), and ar()-type terms and incl_thres are refused. mixture_posterior() reads three fields, which such a family must supply:

K

The number of classes, an integer.

log_pi(dpars)

The log mixing weights: a list of K vectors, each of length 1 or the number of rows, from the family's dpars list. They must sum to one on the natural scale in every row.

comp_lpdf(y, dpars, aterms, k, extra)

The log density of each row under class k, a vector with one value per row of y (NROW(y), so a matrix response gives one per row). The fifth argument, the family's extra parameters as fit_extras() returns them, is passed only when the family declares extra_pars; a family without extra parameters takes four arguments.

The other fields that mixture() stores in mix (ord, ref, type, comp_dpars, comp_sim and its component bookkeeping) are read only for mixture() itself; a family that leaves them out is not read through them. A family may store fields of its own there, as frmtmb.latent's mixture_mvn() stores D and sigma.

How to add a covariance structure

A package that supplies a structured covariance, or a precision matrix that depends on parameters, registers it with frmtmb_register_covstruct() from its .onLoad(). frmtmb.spatial does this for car() and spde(). The steps:

  1. Decide how a formula writes the structure. A bar structure, name(terms | g), covaries the coefficients of one level. A field, a special such as name(M, gr = g), has one intercept per level of gr and covaries the levels through fixed data; give it a term slot.

  2. Write lpdf(b, theta, blk), the log density of the block on the tape, with RTMB's densities and no loop over levels, and vcov(theta, blk), the covariance within one level, on numbers. Name the theta entries that are log standard deviations in sd_idx.

  3. For a field, give natural, the rows confint_varcorr() reports, and draw, one draw of the whole field. Set levels_coupled = TRUE when the levels are correlated, so that an unseen level gets no law it does not have.

  4. Register the rows the structure adds to frm_compat() with frmtmb_register_compat(), AFTER the structure: the structure itself joins the vocabulary when it is registered.

  5. Test the fit against a likelihood you compute without the registry, in a test that calls frmtmb_local_registries() first, so that every registry is restored when the test ends.

Use exported functions only, never frmtmb:::. When a user writes the structure while its package is not loaded, core refuses it by name if its table of extension homes lists the package, which is how car() points at frmtmb.spatial.

A term that correlates the RESIDUALS of the response, rather than adding a latent vector to the linear predictor, is not a covariance structure: it changes the response's density, and it may change its mean. Register it with frmtmb_register_autocor() instead, as frmtmb.spatial does for sar(). Such a term is one affine map from the response to independent innovations, and core writes its density, mean and draws from the map.

What the registries hold you to

The registries an extension fills from its own .onLoad() check what they are given, because a registration that is accepted and then does nothing is indistinguishable from one that worked.

  • frmtmb_register_compat() refuses a rule whose sides it cannot resolve. A side names a feature, a kind, a group or "*"; anything else matches no pair, so the rule is dropped in silence and the table reads as complete while covering less. Name a feature your package supplies in features =, and one another package supplies in expects =. A name expects = gives that the vocabulary already holds is refused too, because it exempts nothing; one still unresolved when frm_compat() is called is reported there rather than dropped.

  • frmtmb_register_aterm() adds the term to that vocabulary itself, as "<name>()" of kind "aterm". A term frm() accepts and frm_compat() cannot describe is a gap by construction, and the seam that creates the term is the only place that can close it without asking the registrant to remember. Register the term BEFORE the rules that name it; declaring it in features = anyway is a no-op. A name the vocabulary already holds under another kind is refused, and nothing is registered on either side.

  • frmtmb_register_covstruct() checks every slot before it commits anything, adds the structure to the compatibility vocabulary as a feature of kind "covstruct", and refuses a name that frmtmb's own structures or formula specials hold.

  • frmtmb_register_autocor() does the same for a residual correlation term, as a feature "<name>()" of kind "autocor", writes the rows of frm_compat() that refuse what every such term refuses, and checks npar and start once per fit.

  • frmtmb_family() takes required_aterms as a conjunction (a character vector) or as one with choices in it (a list, whose elements of length more than one are alternatives). Declare the choice rather than refusing it by hand in valid_y: the framework refuses before the frame is built and names every alternative.

See also

frmtmb_structure() for the protocol these serve, frmtmb_family() for the family object they read, frmtmb-robust-dpars for the accessors a DENSITY uses while the objective is taped, frmtmb-conditions for frm_stop(), frm_warning() and frm_message(), which an extension raises its conditions with, and the registries an extension fills from its own .onLoad(): frmtmb_register_frame_check(), frmtmb_register_aterm(), frmtmb_register_covstruct() and frmtmb_register_compat()

Examples

set.seed(1)
dd <- data.frame(x = rnorm(50))
dd$y <- rnorm(50, 1 + 2 * dd$x, 0.5)
fit <- frm(bf(y ~ x) + gaussian(), data = dd)

rspec <- single_response(fit, "my_family_probs()")
dp <- eval_dpars(fit)[[rspec$resp_name]]
str(dp)
#> List of 2
#>  $ mu   : Named num [1:50] -0.178 1.424 -0.591 4.215 1.712 ...
#>   ..- attr(*, "names")= chr [1:50] "1" "2" "3" "4" ...
#>  $ sigma: Named num [1:50] 0.479 0.479 0.479 0.479 0.479 ...
#>   ..- attr(*, "names")= chr [1:50] "1" "2" "3" "4" ...
head(response_mean(rspec$family, dp, list()))
#>          1          2          3          4          5          6 
#> -0.1776897  1.4240552 -0.5912755  4.2151811  1.7124621 -0.5613004 
fit_extras(fit)                       # NULL: no extra parameters
#> NULL
head(dpar_linpred(fit$frame, fit$estimates, rspec$resp_name, "mu"))
#>          1          2          3          4          5          6 
#> -0.1776897  1.4240552 -0.5912755  4.2151811  1.7124621 -0.5613004 
as_frmtmb_family(gaussian)
#> 
#> Family: gaussian 
#> Link function: identity 
#> Parameters: mu (identity), sigma (log)
#>