| name | r-bayes |
| description | Patterns for Bayesian inference in R using brms, including multilevel models, DAG validation, and marginal effects. Use when mentions "Bayesian in R", "Bayesian with R", "brms", "Stan", "cmdstanr", "multilevel model", "hierarchical model", "random effects", "prior specification", "posterior distribution", "MCMC", "Markov Chain Monte Carlo", "DAG", "causal inference", "marginal effects", "tidybayes", or performing Bayesian statistical analysis in R. ONLY R - do NOT activate for Python Bayesian (PyMC3, PyMC, Pyro), NOT for Julia (Turing.jl). |
| version | 1.1.0 |
| user-invocable | false |
| allowed-tools | Read, Grep, Glob |
Core Packages
library(brms)
library(cmdstanr)
library(dagitty)
library(ggdag)
library(marginaleffects)
library(tidybayes)
library(bayesplot)
Directed Acyclic Graphs (DAGs)
Prior to causal inference, create and validate DAGs with dagitty and ggdag.
Define DAG Structure
dag <- dagitty('
dag {
# Node positions for visualization
exposure [pos="0,1"]
mediator [pos="1,1"]
outcome [pos="2,1"]
confounder [pos="1,0"]
# Edges (arrows)
confounder -> exposure
confounder -> outcome
exposure -> mediator
mediator -> outcome
exposure -> outcome
}
')
Identify Adjustment Sets
adjustmentSets(dag, exposure = "treatment", outcome = "outcome", effect = "direct")
adjustmentSets(dag, exposure = "treatment", outcome = "outcome", effect = "total")
Validate DAG Against Data
implied_cis <- impliedConditionalIndependencies(dag)
ci_results <- localTests(dag, data = analysis_data, type = "cis")
ci_df <- as.data.frame(ci_results)
ci_df$independent <- ci_df$p.value > 0.05
pct_supported <- 100 * mean(ci_df$independent, na.rm = TRUE)
cat(sprintf("DAG support: %.1f%% of implied CIs hold\n", pct_supported))
Visualize DAG
dag_tidy <- tidy_dagitty(dag)
ggplot(dag_tidy, aes(x = x, y = y, xend = xend, yend = yend)) +
geom_dag_edges(edge_colour = "grey50") +
geom_dag_point(size = 20) +
geom_dag_text(size = 3.5, color = "black") +
theme_dag() +
labs(title = "Causal DAG")
Bayesian Regression with brms
Standard Configuration
options(mc.cores = 4)
model <- brm(
formula = outcome ~ predictor1 + predictor2 + (1 | group_id),
data = model_data,
family = bernoulli(link = "logit"),
prior = priors,
sample_prior = "yes",
chains = 4,
cores = 4,
iter = 4000,
warmup = 1000,
control = list(
adapt_delta = 0.95,
max_treedepth = 15
),
seed = 123,
backend = "cmdstanr",
file = "models/model_name",
file_refit = "on_change"
)
Priors
Store priors separately and define explicitly:
priors <- c(
prior(normal(0, 2), class = "Intercept"),
prior(normal(0, 1), class = "b"),
prior(exponential(1), class = "sd"),
prior(lkj(2), class = "cor")
)
get_prior(outcome ~ predictor + (1 | id), data = data, family = bernoulli())
Common Families
family = bernoulli(link = "logit")
family = poisson(link = "log")
family = negbinomial(link = "log")
family = gaussian()
family = student()
family = cumulative(link = "logit")
Multilevel Models
Random Intercepts
outcome ~ predictors + (1 | participant_id)
Random Slopes
outcome ~ time + predictors + (1 + time | participant_id)
Crossed Random Effects
response ~ predictors + (1 | participant_id) + (1 | item_id)
Within-Person Centering
For longitudinal data, separate between-person and within-person effects:
model_data <- data |>
group_by(participant_id) |>
mutate(
predictor_mean = mean(predictor, na.rm = TRUE),
predictor_dev = predictor - predictor_mean,
predictor_sd = sd(predictor, na.rm = TRUE)
) |>
ungroup() |>
mutate(
predictor_mean_z = scale(predictor_mean)[, 1],
predictor_dev_z = scale(predictor_dev)[, 1]
)
model <- brm(
outcome ~ predictor_mean_z + predictor_dev_z + (1 | participant_id),
data = model_data,
family = bernoulli()
)
Lagged Predictors for Temporal Precedence
model_data <- data |>
group_by(participant_id) |>
arrange(time) |>
mutate(
predictor_lag = lag(predictor, order_by = time),
predictor_dev_lag = lag(predictor_dev, order_by = time)
) |>
ungroup()
model_lagged <- brm(
outcome ~ predictor_dev_lag_z + predictor_mean_z + (1 | participant_id),
...
)
Extracting and Interpreting Results
Extract Posterior Samples
posterior <- as_draws_df(model)
samples <- posterior$b_predictor_z
tibble(
estimate = median(samples),
lower_95 = quantile(samples, 0.025),
upper_95 = quantile(samples, 0.975),
lower_80 = quantile(samples, 0.10),
upper_80 = quantile(samples, 0.90),
prob_negative = mean(samples < 0),
prob_positive = mean(samples > 0)
)
Odds Ratios (for logistic models)
effects_df <- effects_df |>
mutate(
OR = exp(estimate),
OR_lower = exp(lower_95),
OR_upper = exp(upper_95)
)
Posterior Probability of Direction
prob_protective <- mean(posterior$b_predictor < 0)
prob_harmful <- mean(posterior$b_predictor > 0)
prob_meaningful <- mean(abs(posterior$b_predictor) > 0.1)
Compare Effect Magnitudes
diff <- abs(posterior$b_predictor_dev_z) - abs(posterior$b_predictor_mean_z)
prob_within_larger <- mean(diff > 0)
cat(sprintf("P(|within| > |between|) = %.1f%%\n", 100 * prob_within_larger))
Marginal Effects with marginaleffects
Average Marginal Effects (AME)
ame <- avg_slopes(
model,
variables = c("predictor1_z", "predictor2_z"),
type = "response"
)
print(ame)
Predictions at Specific Values
predictions <- predictions(
model,
newdata = datagrid(
model = model,
predictor_z = c(-1, 0, 1)
),
type = "response",
re_formula = NA
)
as.data.frame(predictions) |>
select(predictor_z, estimate, conf.low, conf.high)
Marginal Effect Plots
plot_predictions(
model,
by = "predictor_z",
type = "response",
re_formula = NA
) +
labs(
title = "Effect of Predictor on Outcome",
x = "Predictor (standardized)",
y = "P(Outcome)"
) +
scale_y_continuous(labels = scales::percent) +
theme_minimal()
Comparing Slopes Across Models
ame_model1 <- avg_slopes(model1, variables = "predictor_z", type = "response")
ame_model2 <- avg_slopes(model2, variables = "predictor_z", type = "response")
comparison <- bind_rows(
as.data.frame(ame_model1) |> mutate(model = "Full"),
as.data.frame(ame_model2) |> mutate(model = "Simple")
)
Model Diagnostics
Check MCMC Convergence
mcmc_trace(model, pars = c("b_Intercept", "b_predictor_z"))
summary(model)$fixed$Rhat
summary(model)$fixed$Bulk_ESS
summary(model)$fixed$Tail_ESS
Posterior Predictive Checks
pp_check(model)
pp_check(model, type = "stat", stat = "mean")
pp_check(model, type = "stat_2d", stat = c("mean", "sd"))
Prior-Posterior Comparison
prior_summary(model)
mcmc_areas(model, pars = "b_predictor_z", prob = 0.95)
tidybayes for Posterior Manipulation
draws <- model |>
spread_draws(b_predictor1_z, b_predictor2_z) |>
mutate(
OR_predictor1 = exp(b_predictor1_z),
OR_predictor2 = exp(b_predictor2_z)
)
draws |>
median_qi(OR_predictor1, OR_predictor2, .width = c(0.80, 0.95))
draws |>
ggplot(aes(x = OR_predictor1)) +
stat_halfeye() +
geom_vline(xintercept = 1, linetype = "dashed") +
labs(x = "Odds Ratio", y = NULL)
Workflow Summary
- Define causal DAG with dagitty
- Validate DAG against data with
localTests()
- Identify adjustment sets for target effects
- Specify priors based on domain knowledge
- Fit brms model with random effects for nested data
- Check diagnostics (convergence, PPCs)
- Extract posteriors for inference
- Compute marginal effects on interpretable scale
- Visualize effects with uncertainty
Anti-Patterns to Avoid
outcome_t ~ predictor_t
outcome_t ~ predictor_t_minus_1
brm(outcome ~ predictor, data = longitudinal_data)
brm(outcome ~ predictor + (1 | participant_id), data = longitudinal_data)
outcome ~ predictor_mean_z + predictor_dev_z + (1 | id)