Skip to content

Commit d27281c

Browse files
Adding r2_mlm() and possible update to r2() to work with it (#764)
Co-authored-by: Daniel <[email protected]>
1 parent 852c1d9 commit d27281c

File tree

10 files changed

+241
-31
lines changed

10 files changed

+241
-31
lines changed

DESCRIPTION

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Type: Package
22
Package: performance
33
Title: Assessment of Regression Models Performance
4-
Version: 0.12.2.12
4+
Version: 0.12.2.13
55
Authors@R:
66
c(person(given = "Daniel",
77
family = "Lüdecke",
@@ -52,7 +52,11 @@ Authors@R:
5252
"Bacher", ,
5353
5454
role = "ctb",
55-
comment = c(ORCID = "0000-0002-9271-5075")))
55+
comment = c(ORCID = "0000-0002-9271-5075")),
56+
person(given = "Joseph",
57+
family = "Luchman",
58+
role = "ctb",
59+
comment = c(ORCID = "0000-0002-8886-9717")))
5660
Maintainer: Daniel Lüdecke <[email protected]>
5761
Description: Utilities for computing measures to assess model quality,
5862
which are not directly provided by R's 'base' or 'stats' packages.

NAMESPACE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,7 @@ S3method(r2_mcfadden,serp)
486486
S3method(r2_mcfadden,truncreg)
487487
S3method(r2_mcfadden,vglm)
488488
S3method(r2_mckelvey,default)
489+
S3method(r2_mlm,mlm)
489490
S3method(r2_nagelkerke,BBreg)
490491
S3method(r2_nagelkerke,DirichletRegModel)
491492
S3method(r2_nagelkerke,bife)
@@ -610,6 +611,7 @@ export(r2_loo)
610611
export(r2_loo_posterior)
611612
export(r2_mcfadden)
612613
export(r2_mckelvey)
614+
export(r2_mlm)
613615
export(r2_nagelkerke)
614616
export(r2_nakagawa)
615617
export(r2_posterior)

