Monotonic simple effects

I’d like to get an estimate of the simple effects from a monotonic interaction. For instance, in the following code, how do I get the monotonic coefficient moincome for each level of age? emmeans doesn’t seem to encode the monotonic effect in the reference grid.

library(brms)
#> Loading required package: Rcpp
#> Loading 'brms' package (version 2.18.0). 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
income_options <- c("below_20", "20_to_40", "40_to_100", "greater_100")
income <- factor(sample(income_options, 100, TRUE),
  levels = income_options, ordered = TRUE
)
mean_ls <- c(30, 60, 70, 75)
ls <- mean_ls[income] + rnorm(100, sd = 7)
age <- sample(c("old", "young"), size = 100, replace = TRUE)
dat <- data.frame(income, ls, age)

fit <- brm(ls ~ mo(income) * age, data = dat)
#> Compiling Stan program...
#> Start sampling
#> 
#> SAMPLING FOR MODEL 'anon_model' NOW (CHAIN 1).
#> Chain 1: 
#> Chain 1: Gradient evaluation took 6.7e-05 seconds
#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 0.67 seconds.
#> Chain 1: Adjust your expectations accordingly!
#> Chain 1: 
#> Chain 1: 
#> Chain 1: Iteration:    1 / 2000 [  0%]  (Warmup)
#> Chain 1: Iteration:  200 / 2000 [ 10%]  (Warmup)
#> Chain 1: Iteration:  400 / 2000 [ 20%]  (Warmup)
#> Chain 1: Iteration:  600 / 2000 [ 30%]  (Warmup)
#> Chain 1: Iteration:  800 / 2000 [ 40%]  (Warmup)
#> Chain 1: Iteration: 1000 / 2000 [ 50%]  (Warmup)
#> Chain 1: Iteration: 1001 / 2000 [ 50%]  (Sampling)
#> Chain 1: Iteration: 1200 / 2000 [ 60%]  (Sampling)
#> Chain 1: Iteration: 1400 / 2000 [ 70%]  (Sampling)
#> Chain 1: Iteration: 1600 / 2000 [ 80%]  (Sampling)
#> Chain 1: Iteration: 1800 / 2000 [ 90%]  (Sampling)
#> Chain 1: Iteration: 2000 / 2000 [100%]  (Sampling)
#> Chain 1: 
#> Chain 1:  Elapsed Time: 0.518 seconds (Warm-up)
#> Chain 1:                0.469 seconds (Sampling)
#> Chain 1:                0.987 seconds (Total)
#> Chain 1: 
#> 
#> SAMPLING FOR MODEL 'anon_model' NOW (CHAIN 2).
#> Chain 2: 
#> Chain 2: Gradient evaluation took 2.4e-05 seconds
#> Chain 2: 1000 transitions using 10 leapfrog steps per transition would take 0.24 seconds.
#> Chain 2: Adjust your expectations accordingly!
#> Chain 2: 
#> Chain 2: 
#> Chain 2: Iteration:    1 / 2000 [  0%]  (Warmup)
#> Chain 2: Iteration:  200 / 2000 [ 10%]  (Warmup)
#> Chain 2: Iteration:  400 / 2000 [ 20%]  (Warmup)
#> Chain 2: Iteration:  600 / 2000 [ 30%]  (Warmup)
#> Chain 2: Iteration:  800 / 2000 [ 40%]  (Warmup)
#> Chain 2: Iteration: 1000 / 2000 [ 50%]  (Warmup)
#> Chain 2: Iteration: 1001 / 2000 [ 50%]  (Sampling)
#> Chain 2: Iteration: 1200 / 2000 [ 60%]  (Sampling)
#> Chain 2: Iteration: 1400 / 2000 [ 70%]  (Sampling)
#> Chain 2: Iteration: 1600 / 2000 [ 80%]  (Sampling)
#> Chain 2: Iteration: 1800 / 2000 [ 90%]  (Sampling)
#> Chain 2: Iteration: 2000 / 2000 [100%]  (Sampling)
#> Chain 2: 
#> Chain 2:  Elapsed Time: 0.566 seconds (Warm-up)
#> Chain 2:                0.435 seconds (Sampling)
#> Chain 2:                1.001 seconds (Total)
#> Chain 2: 
#> 
#> SAMPLING FOR MODEL 'anon_model' NOW (CHAIN 3).
#> Chain 3: 
#> Chain 3: Gradient evaluation took 3.4e-05 seconds
#> Chain 3: 1000 transitions using 10 leapfrog steps per transition would take 0.34 seconds.
#> Chain 3: Adjust your expectations accordingly!
#> Chain 3: 
#> Chain 3: 
#> Chain 3: Iteration:    1 / 2000 [  0%]  (Warmup)
#> Chain 3: Iteration:  200 / 2000 [ 10%]  (Warmup)
#> Chain 3: Iteration:  400 / 2000 [ 20%]  (Warmup)
#> Chain 3: Iteration:  600 / 2000 [ 30%]  (Warmup)
#> Chain 3: Iteration:  800 / 2000 [ 40%]  (Warmup)
#> Chain 3: Iteration: 1000 / 2000 [ 50%]  (Warmup)
#> Chain 3: Iteration: 1001 / 2000 [ 50%]  (Sampling)
#> Chain 3: Iteration: 1200 / 2000 [ 60%]  (Sampling)
#> Chain 3: Iteration: 1400 / 2000 [ 70%]  (Sampling)
#> Chain 3: Iteration: 1600 / 2000 [ 80%]  (Sampling)
#> Chain 3: Iteration: 1800 / 2000 [ 90%]  (Sampling)
#> Chain 3: Iteration: 2000 / 2000 [100%]  (Sampling)
#> Chain 3: 
#> Chain 3:  Elapsed Time: 0.583 seconds (Warm-up)
#> Chain 3:                0.376 seconds (Sampling)
#> Chain 3:                0.959 seconds (Total)
#> Chain 3: 
#> 
#> SAMPLING FOR MODEL 'anon_model' NOW (CHAIN 4).
#> Chain 4: 
#> Chain 4: Gradient evaluation took 2.4e-05 seconds
#> Chain 4: 1000 transitions using 10 leapfrog steps per transition would take 0.24 seconds.
#> Chain 4: Adjust your expectations accordingly!
#> Chain 4: 
#> Chain 4: 
#> Chain 4: Iteration:    1 / 2000 [  0%]  (Warmup)
#> Chain 4: Iteration:  200 / 2000 [ 10%]  (Warmup)
#> Chain 4: Iteration:  400 / 2000 [ 20%]  (Warmup)
#> Chain 4: Iteration:  600 / 2000 [ 30%]  (Warmup)
#> Chain 4: Iteration:  800 / 2000 [ 40%]  (Warmup)
#> Chain 4: Iteration: 1000 / 2000 [ 50%]  (Warmup)
#> Chain 4: Iteration: 1001 / 2000 [ 50%]  (Sampling)
#> Chain 4: Iteration: 1200 / 2000 [ 60%]  (Sampling)
#> Chain 4: Iteration: 1400 / 2000 [ 70%]  (Sampling)
#> Chain 4: Iteration: 1600 / 2000 [ 80%]  (Sampling)
#> Chain 4: Iteration: 1800 / 2000 [ 90%]  (Sampling)
#> Chain 4: Iteration: 2000 / 2000 [100%]  (Sampling)
#> Chain 4: 
#> Chain 4:  Elapsed Time: 0.527 seconds (Warm-up)
#> Chain 4:                0.4 seconds (Sampling)
#> Chain 4:                0.927 seconds (Total)
#> Chain 4:
fit
#>  Family: gaussian 
#>   Links: mu = identity; sigma = identity 
#> Formula: ls ~ mo(income) * age 
#>    Data: dat (Number of observations: 100) 
#>   Draws: 4 chains, each with iter = 2000; warmup = 1000; thin = 1;
#>          total post-warmup draws = 4000
#> 
#> Population-Level Effects: 
#>                   Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
#> Intercept            32.52      2.64    27.57    37.86 1.00     1707     2362
#> ageyoung             -4.17      3.33   -11.14     1.65 1.00     1558     2118
#> moincome             13.89      1.10    11.72    16.02 1.00     1667     2201
#> moincome:ageyoung     2.00      1.46    -0.81     4.85 1.00     1525     2372
#> 
#> Simplex Parameters: 
#>                       Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS
#> moincome1[1]              0.67      0.04     0.59     0.75 1.00     2821
#> moincome1[2]              0.25      0.04     0.17     0.34 1.00     3861
#> moincome1[3]              0.08      0.04     0.01     0.16 1.00     2583
#> moincome:ageyoung1[1]     0.43      0.24     0.03     0.88 1.00     2677
#> moincome:ageyoung1[2]     0.33      0.22     0.01     0.82 1.00     3283
#> moincome:ageyoung1[3]     0.24      0.19     0.01     0.72 1.00     3131
#>                       Tail_ESS
#> moincome1[1]              2853
#> moincome1[2]              3102
#> moincome1[3]              1589
#> moincome:ageyoung1[1]     1981
#> moincome:ageyoung1[2]     2492
#> moincome:ageyoung1[3]     2592
#> 
#> Family Specific Parameters: 
#>       Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
#> sigma     6.78      0.50     5.89     7.80 1.00     3334     2811
#> 
#> Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
#> and Tail_ESS are effective sample size measures, and Rhat is the potential
#> scale reduction factor on split chains (at convergence, Rhat = 1).

