Nothing
#' @title Conditional, Indirect, and
#' Conditional Indirect Effects
#'
#' @description Compute the conditional
#' effects, indirect effects, or
#' conditional indirect effects in a
#' structural model fitted by [lm()],
#' [lavaan::sem()], or [semTools::sem.mi()].
#'
#' @details
#'
#' For a model with a mediation path
#' moderated by one or more moderators,
#' [cond_indirect_effects()] can be used
#' to compute the conditional indirect
#' effect from one variable to another
#' variable, at one or more set of
#' selected value(s) of the
#' moderator(s).
#'
#' If only the effect for one set of
#' value(s) of the moderator(s) is
#' needed, [cond_indirect()] can be
#' used.
#'
#' If only the mediator(s) is/are
#' specified (`m`) and no values of
#' moderator(s) are specified, then the
#' indirect effect from one variable
#' (`x`) to another variable (`y`) is
#' computed. A convenient wrapper
#' [indirect_effect()] can be used to
#' compute the indirect effect.
#'
#' If only the value(s) of moderator(s)
#' is/are specified (`wvalues` or
#' `wlevels`) and no mediators (`m`) are
#' specified when calling
#' [cond_indirect_effects()] or
#' [cond_indirect()], then the
#' conditional direct effects from one
#' variable to another are computed.
#'
#' All three functions support using
#' nonparametric bootstrapping (for
#' `lavaan` or `lm` outputs) or
#' Monte Carlo simulation (for
#' `lavaan` outputs only) to form
#' confidence intervals.
#' Bootstrapping or Monte Carlo
#' simulation only needs to be done
#' once. These are the possible ways to
#' form bootstrapping:
#'
#' 1. Do bootstrapping or Monte Carlo
#' simulation in the first call
#' to one of these functions, by setting
#' `boot_ci` or `mc_ci` to `TRUE` and
#' `R` to the
#' number of bootstrap samples or
#' replications, `level`
#' to the level of confidence (default
#' .95 or 95%), and `seed` to reproduce
#' the results (`parallel` and `ncores`
#' are optional for bootstrapping).
#' This will take some
#' time to run for bootstrapping. The
#' output will have all
#' bootstrap or Monte Carlo estimates
#' stored. This
#' output, whether it is from
#' [indirect_effect()],
#' [cond_indirect_effects()], or
#' [cond_indirect()], can be reused by
#' any of these three functions by
#' setting `boot_out` (for bootstrapping)
#' or `mc_out` (for Monte Carlo
#' simulation) to this output.
#' They will form the confidence
#' intervals using the stored bootstrap
#' or Monte Carlo
#' estimates.
#'
#' 2. Do bootstrapping using
#' [do_boot()] or Monte Carlo simulation
#' us8ing [do_mc()]. The output can be used
#' in the `boot_out` (for bootstrapping)
#' or `mc_out` (for Monte Carlo simulation)
#' argument of
#' [indirect_effect()],
#' [cond_indirect_effects()] and
#' [cond_indirect()].
#'
#' 3. For bootstrapping,
#' if [lavaan::sem()] is used to fit
#' a model and `se = "boot"` is used,
#' [do_boot()] can extract them to
#' generate a `boot_out`-class object
#' that again can be used in the
#' `boot_out` argument.
#'
#' If `boot_out` or `mc_out`
#' is set, arguments such
#' as `R`, `seed`, and `parallel` will
#' be ignored.
#'
#' ## Multigroup Models
#'
#' Since Version 0.1.14.2, support for
#' multigroup models has been added for models
#' fitted by `lavaan`. Both bootstrapping
#' and Monte Carlo confidence intervals
#' are supported. When used on
#' a multigroup model:
#'
#' - For [cond_indirect()] and
#' [indirect_effect()], users need to
#' specify the `group` argument
#' (by number or label). When using
#' [cond_indirect_effects()], if
#' `group` is not set, all groups wil
#' be used and the indirect effect
#' in each group will be computed,
#' kind of treating group as a moderator.
#'
#' - For [many_indirect_effects()],
#' the paths can be generated from a
#' multigroup models.
#'
#' - Currently, [cond_indirect_effects()]
#' does not support a multigroup model
#' with moderators on the path selected.
#' The function [cond_indirect()] does
#' not have this limitation but users
#' need to manually specify the desired
#' value of the moderator(s).
#'
#' ## [many_indirect_effects()]
#'
#' If bootstrapping or Monte Carlo
#' confidence intervals are requested,
#' it is advised to use [do_boot()]
#' first to simulate the estimates.
#' Nevertheless, In Version 0.1.14.9
#' and later versions, if `boot_ci`
#' or `mc_ci` is `TRUE` when calling
#' [many_indirect_effects()] but
#' `boot_out` or `mc_out` is not set,
#' bootstrapping or simulation will
#' be done only once, and then the
#' bootstrapping or simulated estimates
#' will be used for all paths. This
#' prevents accidentally repeating the
#' process once for each direct path.
#'
#' @return [indirect_effect()] and
#' [cond_indirect()] return an
#' `indirect`-class object.
#'
#' [cond_indirect_effects()] returns a
#' `cond_indirect_effects`-class object.
#'
#' These two classes of objects have
#' their own print methods for printing
#' the results (see [print.indirect()] and [print.cond_indirect_effects()]).
#' They also have a `coef` method for
#' extracting the estimates
#' ([coef.indirect()] and
#' [coef.cond_indirect_effects()]) and a
#' `confint` method for extracting the
#' confidence intervals
#' ([confint.indirect()] and
#' [confint.cond_indirect_effects()]).
#' Addition and subtraction can also be
#' conducted on `indirect`-class object
#' to estimate and test a function of
#' effects (see [math_indirect])
#'
#' @param x Character. The name of the
#' predictor at the start of the path.
#'
#' @param y Character. The name of the
#' outcome variable at the end of the
#' path.
#'
#' @param m A vector of the variable
#' names of the mediator(s). The path
#' goes from the first mediator
#' successively to the last mediator. If
#' `NULL`, the default, the path goes
#' from `x` to `y`.
#'
#' @param fit The fit object. Can be a
#' [lavaan::lavaan-class] object or a
#' list of [lm()] outputs.
#' It can also be
#' a `lavaan.mi` object
#' returned by
#' [semTools::runMI()] or
#' its wrapper, such as [semTools::sem.mi()].
#' If it is a single model fitted by
#' [lm()], it will be automatically converted
#' to a list by [lm2list()].
#'
#' @param est The output of
#' [lavaan::parameterEstimates()]. If
#' `NULL`, the default, it will be
#' generated from `fit`. If supplied,
#' `fit` will be ignored.
#'
#' @param implied_stats Implied means,
#' variances, and covariances of
#' observed variables, of the form of
#' the output of [lavaan::lavInspect()]
#' with `what` set to `"implied"`. The
#' standard deviations are extracted
#' from this object for standardization.
#' Default is `NULL`, and implied
#' statistics will be computed from
#' `fit` if required.
#'
#' @param wvalues A numeric vector of
#' named elements. The names are the
#' variable names of the moderators, and
#' the values are the values to which
#' the moderators will be set to.
#' Default is `NULL`.
#'
#' @param standardized_x Logical.
#' Whether `x` will be standardized.
#' Default is `FALSE`. For multigroup
#' models, model implied standard
#' deviation for the selected group
#' will be used.
#'
#' @param standardized_y Logical.
#' Whether `y` will be standardized.
#' Default is `FALSE`. For multigroup
#' models, model implied standard
#' deviation for the selected group
#' will be used.
#'
#' @param boot_ci Logical. Whether
#' bootstrap confidence interval will be
#' formed. Default is `FALSE`.
#'
#' @param level The level of confidence
#' for the bootstrap confidence
#' interval. Default is .95.
#'
#' @param boot_out If `boot_ci` is
#' `TRUE`, users can supply pregenerated
#' bootstrap estimates. This can be the
#' output of [do_boot()]. For
#' [indirect_effect()] and
#' [cond_indirect_effects()], this can
#' be the output of a previous call to
#' [cond_indirect_effects()],
#' [indirect_effect()], or
#' [cond_indirect()] with bootstrap
#' confidence intervals requested. These
#' stored estimates will be reused such
#' that there is no need to do
#' bootstrapping again. If not supplied,
#' the function will try to generate
#' them from `fit`.
#'
#' @param R Integer. If `boot_ci` is
#' `TRUE`, `boot_out` is `NULL`, and
#' bootstrap standard errors not
#' requested if `fit` is a
#' [lavaan::lavaan-class] object, this function
#' will do bootstrapping on `fit`. `R`
#' is the number of bootstrap samples.
#' Default is 100. For Monte Carlo
#' simulation, this is the number
#' of replications.
#'
#' @param seed If bootstrapping
#' or Monte Carlo simulation is
#' conducted, this is the seed for the
#' bootstrapping or simulation.
#' Default is `NULL` and
#' seed is not set.
#'
#' @param parallel Logical. If
#' bootstrapping is conducted, whether
#' parallel processing will be used.
#' Default is `TRUE`. If `fit` is a list
#' of [lm()] outputs, parallel
#' processing will not be used.
#'
#' @param ncores Integer. The number of
#' CPU cores to use when `parallel` is
#' `TRUE`. Default is the number of
#' non-logical cores minus one (one
#' minimum). Will raise an error if
#' greater than the number of cores
#' detected by
#' [parallel::detectCores()]. If
#' `ncores` is set, it will override
#' `make_cluster_args` in [do_boot()].
#'
#' @param make_cluster_args A named list
#' of additional arguments to be passed
#' to [parallel::makeCluster()]. For
#' advanced users. See
#' [parallel::makeCluster()] for
#' details. Default is `list()`.
#'
#' @param progress Logical. Display
#' bootstrapping progress or not.
#' Default is `TRUE`.
#'
#' @param wlevels The output of
#' [merge_mod_levels()], or the
#' moderator(s) to be passed to
#' [mod_levels_list()]. If all the
#' moderators can be represented by one
#' variable, that is, each moderator is
#' (a) a numeric variable, (b) a
#' dichotomous categorical variable, or
#' (c) a factor or string variable used
#' in [lm()] in `fit`, then it is a
#' vector of the names of the moderators
#' as appeared in the data frame. If at
#' least one of the moderators is a
#' categorical variable represented by
#' more than one variable, such as
#' user-created dummy variables used in
#' [lavaan::sem()], then it must be a
#' list of the names of the moderators,
#' with such moderators represented by a
#' vector of names. For example:
#' `list("w1", c("gpgp2", "gpgp3")`, the
#' first moderator `w1` and the second
#' moderator a three-categorical
#' variable represented by `gpgp2` and
#' `gpgp3`.
#'
#' @param ... Arguments to be passed to
#' [cond_indirect()]
#'
#' @param output_type The type of output
#' of [cond_indirect_effects()]. If
#' `"data.frame"`, the default, the
#' output will be converted to a data
#' frame. If any other values, the
#' output is a list of the outputs from
#' [cond_indirect()].
#'
#' @param save_boot_full If `TRUE`, full
#' bootstrapping results will be stored.
#' Default is `FALSE.`
#'
#' @param prods The product terms found. For internal use.
#'
#' @param get_prods_only IF `TRUE`, will
#' quit early and return the product
#' terms found. The results can be
#' passed to the `prod` argument when
#' calling this function. Default is
#' `FALSE`. This function is for
#' internal use.
#'
#' @param save_boot_out If `boot_out` is
#' supplied, whether it will be saved in
#' the output. Default is `TRUE`.
#'
#' @param mc_ci Logical. Whether
#' Monte Carlo confidence interval will be
#' formed. Default is `FALSE`.
#'
#' @param mc_out If `mc_ci` is
#' `TRUE`, users can supply pregenerated
#' Monte Carlo estimates. This can be the
#' output of [do_mc()]. For
#' [indirect_effect()] and
#' [cond_indirect_effects()], this can
#' be the output of a previous call to
#' [cond_indirect_effects()],
#' [indirect_effect()], or
#' [cond_indirect()] with Monte Carlo
#' confidence intervals requested. These
#' stored estimates will be reused such
#' that there is no need to do
#' Monte Carlo simulation again. If not
#' supplied,
#' the function will try to generate
#' them from `fit`.
#'
#' @param save_mc_full If `TRUE`, full
#' Monte Carlo results will be stored.
#' Default is `FALSE.`
#'
#' @param save_mc_out If `mc_out` is
#' supplied, whether it will be saved in
#' the output. Default is `TRUE`.
#'
#' @param save_ci_full If `TRUE`, full
#' bootstrapping or Monte Carlo results
#' will be stored.
#' Default is `FALSE.`
#'
#' @param save_ci_out If either `mc_out`
#' or `boot_out` is
#' supplied, whether it will be saved in
#' the output. Default is `TRUE`.
#'
#' @param ci_out If `ci_type` is supplied,
#' this is the corresponding argument.
#' If `ci_type` is `"boot"`, this
#' argument will be used as `boot_out`.
#' If `ci_type` is `"mc"`, this
#' argument will be used as `mc_out`.
#'
#' @param ci_type The type of
#' confidence intervals to be formed.
#' Can be either `"boot"` (bootstrapping)
#' or `"mc"` (Monte Carlo). If not
#' supplied or is `NULL`, will check
#' other arguments
#' (e.g, `boot_ci` and `mc_ci`). If
#' supplied, will override `boot_ci`
#' and `mc_ci`.
#'
#' @param group Either the group number
#' as appeared in the [summary()]
#' or [lavaan::parameterEstimates()]
#' output of a [lavaan::lavaan-class] object,
#' or the group label as used in
#' the [lavaan::lavaan-class] object.
#' Used only when the number of
#' groups is greater than one. Default
#' is `NULL`.
#'
#' @param groups Either a vector of
#' group numbers
#' as appeared in the [summary()]
#' or [lavaan::parameterEstimates()]
#' output of a [lavaan::lavaan-class] object,
#' or a vector of group labels as used in
#' the [lavaan::lavaan-class] object.
#' Used only when the number of
#' groups is greater than one. Default
#' is `NULL`.
#'
#' @param boot_type If bootstrap
#' confidence interval is to be formed,
#' the type of bootstrap confidence
#' interval. The supported types
#' are `"perc"` (percentile bootstrap
#' confidence interval, the default and
#' recommended type) and `"bc"`
#' (bias-corrected, or BC, bootstrap
#' confidence interval).
#'
#' @seealso [mod_levels()] and
#' [merge_mod_levels()] for generating
#' levels of moderators. [do_boot] for
#' doing bootstrapping before calling
#' these functions.
#'
#' @examples
#'
#' library(lavaan)
#' dat <- modmed_x1m3w4y1
#' mod <-
#' "
#' m1 ~ a1 * x + d1 * w1 + e1 * x:w1
#' m2 ~ a2 * x
#' y ~ b1 * m1 + b2 * m2 + cp * x
#' "
#' fit <- sem(mod, dat, meanstructure = TRUE, fixed.x = FALSE, se = "none", baseline = FALSE)
#' est <- parameterEstimates(fit)
#' hi_w1 <- mean(dat$w1) + sd(dat$w1)
#'
#' # Examples for cond_indirect():
#'
#' # Conditional effect from x to m1 when w1 is 1 SD above mean
#' cond_indirect(x = "x", y = "m1",
#' wvalues = c(w1 = hi_w1), fit = fit)
#'
#' # Direct effect from x to y (direct because no 'm' variables)
#' indirect_effect(x = "x", y = "y", fit = fit)
#'
#' # Conditional Indirect effect from x1 through m1 to y, when w1 is 1 SD above mean
#' cond_indirect(x = "x", y = "y", m = "m1",
#' wvalues = c(w1 = hi_w1), fit = fit)
#'
#'
#'
#' @export
#'
#' @describeIn cond_indirect Compute
#' conditional, indirect, or conditional
#' indirect effects for one set of
#' levels.
#'
#' @order 1
cond_indirect <- function(x,
y,
m = NULL,
fit = NULL,
est = NULL,
implied_stats = NULL,
wvalues = NULL,
standardized_x = FALSE,
standardized_y = FALSE,
boot_ci = FALSE,
level = .95,
boot_out = NULL,
R = 100,
seed = NULL,
parallel = TRUE,
ncores = max(parallel::detectCores(logical = FALSE) - 1, 1),
make_cluster_args = list(),
progress = TRUE,
save_boot_full = FALSE,
prods = NULL,
get_prods_only = FALSE,
save_boot_out = TRUE,
mc_ci = FALSE,
mc_out = NULL,
save_mc_full = FALSE,
save_mc_out = TRUE,
ci_out = NULL,
save_ci_full = FALSE,
save_ci_out = TRUE,
ci_type = NULL,
group = NULL,
boot_type = c("perc", "bc")) {
fit <- auto_lm2list(fit)
boot_type <- match.arg(boot_type)
fit_type <- cond_indirect_check_fit(fit)
chkpath <- check_path(x = x, y = y, m = m, fit = fit, est = est)
if (!chkpath) {
msg <- paste0("No path from ", sQuote(x), " to ", sQuote(y),
ifelse(is.null(m), "",
paste0(" through ",
paste0(sQuote(m), collapse = ", "))),
". ",
"Please check the arguments x, y, and m.")
stop(msg)
}
# Fix arguments
call_args <- names(match.call())
if (!is.null(ci_type)) {
if (ci_type == "mc") {
mc_ci <- TRUE
boot_ci <- FALSE
if (is.null(mc_out) && !is.null(ci_out)) {
mc_out <- ci_out
ci_out <- NULL
}
if (is.na(match("save_mc_full", call_args))) {
save_mc_full <- save_ci_full
}
if (is.na(match("save_mc_out", call_args))) {
save_mc_out <- save_ci_out
}
}
if (ci_type == "boot") {
boot_ci <- TRUE
mc_ci <- FALSE
if (is.null(boot_out) && !is.null(ci_out)) {
boot_out <- ci_out
ci_out <- NULL
}
if (is.na(match("save_boot_full", call_args))) {
save_boot_full <- save_ci_full
}
if (is.na(match("save_boot_out", call_args))) {
save_boot_out <- save_ci_out
}
}
}
if (all(boot_ci, mc_ci)) stop("Can only request one type of confidence intervals.")
if (mc_ci) {
if (!is.null(mc_out)) {
if (inherits(mc_out, "cond_indirect_effects")) {
mc_out <- attr(mc_out, "mc_out")
if (is.null(mc_out)) {
stop("mc_out not found in the supplied object for 'mc_out'")
}
}
if (inherits(mc_out, "indirect")) {
mc_out <- mc_out$mc_out
if (is.null(mc_out)) {
stop("mc_out not found in the supplied object for 'mc_out'")
}
}
if (!inherits(mc_out, "mc_out")) {
stop("The object at 'mc_out' must be of the class 'mc_out'.")
}
} else {
mc_out <- do_mc(fit = fit,
R = R,
seed = seed,
progress = progress)
}
}
if (boot_ci) {
if (!is.null(boot_out)) {
if (inherits(boot_out, "cond_indirect_effects")) {
boot_out <- attr(boot_out, "boot_out")
if (is.null(boot_out)) {
stop("boot_out not found in the supplied object for 'boot_out'")
}
}
if (inherits(boot_out, "indirect")) {
boot_out <- boot_out$boot_out
if (is.null(boot_out)) {
stop("boot_out not found in the supplied object for 'boot_out'")
}
}
if (!inherits(boot_out, "boot_out")) {
stop("The object at 'boot_out' must be of the class 'boot_out'.")
}
} else {
boot_out <- do_boot(fit = fit,
R = R,
seed = seed,
parallel = parallel,
ncores = ncores,
make_cluster_args = make_cluster_args,
progress = progress)
}
}
if (fit_type == "lavaan") {
fit0 <- fit
if (is.null(est)) est <- lavaan::parameterEstimates(fit)
if (is.null(implied_stats)) implied_stats <- lav_implied_all(fit)
est_vcov <- tryCatch(get_vcov(fit), error = function(e) NULL)
df_residual <- tryCatch(lav_df_residual(fit), error = function(e) NULL)
fit_data <- lavaan::lavInspect(fit, "data")
}
if (fit_type == "lavaan.mi") {
fit0 <- fit
if (is.null(est)) est <- lav_est(fit)
if (is.null(implied_stats)) implied_stats <- lav_implied_all(fit)
est_vcov <- tryCatch(get_vcov(fit), error = function(e) NULL)
df_residual <- tryCatch(lav_df_residual(fit), error = function(e) NULL)
fit_data <- lav_data_used(fit, drop_colon = FALSE)
}
if (fit_type == "lm") {
fit0 <- NULL
lm_est <- lm2ptable(fit)
if (is.null(est)) est <- lm_est$est
if (is.null(implied_stats)) implied_stats <- lm_est$implied_stats
if (is.null(lm_est$vcov)) {
est_vcov <- lm_list_vcov(fit)
} else {
est_vcov <- lm_est$vcov
}
if (is.null(lm_est$df_residual)) {
df_residual <- lm_df_residual(fit)
} else {
df_residual <- lm_est$df_residual
}
fit_data <- lm_est$data
}
if (is.null(prods)) {
prods <- indirect_i(x = x,
y = y,
m = m,
fit = fit0,
est = est,
implied_stats = implied_stats,
wvalues = wvalues,
standardized_x = standardized_x,
standardized_y = standardized_y,
get_prods_only = TRUE,
data = fit_data,
expand = TRUE,
group = group)
}
if (get_prods_only) return(prods)
out0 <- indirect_i(x = x,
y = y,
m = m,
fit = fit0,
est = est,
implied_stats = implied_stats,
wvalues = wvalues,
standardized_x = standardized_x,
standardized_y = standardized_y,
prods = prods,
group = group,
est_vcov = est_vcov,
df_residual = df_residual)
if (mc_ci) {
out_mc <- mapply(indirect_i,
est = lapply(mc_out, function(x) x$est),
implied_stats = lapply(mc_out, function(x) x$implied_stats),
MoreArgs = list(x = x,
y = y,
m = m,
fit = fit0,
wvalues = wvalues,
standardized_x = standardized_x,
standardized_y = standardized_y,
warn = FALSE,
prods = prods,
group = group),
SIMPLIFY = FALSE)
if (save_mc_full) {
out0$mc_full <- out_mc
}
out0$mc_indirect <- sapply(out_mc, function(x) x$indirect)
out0$mc_scale_x <- unname(sapply(out_mc, function(x) x$scale_x))
out0$mc_scale_y <- unname(sapply(out_mc, function(x) x$scale_y))
boot_ci1 <- boot_ci_internal(t0 = out0$indirect,
t = out0$mc_indirect,
level = level,
boot_type = "perc")
out0$mc_ci <- boot_ci1
out0$level <- level
out0$mc_se <- stats::sd(out0$mc_indirect, na.rm = TRUE)
if (save_mc_out) {
out0$mc_out <- mc_out
} else {
out0$mc_out <- NULL
}
}
if (boot_ci) {
out_boot <- mapply(indirect_i,
est = lapply(boot_out, function(x) x$est),
implied_stats = lapply(boot_out, function(x) x$implied_stats),
MoreArgs = list(x = x,
y = y,
m = m,
fit = fit0,
wvalues = wvalues,
standardized_x = standardized_x,
standardized_y = standardized_y,
warn = FALSE,
prods = prods,
group = group),
SIMPLIFY = FALSE)
if (save_boot_full) {
out0$boot_full <- out_boot
}
out0$boot_indirect <- sapply(out_boot, function(x) x$indirect)
out0$boot_scale_x <- unname(sapply(out_boot, function(x) x$scale_x))
out0$boot_scale_y <- unname(sapply(out_boot, function(x) x$scale_y))
boot_ci1 <- boot_ci_internal(t0 = out0$indirect,
t = out0$boot_indirect,
level = level,
boot_type = boot_type)
out0$boot_ci <- boot_ci1
out0$level <- level
out0$boot_p <- est2p(out0$boot_indirect)
out0$boot_se <- stats::sd(out0$boot_indirect, na.rm = TRUE)
out0$boot_type <- boot_type
if (save_boot_out) {
out0$boot_out <- boot_out
} else {
out0$boot_out <- NULL
}
}
out0$cond_indirect_call <- match.call()
out0
}
#' @export
#'
#' @examples
#'
#' # Multigroup model for indirect_effect()
#'
#' dat <- data_med_mg
#' mod <-
#' "
#' m ~ x + c1 + c2
#' y ~ m + x + c1 + c2
#' "
#' fit <- sem(mod, dat, meanstructure = TRUE, fixed.x = FALSE, se = "none", baseline = FALSE,
#' group = "group")
#'
#' # If a model has more than one group,
#' # the argument 'group' must be set.
#' ind1 <- indirect_effect(x = "x",
#' y = "y",
#' m = "m",
#' fit = fit,
#' group = "Group A")
#' ind1
#' ind2 <- indirect_effect(x = "x",
#' y = "y",
#' m = "m",
#' fit = fit,
#' group = 2)
#' ind2
#'
#' @describeIn cond_indirect Compute the
#' indirect effect. A wrapper of
#' [cond_indirect()]. Can be used when
#' there is no moderator.
#'
#' @order 3
indirect_effect <- function(x,
y,
m = NULL,
fit = NULL,
est = NULL,
implied_stats = NULL,
standardized_x = FALSE,
standardized_y = FALSE,
boot_ci = FALSE,
level = .95,
boot_out = NULL,
R = 100,
seed = NULL,
parallel = TRUE,
ncores = max(parallel::detectCores(logical = FALSE) - 1, 1),
make_cluster_args = list(),
progress = TRUE,
save_boot_full = FALSE,
save_boot_out = TRUE,
mc_ci = FALSE,
mc_out = NULL,
save_mc_full = FALSE,
save_mc_out = TRUE,
ci_out = NULL,
save_ci_full = FALSE,
save_ci_out = TRUE,
ci_type = NULL,
boot_type = c("perc", "bc"),
group = NULL) {
fit <- auto_lm2list(fit)
boot_type <- match.arg(boot_type)
cond_indirect(x = x,
y = y,
m = m,
fit = fit,
est = est,
implied_stats = implied_stats,
standardized_x = standardized_x,
standardized_y = standardized_y,
boot_ci = boot_ci,
level = level,
boot_out = boot_out,
R = R,
seed = seed,
parallel = parallel,
ncores = ncores,
make_cluster_args = make_cluster_args,
progress = progress,
save_boot_full = save_boot_full,
save_boot_out = save_boot_out,
mc_ci = mc_ci,
mc_out = mc_out,
save_mc_full = save_mc_full,
save_mc_out = save_mc_out,
ci_out = ci_out,
save_ci_full = save_ci_full,
save_ci_out = save_ci_out,
ci_type = ci_type,
boot_type = boot_type,
group = group)
}
#' @param w_type Character. Whether the
#' moderator is a `"numeric"` variable
#' or a `"categorical"` variable. If
#' `"auto"`, the function will try to
#' determine the type automatically.
#' See [mod_levels_list()] for further
#' information.
#'
#' @param w_method Character, either
#' `"sd"` or `"percentile"`. If `"sd"`,
#' the levels are defined by the
#' distance from the mean in terms of
#' standard deviation. if
#' `"percentile"`, the levels are
#' defined in percentiles. See
#' [mod_levels_list()] for further
#' information.
#'
#' @param sd_from_mean A numeric vector.
#' Specify the distance in standard
#' deviation from the mean for each
#' level. Default is `c(-1, 0, 1)` when
#' there is only one moderator, and
#' `c(-1, 1)` when there are more than
#' one moderator. Ignored if `w_method`
#' is not equal to `"sd"`. See
#' [mod_levels_list()] for further
#' information.
#'
#' @param percentiles A numeric vector.
#' Specify the percentile (in
#' proportion) for each level. Default
#' is `c(.16, .50, .84)` if there is one
#' moderator, and `c(.16, .84)` when
#' there are more than one moderator.
#' Ignored if `w_method` is not equal to
#' `"percentile"`. See
#' [mod_levels_list()] for further
#' information.
#'
#' @param mod_levels_list_args
#' Additional arguments to be passed to
#' [mod_levels_list()] if it is called
#' for creating the levels of
#' moderators. Default is `list()`.
#'
#' @examples
#' # Examples for cond_indirect_effects():
#'
#' # Create levels of w1, the moderators
#' w1levels <- mod_levels("w1", fit = fit)
#' w1levels
#'
#' # Conditional effects from x to m1 when w1 is equal to each of the levels
#' cond_indirect_effects(x = "x", y = "m1",
#' wlevels = w1levels, fit = fit)
#'
#' # Conditional Indirect effect from x1 through m1 to y,
#' # when w1 is equal to each of the levels
#' cond_indirect_effects(x = "x", y = "y", m = "m1",
#' wlevels = w1levels, fit = fit)
#'
#' # Multigroup models for cond_indirect_effects()
#'
#' dat <- data_med_mg
#' mod <-
#' "
#' m ~ x + c1 + c2
#' y ~ m + x + c1 + c2
#' "
#' fit <- sem(mod, dat, meanstructure = TRUE, fixed.x = FALSE, se = "none", baseline = FALSE,
#' group = "group")
#'
#' # If a model has more than one group,
#' # it will be used as a 'moderator'.
#' cond_indirect_effects(x = "x", y = "y", m = "m",
#' fit = fit)
#'
#' @export
#'
#' @describeIn cond_indirect Compute the
#' conditional effects or conditional
#' indirect effects for several sets of
#' levels of the moderator(s).
#'
#' @order 2
cond_indirect_effects <- function(wlevels,
x,
y,
m = NULL,
fit = NULL,
w_type = "auto",
w_method = "sd",
sd_from_mean = NULL,
percentiles = NULL,
est = NULL,
implied_stats = NULL,
boot_ci = FALSE,
R = 100,
seed = NULL,
parallel = TRUE,
ncores = max(parallel::detectCores(logical = FALSE) - 1, 1),
make_cluster_args = list(),
progress = TRUE,
boot_out = NULL,
output_type = "data.frame",
mod_levels_list_args = list(),
mc_ci = FALSE,
mc_out = NULL,
ci_out = NULL,
ci_type = NULL,
boot_type = c("perc", "bc"),
groups = NULL,
...) {
fit <- auto_lm2list(fit)
boot_type <- match.arg(boot_type)
# Check the number of groups and handle multiple-group models
has_group <- FALSE
ngroups <- 1
group_numbers <- NULL
group_labels <- NULL
if (inherits(fit, "lavaan")) {
ngroups <- lavaan::lavTech(fit, "ngroups")
if (ngroups > 1) {
has_group <- TRUE
tmp <- group_labels_and_numbers(groups = groups,
fit = fit)
group_numbers <- tmp$number
group_labels <- tmp$label
} else {
if (!is.null(groups)) {
stop("The model has only one group but 'groups' is set.")
}
}
}
# Check and process the levels of moderators
has_wlevels <- FALSE
if (!missing(wlevels)) {
has_wlevels <- TRUE
wlevels_check <- check_wlevels(wlevels)
if (!is.null(wlevels_check)) {
wlevels <- wlevels_check
} else {
# Call mod_levels_list
# Case 1: A character vector
# Case 2: A list of character vectors
mod_levels_list_args_final <-
utils::modifyList(mod_levels_list_args,
list(w_type = w_type,
w_method = w_method,
sd_from_mean = sd_from_mean,
percentiles = percentiles,
fit = fit,
merge = TRUE))
wlevels <- do.call(mod_levels_list,
args = c(as.list(wlevels),
mod_levels_list_args_final))
}
} else {
wlevels <- NULL
if (!has_group) {
stop("wlevels is required for single-group models.")
}
}
if (has_group && has_wlevels) {
stop("Multiple group models with moderators not yet supported.",
"Will be supported soon.")
}
if (has_wlevels) {
k <- nrow(wlevels)
wlevels1 <- split(wlevels, seq_len(k))
wlevels2 <- lapply(wlevels1, unlist)
names(wlevels2) <- rownames(wlevels)
}
fit_type <- cond_indirect_check_fit(fit)
if ((fit_type == "lm") && !inherits(fit, "lm_list") &&
is.list(fit)) {
fit <- lm2list(fit)
}
# Fix arguments
call_args <- names(match.call())
if (!is.null(ci_type)) {
if (ci_type == "mc") {
mc_ci <- TRUE
boot_ci <- FALSE
if (is.null(mc_out) && !is.null(ci_out)) {
mc_out <- ci_out
ci_out <- NULL
}
}
if (ci_type == "boot") {
boot_ci <- TRUE
mc_ci <- FALSE
if (is.null(boot_out) && !is.null(ci_out)) {
boot_out <- ci_out
ci_out <- NULL
}
}
}
if (all(boot_ci, mc_ci)) stop("Can only request one type of confidence intervals.")
if (mc_ci) {
if (!is.null(mc_out)) {
if (inherits(mc_out, "cond_indirect_effects")) {
mc_out <- attr(mc_out, "mc_out")
if (is.null(mc_out)) {
stop("mc_out not found in the supplied object for 'mc_out'")
}
}
if (inherits(mc_out, "indirect")) {
mc_out <- mc_out$mc_out
if (is.null(mc_out)) {
stop("mc_out not found in the supplied object for 'mc_out'")
}
}
if (!inherits(mc_out, "mc_out")) {
stop("The object at 'mc_out' must be of the class 'mc_out'.")
}
} else {
mc_out <- do_mc(fit = fit,
R = R,
seed = seed)
}
}
if (boot_ci) {
if (!is.null(boot_out)) {
if (inherits(boot_out, "cond_indirect_effects")) {
boot_out <- attr(boot_out, "boot_out")
if (is.null(boot_out)) {
stop("boot_out not found in the supplied object for 'boot_out'")
}
}
if (inherits(boot_out, "indirect")) {
boot_out <- boot_out$boot_out
if (is.null(boot_out)) {
stop("boot_out not found in the supplied object for 'boot_out'")
}
}
if (!inherits(boot_out, "boot_out")) {
stop("The object at 'boot_out' must be of the class 'boot_out'.")
}
} else {
boot_out <- do_boot(fit = fit,
R = R,
seed = seed,
parallel = parallel,
ncores = ncores,
make_cluster_args = make_cluster_args,
progress = progress)
}
}
# TODO:
# Revise cond_indirect and friends such that
# no need to have three very similar blocks.
if (has_wlevels && !has_group) {
prods <- cond_indirect(wvalues = wlevels2[[1]],
x = x,
y = y,
m = m,
fit = fit,
est = est,
implied_stats = implied_stats,
get_prods_only = TRUE,
...)
}
if (!has_wlevels && has_group) {
prods <- cond_indirect(x = x,
y = y,
m = m,
fit = fit,
est = est,
implied_stats = implied_stats,
get_prods_only = TRUE,
group = 1,
...)
}
if (has_wlevels && has_group) {
prods <- cond_indirect(wvalues = wlevels2[[1]],
x = x,
y = y,
m = m,
fit = fit,
est = est,
implied_stats = implied_stats,
get_prods_only = TRUE,
group = 1,
...)
}
# TODO:
# Revise cond_indirect and friends such that
# no need to have three very similar blocks.
if (has_wlevels && !has_group) {
out <- lapply(wlevels2,
function(wv,
x,
y,
m,
fit,
est,
implied_stats,
boot_ci,
boot_out,
R,
seed,
prods,
save_boot_out,
mc_ci,
mc_out,
save_mc_out,
ci_type,
ci_out,
save_ci_out,
boot_type,
...) {
cond_indirect(wvalues = wv,
x = x,
y = y,
m = m,
fit = fit,
est = est,
implied_stats = implied_stats,
boot_ci = boot_ci,
boot_out = boot_out,
R = R,
seed = seed,
prods = prods,
save_boot_out = FALSE,
mc_ci = mc_ci,
mc_out = mc_out,
save_mc_out = FALSE,
ci_type = ci_type,
ci_out = ci_out,
save_ci_out = FALSE,
boot_type = boot_type,
...)
},
x = x,
y = y,
m = m,
fit = fit,
est = est,
implied_stats = implied_stats,
boot_ci = boot_ci,
boot_out = boot_out,
R = R,
seed = seed,
prods = prods,
save_boot_out = FALSE,
mc_ci = mc_ci,
mc_out = mc_out,
save_mc_out = FALSE,
ci_type = ci_type,
ci_out = ci_out,
save_ci_out = FALSE,
boot_type = boot_type,
...)
}
if (!has_wlevels && has_group) {
out <- lapply(group_numbers,
function(gn,
x,
y,
m,
fit,
est,
implied_stats,
boot_ci,
boot_out,
R,
seed,
prods,
save_boot_out,
mc_ci,
mc_out,
save_mc_out,
ci_type,
ci_out,
save_ci_out,
boot_type,
...) {
indirect_effect(x = x,
y = y,
m = m,
fit = fit,
est = est,
implied_stats = implied_stats,
boot_ci = boot_ci,
boot_out = boot_out,
R = R,
seed = seed,
save_boot_out = FALSE,
mc_ci = mc_ci,
mc_out = mc_out,
save_mc_out = FALSE,
ci_type = ci_type,
ci_out = ci_out,
save_ci_out = FALSE,
boot_type = boot_type,
group = gn,
...)
},
x = x,
y = y,
m = m,
fit = fit,
est = est,
implied_stats = implied_stats,
boot_ci = boot_ci,
boot_out = boot_out,
R = R,
seed = seed,
save_boot_out = FALSE,
mc_ci = mc_ci,
mc_out = mc_out,
save_mc_out = FALSE,
ci_type = ci_type,
ci_out = ci_out,
save_ci_out = FALSE,
boot_type = boot_type,
...)
}
if (has_wlevels && has_group) {
# TODO
# - Not yet supported.
# - Need to use expand.grid to create
# all combinations of group and wlevels
group_numbers_long <- NULL
wlevels2_long <- NULL
out <- mapply(function(gn,
wv,
x,
y,
m,
fit,
est,
implied_stats,
boot_ci,
boot_out,
R,
seed,
prods,
save_boot_out,
mc_ci,
mc_out,
save_mc_out,
ci_type,
ci_out,
save_ci_out,
boot_type,
...) {
cond_indirect(wvalues = wv,
x = x,
y = y,
m = m,
fit = fit,
est = est,
implied_stats = implied_stats,
boot_ci = boot_ci,
boot_out = boot_out,
R = R,
seed = seed,
prods = prods,
save_boot_out = FALSE,
mc_ci = mc_ci,
mc_out = mc_out,
save_mc_out = FALSE,
ci_type = ci_type,
ci_out = ci_out,
save_ci_out = FALSE,
boot_type = boot_type,
group = gn,
...)
},
gn = group_numbers_long,
wv = wlevels2_long,
x = x,
y = y,
m = m,
fit = fit,
est = est,
implied_stats = implied_stats,
boot_ci = boot_ci,
boot_out = boot_out,
R = R,
seed = seed,
prods = prods,
save_boot_out = FALSE,
mc_ci = mc_ci,
mc_out = mc_out,
save_mc_out = FALSE,
ci_type = ci_type,
ci_out = ci_out,
save_ci_out = FALSE,
boot_type = boot_type,
...)
}
if (output_type == "data.frame") {
out1 <- cond_indirect_effects_to_df(out,
wlevels = wlevels,
group_numbers = group_numbers,
group_labels = group_labels)
class(out1) <- c("cond_indirect_effects", class(out1))
attr(out1, "call") <- match.call()
attr(out1, "full_output") <- out
attr(out1, "wlevels") <- wlevels
attr(out1, "fit") <- fit
attr(out1, "est") <- est
attr(out1, "implied_stats") <- implied_stats
if (boot_ci) attr(out1, "boot_out") <- boot_out
if (mc_ci) attr(out1, "mc_out") <- mc_out
attr(out1, "prods") <- prods
attr(out1, "x") <- x
attr(out1, "y") <- y
attr(out1, "m") <- m
# TODO:
# - Store the expanded combination of group and wlevels
return(out1)
} else {
return(out)
}
}
#' @describeIn cond_indirect Just
#' an alias to [cond_indirect_effects()],
#' a better name when a path has no
#' moderator.
#'
#' @export
#' @order 4
cond_effects <- cond_indirect_effects
#' @param paths The output of [all_indirect_paths()]
#'
#' @param ... For [many_indirect_effects()],
#' these are arguments to be passed to
#' [indirect_effect()].
#'
#' @examples
#'
#' # Examples for many_indirect_effects():
#'
#' library(lavaan)
#' data(data_serial_parallel)
#' mod <-
#' "
#' m11 ~ x + c1 + c2
#' m12 ~ m11 + x + c1 + c2
#' m2 ~ x + c1 + c2
#' y ~ m12 + m2 + m11 + x + c1 + c2
#' "
#' fit <- sem(mod, data_serial_parallel,
#' fixed.x = FALSE)
#' # All indirect paths from x to y
#' paths <- all_indirect_paths(fit,
#' x = "x",
#' y = "y")
#' paths
#' # Indirect effect estimates
#' out <- many_indirect_effects(paths,
#' fit = fit)
#' out
#'
#' # Multigroup models for many_indirect_effects()
#'
#' data(data_med_complicated_mg)
#' mod <-
#' "
#' m11 ~ x1 + x2 + c1 + c2
#' m12 ~ m11 + c1 + c2
#' m2 ~ x1 + x2 + c1 + c2
#' y1 ~ m11 + m12 + x1 + x2 + c1 + c2
#' y2 ~ m2 + x1 + x2 + c1 + c2
#' "
#' fit <- sem(mod, data_med_complicated_mg, group = "group")
#' summary(fit)
#'
#' paths <- all_indirect_paths(fit,
#' x = "x1",
#' y = "y1")
#' paths
#' # Indirect effect estimates for all paths in all groups
#' out <- many_indirect_effects(paths,
#' fit = fit)
#' out
#'
#' @export
#'
#' @describeIn cond_indirect Compute the
#' indirect effects along more than one paths.
#' It call [indirect_effect()] once for
#' each of the path.
#'
#' @order 5
many_indirect_effects <- function(paths, ...) {
path_names <- names(paths)
xym <- all_paths_to_df(paths)
args <- list(...)
if ((isTRUE(args$boot_ci) && is.null(args$boot_out)) ||
(isTRUE(args$mc_ci) && is.null(args$mc_out))) {
do_sim_once <- TRUE
} else {
do_sim_once <- FALSE
}
if ("group_label" %in% colnames(xym)) {
if (do_sim_once) {
args_tmp <- utils::modifyList(args,
x = xym$x[[1]],
y = xym$y[[1]],
m = xym$m[[1]],
group = xym$group_number[1])
sim_out <- do.call(indirect_effect, args_tmp)
if (isTRUE(args$boot_ci)) {
args_final <- utils::modifyList(args,
list(boot_out = sim_out))
}
if (isTRUE(args$mc_ci)) {
args_final <- utils::modifyList(args,
list(mc_out = sim_out))
}
} else {
args_final <- args
}
out <- mapply(indirect_effect,
x = xym$x,
y = xym$y,
m = xym$m,
group = xym$group_number,
MoreArgs = args_final,
SIMPLIFY = FALSE)
} else {
if (do_sim_once) {
args_tmp <- utils::modifyList(args,
list(x = xym$x[[1]],
y = xym$y[[1]],
m = xym$m[[1]]))
sim_out <- do.call(indirect_effect, args_tmp)
if (isTRUE(args$boot_ci)) {
args_final <- utils::modifyList(args,
list(boot_out = sim_out))
}
if (isTRUE(args$mc_ci)) {
args_final <- utils::modifyList(args,
list(mc_out = sim_out))
}
} else {
args_final <- args
}
out <- mapply(indirect_effect,
x = xym$x,
y = xym$y,
m = xym$m,
MoreArgs = args_final,
SIMPLIFY = FALSE)
}
names(out) <- path_names
class(out) <- c("indirect_list", class(out))
attr(out, "paths") <- paths
attr(out, "call") <- match.call()
out
}
# Check the type of `fit` and return the type as a string
#' @noRd
#'
cond_indirect_check_fit <- function(fit) {
fit_type <- NA
if (inherits(fit, "lavaan")) {
fit_type <- "lavaan"
}
if (inherits(fit, "list")) {
tmp <- sapply(fit, inherits, what = "lm")
if (isTRUE(all(tmp))) {
fit_type <- "lm"
} else {
stop("'fit' is a list but not all the elements are lm outputs.")
}
}
if (inherits(fit, "lavaan.mi")) {
fit_type <- "lavaan.mi"
}
if (is.na(fit_type)) {
stop("'fit' is neither a lavaan object or a list of lm outputs.")
}
fit_type
}
# Convert a list of `indirect`-class objects to a data frame, with
# information on the levels of moderators.
#' @noRd
cond_indirect_effects_to_df <- function(x,
wlevels = NULL,
group_numbers = NULL,
group_labels = NULL) {
has_wlevels <- !is.null(wlevels)
has_group <- !is.null(group_numbers) || !is.null(group_labels)
if (has_wlevels) {
# TOFIX
wlevels_label <- attr(wlevels, "wlevels")
colnames(wlevels_label) <- paste0("[", colnames(wlevels_label), "]")
wlevels2 <- wlevels
colnames(wlevels2) <- paste0("(", colnames(wlevels2), ")")
}
if (has_group) {
group_numbers <- sapply(x, function(x) x$group_number)
group_labels <- sapply(x, function(x) x$group_label)
gp_df <- data.frame(Group = group_labels,
Group_ID = group_numbers)
}
standardized_x <- x[[1]]$standardized_x
standardized_y <- x[[1]]$standardized_y
if (standardized_x || standardized_y) {
standardized_any <- TRUE
} else {
standardized_any <- FALSE
}
indirect <- data.frame(ind = sapply(x,
function(x) {x$indirect_raw}))
if (standardized_x || standardized_y) {
indirect_std <- sapply(x, function(x) x$indirect)
} else {
indirect_std <- NULL
}
cc <- do.call(rbind, sapply(x, function(x) {x$components_conditional},
simplify = FALSE))
has_ci <- FALSE
if (!is.null(x[[1]]$boot_ci)) {
has_ci <- TRUE
ci_type <- "boot"
ci_cname <- "boot_ci"
}
if (!is.null(x[[1]]$mc_ci)) {
has_ci <- TRUE
ci_type <- "mc"
ci_cname <- "mc_ci"
}
if (has_ci) {
bc <- do.call(rbind,
sapply(x, function(x) {x[[ci_cname]]}, simplify = FALSE))
if (standardized_any) {
colnames(bc) <- paste0(c("CILo:", "CIHi:"), colnames(bc))
colnames(bc) <- c("CI.lo", "CI.hi")
} else {
colnames(bc) <- paste0(c("CIStdLo:", "CIStdHi:"), colnames(bc))
colnames(bc) <- c("CI.lo", "CI.hi")
}
}
if (is.null(indirect_std)) {
if (has_ci) {
out <- data.frame(ind = indirect, bc, cc, check.names = FALSE)
} else {
out <- data.frame(ind = indirect, cc, check.names = FALSE)
}
} else {
if (has_ci) {
out <- data.frame(std = indirect_std, bc, cc, ustd = indirect, check.names = FALSE)
} else {
out <- data.frame(std = indirect_std, cc, ustd = indirect, check.names = FALSE)
}
}
if (has_wlevels) {
out <- cbind(wlevels_label, wlevels2, out)
}
if (has_group) {
out <- cbind(gp_df, out)
}
out
}
# Check the argument `wlevels` and convert it to a valid data
# frame of wlevels if possible.
#' @noRd
check_wlevels <- function(ws) {
if (is.data.frame(ws)) {
# A data frame. Assumed to be merged levels
return(ws)
}
if (is.list(ws)) {
tmp <- sapply(ws, function(x) !is.null(attr(x, which = "wlevels")))
if (all(tmp)) {
# A list of wlevels. Merge them
out <- merge_mod_levels(ws)
return(out)
}
}
# Cannot convert to wlevels
return(NULL)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.