R Markdown

There are quite a few proposed measures of effect size for simmple mediation models. Yuan, Wang and Liu (2026) https://doi.org/10.1016/j.metip.2025.100224 have proposed a new measure and reviewed a range of previous proposed measures. Each has pros and cons but I think the knew measure \(R^2_{med}\) looks quite useful. The basic idea is to isolate the proportion of the direct effect of X (the predictor) on Y (the outcome) that is mediated by M (the moderator). I’m not in general a fan of \(R^2\) measures but I think this is a sensible use case. I think I actually prefer the proportion mediated of the total effect as even simpler to interpret, but the measures behave broadly similarly.

I imagine researchers will like \(R^2_{med}\) because it tends to be bigger than \(\Delta{med}\) which is the total variance of the direct effect accounted for by the indirect effect. Its important not to confuse these! In fact its useful to report the total \(R^2\) accounted for by X on Y. \(R^2_{med}\) will be 1 when mediation accounts for all the variance of the simple effect (direct and indirect) and 0 when it accounts for none. Yuan, Wang and Liu (2026) reccomend reporting \(R^2_{med}\) with this total because even near full mediation can coexist with tiny total effects. Of course the total \(R^2\) is a traditional \(R^2\) measure that comes with the usual issues of measurement error and so forth so (as Yuan et al. note) small values should not be dismissed out of hand, the interpretation depends on context. I’d argue that reporting \(\Delta{med}\) might be more useful than total \(R^2\) alongside \(R^2_{med}\). One tells you the mediation effect size in terms of the total variance of Y and the other as a proportion of the total possible. Note that \(R^2_{med} \times R^2_{total} = \Delta{med}\) so reporting any two should be sufficient.

A big caveat is that this all presumes consistent mediation. Inconsistant medaition is inherently problematic because you can’t really decompose the total effect into the direct and indirect effect. In inconsistent mediation the direct and indirect effects have different signs. So the total effect will be smaller than one of the components. In the most degnerate case the total effect is 0 and the direct and indirect effects are equal and opposite in sign. Thus you are trying to decompose zero effect (or worse still zero variance) into two components accounting for non-zero effects (and non-zero \(R^2\)).

Yuan, Wang and Liu (2026) propose a variant of \(R^2_{med}\) termed \(R^2_{im}\) for the inconsistent case. Its value is equal to \(R^2_{med}\) when mediation is consistent and thus indicates that \(R^2_{med}\) should not be interpreted if the two values differ. \(R^2_{im}\) is a little wierd and I’m not sure how useful it is (though its very clear that Yuan et al. are proposing it be interpreted very carefully). In essence it will tell you how much more variance than the total normally possible is ‘magically’ uncovered in the inconsistent case. I’m not fond of this way of describing it. There’s nothing magic going on in my view. You have two effects that partially cancel each other out. It looks as though variance is created because we benchmark against the total case rather than compare the direct and indirect effects. There are links here to suppression (and I may write a note on that in future).

More importantly \(R^2_{im}\) is going to be very unstable as the estimate of total effect approaches zero (and undefined for 0). In these cases small changes in the estimate of c (owing to sampling error) will produce dramatic changes in in \(R^2_{im}\). This is my main concern for interpretation.