emmeans::ref_grid(fit)
#> 'emmGrid' object with variables:
#>     age = old, young
#>     income = below_20, 20_to_40, 40_to_100, greater_100

Created on 2022-11-07 with reprex v2.0.2

Session info
sessioninfo::session_info()
#> ─ Session info ───────────────────────────────────────────────────────────────
#>  setting  value
#>  version  R version 4.2.1 (2022-06-23)
#>  os       macOS Monterey 12.6
#>  system   aarch64, darwin20
#>  ui       X11
#>  language (EN)
#>  collate  en_AU.UTF-8
#>  ctype    en_AU.UTF-8
#>  tz       Europe/London
#>  date     2022-11-07
#>  pandoc   2.19.2 @ /opt/homebrew/bin/ (via rmarkdown)
#> 
#> ─ Packages ───────────────────────────────────────────────────────────────────
#>  package        * version date (UTC) lib source
#>  abind            1.4-5   2016-07-21 [1] CRAN (R 4.2.0)
#>  assertthat       0.2.1   2019-03-21 [1] CRAN (R 4.2.0)
#>  backports        1.4.1   2021-12-13 [1] CRAN (R 4.2.0)
#>  base64enc        0.1-3   2015-07-28 [1] CRAN (R 4.2.0)
#>  bayesplot        1.9.0   2022-03-10 [1] CRAN (R 4.2.0)
#>  bridgesampling   1.1-2   2021-04-16 [1] CRAN (R 4.2.0)
#>  brms           * 2.18.0  2022-09-19 [1] CRAN (R 4.2.0)
#>  Brobdingnag      1.2-9   2022-10-19 [1] CRAN (R 4.2.0)
#>  callr            3.7.2   2022-08-22 [1] CRAN (R 4.2.0)
#>  checkmate        2.1.0   2022-04-21 [1] CRAN (R 4.2.0)
#>  cli              3.4.1   2022-09-23 [1] CRAN (R 4.2.0)
#>  coda             0.19-4  2020-09-30 [1] CRAN (R 4.2.0)
#>  codetools        0.2-18  2020-11-04 [1] CRAN (R 4.2.1)
#>  colorspace       2.0-3   2022-02-21 [1] CRAN (R 4.2.0)
#>  colourpicker     1.1.1   2021-10-04 [1] CRAN (R 4.2.0)
#>  crayon           1.5.2   2022-09-29 [1] CRAN (R 4.2.1)
#>  crosstalk        1.2.0   2021-11-04 [1] CRAN (R 4.2.0)
#>  curl             4.3.2   2021-06-23 [1] CRAN (R 4.2.0)
#>  DBI              1.1.3   2022-06-18 [1] CRAN (R 4.2.0)
#>  digest           0.6.30  2022-10-18 [1] CRAN (R 4.2.0)
#>  distributional   0.3.1   2022-09-02 [1] CRAN (R 4.2.0)
#>  dplyr            1.0.10  2022-09-01 [1] CRAN (R 4.2.0)
#>  DT               0.26    2022-10-19 [1] CRAN (R 4.2.0)
#>  dygraphs         1.1.1.6 2018-07-11 [1] CRAN (R 4.2.0)
#>  ellipsis         0.3.2   2021-04-29 [1] CRAN (R 4.2.0)
#>  emmeans          1.8.2   2022-10-27 [1] CRAN (R 4.2.0)
#>  estimability     1.4.1   2022-08-05 [1] CRAN (R 4.2.0)
#>  evaluate         0.16    2022-08-09 [1] CRAN (R 4.2.0)
#>  fansi            1.0.3   2022-03-24 [1] CRAN (R 4.2.0)
#>  farver           2.1.1   2022-07-06 [1] CRAN (R 4.2.0)
#>  fastmap          1.1.0   2021-01-25 [1] CRAN (R 4.2.0)
#>  fs               1.5.2   2021-12-08 [1] CRAN (R 4.2.0)
#>  generics         0.1.3   2022-07-05 [1] CRAN (R 4.2.0)
#>  ggplot2          3.3.6   2022-05-03 [1] CRAN (R 4.2.0)
#>  ggridges         0.5.4   2022-09-26 [1] CRAN (R 4.2.0)
#>  glue             1.6.2   2022-02-24 [1] CRAN (R 4.2.0)
#>  gridExtra        2.3     2017-09-09 [1] CRAN (R 4.2.0)
#>  gtable           0.3.1   2022-09-01 [1] CRAN (R 4.2.0)
#>  gtools           3.9.3   2022-07-11 [1] CRAN (R 4.2.0)
#>  highr            0.9     2021-04-16 [1] CRAN (R 4.2.0)
#>  htmltools        0.5.3   2022-07-18 [1] CRAN (R 4.2.0)
#>  htmlwidgets      1.5.4   2021-09-08 [1] CRAN (R 4.2.0)
#>  httpuv           1.6.6   2022-09-08 [1] CRAN (R 4.2.0)
#>  igraph           1.3.5   2022-09-22 [1] CRAN (R 4.2.0)
#>  inline           0.3.19  2021-05-31 [1] CRAN (R 4.2.0)
#>  jsonlite         1.8.0   2022-02-22 [1] CRAN (R 4.2.0)
#>  knitr            1.40    2022-08-24 [1] CRAN (R 4.2.0)
#>  later            1.3.0   2021-08-18 [1] CRAN (R 4.2.0)
#>  lattice          0.20-45 2021-09-22 [1] CRAN (R 4.2.1)
#>  lifecycle        1.0.3   2022-10-07 [1] CRAN (R 4.2.0)
#>  loo              2.5.1   2022-03-24 [1] CRAN (R 4.2.0)
#>  magrittr         2.0.3   2022-03-30 [1] CRAN (R 4.2.0)
#>  markdown         1.2     2022-10-19 [1] CRAN (R 4.2.0)
#>  Matrix           1.5-1   2022-09-13 [1] CRAN (R 4.2.0)
#>  matrixStats      0.62.0  2022-04-19 [1] CRAN (R 4.2.0)
#>  mime             0.12    2021-09-28 [1] CRAN (R 4.2.0)
#>  miniUI           0.1.1.1 2018-05-18 [1] CRAN (R 4.2.0)
#>  munsell          0.5.0   2018-06-12 [1] CRAN (R 4.2.0)
#>  mvtnorm          1.1-3   2021-10-08 [1] CRAN (R 4.2.0)
#>  nlme             3.1-157 2022-03-25 [1] CRAN (R 4.2.1)
#>  pillar           1.8.1   2022-08-19 [1] CRAN (R 4.2.0)
#>  pkgbuild         1.3.1   2021-12-20 [1] CRAN (R 4.2.0)
#>  pkgconfig        2.0.3   2019-09-22 [1] CRAN (R 4.2.0)
#>  plyr             1.8.7   2022-03-24 [1] CRAN (R 4.2.0)
#>  posterior        1.3.1   2022-09-06 [1] CRAN (R 4.2.0)
#>  prettyunits      1.1.1   2020-01-24 [1] CRAN (R 4.2.0)
#>  processx         3.7.0   2022-07-07 [1] CRAN (R 4.2.0)
#>  promises         1.2.0.1 2021-02-11 [1] CRAN (R 4.2.0)
#>  ps               1.7.1   2022-06-18 [1] CRAN (R 4.2.0)
#>  purrr            0.3.4   2020-04-17 [1] CRAN (R 4.2.0)
#>  R.cache          0.16.0  2022-07-21 [1] CRAN (R 4.2.0)
#>  R.methodsS3      1.8.2   2022-06-13 [1] CRAN (R 4.2.0)
#>  R.oo             1.25.0  2022-06-12 [1] CRAN (R 4.2.0)
#>  R.utils          2.12.0  2022-06-28 [1] CRAN (R 4.2.0)
#>  R6               2.5.1   2021-08-19 [1] CRAN (R 4.2.0)
#>  Rcpp           * 1.0.9   2022-07-08 [1] CRAN (R 4.2.0)
#>  RcppParallel     5.1.5   2022-01-05 [1] CRAN (R 4.2.0)
#>  reprex           2.0.2   2022-08-17 [1] CRAN (R 4.2.0)
#>  reshape2         1.4.4   2020-04-09 [1] CRAN (R 4.2.0)
#>  rlang            1.0.6   2022-09-24 [1] CRAN (R 4.2.0)
#>  rmarkdown        2.16    2022-08-24 [1] CRAN (R 4.2.0)
#>  rstan            2.26.13 2022-10-21 [1] local
#>  rstantools       2.2.0   2022-04-08 [1] CRAN (R 4.2.0)
#>  scales           1.2.1   2022-08-20 [1] CRAN (R 4.2.0)
#>  sessioninfo      1.2.2   2021-12-06 [1] CRAN (R 4.2.0)
#>  shiny            1.7.2   2022-07-19 [1] CRAN (R 4.2.0)
#>  shinyjs          2.1.0   2021-12-23 [1] CRAN (R 4.2.0)
#>  shinystan        2.6.0   2022-03-03 [1] CRAN (R 4.2.0)
#>  shinythemes      1.2.0   2021-01-25 [1] CRAN (R 4.2.0)
#>  StanHeaders      2.26.13 2022-10-21 [1] local
#>  stringi          1.7.8   2022-07-11 [1] CRAN (R 4.2.0)
#>  stringr          1.4.1   2022-08-20 [1] CRAN (R 4.2.0)
#>  styler           1.7.0   2022-03-13 [1] CRAN (R 4.2.0)
#>  tensorA          0.36.2  2020-11-19 [1] CRAN (R 4.2.0)
#>  threejs          0.3.3   2020-01-21 [1] CRAN (R 4.2.0)
#>  tibble           3.1.8   2022-07-22 [1] CRAN (R 4.2.0)
#>  tidyselect       1.1.2   2022-02-21 [1] CRAN (R 4.2.0)
#>  utf8             1.2.2   2021-07-24 [1] CRAN (R 4.2.0)
#>  V8               4.2.1   2022-08-07 [1] CRAN (R 4.2.0)
#>  vctrs            0.5.0   2022-10-22 [1] CRAN (R 4.2.0)
#>  withr            2.5.0   2022-03-03 [1] CRAN (R 4.2.0)
#>  xfun             0.33    2022-09-12 [1] CRAN (R 4.2.0)
#>  xtable           1.8-4   2019-04-21 [1] CRAN (R 4.2.0)
#>  xts              0.12.2  2022-10-16 [1] CRAN (R 4.2.0)
#>  yaml             2.3.5   2022-02-21 [1] CRAN (R 4.2.0)
#>  zoo              1.8-11  2022-09-17 [1] CRAN (R 4.2.0)
#> 
#>  [1] /Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/library
#> 
#> ──────────────────────────────────────────────────────────────────────────────

