# Set Moderator Levels

#### Shu Fai Cheung & Sing-Hang Cheung

#### 2024-12-08

# 1 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.

# 2 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)

## 2.1 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

## 2.2 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

## 2.3 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`.

## 2.4 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

## 2.5 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

# 3 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.

## 3.1 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)

## 3.2 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).

## 3.3 User-Supplied Levels

To have full control on the labels and coding, use `values` and supply a named list of numeric vectors: the name is the group label and the vector is the coding:

    w1levels <- mod_levels(w = c("w1group2", "w1group3"), fit = fit,
                           values = list(group1 = c(0, 0),
                                         group2 = c(1, 0),
                                         group3 = c(0, 1)))
    w1levels
    #>        w1group2 w1group3
    #> group1        0        0
    #> group2        1        0
    #> group3        0        1

# 4 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

# 5 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"`.

# 6 Further Information

For further information on `mod_levels()`, `mod_levels_list()`, and `merge_mod_levels()`, please refer to their help pages.

# 7 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