Skip to contents

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() or kfold() 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 for frm() and frm_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.