I believe the marginaleffects package can handle monotonic effects. If you do a quick word search on the GitHub repo, the term pops up a few times.

Thanks Solomon! (Thanks for your Statistical Rethinking brms translation btw.)

I tried marginaleffects but I can’t recreate the brms output.

I’d like to get average marginal effects for a monotonic effect each level of another categorical variable.

I followed Monotonic effects bug · Issue #220 · vincentarelbundock/marginaleffects · GitHub to create sequential contrasts and then averaged over them to get what I thought is the equivalent of the brms monotonic effect beta coefficient. Burkner and Charpentier (2020) say that “if the monotonic effect is used in a linear model, b can be interpreted as the expected average difference between two adjacent categories of x” (p. 425). However, when I average over each the levels of the second variable or just use a model with no interaction term, I can’t recreate the coefficient and CIs from the brms output. @paul.buerkner am I misunderstanding the monotonic effects article here?

library(brms)
#> Loading required package: Rcpp
#> Loading 'brms' package (version 2.18.0). 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(marginaleffects)
#> Warning: package 'marginaleffects' was built under R version 4.2.2
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

income_options <- c("below_20", "20_to_40", "40_to_100", "greater_100")
income <- factor(sample(income_options, 100, TRUE),
  levels = income_options, ordered = TRUE
)
mean_ls <- c(30, 60, 70, 75)
ls <- mean_ls[income] + rnorm(100, sd = 7)
age <- sample(c("old", "young"), size = 100, replace = TRUE)
dat <- data.frame(income, ls, age)

