vignette("brms-migration", package = "frmtmb") ports the
model GRAMMAR: what bf(), the addition terms, the
group-level structures and the families do here, and how a
brm() call becomes a frm() call. None of that
changes when you sample. frm_sample() takes the same
bf() object, assembles it through the same code path, and
refuses what frm() refuses.
This page is the other half: what happens to a brms POSTERIOR workflow. The draws come from Stan’s NUTS sampler, but through tmbstan on an RTMB objective rather than a generated Stan program, and that one difference explains every entry below.
Every chunk here is shown and not run, because every one needs a
sampler this package only suggests. vignette("sampling")
says more about why, and its own prior tables do run.
The call
library(frmtmb)
library(frmtmb.sample)
# brms
brm(y ~ x + (1 | g), data = dd, family = gaussian(),
chains = 4, iter = 2000, cores = 4)
# here
frm_sample(bf(y ~ x + (1 | g)) + gaussian(), data = dd,
chains = 4, iter = 2000, cores = 4)| brms | here |
|---|---|
brm(formula, data, family) |
frm_sample(formula, data =, family =), or
frm_sample(fit) on a fitted model |
chains, iter, warmup,
thin, cores, seed,
refresh
|
the same names, passed straight through to
tmbstan::tmbstan()
|
prior = prior(...) |
prior = set_prior(...); a brmsprior that
brms itself built is translated row by row |
| the default prior set | applied on both routes, read off brms::default_prior();
prior = "flat" is brms’s (flat) in every
slot |
init =, init_r =
|
init =, init_jitter =; from a fit the
maximum-likelihood mode anchors the chains |
control = list(adapt_delta = ) |
the same name and the same meaning, passed to rstan through
tmbstan::tmbstan()
|
backend =, threads =,
stan_model_args =
|
nothing to configure: there is no Stan program to compile |
sample_prior = "only" |
frmtmb::frm_simulate(), which draws a parameter vector
per simulation and returns it with the responses |
control is the sampler’s control list here, as it is in
brms, so a call that tightens the adaptation ports across unchanged:
ds <- frm_sample(fit, chains = 4,
control = list(adapt_delta = 0.99, max_treedepth = 12))The fit-time options frm() takes are
fit_control in this function, and they apply only on the
formula route, where there is a model to assemble. Earlier releases
spelled them control, so a call written then is refused by
name rather than reinterpreted.
Two routes, and which one to take
frm_sample() takes a fit or a formula, and both sample
the same posterior under the same priors. Coming from brms, the formula
route is the familiar one: it is brm() with a different
function name. The fit route is the one brms has no counterpart to.
fit <- frm(bf(y ~ x + (1 | g)) + gaussian(), data = dd)
ds <- frm_sample(fit, chains = 4)Fitting first is worth the seconds it takes. The optimizer either
finds the mode or reports why it could not, diagnose(fit)
says whether the model is identified before any chain runs, and the mode
then anchors the chains and shortens warmup. A model that will not
converge under frm() is a model whose posterior geometry is
about to be difficult, and finding that out in a second is better than
finding it out in an hour. vignette("sampling") has the
detail on both routes, the default priors and the non-centered
parameterization.
The names are brms’s
The draws carry brms’s names: b_Intercept and
b_x for the coefficients, r_g[1,Intercept] for
a group-level coefficient. A ported hypothesis() string
needs no edit either: brms’s default class = "b" reads
x as b_x, and a natural-scale name such as
sd_g__Intercept takes class = NULL, as in
brms.
The group-level standard deviations and correlations are brms’s
columns too, sd_g__Intercept and
cor_g__Intercept__x, so
as_draws_df(ds)$sd_g__Intercept works as on a brmsfit. The
sampler samples the log standard deviation, not the standard deviation,
and that column stays beside them as theta_1, the name
confint() gives the same parameter on the fit;
sd_g__Intercept is computed from it, draw by draw. A
residual standard deviation with no formula of its own is the column
sigma, on its natural scale, as in brms; written out as
sigma ~ 1 it is the log-scale coefficient
b_sigma_Intercept, as in brms too.
variables(ds)
hypothesis(ds, "sd_g__Intercept^2 / (sd_g__Intercept^2 + sigma^2) = 0",
class = NULL)nuts_params() is the exception. It reads the
stanfit directly, as brms’s own nuts_params()
does, and its rows are the sampler’s quantities rather than model
parameters.
The method surface ports
The post-processing generics are brms’s own, on a
frmtmb_draws object, and they return brms’s SHAPES: the
summary matrices and arrays, the raw draws at
summary = FALSE, and the same list
hypothesis() gives, under frmtmb’s own class rather than
brms’s brmshypothesis. So most ported code runs unchanged:
fixef(), ranef(), VarCorr(),
posterior_epred(), posterior_predict(),
posterior_linpred(), log_lik(),
loo(), waic(), bayes_R2(),
hypothesis(), conditional_effects() (with
spaghetti), conditional_smooths(),
posterior_average(), pp_check(),
mcmc_plot(), prior_summary(),
as_draws() and the posterior and
coda conversions. posterior_average() takes
numeric weights or brms’s "stacking",
"pseudobma", "loo" and "waic";
its "kfold" and "bma" need the refits and the
marginal likelihood described below, and are refused.
loo() here is the real leave-one-out, not the
AIC() substitute core offers, and it returns the loo
package’s own object.
What refuses, and what to write instead
Every method below is defined, and every one stops with its reason and its replacement, so a ported script fails where it went wrong rather than at “could not find function”.
| brms call | why it stops | write instead |
|---|---|---|
stancode(ds) |
there is no Stan program; the R closure is the source |
ds$fit$obj$fn, ds$fit$frame
|
standata(ds) |
nothing is exported to a Stan data list |
ds$fit$frame, model.matrix(),
getME()
|
expose_functions(ds) |
a custom family is already plain R | call the lpdf you passed to
custom_family()
|
update(ds) |
draws carry no formula to revise |
update() the fit, then frm_sample()
it |
restructure(ds) |
no older-object upgrade path exists | re-run frm_sample()
|
plot(ds) does not stop: it draws brms’s histogram and
trace panels, nvariables parameters to a page, chosen with
variable and regex as in brms.
add_criterion(ds, "loo") stores the criterion in the draws,
and a later loo(ds) reads it, as in brms.
brms’s deprecated spellings posterior_samples(),
nsamples() and parnames() do not stop: each
answers with brms’s deprecation warning, as brms does. In new code write
as_draws(ds), ndraws(ds) and
variables(ds).
Two groups refuse for reasons that are not spelling.
The refit-based loo remedies. reloo(),
kfold(), loo_moment_match() and
loo_subsample() each need to refit the model on modified
data, and that machinery is not here. When loo() reports
high Pareto k values, sample under the default priors rather than
prior = "flat" and read the number again; if the warning
survives, compare the maximum-likelihood fits by AIC(), or
measure the influence with frmtmb::frm_bootstrap().
The marginal-likelihood family.
bridge_sampler(), bayes_factor() and
post_prob() are integrals against the prior. They are
undefined under prior = "flat", and even under the default
priors bridge sampling needs a normalized log-posterior evaluator that
the RTMB tape does not expose. This is the one brms capability with no
route here at all.
When you still want brms
- A Bayes factor or any other marginal-likelihood quantity.
-
reloo()orkfold()on a model whose LOO approximation fails. - A family, a term or an addition term frmtmb does not implement.
frmtmb::frm_compat()answers that per feature, and the answer is the same forfrm()andfrm_sample(). - Stan code you intend to read, extend or hand to somebody else.
A practical division: screen models with frm(), which
costs seconds, sample the survivors with frm_sample(), and
keep brms for the questions in that list.