This may be related to this older post.
My understanding is that one can use brms::hypothesis()
to conduct hypothesis tests on the posterior probability of a specified test. For example, in the below code I think I’m doing the un-meaningful comparison of whether am1
is equal to the Intercept
parameter. My understanding is that internally, brms
would take all the draws of b_am1
, then subtract them by all the draws of the Intercept
parameter.
However, my question is that it seems to be possible to perform a contrast between am0 - am1
, despite:
b_am0
not existing (as shown below)Intercept
not corresponding to the estimate ofam0
per-se, but rather of the expected value of mpg whenam = 0
andvs = 0
.
If emmeans
had refit the model in the background, such that every categorical variable was index coded, I would have understood–but based on how fast things are going and etc, I doubt this happened…
So then my questions are: how did emmeans
manage to contrast the levels of a categorical variable when it didn’t have the draws to do so? Is there a way to get hypothesis()
to do what emmeans()
managed to do without refitting the model? Is there a reason why I shouldn’t be using emmeans()
's approach?
library(cmdstanr)
#> This is cmdstanr version 0.8.1.9000
#> - CmdStanR documentation and vignettes: mc-stan.org/cmdstanr
#> - CmdStan path: /home/mstruong/.cmdstan/cmdstan-2.36.0
#> - CmdStan version: 2.36.0
library(brms)
#> Loading required package: Rcpp
#> Loading 'brms' package (version 2.22.8). Useful instructions
#> can be found by typing help('brms'). A more detailed introduction
#> to the package is available through vignette('brms_overview').
#>
#> Attaching package: 'brms'
#> The following object is masked from 'package:stats':
#>
#> ar
library(emmeans)
#> Welcome to emmeans.
#> Caution: You lose important information if you filter this package's results.
#> See '? untidy'
library(dplyr)
#>
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#>
#> filter, lag
#> The following objects are masked from 'package:base':
#>
#> intersect, setdiff, setequal, union
d <- mtcars |>
dplyr::mutate(vs = as.factor(vs),
am = as.factor(am))
fit1 <- brms::brm(
mpg ~ vs + am,
data = d, family = gaussian(),
backend = "cmdstanr",
cores = 4,
refresh = 0,
silent = 2
)
#> Loading required package: rstan
#> Loading required package: StanHeaders
#>
#> rstan version 2.32.7 (Stan version 2.32.2)
#> For execution on a local, multicore CPU with excess RAM we recommend calling
#> options(mc.cores = parallel::detectCores()).
#> To avoid recompilation of unchanged Stan programs, we recommend calling
#> rstan_options(auto_write = TRUE)
#> For within-chain threading using `reduce_sum()` or `map_rect()` Stan functions,
#> change `threads_per_chain` option:
#> rstan_options(threads_per_chain = 1)
emmeans(fit1, ~ am) |>
pairs() |>
summary(point.est = mean)
#> contrast estimate lower.HPD upper.HPD
#> am0 - am1 -6.08 -8.49 -3.22
#>
#> Results are averaged over the levels of: vs
#> Point estimate displayed: mean
#> HPD interval probability: 0.95
posterior::summarize_draws(fit1) |>
dplyr::filter(variable == "b_am1")
#> # A tibble: 1 × 10
#> variable mean median sd mad q5 q95 rhat ess_bulk ess_tail
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 b_am1 6.08 6.08 1.33 1.31 3.92 8.22 1.00 4366. 2693.
hypothesis(fit1, "am1 = Intercept")
#> Hypothesis Tests for class b:
#> Hypothesis Estimate Est.Error CI.Lower CI.Upper Evid.Ratio
#> 1 (am1)-(Intercept) = 0 -8.51 1.94 -12.35 -4.72 NA
#> Post.Prob Star
#> 1 NA *
#> ---
#> 'CI': 90%-CI for one-sided and 95%-CI for two-sided hypotheses.
#> '*': For one-sided hypotheses, the posterior probability exceeds 95%;
#> for two-sided hypotheses, the value tested against lies outside the 95%-CI.
#> Posterior probabilities of point hypotheses assume equal prior probabilities.
hypothesis(fit1, "am1 = am0")
#> Error: Some parameters cannot be found in the model:
#> 'b_am0'
Created on 2025-06-19 with reprex v2.1.1
- Operating System: Ubuntu 22.04
- brms Version: 2.22.8