fit <- brm(ls ~ mo(income) * age, data = dat)
fit
#>  Family: gaussian 
#>   Links: mu = identity; sigma = identity 
#> Formula: ls ~ mo(income) * age 
#>    Data: dat (Number of observations: 100) 
#>   Draws: 4 chains, each with iter = 2000; warmup = 1000; thin = 1;
#>          total post-warmup draws = 4000
#> 
#> Population-Level Effects: 
#>                   Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
#> Intercept            30.62      2.05    26.56    34.77 1.00     2439     2899
#> ageyoung             -3.15      2.58    -8.32     1.91 1.00     2154     2580
#> moincome             14.53      0.94    12.71    16.41 1.00     2062     2577
#> moincome:ageyoung     1.67      1.30    -0.87     4.19 1.00     1951     2536
#> 
#> Simplex Parameters: 
#>                       Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS
#> moincome1[1]              0.64      0.05     0.55     0.74 1.00     4188
#> moincome1[2]              0.29      0.06     0.17     0.39 1.00     4675
#> moincome1[3]              0.07      0.04     0.00     0.17 1.00     2533
#> moincome:ageyoung1[1]     0.34      0.23     0.01     0.83 1.00     4067
#> moincome:ageyoung1[2]     0.28      0.21     0.01     0.76 1.00     4177
#> moincome:ageyoung1[3]     0.39      0.24     0.02     0.86 1.00     3639
#>                       Tail_ESS
#> moincome1[1]              3012
#> moincome1[2]              2999
#> moincome1[3]              1462
#> moincome:ageyoung1[1]     2256
#> moincome:ageyoung1[2]     2939
#> moincome:ageyoung1[3]     2585
#> 
#> Family Specific Parameters: 
#>       Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
#> sigma     7.94      0.59     6.84     9.20 1.00     4350     2579
#> 
#> Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
#> and Tail_ESS are effective sample size measures, and Rhat is the potential
#> scale reduction factor on split chains (at convergence, Rhat = 1).