Therefore for reporting and interpreting inconsistent mediation I actually think it would make more sense to generalize the proportion measure of effect size. The simple proportion measure is \(Prop_{med} = ab / c = ab / (ab + c')\) where ab is the indirect effect and c is the total effect (in the standard notation). So a if the total effect is 4 and the indirect effect is 1 then the proportion accounted for by the mediation is .25 (or 25% if you prefer). This is easy to generalize to inconsistent mediation, although the interpretation changes!

My proposal is to simply use the absolute values of the terms thus: \(Prop_{im} = |ab| / (|ab| + |c'|)\). Now the mediation is expressed as a proportion of the total absolute effect. This could also be interpreted as the proportion of the indirect of the difference in the two effects. If the two effects are exactly balanced (when c = 1) this would be 0.5 and the proportion gives you an idea of the relative but opposite contributions of the direct and indirect effect. As far as I know this measure is new but its not really novel as it is a simple transformation of the reasonably well know ratio measure of effect size (Alwin & Hauser, 1975; https://doi.org/10.2307/2094445). The absolute value of \(Ratio_{med} / (1 + Ratio_{med})}\) is identical to \(Prop_{im}\). The ratio measure of effect size handles inconsistent mediation reasonably well but its well known that people find proportions slightly easier to work with than ratios (e.g., with probabilities versus odds ratios). None of this is to say that one should not be extra careful interpreating effect sizes with inconsistent mediation.

All well and good, but how to you calculate them? Yuan, Wang and Liu (2026) provide R code and a function for consistent mediation that takes a covariance matrix as an input. I have adapted it a little to change the output format and allow a correlation matrix as input (in which case you also need a vector of the SDs). I also added \(R^2_{im}\) and the total \(R^2\) to the output as well as \(Prop_{im}\). The output also includes \(c'\), a, b and ab so you can check your mediation output easily

A brief example of input from covariance matrix, correlation matrix and for inconsistent mediation is provided below. Please cite Yuan, Wang and Liu (2026) in published work (definitely don’t just cite me!).

mediation_effect_size <- function(matrix,cor_mat = FALSE, sd_vector = NULL) {
  # R code adapted from Yuan, Wang and Liu (2026) https://doi.org/10.1016/j.metip.2025.100224
  # specify cov_mat or cor_mat (simple mediation only)
  # matrix must be in the order x, m, y
  # if cor_mat = TRUE a vector of three SDs must be supplied in the order x, m, y
  
  if (cor_mat == FALSE)
    cov_mat <- matrix
  else
    cov_mat <- matrix * tcrossprod(sd_vector)
  
  a <- cov_mat[2, 1] / cov_mat[1, 1]
  cb <- solve(cov_mat[1:2, 1:2]) %*% as.matrix(cov_mat[1:2, 3])
  c_1 <- cb[1, 1] #c_1 is for the direct effect c`
  b <- cb[2, 1]
  ab <- a * b
  
  c <- c_1 + ab #total effect
  
  sig_x <- sqrt(cov_mat[1, 1]) #SD_x
  sig_y <- sqrt(cov_mat[3, 3]) #SD_y
  
  if (c == 0) {
    P_m <- -1.0
    print("P_m=-1 because proportion is not defined at c=0")
  } else {
    P_m <- ab / c
  }
  
  if (c_1 == 0) {
    R_m <- -1.0
    print("R_m=-1 because ratio is not defined at c'=0")
  } else {
    R_m <- ab / c_1
  }
  
  Ind_ps <- ab / sig_y
  Ind_cs <- Ind_ps * sig_x
  ES_set2 <- array(c(P_m, R_m, Ind_ps, Ind_cs), dim = c(1, 4),
                   dimnames = list(NULL, c('Prop_med', 'Ratio_med',
                                           'Ind_ps', 'Ind_cs')))
  
  #standardized coefficient
  D_mat <- sqrt(diag(cov_mat))
  Din_mat <- diag(1 / D_mat)
  corr <- Din_mat %*% cov_mat %*% Din_mat
  beta_a <- corr[2, 1]
  beta2_a <- beta_a * beta_a #beta_a square
  
  beta_c <- corr[3, 1]
  beta2_c <- beta_c * beta_c #beta_c square
  
  beta_cb <- solve(corr[1:2, 1:2]) %*% as.matrix(corr[1:2, 3])
  
  beta_c1 <- beta_cb[1, 1]
  beta2_c1 <- beta_c1 * beta_c1 #beta_c' square
  
  beta_b <- beta_cb[2, 1]
  beta2_b <- beta_b * beta_b #beta_b square
  
  beta_ab <- beta_a * beta_b #beta_a beta_b
  
  ES_delta <- beta2_a * beta2_b + 2 * beta_a * beta_b * beta_c1
  if (beta2_c > 0) {
    R2_med <- ES_delta / beta2_c
  } else {
    R2_med <- -1.0
  }
  
  R2_1 <- ES_delta + beta2_a * beta2_c1
  if ((1 - beta2_c) > 0) {
    R2_2 <- beta2_a * beta2_b * (1 - beta2_a) / (1 - beta2_c)
  } else {
    R2_2 <- -1
  }
  
  if ((beta2_c + beta2_b * (1 - beta2_a)) > 0) {
    R2_3 <- R2_2 / (beta2_c + beta2_b * (1 - beta2_a))
  } else {
    R2_3 <- -1
  }
  
  v <- beta2_a * beta2_b
  ES_set1 <- array(
    c(R2_med, ES_delta, R2_1, R2_2, R2_3, v),
    dim = c(1, 6),
    dimnames = list(NULL, c(
      'R^2_med', 'ES_delta', 'R^2_1', 'R^2_2', 'R^2_3', 'v'
    ))
  )
  
  if ((abs(P_m) > 0.8 & abs(beta_c) > 0.2)) {
    F <- 1
  } else {
    F <- 0
  }
  
  if ((abs(beta_c) > 0.15 & abs(beta_c1) < 0.15)) {
    S_c <- 1
  } else {
    S_c <- 0
  }
  
  if ((abs(beta_ab) > 0.15 & abs(beta_c1) < 0.15)) {
    S_ab <- 1
  } else {
    S_ab <- 0
  }
  
  ES_set3 <- array(c(F, S_c, S_ab), dim = c(1, 3),
                   dimnames = list(NULL, c('F', 'S_c', 'S_ab')))
  
  R2_im <- 1 - sign(ab * c_1) * (c_1 / c)^2
  P_im <- abs(ab) / (abs(ab) + abs(c_1))
  
  ES_set4 <- array(c(R2_med, R2_im, P_im), dim = c(1, 3),
                   dimnames = list(NULL, c('R^2_med', 'R^2_im', 'Prop_im')))
  
  print("-------------------R-square Type Measures--------------------",quote=F)
  print(ES_set1)
  cat("\n")
  
  print("-----Proportion, Ratio, and Standardized Indirect Effect-----",quote=F)
  print(ES_set2)
  cat("\n")
  
  print("-----------Full and Substantial Mediation Criteria-----------",quote=F)
  print(ES_set3)
  cat("\n")
  
  print("----Inconsistent mediation if R^2_med not equal to R^2_im----",quote=F)
  print(ES_set4)
  cat("\n")
  
  print("------------Understandardized Parameter Estimates------------",quote=F)
  R2_tot <- beta2_c
  parest <- array(c(c_1, a, b, ab, R2_tot), dim = c(1, 5),
                  dimnames = list(NULL, c('direct (c prime)','a path','b path',
                                          'indirect (ab)','R^2 total')))
  print(parest)
}

consistent mediation from cov matrix

This call uses the covariance matrix. Be sure to enter the variables in the order X, M and Y. This example is from Yuan et al.

S_mat <- matrix(c(
  0.9884877, 0.6507525, -0.189916,
  0.6507525, 1.4139198, -0.346207,
  -0.189916, -0.346207, 0.6759974
), nrow = 3, ncol = 3, byrow = TRUE)

mediation_effect_size(S_mat, cor_mat=FALSE)
## [1] -------------------R-square Type Measures--------------------
##        R^2_med   ES_delta      R^2_1      R^2_2     R^2_3         v
## [1,] 0.9466487 0.05109703 0.05196958 0.02351892 0.1845946 0.0319215
## 
## [1] -----Proportion, Ratio, and Standardized Indirect Effect-----
##       Prop_med Ratio_med     Ind_ps     Ind_cs
## [1,] 0.7690211  3.329399 -0.1797033 -0.1786659
## 
## [1] -----------Full and Substantial Mediation Criteria-----------
##      F S_c S_ab
## [1,] 0   1    1
## 
## [1] ----Inconsistent mediation if R^2_med not equal to R^2_im----
##        R^2_med    R^2_im   Prop_im
## [1,] 0.9466487 0.9466487 0.7690211
## 
## [1] ------------Understandardized Parameter Estimates------------
##      direct (c prime)    a path     b path indirect (ab)  R^2 total
## [1,]      -0.04437748 0.6583314 -0.2244316    -0.1477504 0.05397676

consistent mediation from cor matrix

This call uses the correlation matrix. Be sure to enter the variables in the order X, M and Y. A vector of the three SDs in order must be included. This is a made up example example just to illustrate the input.

R_mat <- matrix(c(
  1, .6, 0.6,
  0.6, 1, 0.6,
  0.6, 0.6, 1
), nrow = 3, ncol = 3, byrow = TRUE)

mediation_effect_size(R_mat, cor_mat=TRUE, sd_vector =c(3,10,3))
## [1] -------------------R-square Type Measures--------------------
##       R^2_med ES_delta R^2_1    R^2_2  R^2_3        v
## [1,] 0.609375 0.219375  0.27 0.050625 0.1125 0.050625
## 
## [1] -----Proportion, Ratio, and Standardized Indirect Effect-----
##      Prop_med Ratio_med Ind_ps Ind_cs
## [1,]    0.375       0.6  0.075  0.225
## 
## [1] -----------Full and Substantial Mediation Criteria-----------
##      F S_c S_ab
## [1,] 0   0    0
## 
## [1] ----Inconsistent mediation if R^2_med not equal to R^2_im----
##       R^2_med   R^2_im Prop_im
## [1,] 0.609375 0.609375   0.375
## 
## [1] ------------Understandardized Parameter Estimates------------
##      direct (c prime) a path b path indirect (ab) R^2 total
## [1,]            0.375      2 0.1125         0.225      0.36

inconsistent mediation from cov matrix

This call uses the covariance matrix. Be sure to enter the variables in the order X, M and Y. This is a made up example example just to illustrate the input.

S_mat <- matrix(c(
  25, 12, -4.5,
  12, 16, 8,
  -4.5, -8, 36
), nrow = 3, ncol = 3, byrow = TRUE)

mediation_effect_size(S_mat, cor_mat=FALSE)
## [1] -------------------R-square Type Measures--------------------
##       R^2_med   ES_delta      R^2_1     R^2_2     R^2_3         v
## [1,] -12.2921 -0.2765723 -0.1689063 0.1031266 0.3408948 0.1575098
## 
## [1] -----Proportion, Ratio, and Standardized Indirect Effect-----
##       Prop_med  Ratio_med   Ind_ps   Ind_cs
## [1,] -2.645833 -0.7257143 0.079375 0.396875
## 
## [1] -----------Full and Substantial Mediation Criteria-----------
##      F S_c S_ab
## [1,] 0   0    0
## 
## [1] ----Inconsistent mediation if R^2_med not equal to R^2_im----
##       R^2_med  R^2_im   Prop_im
## [1,] -12.2921 14.2921 0.4205298
## 
## [1] ------------Understandardized Parameter Estimates------------
##      direct (c prime) a path    b path indirect (ab) R^2 total
## [1,]         -0.65625   0.48 0.9921875       0.47625    0.0225