brms
Misc
- Packages
- {IJSE} - Provides a function to calculate infinite-jackknife-based standard errors for fixed effects parameters in ‘brms’ models, handling both clustered and independent data. Also handles quantile regression.
- Resources
- Docs
- Applied Bayesian Regression Modelling Using R and Stan - Bürkner
- Model Comparison, Linear Models, GLMs, Linear Mixed Models, GLMMs.
- Applied Modelling in Drug Development: Flexible Regression Modeling in Stan via brms
- Case studies that use survival analysis and mixed models
- How brms fits models
- The marginal Sigma posterior distribution is the distribution of residuals
- Sampling the posterior
Logistic
# sampling the posterior <- f fitted(b3.1, summary = F, # says we want simulated draws and not summary stats scale = "linear") %>% # linear outputs probabilities as_tibble() %>% set_names("p")
- Optimization
- {cmdstanr} as the backend
In the brms model function
="cmdstanr", stan_model_args=list(stanc_options = list("O1")) backend
stanc_options = list("O1")
might be made default in a future update- See thread for details
Set using options:
options(brms.backend = "cmdstanr"
- {cmdstanr} as the backend
- Syntax Helpers
bf
(doc) - An alias forbrmsformula
that lets you specify model formulas
Linear Regression
Example: lm, Cont ~ Cont (SR, Ch.4)
.3 <- brm(data = dat, family = gaussian, b4~ 1 + weight, height prior = c(prior(normal(178, 100), class = Intercept), prior(normal(0, 10), class = b), prior(uniform(0, 50), class = sigma)), iter = 41000, warmup = 40000, chains = 4, cores = 4, seed = 4, backend = "cmdstanr", )
Example: Multivariable, Cont ~ Cont + Cont (SR, Ch.5)
.3 <- brm(data = dat, family = gaussian, b5~ 1 + Marriage_s + MedianAgeMarriage_s, Divorce prior = c(prior(normal(10, 10), class = Intercept), prior(normal(0, 1), class = b), prior(exponential(1), class = sigma)), iter = 2000, warmup = 1000, chains = 4, cores = 4, seed = 5, backend = "cmdstanr", file = "fits/b05.03")
- Outcome = Divorce rate, Predictor 1 = state’s marriage rate, Predictor 2 = state’s median marriage age
Example: Binary/Cat, Cont ~ Binary (SR, Ch. 5)
data(Howell1, package = "rethinking") <- Howell1 d <- d %>% d mutate(sex = ifelse(male == 1, 2, 1), # create index variable sex = factor(sex)) # transforming it into a factor tells brms it's indexed .8 <- brm(data = d, b5family = gaussian, ~ 0 + sex, # "0 +" notation means calculate separate intercepts for each category height prior = c(prior(normal(178, 20), class = b), prior(exponential(1), class = sigma)), iter = 2000, warmup = 1000, chains = 4, cores = 4, seed = 5, backend = "cmdstanr", file = "fits/b05.08") print(b5.8)
- For nominal variable, increase the s.d. of the α prior (as compared to the binary prior) “to allow the different
to disperse, if the data wants them to.” - “I encourage you to play with that prior and repeatedly re-approximate the posterior so you can see how the posterior differences among the categories depend upon it.”
- For nominal variable, increase the s.d. of the α prior (as compared to the binary prior) “to allow the different
Example: Multi-Categorical, Cont ~ Cat + Cat (SR, Ch. 5)
.11 <- brm(data = d, b5family = gaussian, # bf() is an alias for brmsformula() that lets you specify model formulas bf(kcal.per.g_s ~ 0 + a + h, ~ 0 + clade, a ~ 0 + house, h # tells brm we're using non-linear syntax nl = TRUE), prior = c(prior(normal(0, 0.5), nlpar = a), prior(normal(0, 0.5), nlpar = h), prior(exponential(1), class = sigma)), iter = 2000, warmup = 1000, chains = 4, cores = 4, seed = 5, backend = "cmdstanr", file = "fits/b05.11")
(As of May 2022) When using the typical formula syntax with more than one categorical variable, {brms} drops a category from every categorical variable except for the first one in the formula.
{brms} was orginally designed to wrap Stan multi-level models w/lme4 syntax, so maybe that has something do with it.
Kurz has links and discussion in Section 5.3.2 of his ebook
Example: Interaction, continuous * categorical (SR, Ch.8)
# same as coding for cat vars except adding a slope that is also conditioned on the index .3 <- b8brm(data = dd, family = gaussian, # bf = "brms formula" bf(log_gdp_std ~ 0 + a + b * rugged_std_c, ~ 0 + cid, a ~ 0 + cid, b # nl = "nonlinear" syntax nl = TRUE), prior = c(prior(normal(1, 0.1), class = b, coef = cid1, nlpar = a), prior(normal(1, 0.1), class = b, coef = cid2, nlpar = a), prior(normal(0, 0.3), class = b, coef = cid1, nlpar = b), prior(normal(0, 0.3), class = b, coef = cid2, nlpar = b), prior(exponential(1), class = sigma)), iter = 2000, warmup = 1000, chains = 4, cores = 4, seed = 8, backend = "cmdstanr", file = "fits/b08.03")
- Categorical variable needs an interaction spec and an intercept (main effect) spec
Example: Interaction, continuous * continuous (SR, Ch.8)
.5 <- b8brm(data = d, family = gaussian, ~ 1 + water_cent + shade_cent + water_cent:shade_cent, blooms_std prior = c(prior(normal(0.5, 0.25), class = Intercept), prior(normal(0, 0.25), class = b, coef = water_cent), prior(normal(0, 0.25), class = b, coef = shade_cent), prior(normal(0, 0.25), class = b, coef = "water_cent:shade_cent"), prior(exponential(1), class = sigma)), iter = 2000, warmup = 1000, chains = 4, cores = 4, seed = 8, backend = "cmdstanr", file = "fits/b08.05")
Example: Interaction, categorical * categorial (mc-stan question)(SR, Lecture 9 video, Ch. 11 Note)
<- fit brm(bf(y ~ 0 + a + b + c, ~ 0 + F1, a ~ 0 + F2, b # this is the interaction ~ (0 + F1) : (0 + F2), c nl = TRUE), data = dat) # interaction-only model (should include main effects) <- brm(data = ucb_01, brm_mod family = bernoulli, bf(admitted ~ 0 + gd, # this is the interaction ~ (0 + gender) : (0 + dept), gd nl = TRUE), prior = prior(normal(0,1), nlpar = gd), iter = 2000, warmup = 1000, cores = 3, chains = 3, seed = 10, backend = "cmdstanr")
Logistic Regression
Example: Logistic, cont ~ cat + cat (SR, Ch.11)
<- brm(data = dat_sim, b11_bern family = bernoulli(link = "logit"), bf(admit ~ a + d, ~ 0 + gid, a ~ 0 + dept, d nl = TRUE), prior = c(prior(normal(0, 1), nlpar = a), prior(normal(0, 1), nlpar = d)), iter = 4000, warmup = 1000, cores = 4, chains = 4, seed = 11, backend = "cmdstanr") print(b11_bern) -95% CI u-95% CI Rhat Bulk_ESS Tail_ESS Estimate Est.Error l-1.06 0.51 -2.05 -0.07 1.00 1765 2356 a_gidmale -0.97 0.50 -1.96 0.00 1.00 1758 2366 a_gidfemale -1.54 0.51 -2.55 -0.54 1.00 1773 2419 d_dept1 -0.50 0.50 -1.46 0.48 1.00 1763 2249 d_dept2 .4_bin <- brm(data = dat_sim, b11family = binomial, bf(admit | trials(1) ~ a + d, ~ 0 + gid, a ~ 0 + dept, d nl = TRUE), prior = c(prior(normal(0, 1), nlpar = a), prior(normal(0, 1), nlpar = d)), iter = 2000, warmup = 1000, chains = 4, cores = 4, seed = 11, backend = "cmdstanr") print(b11_bin) -95% CI u-95% CI Rhat Bulk_ESS Tail_ESS Estimate Est.Error l-1.02 0.52 -2.05 0.01 1.01 702 832 a_gidmale -0.94 0.52 -1.98 0.06 1.01 688 808 a_gidfemale -1.58 0.53 -2.60 -0.53 1.01 693 787 d_dept1 -0.53 0.52 -1.53 0.49 1.01 685 718 d_dept2
- Slightly different results (100ths). I feel more comfortable using the bernoulli spec if it’s just a typical logistic regression.
- The “1” in “|trials(1)” says that this is case-level data
- Including a | bar on the left side of a formula indicates we have extra supplementary information about our criterion. In this case, that information is that each pulled_left value corresponds to a single trial (i.e., trials(1)), which itself corresponds to the n = 1 in the model specification (above) for the outcome variable (e.g. pulled_left).
- The rest of the brms specification is standard for having two categorial explanatory variables (e.g. actor, treatment) (see Ch.5 >> categoricals >> multiple nominal)
- i.e. ingredients for Logistic Regression: family = Binomial and binary_outcome|trials(1)
Example: Multinomial Logistic Regression (SR, Ch. 11)
.13io <- b11brm(data = d, # refcat sets the reference category to the 3rd level family = categorical(link = logit, refcat = 3), ~ 1, career prior = c(prior(normal(0, 1), class = Intercept, dpar = mu1), prior(normal(0, 1), class = Intercept, dpar = mu2)), iter = 2000, warmup = 1000, cores = 4, chains = 4, seed = 11, backend - "cmdstanr", file = "fits/b11.13io") .13io b11## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS ## mu1_Intercept -2.01 0.15 -2.30 -1.73 1.00 3324 2631 ## mu2_Intercept -1.53 0.12 -1.77 -1.29 1.00 2993 2768
as of brms 2.12.0, “specifying global priors for regression coefficients in categorical models is deprecated.” Meaning — if we want to use the same prior for both, we need to use the
dpar
argument for eachThe reference level,
refcat
, was set to the 3rd level so the scores for the levels 1 and 2 are shown- Default is level 1.
- Had we used the brms default and used the first level of “career” as the pivot (aka reference category), those lines would have instead been dpar = mu2 , dpar = mu3
These are the score values that we’d get if we centered all the scores at level 3’s score value
- Nothing to infer from these. We want the probabilities which are in the next section.
Alternate ways to fit this model
# verbose syntax .13io_verbose <- b11brm(data = d, family = categorical(link = logit, refcat = 3), bf(career ~ 1, ~ 1, mu1 ~ 1), mu2 prior = c(prior(normal(0, 1), class = Intercept, dpar = mu1), prior(normal(0, 1), class = Intercept, dpar = mu2)), iter = 2000, warmup = 1000, cores = 4, chains = 4, seed = 11, file = "fits/b11.13io_verbose") # nonlinear syntax .13io_nonlinear <- b11brm(data = d, family = categorical(link = logit, refcat = 3), bf(career ~ 1, nlf(mu1 ~ a1), nlf(mu2 ~ a2), + a2 ~ 1), a1 prior = c(prior(normal(0, 1), class = b, nlpar = a1), prior(normal(0, 1), class = b, nlpar = a2)), iter = 2000, warmup = 1000, cores = 4, chains = 4, seed = 11, file = "fits/b11.13io_nonlinear")
- The verbose (and nonlinear) syntax makes it clear that we are fitting k-1 models, since “career” has 3 categories
- Had we used the brms default and used the first level of career as the pivot (aka reference category), those lines would have instead been mu2 ~ 1, mu3 ~ 1
Discrete Distribution Models
Binomial (SR Ch. 11)
.6 <- b11brm(data = d_aggregated, family = binomial, bf(left_pulls | trials(18) ~ a + b, ~ 0 + actor, a ~ 0 + treatment, b nl = TRUE), prior = c(prior(normal(0, 1.5), nlpar = a), prior(normal(0, 0.5), nlpar = b)), iter = 2000, warmup = 1000, chains = 4, cores = 4, seed = 11, backend = "cmdstanr", file = "fits/b11.06")
Including a | bar on the left side of a formula indicates we have extra supplementary information about our criterion.
- To fit an aggregated binomial model with brms, we augment the <criterion> | trials() syntax where the value that goes in trials() is either a fixed number, as in this case, or variable (e.g.
trials(applications)
) in the data indexing the number of trials, n.
- To fit an aggregated binomial model with brms, we augment the <criterion> | trials() syntax where the value that goes in trials() is either a fixed number, as in this case, or variable (e.g.
Can also use a logistic model, but need case-level data (e.g. 0/1)
- Deaggregate count data into 0/1 case-level data
data(UCBadmit, package = "rethinking") <- UCBadmit %>% ucb mutate(applicant.gender = relevel(applicant.gender, ref = "male")) # deaggregate to 1/0 <- function(x, y) { deagg_ucb %>% UCBadmit select(-applications) %>% group_by(dept, applicant.gender) %>% ::uncount(weights = !!sym(x)) %>% tidyrmutate(admitted = y) %>% select(dept, gender = applicant.gender, admitted) }<- purrr::map2_dfr(c("admit", "reject"), ucb_01 c(1, 0), ~ disagg_ucb(.x, .y) )
Logistic (SR Ch. 11)
<- brm(bf(admitted ~ 0 + g + d + gd, full_mod ~ 0 + gender, g ~ 0 + dept, d # this is the interaction ~ (0 + gender) : (0 + dept), gd nl = TRUE), prior = c(prior(normal(0,1), nlpar = g), prior(normal(0,1), nlpar = d), prior(normal(0,1), nlpar = gd)), data = ucb_01, family = bernoulli, iter = 2000, warmup = 1000, cores = 3, chains = 3, seed = 10, backend = "cmdstanr")
Poisson (SR Ch. 11)
# cat * cont interaction model .10 <- b11brm(data = d, family = poisson, bf(total_tools ~ a + b * log_pop_std, + b ~ 0 + cid, a nl = TRUE), prior = c(prior(normal(3, 0.5), nlpar = a), prior(normal(0, 0.2), nlpar = b)), iter = 2000, warmup = 1000, chains = 4, cores = 4, seed = 11, backend = "cmdstanr", file = "fits/b11.10")
Ordinal
Non-linear
Example: Basis Splines (SR, Ch.4)
# get recommended prior specifications # s is the basis function brms imports from mgcv pkg ::get_prior(data = d2, brmsfamily = gaussian, ~ 1 + s(year)) doy ## prior class coef group resp dpar nlpar bound source ## (flat) b default ## (flat) b syear_1 (vectorized) ## student_t(3, 105, 5.9) Intercept default ## student_t(3, 0, 5.9) sds default ## student_t(3, 0, 5.9) sds s(year) (vectorized) ## student_t(3, 0, 5.9) sigma default # multi-level method .11 <- brm(data = d2, b4family = gaussian, # k = 19, corresponds to 17 basis functions I guess ::shrugs:: # The default for s() is to use what’s called a thin plate regression spline # bs uses a basis spline ~ 1 + s(year, bs = "bs", k = 19), temp prior = c(prior(normal(100, 10), class = Intercept), prior(normal(0, 10), class = b), prior(student_t(3, 0, 5.9), class = sds), prior(exponential(1), class = sigma)), iter = 2000, warmup = 1000, chains = 4, cores = 4, seed = 4, backend = "cmdstanr", control = list(adapt_delta = .99))