marginal_effects <-
  fit |>
  comparisons(
    variables = list(income = "sequential"),
    newdata = datagrid(
      age = c("old", "young")
    )
  )

## Marginal effects for interaction

marginal_effects |>
  group_by(age) |>
  summarise(
    estimate = mean(comparison),
    conf.low = mean(conf.low),
    conf.high = mean(conf.high)
  )
#> # A tibble: 2 × 4
#>   age   estimate conf.low conf.high
#>   <chr>    <dbl>    <dbl>     <dbl>
#> 1 old       14.5     10.2      19.2
#> 2 young     16.1     11.5      21.3

## But overall estimate is not the same as the model coefficient and CIs

marginal_effects |>
  summarise(
    estimate = mean(comparison),
    conf.low = mean(conf.low),
    conf.high = mean(conf.high)
  )
#>   estimate conf.low conf.high
#> 1 15.31636 10.85506   20.2458

fit |>
  fixef(pars = "moincome")
#>          Estimate Est.Error     Q2.5    Q97.5
#> moincome 14.53432 0.9419654 12.71247 16.40808

## Even without an interaction, `comparisons` doesn't reproduce the model summary
## coefficient and CIs

fit_no_interaction <- brm(ls ~ mo(income), data = dat)
fit_no_interaction
#>  Family: gaussian 
#>   Links: mu = identity; sigma = identity 
#> Formula: ls ~ mo(income) 
#>    Data: dat (Number of observations: 100) 
#>   Draws: 4 chains, each with iter = 2000; warmup = 1000; thin = 1;
#>          total post-warmup draws = 4000
#> 
#> Population-Level Effects: 
#>           Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
#> Intercept    28.98      1.52    26.00    32.00 1.00     2547     2313
#> moincome     15.34      0.67    14.00    16.66 1.00     2545     2446
#> 
#> Simplex Parameters: 
#>              Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
#> moincome1[1]     0.63      0.04     0.54     0.71 1.00     2946     2072
#> moincome1[2]     0.29      0.05     0.19     0.38 1.00     2938     2238
#> moincome1[3]     0.08      0.04     0.01     0.17 1.00     2671     1047
#> 
#> Family Specific Parameters: 
#>       Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
#> sigma     7.91      0.58     6.84     9.12 1.00     3107     2259
#> 
#> Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
#> and Tail_ESS are effective sample size measures, and Rhat is the potential
#> scale reduction factor on split chains (at convergence, Rhat = 1).

