frm_linpred(se.fit = TRUE) builds a matrix A with one row per
prediction and one column per contributing coefficient, forms
A V A' and keeps only its diagonal. Every delta-method quantity
over a fitted curve needs the whole thing: a contrast between two
grids, an average marginal effect with a correct standard error, a
simultaneous band, a derivative, the time of a peak. This returns the
pieces so that an extension does not have to rebuild A by
perturbation, one predict() call per coefficient.
Usage
frm_lp_basis(
object,
newdata = NULL,
dpar = NULL,
resp = NULL,
re_formula = NULL,
allow_new_levels = FALSE,
extra_cov = FALSE
)Arguments
- object
A fitted
frmtmb_fit.- newdata
Data to build the design at, or
NULLfor the training data.- dpar, resp
The distributional parameter and response to take the linear predictor of. Both default the way
predict()defaults them.- re_formula
NULLkeeps every random effect,NAdrops the group-level terms and keeps every smooth, a one-sided formula keeps the terms it names. Seefrm_linpred()for the full rule.- allow_new_levels
Whether a grouping level the fit never saw is allowed.
- extra_cov
If
TRUE, also returnextra_cov, the full covariance over the rows of the variance inextra_var.
Value
A list with
etathe linear predictor, exactly
frm_linpred(type = "link").An x p;d eta / d coef.coef_poslength
p; the rows ofVthe columns ofAbelong to, inV's own order.Vp x p;frm_joint_cov()subset tocoef_pos.coef_nameslength
p; the labels of those rows.extra_varlength
n; variance that is NOT coefficient uncertainty, kept separate rather than folded intoA V A'because an exactgp()'s kriging variance and a new grouping level's marginal variance are not.nonestlength
n; rows that load on a direction the rank-deficient design could not identify.extra_covonly with
extra_cov = TRUE:n x n; the covariance between rows of the variance inextra_var, whose diagonal it is, exactly. A sparseMatrix::Matrix()when its blocks cover little of the grid (all zero when nothing contributes), a dense matrix otherwise.
var(eta) is rowSums((A %*% V) * A) + extra_var, and
frm_linpred(se.fit = TRUE) is written that way. The covariance of
the whole prediction is A V A' + extra_cov.
Beside a latent residual (ar(), ma(), arma(), cosy() or unstr()
on a family with no residual), coef_pos leaves out every column that no
row of A loads: a latent, a level the rows do not name, a coefficient
whose covariate is 0 on every row. Such a column adds nothing to A V A'.
A new row loads no latent, so A and V stay small. The
fitted rows load every latent, and there A and V are dense, about
16 n^2 bytes for n rows (1 GB at 8000 rows). A V of more than
getOption("frmtmb.latres_dense_rows", 20000)^2 numbers is refused.
frm_linpred(se.fit = TRUE) reads the fitted rows from the sparse factor
of the joint precision instead, at no such cost.
Under a cov = FALSE autocorrelation term eta is the predictor without the
term, as brms's emmeans() reads it (incl_autocor = FALSE), and
frm_linpred() adds the one-step part from the earlier residuals.
With no dpar on a predictor that frm_linpred() returns as several
columns (a cs() term, several location predictors), every row of the grid
comes once per column, the column slowest, so eta is as.numeric() of that
matrix, and the list also has layers, the column names. Under a cs() term
A has the term's coefficients beside the shared design. dpar = "mu" is
the shared part.
On a fit whose fixed design is ill-conditioned (a raw polynomial of high degree, a covariate far from zero) the list also has
V_rootp x k; a factor of the coefficient covariance.V_restp x p; the part ofVthatV_rootdoes not carry, zero without random effects.
with V = V_rest + V_root V_root'. Use
rowSums((A %*% V_root)^2) + rowSums((A %*% V_rest) * A) for
var(eta), as frm_linpred() does. V is accurate entry by entry,
but A V A' formed from it in double precision is not: on a raw
polynomial of degree 8 it can be wrong by a factor of 3.
Details
The name follows emmeans::emm_basis(), which is the same idea for
the fixed block alone.
Variance that is not coefficient uncertainty
Three sources reach extra_var. A new row of a me() term adds the
variance of its latent value's draw from normal(x, sdx); rows at one
level of me(gr = ) read one draw and covary fully, and other rows are
independent. An exact gp() evaluated at a position
the fit did not see adds the field's conditional (kriging) covariance
given its values at the fitted positions,
k(x, x') - k(x, X) K^-1 k(X, x'); rows at an observed position carry
none. Every unseen row of one gp() term loads the SAME draw of the
field, so those rows covary, and a simultaneous band, a difference
between two grids or a derivative needs the whole matrix rather than
its diagonal. A grouping level the fit never saw
(allow_new_levels = TRUE) adds its block's covariance: rows at the
same unseen level load one draw of its effect and covary fully, and
rows at two different unseen levels load independent draws.
For a difference between two grids, call this once on the stacked
grid rbind(newdata, newdata2): the off-diagonal block of
extra_cov is the covariance between them. For a DERIVATIVE of the
curve, do not difference extra_cov: a finite difference of a
covariance divides its rounding by the step to the power of twice
the order. frm_extra_cov_deriv() returns that covariance from its
sources.
A nonlinear body
For a nonlinear predictor A is a JACOBIAN rather than a design, and
it is computed by taping the body against the coefficients it reaches
through. That includes a ps() block, whose coefficients enter the
body through a spline evaluated at an argument the parameters move.
frm_linpred(se.fit = TRUE) stays refused for a nonlinear predictor;
this is the route.
The Jacobian is exact, and the delta method built on it is still a
first-order approximation, which for a warped curve is a stronger
assumption than it is for a linear one. allow_new_levels = TRUE is
refused there, and so is a contributing exact gp(), because neither
variance has a chain rule through the body that has been measured.
A reduced-rank block
An rr() block's loadings live in theta, so a design over
(beta, b) alone is INCOMPLETE. A carries the loading columns
too, through rr_jacobians(), and coef_pos names their theta
rows; a caller therefore gets the whole delta method rather than
discovering a missing piece.
See also
frm_joint_cov() for the covariance alone,
frmtmb-extension-api
Examples
set.seed(1)
dd <- data.frame(x = rnorm(120), g = factor(rep(1:12, each = 10)))
dd$y <- rnorm(120, 1 + 2 * dd$x + rnorm(12, 0, 0.5)[dd$g], 0.4)
fit <- frm(bf(y ~ x + (1 | g)), data = dd)
nd <- data.frame(x = c(-1, 0, 1), g = factor(1, levels = levels(dd$g)))
lb <- frm_lp_basis(fit, newdata = nd, re_formula = NA)
str(lb$A)
#> num [1:3, 1:2] 1 1 1 -1 0 1
#> - attr(*, "dimnames")=List of 2
#> ..$ : chr [1:3] "1" "2" "3"
#> ..$ : chr [1:2] "(Intercept)" "x"
lb$coef_names
#> [1] "beta.(Intercept)" "beta.x"
# the covariance of the WHOLE grid, which frm_linpred() reduces to
# diagonal
Sigma <- lb$A %*% lb$V %*% t(lb$A)
all.equal(sqrt(diag(Sigma)),
frm_linpred(fit, newdata = nd, re_formula = NA,
se.fit = TRUE)$se.fit)
#> [1] TRUE