Skip to Content
Course content

117: Bayesian Statistics with brms

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you've spent any time in the frequentist world, you're used to lm() or glm() returning a result almost instantly. Bayesian modeling with brms is a different beast. It doesn't just "calculate" a point estimate; it uses Markov Chain Monte Carlo (MCMC) to sample a posterior distribution. This is powerful, but it's also where things usually break for the first time.

Here is a snippet of code I see all the time from people moving into Bayesian work. They've got a dataset of reaction times across different conditions and subjects, and they're trying to run a simple hierarchical model:

library(brms)

# Fake data: reaction time (rt) by condition and subject
df <- data.frame(
  rt = rgamma(100, shape = 2, scale = 200), 
  condition = rep(c("A", "B"), each = 50),
  subject = rep(1:20, each = 5)
)

# The "naive" model
fit <- brm(
  rt ~ condition + (1 | subject), 
  data = df, 
  family = gaussian()
)

The Divergent Transitions Warning

When you run this, you might not get a hard error, but you'll likely see a terrifying warning in your console: "There were 4 divergent transitions after warmup". To a beginner, this looks like a minor glitch. To a Bayesian, this is a red flag. It means the sampler (Stan, which brms uses under the hood) hit a region of the parameter space where the gradient was too steep, and it essentially "flew off the map."

Why is this happening? By default, brms uses very flat, vague priors. If your data is sparse or your scales are weird (like reaction times, which can be large), the sampler spends too much time wandering around "impossible" areas of the distribution. It's like trying to find a needle in a haystack, but the haystack is the size of the solar system.

Guiding the Sampler with Weakly Informative Priors

The fix isn't to ignore the warning; it's to give the model a "nudge" in the right direction. We do this with weakly informative priors. We aren't forcing the answer, but we're telling the model, "Hey, I know the reaction time isn't going to be negative, and it's probably not 10,000 seconds."

Here is how I would rewrite that model to make it stable:

# 1. Define our priors
# We expect the intercept (average RT) to be around 400ms, 
# but we'll give it a wide enough window to be flexible.
my_priors <- c(
  set_prior("normal(400, 100)", class = "Intercept"),
  set_prior("normal(0, 50)", class = "b") # Coefficients for condition
)

# 2. Fit the model with the priors
fit_stable <- brm(
  rt ~ condition + (1 | subject), 
  data = df, 
  family = gaussian(), 
  prior = my_priors,
  chains = 4, 
  iter = 2000, 
  warmup = 1000,
  cores = 4
)

By adding set_prior(), we've constrained the search space. The sampler no longer wanders off into the void, and those divergent transitions usually vanish. I always recommend starting with normal(0, some_reasonable_std) for coefficients unless you have a very strong reason to believe otherwise.

Verifying the Fit with Posterior Predictive Checks

Once the model converges, you can't just trust the coefficients. You need to see if the model's "imagined" data looks like your actual data. This is where pp_check() becomes your best friend.

pp_check(fit_stable)

This generates a plot comparing the density of your observed data (the dark line) with several simulated datasets from the posterior (the light lines). If the dark line is a weird outlier or doesn't overlap with the light lines, your model is mis-specified. For reaction times, you'll often find that family = gaussian() is a poor choice because RTs are skewed. In that case, you'd switch to family = lognormal() or family = Gamma(). That's the beauty of brms: changing the distribution is often as simple as changing one argument.




📋 Practical Task

Fixing Convergence in a Species Abundance Model

You are analyzing a dataset of bird sightings across different forest plots. You've written a model to predict the count of birds based on the forest type, but the model is struggling to converge and is throwing "R-hat" warnings (indicating the chains haven't mixed well).

Your goal: Implement a Poisson regression using brms with a weakly informative prior on the intercept to stabilize the model.

Dataset provided:

library(brms)
bird_data <- data.frame(
  count = c(5, 8, 2, 12, 1, 0, 15, 7, 3, 10),
  forest_type = factor(c("OldGrowth", "OldGrowth", "NewGrowth", "OldGrowth", "NewGrowth", "NewGrowth", "OldGrowth", "NewGrowth", "NewGrowth", "OldGrowth"))
)


Requirements:

  1. Define a prior for the Intercept using a normal distribution (suggested: mean of 2, sd of 1).
  2. Fit a brm model using the poisson() family.
  3. Include the prior you defined in the model call.
  4. Run a posterior predictive check using pp_check() to ensure the Poisson distribution is a reasonable fit for the count data.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.