marginal_effects_no_interaction <-
  fit_no_interaction |>
  comparisons(
    variables = list(income = "sequential"),
    newdata = datagrid()
  )

marginal_effects_no_interaction |>
  summarise(
    estimate = mean(comparison),
    conf.low = mean(conf.low),
    conf.high = mean(conf.high)
  )
#>   estimate conf.low conf.high
#> 1 15.31832 11.17472  19.58443

fit_no_interaction |>
  fixef(pars = "moincome")
#>          Estimate Est.Error     Q2.5    Q97.5
#> moincome 15.34083  0.673009 13.99584 16.66372

Created on 2022-11-29 with reprex v2.0.2

Session info
sessioninfo::session_info()
#> ─ Session info ───────────────────────────────────────────────────────────────
#>  setting  value
#>  version  R version 4.2.1 (2022-06-23)
#>  os       macOS Monterey 12.6
#>  system   aarch64, darwin20
#>  ui       X11
#>  language (EN)
#>  collate  en_AU.UTF-8
#>  ctype    en_AU.UTF-8
#>  tz       Asia/Jerusalem
#>  date     2022-11-29
#>  pandoc   2.19.2 @ /opt/homebrew/bin/ (via rmarkdown)
#> 
#> ─ Packages ───────────────────────────────────────────────────────────────────
#>  package         * version  date (UTC) lib source
#>  abind             1.4-5    2016-07-21 [1] CRAN (R 4.2.0)
#>  assertthat        0.2.1    2019-03-21 [1] CRAN (R 4.2.0)
#>  backports         1.4.1    2021-12-13 [1] CRAN (R 4.2.0)
#>  base64enc         0.1-3    2015-07-28 [1] CRAN (R 4.2.0)
#>  bayesplot         1.9.0    2022-03-10 [1] CRAN (R 4.2.0)
#>  bridgesampling    1.1-2    2021-04-16 [1] CRAN (R 4.2.0)
#>  brms            * 2.18.0   2022-09-19 [1] CRAN (R 4.2.0)
#>  Brobdingnag       1.2-9    2022-10-19 [1] CRAN (R 4.2.0)
#>  callr             3.7.2    2022-08-22 [1] CRAN (R 4.2.0)
#>  checkmate         2.1.0    2022-04-21 [1] CRAN (R 4.2.0)
#>  cli               3.4.1    2022-09-23 [1] CRAN (R 4.2.0)
#>  coda              0.19-4   2020-09-30 [1] CRAN (R 4.2.0)
#>  codetools         0.2-18   2020-11-04 [1] CRAN (R 4.2.1)
#>  colorspace        2.0-3    2022-02-21 [1] CRAN (R 4.2.0)
#>  colourpicker      1.1.1    2021-10-04 [1] CRAN (R 4.2.0)
#>  crayon            1.5.2    2022-09-29 [1] CRAN (R 4.2.1)
#>  crosstalk         1.2.0    2021-11-04 [1] CRAN (R 4.2.0)
#>  curl              4.3.2    2021-06-23 [1] CRAN (R 4.2.0)
#>  data.table        1.14.6   2022-11-16 [1] CRAN (R 4.2.0)
#>  DBI               1.1.3    2022-06-18 [1] CRAN (R 4.2.0)
#>  digest            0.6.30   2022-10-18 [1] CRAN (R 4.2.0)
#>  distributional    0.3.1    2022-09-02 [1] CRAN (R 4.2.0)
#>  dplyr           * 1.0.10   2022-09-01 [1] CRAN (R 4.2.0)
#>  DT                0.26     2022-10-19 [1] CRAN (R 4.2.0)
#>  dygraphs          1.1.1.6  2018-07-11 [1] CRAN (R 4.2.0)
#>  ellipsis          0.3.2    2021-04-29 [1] CRAN (R 4.2.0)
#>  emmeans           1.8.2    2022-10-27 [1] CRAN (R 4.2.0)
#>  estimability      1.4.1    2022-08-05 [1] CRAN (R 4.2.0)
#>  evaluate          0.16     2022-08-09 [1] CRAN (R 4.2.0)
#>  fansi             1.0.3    2022-03-24 [1] CRAN (R 4.2.0)
#>  farver            2.1.1    2022-07-06 [1] CRAN (R 4.2.0)
#>  fastmap           1.1.0    2021-01-25 [1] CRAN (R 4.2.0)
#>  fs                1.5.2    2021-12-08 [1] CRAN (R 4.2.0)
#>  generics          0.1.3    2022-07-05 [1] CRAN (R 4.2.0)
#>  ggplot2           3.3.6    2022-05-03 [1] CRAN (R 4.2.0)
#>  ggridges          0.5.4    2022-09-26 [1] CRAN (R 4.2.0)
#>  glue              1.6.2    2022-02-24 [1] CRAN (R 4.2.0)
#>  gridExtra         2.3      2017-09-09 [1] CRAN (R 4.2.0)
#>  gtable            0.3.1    2022-09-01 [1] CRAN (R 4.2.0)
#>  gtools            3.9.3    2022-07-11 [1] CRAN (R 4.2.0)
#>  highr             0.9      2021-04-16 [1] CRAN (R 4.2.0)
#>  htmltools         0.5.3    2022-07-18 [1] CRAN (R 4.2.0)
#>  htmlwidgets       1.5.4    2021-09-08 [1] CRAN (R 4.2.0)
#>  httpuv            1.6.6    2022-09-08 [1] CRAN (R 4.2.0)
#>  igraph            1.3.5    2022-09-22 [1] CRAN (R 4.2.0)
#>  inline            0.3.19   2021-05-31 [1] CRAN (R 4.2.0)
#>  insight           0.18.8.2 2022-11-26 [1] https://easystats.r-universe.dev (R 4.2.2)
#>  jsonlite          1.8.0    2022-02-22 [1] CRAN (R 4.2.0)
#>  knitr             1.40     2022-08-24 [1] CRAN (R 4.2.0)
#>  later             1.3.0    2021-08-18 [1] CRAN (R 4.2.0)
#>  lattice           0.20-45  2021-09-22 [1] CRAN (R 4.2.1)
#>  lifecycle         1.0.3    2022-10-07 [1] CRAN (R 4.2.0)
#>  loo               2.5.1    2022-03-24 [1] CRAN (R 4.2.0)
#>  magrittr          2.0.3    2022-03-30 [1] CRAN (R 4.2.0)
#>  marginaleffects * 0.8.1    2022-11-28 [1] https://vincentarelbundock.r-universe.dev (R 4.2.2)
#>  markdown          1.2      2022-10-19 [1] CRAN (R 4.2.0)
#>  Matrix            1.5-1    2022-09-13 [1] CRAN (R 4.2.0)
#>  matrixStats       0.62.0   2022-04-19 [1] CRAN (R 4.2.0)
#>  mime              0.12     2021-09-28 [1] CRAN (R 4.2.0)
#>  miniUI            0.1.1.1  2018-05-18 [1] CRAN (R 4.2.0)
#>  munsell           0.5.0    2018-06-12 [1] CRAN (R 4.2.0)
#>  mvtnorm           1.1-3    2021-10-08 [1] CRAN (R 4.2.0)
#>  nlme              3.1-157  2022-03-25 [1] CRAN (R 4.2.1)
#>  pillar            1.8.1    2022-08-19 [1] CRAN (R 4.2.0)
#>  pkgbuild          1.3.1    2021-12-20 [1] CRAN (R 4.2.0)
#>  pkgconfig         2.0.3    2019-09-22 [1] CRAN (R 4.2.0)
#>  plyr              1.8.7    2022-03-24 [1] CRAN (R 4.2.0)
#>  posterior         1.3.1    2022-09-06 [1] CRAN (R 4.2.0)
#>  prettyunits       1.1.1    2020-01-24 [1] CRAN (R 4.2.0)
#>  processx          3.7.0    2022-07-07 [1] CRAN (R 4.2.0)
#>  promises          1.2.0.1  2021-02-11 [1] CRAN (R 4.2.0)
#>  ps                1.7.1    2022-06-18 [1] CRAN (R 4.2.0)
#>  purrr             0.3.4    2020-04-17 [1] CRAN (R 4.2.0)
#>  R.cache           0.16.0   2022-07-21 [1] CRAN (R 4.2.0)
#>  R.methodsS3       1.8.2    2022-06-13 [1] CRAN (R 4.2.0)
#>  R.oo              1.25.0   2022-06-12 [1] CRAN (R 4.2.0)
#>  R.utils           2.12.0   2022-06-28 [1] CRAN (R 4.2.0)
#>  R6                2.5.1    2021-08-19 [1] CRAN (R 4.2.0)
#>  Rcpp            * 1.0.9    2022-07-08 [1] CRAN (R 4.2.0)
#>  RcppParallel      5.1.5    2022-01-05 [1] CRAN (R 4.2.0)
#>  reprex            2.0.2    2022-08-17 [1] CRAN (R 4.2.0)
#>  reshape2          1.4.4    2020-04-09 [1] CRAN (R 4.2.0)
#>  rlang             1.0.6    2022-09-24 [1] CRAN (R 4.2.0)
#>  rmarkdown         2.16     2022-08-24 [1] CRAN (R 4.2.0)
#>  rstan             2.26.13  2022-10-21 [1] local
#>  rstantools        2.2.0    2022-04-08 [1] CRAN (R 4.2.0)
#>  scales            1.2.1    2022-08-20 [1] CRAN (R 4.2.0)
#>  sessioninfo       1.2.2    2021-12-06 [1] CRAN (R 4.2.0)
#>  shiny             1.7.2    2022-07-19 [1] CRAN (R 4.2.0)
#>  shinyjs           2.1.0    2021-12-23 [1] CRAN (R 4.2.0)
#>  shinystan         2.6.0    2022-03-03 [1] CRAN (R 4.2.0)
#>  shinythemes       1.2.0    2021-01-25 [1] CRAN (R 4.2.0)
#>  StanHeaders       2.26.13  2022-10-21 [1] local
#>  stringi           1.7.8    2022-07-11 [1] CRAN (R 4.2.0)
#>  stringr           1.4.1    2022-08-20 [1] CRAN (R 4.2.0)
#>  styler            1.7.0    2022-03-13 [1] CRAN (R 4.2.0)
#>  tensorA           0.36.2   2020-11-19 [1] CRAN (R 4.2.0)
#>  threejs           0.3.3    2020-01-21 [1] CRAN (R 4.2.0)
#>  tibble            3.1.8    2022-07-22 [1] CRAN (R 4.2.0)
#>  tidyselect        1.1.2    2022-02-21 [1] CRAN (R 4.2.0)
#>  utf8              1.2.2    2021-07-24 [1] CRAN (R 4.2.0)
#>  V8                4.2.1    2022-08-07 [1] CRAN (R 4.2.0)
#>  vctrs             0.5.1    2022-11-16 [1] CRAN (R 4.2.0)
#>  withr             2.5.0    2022-03-03 [1] CRAN (R 4.2.0)
#>  xfun              0.33     2022-09-12 [1] CRAN (R 4.2.0)
#>  xtable            1.8-4    2019-04-21 [1] CRAN (R 4.2.0)
#>  xts               0.12.2   2022-10-16 [1] CRAN (R 4.2.0)
#>  yaml              2.3.5    2022-02-21 [1] CRAN (R 4.2.0)
#>  zoo               1.8-11   2022-09-17 [1] CRAN (R 4.2.0)
#> 
#>  [1] /Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/library
#> 
#> ──────────────────────────────────────────────────────────────────────────────

