Skip to content

Commit

Permalink
Marginal R2 is a misnomer in brms (#753)
Browse files Browse the repository at this point in the history
  • Loading branch information
strengejacke authored Jul 14, 2024
1 parent 6e1eb60 commit 96cdb6c
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 29 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Type: Package
Package: performance
Title: Assessment of Regression Models Performance
Version: 0.12.0.11
Version: 0.12.0.12
Authors@R:
c(person(given = "Daniel",
family = "Lüdecke",
Expand Down
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
* New function `r2_ferrari()` to compute Ferrari & Cribari-Neto's R2 for
generalized linear models, in particular beta-regression.

* Improved documentation of some functions.

## Bug fixes

* Fixed issue in `check_model()` when model contained a transformed response
Expand Down
56 changes: 43 additions & 13 deletions R/r2_bayes.R
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,20 @@
#' credible intervals for the R2 values are saved as attributes.
#'
#' @details
#' `r2_bayes()` returns an "unadjusted" R2 value. See [r2_loo()] to calculate a
#' `r2_bayes()` returns an "unadjusted" R2 value. See [`r2_loo()`] to calculate a
#' LOO-adjusted R2, which comes conceptually closer to an adjusted R2 measure.
#'
#' For mixed models, the conditional and marginal R2 are returned. The marginal
#' R2 considers only the variance of the fixed effects, while the conditional R2
#' takes both the fixed and random effects into account.
#' takes both the fixed and random effects into account. Technically, since
#' `r2_bayes()` relies on [`rstantools::bayes_R2()`], the "marginal" R2 calls
#' `bayes_R2(re.form = NA)`, while the "conditional" R2 calls
#' `bayes_R2(re.form = NULL)`. The `re.form` argument is passed to
#' [`rstantools::posterior_epred()`], which is internally called in `bayes_R2()`.
#'
#' Note that for "marginal" and "conditional", we refer to the wording suggested
#' by _Nakagawa et al. 2017_. Thus, we don't use the term "marginal" in the sense
#' that the random effects are integrated out, but are "ignored".
#'
#' `r2_posterior()` is the actual workhorse for `r2_bayes()` and returns a
#' posterior sample of Bayesian R2 values.
Expand Down Expand Up @@ -72,9 +80,13 @@
#' r2_bayes(model)
#' }
#' @references
#' Gelman, A., Goodrich, B., Gabry, J., and Vehtari, A. (2018). R-squared for
#' Bayesian regression models. The American Statistician, 1–6.
#' \doi{10.1080/00031305.2018.1549100}
#' - Gelman, A., Goodrich, B., Gabry, J., and Vehtari, A. (2018). R-squared for
#' Bayesian regression models. The American Statistician, 1–6.
#' \doi{10.1080/00031305.2018.1549100}
#' - Nakagawa, S., Johnson, P. C. D., and Schielzeth, H. (2017). The
#' coefficient of determination R2 and intra-class correlation coefficient from
#' generalized linear mixed-effects models revisited and expanded. Journal of
#' The Royal Society Interface, 14(134), 20170213.
#' @export
r2_bayes <- function(model, robust = TRUE, ci = 0.95, verbose = TRUE, ...) {
r2_bayesian <- r2_posterior(model, verbose = verbose, ...)
Expand Down Expand Up @@ -185,20 +197,38 @@ r2_posterior.brmsfit <- function(model, verbose = TRUE, ...) {
names(br2) <- res
}
} else if (mi$is_mixed) {
br2 <- list(
R2_Bayes = as.vector(rstantools::bayes_R2(
if (inherits(model, "stanreg")) {
pred_cond <- rstanarm::posterior_epred(
model,
re.form = NULL,
re_formula = NULL,
summary = FALSE
)),
R2_Bayes_marginal = as.vector(rstantools::bayes_R2(
)
pred_marginal <- rstanarm::posterior_epred(
model,
re.form = NA,
re_formula = NA,
summary = FALSE
))
)
)
y <- insight::get_response(model)
br2 <- list(
R2_Bayes = as.vector(rstantools::bayes_R2(pred_cond, y = y)),
R2_Bayes_marginal = as.vector(rstantools::bayes_R2(pred_marginal, y = y))
)
} else {
br2 <- list(
R2_Bayes = as.vector(rstantools::bayes_R2(
model,
re.form = NULL,
re_formula = NULL,
summary = FALSE
)),
R2_Bayes_marginal = as.vector(rstantools::bayes_R2(
model,
re.form = NA,
re_formula = NA,
summary = FALSE
))
)
}
names(br2$R2_Bayes) <- rep("Conditional R2", length(br2$R2_Bayes))
names(br2$R2_Bayes_marginal) <- rep("Marginal R2", length(br2$R2_Bayes))
} else {
Expand Down
14 changes: 8 additions & 6 deletions man/icc.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 17 additions & 3 deletions man/r2_bayes.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 8 additions & 6 deletions man/r2_nakagawa.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions tests/testthat/test-r2_bayes.R
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,15 @@ test_that("r2_BayesFactor", {
expect_equal(r_CI$CI_low, 0.27, tolerance = 0.05)
expect_equal(r_CI$CI_high, 0.54, tolerance = 0.05)
})

test_that("r2_bayes", {
skip_on_cran()
skip_if_not_installed("rstanarm")
skip_if_not_installed("rstantools")
skip_if_not_installed("httr2")
model <- insight::download_model("stanreg_lmerMod_1")
set.seed(123)
out <- r2_bayes(model)
expect_equal(out$R2_Bayes, 0.642, tolerance = 1e-3)
expect_equal(out$R2_Bayes_marginal, 0.58534, tolerance = 1e-3)
})

0 comments on commit 96cdb6c

Please sign in to comment.