Set Moderator Levels
Shu Fai Cheung & Sing-Hang Cheung
2024-12-08
Source:vignettes/mod_levels.Rmd
mod_levels.Rmd
Introduction
This article is a brief illustration of how to use
mod_levels()
, mod_levels_list()
, and
merge_mod_levels()
from the package manymome (Cheung & Cheung,
2023) to generate a table of moderator levels for use by
cond_indirect_effects()
. No need to use these functions if
the default levels generated by cond_indirect_effects()
is
appropriated, as illustrated in vignette("manymome")
.
Use these functions only when users want to use levels of the moderators other than those default levels.
Numeric Moderators
We first use the sample data set data_med_mod_ab
:
library(manymome)
dat <- data_med_mod_ab
print(head(dat), digits = 3)
#> x w1 w2 m y c1 c2
#> 1 9.27 4.97 2.66 3.46 8.80 9.26 3.14
#> 2 10.79 4.13 3.33 4.05 7.37 10.71 5.80
#> 3 11.10 5.91 3.32 4.04 8.24 10.60 5.45
#> 4 9.53 4.78 2.32 3.54 8.37 9.22 3.83
#> 5 10.00 4.38 2.95 4.65 8.39 9.58 4.26
#> 6 12.25 5.81 4.04 4.73 9.65 9.51 4.01
Suppose this is the model being fitted:
library(lavaan)
#> This is lavaan 0.6-19
#> lavaan is FREE software! Please report any bugs.
dat$w1x <- dat$w1 * dat$x
dat$w2m <- dat$w2 * dat$m
mod <-
"
m ~ x + w1 + w1x
y ~ m + w2 + w2m
m ~~ w2 + w2m
w2 ~~ w2m + x + w1 + w1x
w2m ~~ x + w1 + w1x
x ~~ w1 + w1x
w1 ~~ w1x
"
fit <- sem(model = mod, data = dat)
SD and Mean
It has two numeric moderators, w1
and w2
.
To generate these three levels for w1
, one standard
deviation (SD) below mean, mean, and one SD above mean, just call
mod_levels()
:
w1levels <- mod_levels(w = "w1", fit = fit)
w1levels
#> w1
#> M+1.0SD 6.173157
#> Mean 5.105602
#> M-1.0SD 4.038047
This is not necessary in calling cond_indirect_effects()
because it will automatically generate these default levels.
Suppose we want to use only two levels, one SD below and one SD above
mean, we can set the argument sd_from_mean
to a vector of
distances from means, which are -1
and 1
in
this example:
w1levels <- mod_levels(w = "w1", fit = fit,
sd_from_mean = c(-1, 1))
w1levels
#> w1
#> M+1.0SD 6.173157
#> M-1.0SD 4.038047
Percentiles
To generate the levels based on percentiles, set the argument
w_method
to "percentile"
:
w1levels <- mod_levels(w = "w1", fit = fit,
w_method = "percentile")
w1levels
#> w1
#> 84% 6.207972
#> 50% 5.215974
#> 16% 3.932444
The default percentiles are 16th, 50th, and 84th, corresponding to one SD below mean, mean, and one SD above mean in a normal distribution.
Suppose we want to change the percentiles to be used, for example,
25th and 75th, set the argument percentiles
as shown
below:
w1levels <- mod_levels(w = "w1", fit = fit,
w_method = "percentile",
percentiles = c(.25, .75))
w1levels
#> w1
#> 75% 5.808579
#> 25% 4.196759
Specific Values
If there are values that are meaningful for a moderator, they can be
used by setting values
to a vector of values:
w1levels <- mod_levels(w = "w1", fit = fit,
values = c(2, 4, 8))
w1levels
#> w1
#> 8 8
#> 4 4
#> 2 2
The output of mod_levels
can be used when calling
cond_indirect_effects()
:
out <- cond_indirect_effects(wlevels = w1levels,
x = "x", y = "m",
fit = fit)
out
#>
#> == Conditional effects ==
#>
#> Path: x -> m
#> Conditional on moderator(s): w1
#> Moderator(s) represented by: w1
#>
#> [w1] (w1) ind SE Stat pvalue Sig CI.lo CI.hi
#> 1 8 8 0.964 0.308 3.130 0.002 ** 0.360 1.568
#> 2 4 4 0.165 0.164 1.007 0.314 -0.156 0.486
#> 3 2 2 -0.235 0.314 -0.747 0.455 -0.850 0.381
#>
#> - [SE] are 'lavaan' standard errors.
#> - [Stat] are the z statistics used to test the effects.
#> - [pvalue] are p-values computed from 'Stat'.
#> - [Sig]: 0 '***' 0.001 '**' 0.01 '*' 0.05 ' ' 1.
#> - [CI.lo to CI.hi] are 95.0% confidence interval computed from 'lavaan'
#> standard errors.
#> - IMPORTANT: For a model fitted by structural equation model, the
#> p-values and confidence intervals for the conditional effects
#> computed from standard errors can only be trusted if all covariances
#> involving the product terms are free. Otherwise, the model may not
#> be invariant to linear transformation of the variables.
#> - The 'ind' column shows the conditional effects.
#>
cond_indirect_effects()
will determine the moderators
automatically from the object assigned to wlevels
.
Merging the Levels of Two Or More Moderators
In the previous example, there are two moderators. We can call
mod_levels()
once for each of them, or call
mod_levels_list()
:
wlevels_list <- mod_levels_list("w1", "w2", fit = fit)
wlevels_list
#> [[1]]
#> w1
#> M+1.0SD 6.173157
#> M-1.0SD 4.038047
#>
#> [[2]]
#> w2
#> M+1.0SD 4.040487
#> M-1.0SD 2.055091
The output is a list of the output of mod_levels()
. With
two or more moderators, the default levels are two: one SD below mean
and one SD above mean.
The function mod_levels_list()
can merge the output into
one table by setting merge
to TRUE
:
wlevels_list <- mod_levels_list("w1", "w2", fit = fit,
merge = TRUE)
wlevels_list
#> w1 w2
#> w1: M+1.0SD; w2: M+1.0SD 6.173157 4.040487
#> w1: M+1.0SD; w2: M-1.0SD 6.173157 2.055091
#> w1: M-1.0SD; w2: M+1.0SD 4.038047 4.040487
#> w1: M-1.0SD; w2: M-1.0SD 4.038047 2.055091
Calling mod_levels_list()
is useful when the same
settings will be used for all moderators. Most arguments of
mod_levels()
can be used in mod_levels_list()
.
For example, if we want to use 25th and 75th percentiles for both
w1
and w2
, use w_method
and
percentiles
as before:
wlevels_list <- mod_levels_list("w1", "w2", fit = fit,
w_method = "percentile",
percentiles = c(.25, .75),
merge = TRUE)
wlevels_list
#> w1 w2
#> w1: 75%; w2: 75% 5.808579 3.692675
#> w1: 75%; w2: 25% 5.808579 2.430643
#> w1: 25%; w2: 75% 4.196759 3.692675
#> w1: 25%; w2: 25% 4.196759 2.430643
Different Settings For Moderators
If we need to use different settings for the two moderators, then we
need to call mod_levels()
once for each of them, and merge
the results by merge_mod_levels()
:
w1levels <- mod_levels(w = "w1", fit = fit)
w1levels
#> w1
#> M+1.0SD 6.173157
#> Mean 5.105602
#> M-1.0SD 4.038047
w2levels <- mod_levels(w = "w2", fit = fit, values = c(2, 5))
w2levels
#> w2
#> 5 5
#> 2 2
wlevels_all <- merge_mod_levels(w1levels, w2levels)
wlevels_all
#> w1 w2
#> w1: M+1.0SD; w2: 5 6.173157 5
#> w1: M+1.0SD; w2: 2 6.173157 2
#> w1: Mean; w2: 5 5.105602 5
#> w1: Mean; w2: 2 5.105602 2
#> w1: M-1.0SD; w2: 5 4.038047 5
#> w1: M-1.0SD; w2: 2 4.038047 2
Categorical Moderators
We use the dataset data_med_mod_serial_cat
for
illustration:
dat <- data_med_mod_serial_cat
print(head(dat), digits = 3)
#> x w1 w2 m1 m2 y c1 c2
#> 1 8.09 group2 team1 7.70 5.26 3.17 4.31 5.03
#> 2 6.16 group2 team2 5.71 4.36 3.12 4.69 3.19
#> 3 6.03 group2 team2 5.40 5.22 4.23 4.07 4.25
#> 4 6.92 group1 team1 7.26 5.98 3.65 5.87 5.35
#> 5 7.76 group3 team1 4.49 4.83 4.01 4.35 4.16
#> 6 6.84 group2 team2 5.72 6.31 3.38 4.32 4.48
It has two categorical moderators, w1
with three
categories and w2
with two categories. We use only
w1
here for illustration.
Create Dummy Variables and Fit the Model
To fit a model using path analysis, two dummy variables need to be
created for w1
. This can be done by
factor2var()
or similar functions from other packages.
w1dummies <- factor2var(dat$w1, prefix = "w1")
head(w1dummies)
#> w1group2 w1group3
#> group2 1 0
#> group2 1 0
#> group2 1 0
#> group1 0 0
#> group3 0 1
#> group2 1 0
# Add them to the dataset
dat[, c("w1group2", "w1group3")] <- w1dummies
print(head(dat), digits = 3)
#> x w1 w2 m1 m2 y c1 c2 w1group2 w1group3
#> 1 8.09 group2 team1 7.70 5.26 3.17 4.31 5.03 1 0
#> 2 6.16 group2 team2 5.71 4.36 3.12 4.69 3.19 1 0
#> 3 6.03 group2 team2 5.40 5.22 4.23 4.07 4.25 1 0
#> 4 6.92 group1 team1 7.26 5.98 3.65 5.87 5.35 0 0
#> 5 7.76 group3 team1 4.49 4.83 4.01 4.35 4.16 0 1
#> 6 6.84 group2 team2 5.72 6.31 3.38 4.32 4.48 1 0
This is the model:
dat$w1group2x <- dat$w1group2 * dat$x
dat$w1group3x <- dat$w1group3 * dat$x
mod <-
"
m1 ~ x + w1group2 + w1group3 + w1group2x + w1group3x
y ~ m1 + x
"
fit <- sem(model = mod, data = dat)
Default Levels
The levels of a categorical moderator are just the categories (unique
combinations of the coding). This can be generated by
mod_levels()
. w
should be a vector of the
dummy variables:
w1levels <- mod_levels(w = c("w1group2", "w1group3"), fit = fit)
w1levels
#> w1group2 w1group3
#> Reference 0 0
#> 2 1 0
#> 3 0 1
The names of group2
and group3
are
2
and 3
because they are inferred from the
names of the dummy variables. The common part, "w1group"
,
is removed.
To tell mod_levels
which part to be removed, set
prefix
to the part to be removed:
w1levels <- mod_levels(w = c("w1group2", "w1group3"), fit = fit,
prefix = "w1")
w1levels
#> w1group2 w1group3
#> Reference 0 0
#> group2 1 0
#> group3 0 1
By default, the group with 0s on all dummy variables is labelled
Reference
because its name cannot be determined from the
names of the dummy variables. The label can be changed by setting
reference_group_label
:
w1levels <- mod_levels(w = c("w1group2", "w1group3"), fit = fit,
prefix = "w1",
reference_group_label = "group1")
w1levels
#> w1group2 w1group3
#> group1 0 0
#> group2 1 0
#> group3 0 1
The output can then be used in
cond_indirect_effects()
:
out <- cond_indirect_effects(wlevels = w1levels,
x = "x", y = "y", m = "m1",
fit = fit)
out
#>
#> == Conditional indirect effects ==
#>
#> Path: x -> m1 -> y
#> Conditional on moderator(s): w1
#> Moderator(s) represented by: w1group2, w1group3
#>
#> [w1] (w1group2) (w1group3) ind m1~x y~m1
#> 1 group1 0 0 0.263 1.002 0.262
#> 2 group2 1 0 0.249 0.950 0.262
#> 3 group3 0 1 0.122 0.467 0.262
#>
#> - The 'ind' column shows the conditional indirect effects.
#> - 'm1~x','y~m1' is/are the path coefficient(s) along the path
#> conditional on the moderator(s).
Mixing Numeric and Categorical Moderators
Numeric and categorical moderators can be mixed but they need to be
generated separately and then merged by
merge_mod_levels()
.
Using the example in the previous section, pretending that
x
is a numeric moderator:
xlevels <- mod_levels(w = "x", fit = fit,
sd_from_mean = c(-1, 1))
xlevels
#> x
#> M+1.0SD 7.149362
#> M-1.0SD 5.087437
w1levels <- mod_levels(w = c("w1group2", "w1group3"), fit = fit,
prefix = "w1",
reference_group_label = "group1")
w1levels
#> w1group2 w1group3
#> group1 0 0
#> group2 1 0
#> group3 0 1
wlevels_all <- merge_mod_levels(xlevels, w1levels)
wlevels_all
#> x w1group2 w1group3
#> x: M+1.0SD; w1: group1 7.149362 0 0
#> x: M+1.0SD; w1: group2 7.149362 1 0
#> x: M+1.0SD; w1: group3 7.149362 0 1
#> x: M-1.0SD; w1: group1 5.087437 0 0
#> x: M-1.0SD; w1: group2 5.087437 1 0
#> x: M-1.0SD; w1: group3 5.087437 0 1
Determining The Type of a Moderator
The function mod_levels()
and
mod_levels_list()
usually can determine the type correctly.
However, if the detected type is wrong or users want to specify
explicitly the type, set the argument w_type
to either
"numeric"
or "categorical"
.
Further Information
For further information on mod_levels()
,
mod_levels_list()
, and merge_mod_levels()
,
please refer to their help pages.
Reference
Cheung, S. F., & Cheung, S.-H. (2023). manymome: An R package for computing the indirect effects, conditional effects, and conditional indirect effects, standardized or unstandardized, and their bootstrap confidence intervals, in many (though not all) models. Behavior Research Methods. https://doi.org/10.3758/s13428-023-02224-z