By the way, this is the manual computation marginaleffects does (from the maintainer):

library(brms)
library(marginaleffects)
income_options <- c("below_20", "20_to_40", "40_to_100", "greater_100")
income <- factor(sample(income_options, 100, TRUE),
  levels = income_options, ordered = TRUE
)
mean_ls <- c(30, 60, 70, 75)
ls <- mean_ls[income] + rnorm(100, sd = 7)
age <- sample(c("old", "young"), size = 100, replace = TRUE)
dat <- data.frame(income, ls, age)

fit <- brm(ls ~ mo(income), data = dat)
nd <- data.frame(income = unique(dat$income)[1:2])
pred <- posterior_epred(fit, newdata = nd)
quantile(pred[, 1] - pred[, 2], probs = c(.5, .025, .975))

         50%     2.5%    97.5% 
    36.58375 33.10761 40.12760 

comparisons(fit, newdata = nd[1, , drop = FALSE], variables = "income")

      rowid     type   term               contrast comparison conf.low conf.high
    1     1 response income    20_to_40 - below_20   29.24994 24.86779  33.44431
    2     1 response income   40_to_100 - below_20   36.58375 33.10761  40.12760
    3     1 response income greater_100 - below_20   42.61998 38.97886  46.09694
      predicted predicted_hi predicted_lo    income       ls
    1  69.49153     62.15328     32.88527 40_to_100 64.83888
    2  69.49153     69.49153     32.88527 40_to_100 64.83888
    3  69.49153     75.52529     32.88527 40_to_100 64.83888