R/print-methods.R

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,23 +68,39 @@ print.r2_pseudo <- function(x, digits = 3, ...) {
6868
#' @export
6969
print.r2_mlm <- function(x, digits = 3, ...) {
7070
model_type <- attr(x, "model_type")
71-
if (!is.null(model_type)) {
72-
insight::print_color(sprintf("# R2 for %s Regression\n\n", model_type), "blue")
71+
is_multivar_r2 <- all(names(x) == c("Symmetric Rxy", "Asymmetric Pxy"))
72+
if (!is.null(model_type) && !is_multivar_r2) {
73+
insight::print_color(
74+
sprintf("# R2 for %s Regression\n\n", model_type),
75+
"blue"
76+
)
77+
} else if (!is.null(model_type) && is_multivar_r2) {
78+
insight::print_color(
79+
sprintf("# Multivariate R2 for %s Regression\n", model_type),
80+
"blue"
81+
)
7382
} else {
7483
insight::print_color("# R2\n\n", "blue")
7584
}
7685

77-
for (i in names(x)) {
78-
insight::print_color(sprintf("## %s\n", i), "cyan")
79-
out <- paste0(
80-
c(
81-
sprintf(" R2: %.*f", digits, x[[i]]$R2),
82-
sprintf(" adj. R2: %.*f", digits, x[[i]]$R2_adjusted)
83-
),
84-
collapse = "\n"
85-
)
86-
cat(out)
86+
if (is_multivar_r2) {
87+
cat(sprintf(" Symmetric Rxy: %.*f", digits, x[["Symmetric Rxy"]]))
88+
cat("\n")
89+
cat(sprintf("Asymmetric Pxy: %.*f", digits, x[["Asymmetric Pxy"]]))
8790
cat("\n\n")
91+
} else {
92+
for (i in names(x)) {
93+
insight::print_color(sprintf("## %s\n", i), "cyan")
94+
out <- paste(
95+
c(
96+
sprintf(" R2: %.*f", digits, x[[i]]$R2),
97+
sprintf(" adj. R2: %.*f", digits, x[[i]]$R2_adjusted)
98+
),
99+
collapse = "\n"
100+
)
101+
cat(out)
102+
cat("\n\n")
103+
}
88104
}
89105
invisible(x)
90106
}

R/r2.R

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
#' (`TRUE`) or not (`FALSE`)?
1111
#' @param ci Confidence interval level, as scalar. If `NULL` (default), no
1212
#' confidence intervals for R2 are calculated.
13+
#' @param multivariate Logical. Should multiple R2 values be reported as
14+
#' separated by response (FALSE) or should a single R2 be reported as
15+
#' combined across responses computed by [`r2_mlm`] (TRUE).
1316
#' @param ... Arguments passed down to the related r2-methods.
1417
#' @inheritParams r2_nakagawa
1518
#'
@@ -31,7 +34,7 @@
3134
#' @seealso
3235
#' [`r2_bayes()`], [`r2_coxsnell()`], [`r2_kullback()`], [`r2_loo()`],
3336
#' [`r2_mcfadden()`], [`r2_nagelkerke()`], [`r2_nakagawa()`], [`r2_tjur()`],
34-
#' [`r2_xu()`] and [`r2_zeroinflated()`].
37+
#' [`r2_xu()`], [`r2_zeroinflated()`], and [`r2_mlm()`].
3538
#'
3639
#' @examplesIf require("lme4")
3740
#' # Pseudo r-quared for GLM
@@ -245,24 +248,29 @@ r2.aov <- function(model, ci = NULL, ...) {
245248
structure(class = "r2_generic", out)
246249
}
247250

248-
251+
#' @rdname r2
249252
#' @export
250-
r2.mlm <- function(model, ...) {
251-
model_summary <- summary(model)
253+
r2.mlm <- function(model, multivariate = TRUE, ...) {
252254

253-
out <- lapply(names(model_summary), function(i) {
254-
tmp <- list(
255-
R2 = model_summary[[i]]$r.squared,
256-
R2_adjusted = model_summary[[i]]$adj.r.squared,
257-
Response = sub("Response ", "", i, fixed = TRUE)
258-
)
259-
names(tmp$R2) <- "R2"
260-
names(tmp$R2_adjusted) <- "adjusted R2"
261-
names(tmp$Response) <- "Response"
262-
tmp
263-
})
255+
if (multivariate) {
256+
out <- r2_mlm(model)
257+
} else {
258+
model_summary <- summary(model)
259+
260+
out <- lapply(names(model_summary), function(i) {
261+
tmp <- list(
262+
R2 = model_summary[[i]]$r.squared,
263+
R2_adjusted = model_summary[[i]]$adj.r.squared,
264+
Response = sub("Response ", "", i, fixed = TRUE)
265+
)
266+
names(tmp$R2) <- "R2"
267+
names(tmp$R2_adjusted) <- "adjusted R2"
268+
names(tmp$Response) <- "Response"
269+
tmp
270+
})
264271

265-
names(out) <- names(model_summary)
272+
names(out) <- names(model_summary)
273+
}
266274

267275
attr(out, "model_type") <- "Multivariate Linear"
268276
structure(class = "r2_mlm", out)

R/r2_mlm.R

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#' @title Multivariate R2
2+
#' @name r2_mlm
3+
#'
4+
#' @description
5+
#' Calculates two multivariate R2 values for multivariate linear regression.
6+
#'
7+
#' @param model Multivariate linear regression model.
8+
#' @param ... Currently not used.
9+
#'
10+
#' @details
11+
#' The two indexes returned summarize model fit for the set of predictors
12+
#' given the system of responses. As compared to the default
13+
#' [r2][performance::r2] index for multivariate linear models, the indexes
14+
#' returned by this function provide a single fit value collapsed across
15+
#' all responses.
16+
#'
17+
#' The two returned indexes were proposed by *Van den Burg and Lewis (1988)*
18+
#' as an extension of the metrics proposed by *Cramer and Nicewander (1979)*.
19+
#' Of the numerous indexes proposed across these two papers, only two metrics,
20+
#' the \eqn{R_{xy}} and \eqn{P_{xy}}, are recommended for use
21+
#' by *Azen and Budescu (2006)*.
22+
#'
23+
#' For a multivariate linear regression with \eqn{p} predictors and
24+
#' \eqn{q} responses where \eqn{p > q}, the \eqn{R_{xy}} index is
25+
#' computed as:
26+
#'
27+
#' \deqn{R_{xy} = 1 - \prod_{i=1}^p (1 - \rho_i^2)}
28+
#'
29+
#' Where \eqn{\rho} is a canonical variate from a
30+
#' [canonical correlation][cancor] between the predictors and responses.
31+
#' This metric is symmetric and its value does not change when the roles of
32+
#' the variables as predictors or responses are swapped.
33+
#'
34+
#' The \eqn{P_{xy}} is computed as:
35+
#'
36+
#' \deqn{P_{xy} = \frac{q - trace(\bf{S}_{\bf{YY}}^{-1}\bf{S}_{\bf{YY|X}})}{q}}
37+
#'
38+
#' Where \eqn{\bf{S}_{\bf{YY}}} is the matrix of response covariances and
39+
#' \eqn{\bf{S}_{\bf{YY|X}}} is the matrix of residual covariances given
40+
#' the predictors. This metric is asymmetric and can change
41+
#' depending on which variables are considered predictors versus responses.
42+
#'
43+
#' @return A named vector with the R2 values.
44+
#'
45+
#' @examples
46+
#' model <- lm(cbind(qsec, drat) ~ wt + mpg + cyl, data = mtcars)
47+
#' r2_mlm(model)
48+
#'
49+
#' model_swap <- lm(cbind(wt, mpg, cyl) ~ qsec + drat, data = mtcars)
50+
#' r2_mlm(model_swap)
51+
#'
52+
#' @references
53+
#' - Azen, R., & Budescu, D. V. (2006). Comparing predictors in
54+
#' multivariate regression models: An extension of dominance analysis.
55+
#' Journal of Educational and Behavioral Statistics, 31(2), 157-180.
56+
#'- Cramer, E. M., & Nicewander, W. A. (1979). Some symmetric,
57+
#' invariant measures of multivariate association. Psychometrika, 44, 43-54.
58+
#' - Van den Burg, W., & Lewis, C. (1988). Some properties of two
59+
#' measures of multivariate association. Psychometrika, 53, 109-122.
60+
#'
61+
#' @author Joseph Luchman
62+
#'
63+
#' @export
64+
r2_mlm <- function(model, ...) {
65+
UseMethod("r2_mlm")
66+
}
67+
68+
# methods ---------------------------
69+
70+
#' @export
71+
r2_mlm.mlm <- function(model, verbose = TRUE, ...) {
72+
rho2_vec <- 1 - stats::cancor(
73+
insight::get_predictors(model),
74+
insight::get_response(model)
75+
)$cor^2
76+
R_xy <- 1 - Reduce(`*`, rho2_vec, 1)
77+
78+
resid_cov <- stats::cov(residuals(model))
79+
resp_cov <- stats::cov(insight::get_response(model))
80+
qq <- ncol(insight::get_response(model))
81+
V_xy <- qq - sum(diag(solve(resp_cov) %*% resid_cov))
82+
P_xy <- V_xy / qq
83+
84+
c("Symmetric Rxy" = R_xy, "Asymmetric Pxy" = P_xy)
85+
}

inst/WORDLIST

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Ankerst
99
Archimbaud
1010
Arel
1111
Asq
12+
Azen
1213
BCI
1314
BFBayesFactor
1415
BMJ
@@ -27,6 +28,7 @@ Breunig
2728
Breusch
2829
BRM
2930
Bryk
31+
Budescu
3032
Bundock
3133
Burnham
3234
Byrne
@@ -40,6 +42,7 @@ CochransQ
4042
CompQuadForm
4143
Concurvity
4244
Confounder
45+
Cramer
4346
Cribari
4447
Cronbach's
4548
Crujeiras
@@ -162,6 +165,7 @@ Neto's
162165
Nondegenerate
163166
Nordhausen
164167
Normed
168+
Nicewander
165169
ORCID
166170
OSF
167171
OSX

man/performance-package.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/r2.Rd

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/r2_mlm.Rd

Lines changed: 74 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-r2_mlm.R

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
test_that("r2_mlm_Rxy", {
2+
model <- lm(cbind(qsec, drat) ~ wt + mpg, data = mtcars)
3+
expect_equal(r2_mlm(model)[["Symmetric Rxy"]], 0.68330688076502, tolerance = 1e-3)
4+
})
5+
6+
test_that("r2_mlm_Pxy", {
7+
model <- lm(cbind(qsec, drat) ~ wt + mpg, data = mtcars)
8+
expect_equal(r2_mlm(model)[["Asymmetric Pxy"]], 0.407215267524997, tolerance = 1e-3)
9+
})

0 commit comments

Comments
 (0)