This vignette walks through the full workflow for compressing a brms::brmsfit, then reconstructing it for use with the standard brms post-processing functions (predict(), posterior_predict(), pp_check(), …). It requires brms + cmdstanr + a working CmdStan install.

The chunks below that fit Stan models are gated with instantiate::stan_cmdstan_exists(), so the vignette still builds on machines that do not have CmdStan installed (e.g. CI runners). Install CmdStan once with:

poco::check_and_install_cmdstanr()
cmdstanr::check_cmdstan_toolchain(fix = TRUE)
cmdstanr::install_cmdstan()

Fit a brms model

The model must use the cmdstanr backend so that compress_brmsfit() can reach the CSV draws.

library(brms)
#> Loading required package: Rcpp
#> Loading 'brms' package (version 2.23.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(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

set.seed(1)
n_groups <- 20; n_per <- 15
dat <- expand.grid(group = 1:n_groups, rep = 1:n_per)
dat$x  <- rnorm(nrow(dat))
re     <- rnorm(n_groups, 0, 0.5)
dat$y  <- 2 + 1.5 * dat$x + re[dat$group] + rnorm(nrow(dat), 0, 0.8)

fit <- brm(
  y ~ x + (1 | group),
  data    = dat,
  backend = "cmdstanr",
  chains  = 4,
  iter    = 2000,
  warmup  = 1000,
  refresh = 0
)
#> Start sampling
#> Running MCMC with 4 sequential chains...
#> 
#> Chain 1 finished in 0.4 seconds.
#> Chain 2 finished in 0.4 seconds.
#> Chain 3 finished in 0.4 seconds.
#> Chain 4 finished in 0.4 seconds.
#> 
#> All 4 chains finished successfully.
#> Mean chain execution time: 0.4 seconds.
#> Total execution time: 2.0 seconds.
#> Loading required namespace: rstan

Compress and save

A single call returns both the compressed posterior and the brms fit structure. Save them separately so the structure can be reused with any future updates to the compressed posterior.

result <- compress_brmsfit(
  fit,
  method       = "mclust",
  n_components = 5
)
#>  mclust: trying all 14 covariance models c(EII, VII, EEI, VEI, EVI, VVI, EEE, VEE, EVE, VVE, EEV, VEV, EVV, VVV) and picking the best by BIC (n = 4000, d = 27).
#>  mclust: selected model 'VEV' with G = 5 (BIC = 205,090.9) out of 14 candidate models: EII, VII, EEI, VEI, EVI, VVI, EEE, VEE, EVE, VVE, EEV, VEV, EVV, VVV.

result$compressed carries the GMM parameters; result$structure is the original brmsfit, which we keep so that downstream methods can be re-attached.

Reconstruct and use the model


fit_recon <- reconstruct_brmsfit(result)

new_data <- data.frame(x = seq(-2, 2, length.out = 20),
                       group = rep(1:5, each = 4))

p  <- predict(fit_recon, newdata = new_data)
pp <- posterior_predict(fit_recon, newdata = new_data)

Compare pairwise posterior geometry

Use bayesplot::mcmc_pairs() to compare the original and reconstructed draw geometry on the same subset of parameters.

library(bayesplot)
#> This is bayesplot version 1.15.0
#> - Online documentation and vignettes at mc-stan.org/bayesplot
#> - bayesplot theme set to bayesplot::theme_default()
#>    * Does _not_ affect other ggplot2 plots
#>    * See ?bayesplot_theme_set for details on theme setting
#> 
#> Attaching package: 'bayesplot'
#> The following object is masked from 'package:brms':
#> 
#>     rhat

pair_pars <- c("b_Intercept", "b_x", "sd_group__Intercept", "sigma")

draws_orig  <- posterior::as_draws_array(fit, variable = pair_pars)
draws_recon <- posterior::as_draws_array(fit_recon, variable = pair_pars)

bayesplot::mcmc_pairs(draws_orig,  pars = pair_pars)

bayesplot::mcmc_pairs(draws_recon, pars = pair_pars)

MCMC diagnostics (Rhat, ESS) are not preserved by the regenerated draws.

Other methods

compress_brmsfit() shares the same method argument as compress_posterior(). To use a different backend, e.g. mvdens GMM:

result <- compress_brmsfit(
  fit,
  method       = "mvdens_gmm",
  n_components = 5
)

(Requires the suggested mvdens package - remotes::install_github("NKI-CCB/mvdens").)

Session info

sessionInfo()
#> R version 4.6.0 (2026-04-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.4 LTS
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so;  LAPACK version 3.12.0
#> 
#> locale:
#>  [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8       
#>  [4] LC_COLLATE=C.UTF-8     LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
#>  [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C          
#> [10] LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   
#> 
#> time zone: UTC
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] bayesplot_1.15.0 dplyr_1.2.1      brms_2.23.0      Rcpp_1.1.1-1.1  
#> [5] poco_0.2.0      
#> 
#> loaded via a namespace (and not attached):
#>  [1] gtable_0.3.6          tensorA_0.36.2.1      QuickJSR_1.9.2       
#>  [4] xfun_0.57             bslib_0.10.0          ggplot2_4.0.3        
#>  [7] htmlwidgets_1.6.4     processx_3.9.0        inline_0.3.21        
#> [10] lattice_0.22-9        callr_3.7.6           vctrs_0.7.3          
#> [13] tools_4.6.0           ps_1.9.3              generics_0.1.4       
#> [16] stats4_4.6.0          parallel_4.6.0        tibble_3.3.1         
#> [19] cmdstanr_0.9.0        pkgconfig_2.0.3       Matrix_1.7-5         
#> [22] data.table_1.18.4     checkmate_2.3.4       RColorBrewer_1.1-3   
#> [25] S7_0.2.2              desc_1.4.3            RcppParallel_5.1.11-2
#> [28] distributional_0.7.0  lifecycle_1.0.5       compiler_4.6.0       
#> [31] farver_2.1.2          stringr_1.6.0         textshaping_1.0.5    
#> [34] Brobdingnag_1.2-9     codetools_0.2-20      htmltools_0.5.9      
#> [37] mvdens_1.1.0          sass_0.4.10           yaml_2.3.12          
#> [40] pillar_1.11.1         pkgdown_2.2.0         jquerylib_0.1.4      
#> [43] cachem_1.1.0          StanHeaders_2.32.10   bridgesampling_1.2-1 
#> [46] abind_1.4-8           mclust_6.1.2          nlme_3.1-169         
#> [49] rstan_2.32.7          posterior_1.7.0       tidyselect_1.2.1     
#> [52] digest_0.6.39         mvtnorm_1.3-7         stringi_1.8.7        
#> [55] reshape2_1.4.5        labeling_0.4.3        fastmap_1.2.0        
#> [58] grid_4.6.0            cli_3.6.6             magrittr_2.0.5       
#> [61] loo_2.9.0             pkgbuild_1.4.8        withr_3.0.2          
#> [64] scales_1.4.0          backports_1.5.1       rmarkdown_2.31       
#> [67] matrixStats_1.5.0     otel_0.2.0            gridExtra_2.3        
#> [70] instantiate_0.2.3     ragg_1.5.2            coda_0.19-4.1        
#> [73] evaluate_1.0.5        knitr_1.51            rstantools_2.6.0     
#> [76] rlang_1.2.0           glue_1.8.1            jsonlite_2.0.0       
#> [79] plyr_1.8.9            R6_2.6.1              systemfonts_1.3.2    
#> [82] fs_2.1.0