A trunc()ed response simulates by rejection within its bounds, and a
row whose window keeps too little of its mass for rejection to fill
it in 20 passes by inverting the family's CDF, so
every draw lies in [lb, ub] and posterior-predictive checks
(dharma_residuals(), pp_check()) see the same support the
likelihood was normalized on.
Usage
# S3 method for class 'frmtmb_fit'
simulate(
object,
nsim = 1,
seed = NULL,
re_formula = NULL,
censored = FALSE,
newdata = NULL,
allow_new_levels = FALSE,
...
)Arguments
- object
A
frmtmb_fit.- nsim
Number of simulated response vectors.
- seed
Optional RNG seed. Follows the
stats::simulate()contract: the global RNG state is restored afterwards, and the seed used is attached as the"seed"attribute.- re_formula
Which group-level terms the draws condition on:
NULL(default) all of them,NA(or~0,~1) none, and a one-sided formula the terms it names. A term not kept is redrawn from its estimated distribution in each replicate (see Group-level terms).- censored
Apply the fitted
cens()mechanism to the draws (see Censored responses). Ignored withoutcens().- newdata
Optional data frame to simulate the responses of, instead of the fitted rows (see New data).
- allow_new_levels
With
newdata: draw the effect of a grouping level the fit never saw from its term's estimated distribution, rather than refuse it. brms's spelling.- ...
Refused: an argument the method does not have is an error naming it, rather than silently changing nothing.
Value
A data frame with nsim columns and a "seed" attribute,
with one row per fitted row, or per row of newdata.
Structured draws
Most families draw each row on its own. Some cannot, and those go
through one implementation that simulate(), posterior_predict()
and frm_simulate() all reach (see sim_ctx in
frmtmb_family()):
a
mixture(groups = ~g)draw takes one class per GROUP and then simulates each row from its group's component;a multivariate normal mixture (frmtmb.latent's
mixture_mvn()) takes a class per row and then a multivariate normal with that class's own covariance;a residual correlation term (
ar(),ma(),cosy(), ...) is one multivariate residual draw per group added to the mean predictor, so the draws carry the fitted autocorrelation;a family from an extension package draws through whatever its structure declares, by the same route.
A structured draw covers whole sequences or groups, so trunc()
rejection cannot resample single rows within it (every structured
model refuses trunc() when the frame is assembled). At newdata a
structured FAMILY is refused, because its structure indexes the rows
the model was fitted on: mixture(groups = ), a hidden Markov
family, a learning family, and a multivariate normal mixture, which
predict(newdata = ) refuses as well. A residual correlation term
is rebuilt on the new rows instead: rows that share a group are
drawn jointly, and a time the fit never saw is refused. A lag is
counted in the FITTED time levels, as the likelihood counts it, so
rows at times 2 and 4 are two levels apart even with nothing at
time 3 in newdata. brms counts a lag by a row's position among its
group's newdata rows instead, which makes the correlation of two
rows depend on which other rows newdata holds; frmtmb departs from
it on purpose.
Group-level terms
re_formula chooses which group-level terms the draws condition on,
read as predict.frmtmb_fit() reads it: NULL keeps every term,
NA, ~0 and ~1 keep none, and a one-sided formula keeps the terms
it names (a term the fit does not have is an error). A term that is
kept enters at its estimated effects. A term that is not kept is
REDRAWN from its estimated distribution in every replicate, which is
lme4's unconditional simulation. That is where simulate() and
predict() differ: a prediction is for an average group, so a
dropped term contributes nothing there, and a simulated response
needs a group, so here it gets a new one. When a formula keeps some
columns of a term and drops others, as ~ (1 | g) does on a
(1 + x | g) fit, the dropped columns are drawn given the kept ones
at their estimates.
A SMOOTH is never redrawn, gp() and hsgp() curves included and a
smooth indexed by a grouping factor included (s(g, bs = "re"),
s(x, g, bs = "fs"), a t2() with an re margin). Its coefficients
are penalized coefficients of a term of the formula, not group
effects, and re_formula = NA keeps every smooth as brms does, so
the draws at NA are the conditional draws on a fit whose only
group-indexed content is a smooth. frm_bootstrap() reads
re_formula the same way.
New data
With newdata the draws are for its rows. The response column is
not needed. At a grouping level the fit saw, a kept term enters at
that level's estimate, and a redrawn term shares one draw across the
rows of the level, so newdata must carry the grouping column. A level
the fit never saw is an error unless allow_new_levels = TRUE, which
draws its effect from the term's estimated distribution, as
predict() does. Under re_formula = NA (or ~0, ~1) every
group-level term is redrawn anyway, so an unseen level of one is one
more fresh level and needs nothing. A SMOOTH indexed by a grouping
factor is not redrawn and not dropped, so it is the exception at every
re_formula: newdata must carry its grouping column, and an unseen
level is refused unless allow_new_levels = TRUE, which gives it the
population curve for an fs basis and is refused for an re basis or
margin, exactly as in predict().
Censored responses
On a cens() fit the default draws the LATENT, uncensored response:
the model describes the latent distribution, and censoring is a
property of the observation process, not of the response. This
matches brms, whose posterior_predict() also ignores cens() (and
whose pp_check() therefore drops the censored rows). The draws are
then not comparable with the observed values on censored rows, which
is why dharma_residuals() and residuals(type = "osa") refuse or
skip them.
censored = TRUE applies the fitted censoring mechanism to each
draw instead, so the draws are directly comparable with the observed
data: every draw is recorded at the edge of the observation window
it falls outside, capped above by the right-censoring point and
below by the left-censoring point. Those points are the response
values of the censored rows, and they must be the same for every
censored row on a side (type-I censoring): with row-varying
censoring times an uncensored row's censoring point is unknown, so
the mechanism cannot be applied to its draws and the call is
refused. Interval censoring has no single-value representation and
is refused too. At newdata the same fitted window applies.
Examples
set.seed(1)
dd <- data.frame(x = rnorm(100), g = factor(rep(1:10, 10)))
dd$y <- rpois(100, exp(0.3 + 0.4 * dd$x + rnorm(10, 0, 0.6)[dd$g]))
fit <- frm(bf(y ~ x + (1 | g)) + poisson(), data = dd)
# one column per draw; the seed used is attached
sims <- simulate(fit, nsim = 5, seed = 42)
str(sims)
#> 'data.frame': 100 obs. of 5 variables:
#> $ sim_1: int 2 3 0 4 1 2 2 1 2 5 ...
#> $ sim_2: int 1 0 0 2 3 6 2 3 2 0 ...
#> $ sim_3: int 1 1 2 2 0 2 4 2 1 2 ...
#> $ sim_4: int 0 1 0 2 2 5 1 1 1 3 ...
#> $ sim_5: int 0 1 1 2 2 1 4 1 0 3 ...
#> - attr(*, "seed")= num 42
#> ..- attr(*, "kind")=List of 3
#> .. ..$ : chr "Mersenne-Twister"
#> .. ..$ : chr "Inversion"
#> .. ..$ : chr "Rejection"
attr(sims, "seed")
#> [1] 42
#> attr(,"kind")
#> attr(,"kind")[[1]]
#> [1] "Mersenne-Twister"
#>
#> attr(,"kind")[[2]]
#> [1] "Inversion"
#>
#> attr(,"kind")[[3]]
#> [1] "Rejection"
#>
# re_formula = NA redraws the group effects, which is the right choice
# for a parametric bootstrap over new groups
sims_m <- simulate(fit, nsim = 5, re_formula = NA, seed = 42)
apply(sims_m, 2, var) > apply(sims, 2, var)
#> sim_1 sim_2 sim_3 sim_4 sim_5
#> TRUE FALSE FALSE FALSE TRUE
# draws for rows the fit never saw, at a known group and a new one
nd <- data.frame(x = c(-1, 1), g = factor(c("3", "new")))
simulate(fit, nsim = 3, seed = 1, newdata = nd, allow_new_levels = TRUE)
#> sim_1 sim_2 sim_3
#> 1 1 2 0
#> 2 3 1 1
# a posterior-predictive check by hand: does the fit reproduce the
# share of zeros in the data?
mean(dd$y == 0)
#> [1] 0.29
colMeans(simulate(fit, nsim = 20, seed = 1) == 0)
#> sim_1 sim_2 sim_3 sim_4 sim_5 sim_6 sim_7 sim_8 sim_9 sim_10 sim_11
#> 0.30 0.24 0.35 0.30 0.25 0.25 0.23 0.24 0.33 0.32 0.27
#> sim_12 sim_13 sim_14 sim_15 sim_16 sim_17 sim_18 sim_19 sim_20
#> 0.27 0.30 0.37 0.27 0.29 0.24 0.29 0.28 0.34