R/cond_indirect_diff.R

Defines functions confint.cond_indirect_diff coef.cond_indirect_diff print.cond_indirect_diff cond_indirect_diff

Documented in coef.cond_indirect_diff cond_indirect_diff confint.cond_indirect_diff print.cond_indirect_diff

#' @title Differences In Conditional
#' Indirect Effects
#'
#' @description Compute the difference
#' in conditional indirect effects
#' between two sets of levels of the
#' moderators.
#'
#' @details
#'
#' Ths function takes the output of
#' [cond_indirect_effects()] and
#' computes the difference in
#' conditional indirect effects between
#' any two rows, that is, between levels
#' of the moderator, or two sets of
#' levels of the moderators when the
#' path has more than one moderator.
#'
#' The difference is meaningful when the
#' difference between the two levels or
#' sets of levels are meaningful. For
#' example, if the two levels are the
#' mean of the moderator and one
#' standard deviation above mean of the
#' moderator, then this difference is
#' the change in indirect effect when
#' the moderator increases by one
#' standard deviation.
#'
#' If the two levels are 0 and 1, then
#' this difference is the index of
#' moderated mediation as proposed by
#' Hayes (2015). (This index can also be
#' computed directly by
#' [index_of_mome()], designed
#' specifically for this purpose.)
#'
#' The function can also compute the
#' change in the standardized indirect
#' effect between two levels of a
#' moderator or two sets of levels of
#' the moderators.
#'
#' This function is intended to be a
#' general purpose function that allows
#' users to compute the difference
#' between any two levels or sets of
#' levels that are meaningful in a
#' context.
#'
#' This function itself does not set the
#' levels of comparison. The levels to
#' be compared need to be set when
#' calling [cond_indirect_effects()].
#' This function extracts required
#' information from the output of
#' [cond_indirect_effects()].
#'
#' If bootstrap or Monte Carlo
#' estimates are available
#' in the input or bootstrap
#' or Monte Carlo confidence
#' intervals are requested in calling
#' [cond_indirect_effects()],
#' [cond_indirect_diff()] will also form
#' the bootstrap confidence
#' interval for the difference in
#' conditional indirect effects
#' using the stored estimates.
#'
#' If bootstrap confidence interval is
#' to be formed and both effects used
#' the same type of interval, then that
#' type will be used. Otherwise,
#' percentile confidence interval will
#' be formed.
#'
#' @return A `cond_indirect_diff`-class
#' object. This class has a `print`
#' method
#' ([print.cond_indirect_diff()]), a
#' `coef` method
#' ([coef.cond_indirect_diff()]), and a
#' `confint` method
#' ([confint.cond_indirect_diff()]).
#'
#'
#' @param output A
#' `cond_indirect_effects`-class object:
#' The output of
#' [cond_indirect_effects()].
#'
#' @param from A row number of `output`.
#'
#' @param to A row number of `output`.
#' The change in indirect effects is
#' computed by the change in the
#' level(s) of the moderator(s) from Row
#' `from` to Row `to`.
#'
#' @param level The level of confidence
#' for the confidence
#' interval. Default is .95.
#'
#'
#' @seealso [index_of_mome()] for
#' computing the index of moderated
#' mediation, [index_of_momome()] for
#' computing the index of moderated
#' moderated mediation,
#' [cond_indirect_effects()],
#' [mod_levels()], and
#' [merge_mod_levels()] for preparing
#' the levels to be compared.
#'
#' @references
#' Hayes, A. F. (2015). An index and test of linear moderated mediation.
#' *Multivariate Behavioral Research, 50*(1), 1-22.
#' \doi{10.1080/00273171.2014.962683}
#'
#' @examples
#'
#' library(lavaan)
#' dat <- modmed_x1m3w4y1
#' dat$xw1 <- dat$x * dat$w1
#' mod <-
#' "
#' m1 ~ a * x  + f * w1 + d * xw1
#' y  ~ b * m1 + cp * x
#' "
#' fit <- sem(mod, dat,
#'            meanstructure = TRUE, fixed.x = FALSE,
#'            se = "none", baseline = FALSE)
#' est <- parameterEstimates(fit)
#'
#' # Create levels of w1, the moderators
#' w1levels <- mod_levels("w1", fit = fit)
#' w1levels
#'
#' # Conditional effects from x to y when w1 is equal to each of the levels
#' boot_out <- fit2boot_out_do_boot(fit, R = 40, seed = 4314, progress = FALSE)
#' out <- cond_indirect_effects(x = "x", y = "y", m = "m1",
#'                              wlevels = w1levels, fit = fit,
#'                              boot_ci = TRUE, boot_out = boot_out)
#' out
#' out_ind <- cond_indirect_diff(out, from = 2, to = 1)
#' out_ind
#' coef(out_ind)
#' confint(out_ind)
#'
#'
#'
#' @export
#'
#' @describeIn cond_indirect_diff
#' Compute the difference in in
#' conditional indirect effect between
#' two rows in the output of
#' [cond_indirect_effects()].
#'
#' @order 1

cond_indirect_diff <- function(output,
                          from = NULL,
                          to = NULL,
                          level = .95) {
    has_wlevels <- cond_indirect_effects_has_wlevels(output)
    has_groups <- cond_indirect_effects_has_groups(output)
    if (has_wlevels && has_groups) {
        stop("Objects with both wlevels and groups not yet supported")
      }
    if (missing(output)) {
        stop("'output' is missing.")
      }
    if (!inherits(output, "cond_indirect_effects")) {
        stop("'output' is not a 'cond_indirect_effects'-class object.")
      }
    if (is.null(from) || is.null(to)) {
        stop("Either 'from' or 'to' (or both) is/are missing")
      }
    if ((level >= 1) || (level <= .50)) {
        stop("'level' must be greater than .50 and less than 1.")
      }
    output_full <- attr(output, "full_output")
    output_full_from <- output_full[[from]]
    output_full_to <- output_full[[to]]
    boot_i_from <- output_full_from$boot_indirect
    boot_i_to <- output_full_to$boot_indirect
    mc_i_from <- output_full_from$mc_indirect
    mc_i_to <- output_full_to$mc_indirect
    effect_diff <- stats::coef(output_full_to) - stats::coef(output_full_from)
    if (is.null(boot_i_from) || is.null(boot_i_to)) {
        has_boot <- FALSE
        boot_type <- NULL
      } else {
        has_boot <- TRUE
      }
    if (is.null(mc_i_from) || is.null(mc_i_to)) {
        has_mc <- FALSE
      } else {
        has_mc <- TRUE
      }
    if (all(has_mc, has_boot)) stop("Cannot form both Monte Carlo and bootstrapping confidence intervals.")
    if (has_mc) {
        mc_diff <- mc_i_to - mc_i_from
        mc_diff_ci <- boot_ci_internal(t0 = effect_diff,
                                       t = mc_diff,
                                       level = level,
                                       boot_type = "perc")
        mc_diff_se <- stats::sd(mc_diff, na.rm = TRUE)
      } else {
        mc_diff <- NA
        mc_diff_ci <- c(NA, NA)
        mc_diff_se <- NA
      }
    if (has_boot) {
        if (identical(output_full_from$boot_type,
                      output_full_to$boot_type)) {
            boot_type <- output_full_from$boot_type
          } else {
            boot_type <- "perc"
          }
        boot_diff <- boot_i_to - boot_i_from
        boot_diff_ci <- boot_ci_internal(t0 = effect_diff,
                                t = boot_diff,
                                level = level,
                                boot_type = boot_type)
        boot_diff_p <- est2p(boot_diff)
        boot_diff_se <- stats::sd(boot_diff, na.rm = TRUE)
      } else {
        boot_diff <- NA
        boot_diff_ci <- c(NA, NA)
        boot_diff_p <- NA
        boot_diff_se <- NA
      }
    if (has_wlevels && !has_groups) {
        wlevels <- attr(output, "wlevels")
        wlevels_from <- wlevels[from, , drop = FALSE]
        wlevels_to <- wlevels[to, , drop = FALSE]
        final_from <- wlevels_from
        final_to <- wlevels_to
      }
    if (!has_wlevels && has_groups) {
        group_labels <- output$Group
        group_numbers <- output$Group_ID
        group_from <- paste0(group_labels[from], " [",
                             group_numbers[from], "]")
        group_to <- paste0(group_labels[to], " [",
                           group_numbers[to], "]")
        names(group_from) <- "Group"
        names(group_to) <- "Group"
        final_from <- group_from
        final_to <- group_to
      }
    if (has_wlevels && has_groups) {
        # TODO:
        # - Add support for objects with both wlevels and groups.
      }
    out_diff_ci <- c(NA, NA)
    out_diff_se <- NA
    if (has_mc) out_diff_ci <- mc_diff_ci
    if (has_boot) out_diff_ci <- boot_diff_ci
    if (has_mc) out_diff_se <- mc_diff_se
    if (has_boot) out_diff_se <- boot_diff_se
    ci_type <- NA
    if (has_mc) ci_type <- "mc"
    if (has_boot) ci_type <- "boot"
    out <- list(index = effect_diff,
                ci = out_diff_ci,
                pvalue = boot_diff_p,
                se = out_diff_se,
                level = level,
                from = final_from,
                to = final_to,
                output = output[c(to, from), ],
                boot_diff = boot_diff,
                mc_diff = mc_diff,
                has_groups = has_groups,
                has_wlevels = has_wlevels,
                boot_type = boot_type,
                ci_type = ci_type)
    class(out) <- c("cond_indirect_diff", class(out))
    out
  }

#' @title Print the Output of
#' 'cond_indirect_diff'
#'
#' @description Print the output of
#' [cond_indirect_diff()].
#'
#' @details The `print` method of the
#' `cond_indirect_diff`-class object.
#'
#' If bootstrapping confidence interval
#' was requested, this method has the
#' option to print a
#' *p*-value computed by the
#' method presented in Asparouhov and Muthén (2021).
#' Note that this *p*-value is asymmetric
#' bootstrap *p*-value based on the
#' distribution of the bootstrap estimates.
#' It is not computed based on the
#' distribution under the null hypothesis.
#'
#' For a *p*-value of *a*, it means that
#' a 100(1 - *a*)% bootstrapping confidence
#' interval
#' will have one of its limits equal to
#' 0. A confidence interval
#' with a higher confidence level will
#' include zero, while a confidence
#' interval with a lower confidence level
#' will exclude zero.
#'
#' @return It returns `x` invisibly.
#' Called for its side effect.
#'
#' @param x The output of
#' [cond_indirect_diff()].
#'
#' @param digits The number of decimal
#' places in the printout.
#'
#' @param pvalue Logical. If `TRUE`,
#' asymmetric *p*-value based on
#' bootstrapping will be printed if
#' available. Default is `FALSE.`
#'
#' @param pvalue_digits Number of decimal
#' places to display for the *p*-value.
#' Default is 3.
#'
#' @param se Logical. If `TRUE` and
#' confidence intervals are available,
#' the standard errors of the estimates
#' are also printed. They are simply the
#' standard deviations of the bootstrap
#' estimates or Monte Carlo simulated
#' values, depending on the method used
#' to form the confidence intervals.
#'
#' @param ... Optional arguments.
#' Ignored.
#'
#'
#' @references
#' Asparouhov, A., & Muthén, B. (2021). Bootstrap p-value computation.
#' Retrieved from https://www.statmodel.com/download/FAQ-Bootstrap%20-%20Pvalue.pdf
#'
#' @seealso [cond_indirect_diff()]
#'
#' @export

print.cond_indirect_diff <- function(x,
                                     digits = 3,
                                     pvalue = FALSE,
                                     pvalue_digits = 3,
                                     se = FALSE,
                                     ...) {
    full_output_attr <- attr(x$output, "full_output")[[1]]
    has_groups <- is.numeric(full_output_attr$group_number)
    has_wlevels <- !is.null(full_output_attr$wvalues)
    print(x$output, digits = digits, annotation = FALSE, ...)
    x_type <- x$type
    if (!is.null(x_type)) {
        tmp <- switch(x_type,
            index_of_mome = "\n== Index of Moderated Mediation ==",
            index_of_momome = "\n== Index of Moderated Moderated Mediation =="
          )
      } else {
        tmp <- "\n== Difference in Conditional Indirect Effect =="
      }
    cat(tmp)
    cat("\n")
    xto0 <- sapply(x$to, function(xx) {
                    ifelse(is.numeric(xx),
                           formatC(xx, digits = digits, format = "f"),
                           xx)
                  })
    xfrom0 <- sapply(x$from, function(xx) {
                    ifelse(is.numeric(xx),
                           formatC(xx, digits = digits, format = "f"),
                           xx)
                  })
    tofrom <- rbind(xto0, xfrom0)
    rownames(tofrom) <- paste0(c("To:   ", "From: "),
                               c(rownames(x$to), rownames(x$from)))
    if (!is.null(x_type)) {
        if (x_type == "index_of_mome") {
            cat("\n")
            cat("Levels compared: Row 1 - Row 2")
            cat("\n")
          }
        if (x_type == "index_of_momome") {
            cat("\n")
            cat("Levels compared:\n")
            cat("(Row 1 - Row 2) - (Row 3 - Row 4)")
            cat("\n")
          }
      } else {
        cat("\nLevels: \n")
        print(tofrom, quote = FALSE)
        if (!has_wlevels && has_groups) {
            tmp <- paste0("Levels compared: ",
                          xto0,
                          " - ",
                          xfrom0)
          } else {
            tmp <- "Levels compared: Row 1 - Row 2"
          }
        cat("\n", tmp, "\n", sep = "")
      }
    index_df <- data.frame(x = full_output_attr$x,
                           y = full_output_attr$y,
                           Change = formatC(x$index, digits = digits, format = "f"))
    has_ci <- !all(is.na(x$ci))
    ci_type <- x$ci_type
    if (has_ci) {
        index_df$CI.lo <- formatC(x$ci[1], digits = digits, format = "f")
        index_df$CI.hi <- formatC(x$ci[2], digits = digits, format = "f")
        if (!identical(NA, x$boot_diff) && !is.na(x$pvalue) &&
            pvalue) {
            index_df$pvalue <- formatC(x$pvalue,
                                       digits = pvalue_digits,
                                       format = "f")
          }
        if ((!identical(NA, x$boot_diff) || !identical(NA, x$mc_diff)) &&
            !is.na(x$se) &&
            se) {
            index_df$SE <- formatC(x$se,
                                   digits = digits,
                                   format = "f")
          }
      }
    if (!is.null(x_type)) {
        rownames(index_df) <- "Index"
        colnames(index_df)[colnames(index_df) %in% "Change"] <- "Index"
      } else {
        rownames(index_df) <- "Change"
      }
    if (!is.null(x_type)) {
        tmp <- "\n"
      } else {
        tmp <- "\nChange in Indirect Effect:\n\n"
      }
    cat(tmp)
    print(index_df, nd = digits)
    cat("\n ")
    if (has_ci) {
        boot_type <- x$boot_type
        tmp <- switch(ci_type,
                      mc = "Monte Carlo",
                      boot = switch(boot_type,
                                    perc = "percentile",
                                    bc = "bias-corrected"))
        cat(strwrap(paste0("- [CI.lo, CI.hi]: ",
                          x$level * 100,
                          "% ", tmp, " confidence interval."), exdent = 3),
                          sep = "\n")
        if (!identical(NA, x$boot_diff) && !is.na(x$pvalue) &&
            pvalue) {
            cat(" - P-value is asymmetric bootstrap p-value.\n", sep = "")
          }
        if ((!identical(NA, x$boot_diff) || !identical(NA, x$mc_diff)) &&
            !is.na(x$se) &&
            se) {
            cat(" - [SE]: Standard error.\n", sep = "")
          }
      }
    if (full_output_attr$standardized_x) {
        cat(" - ", full_output_attr$x, " standardized.\n", sep = "")
      }
    if (full_output_attr$standardized_y) {
        cat(" - ", full_output_attr$y, " standardized.\n", sep = "")
      }
    cat("\n")
    invisible(x)
  }

#' @title Print the Output of
#' 'cond_indirect_diff()'
#'
#' @description Extract the change in
#' conditional indirect effect.
#'
#' @details The `coef` method of the
#' `cond_indirect_diff`-class object.
#'
#' @return Scalar: The change of
#' conditional indirect effect in
#' `object`.
#'
#' @param object The output of
#' [cond_indirect_diff()].
#'
#' @param ... Optional arguments.
#' Ignored.
#'
#' @seealso [cond_indirect_diff()]
#'
#' @export

coef.cond_indirect_diff <- function(object, ...) {
    full_output_attr <- attr(object$output, "full_output")[[1]]
    out <- object$index
    names(out) <- paste0(full_output_attr$y,
                         "~",
                         paste0(rev(full_output_attr$m), collapse = "~"),
                         "~",
                         full_output_attr$x)
    out
  }


#' @title Confidence Interval of the
#' Output of 'cond_indirect_diff()'
#'
#' @description Extract the confidence
#' interval the output of
#' [cond_indirect_diff()].
#'
#' @details The `confint` method of the
#' `cond_indirect_diff`-class object.
#'
#' The type of confidence intervals
#' depends on the call used to
#' create the object. This function
#' merely extracts the stored
#' confidence intervals.
#'
#' @return A one-row-two-column data
#' frame of the confidence limits. If
#' confidence interval is not available,
#' the limits are `NA`s.
#'
#' @param object The output of
#' [cond_indirect_diff()].
#'
#' @param parm Ignored.
#'
#' @param level The level of confidence
#' for the confidence
#' interval. Default is .95. Must match
#' the level of the stored confidence
#' interval.
#'
#' @param ... Optional arguments.
#' Ignored.
#'
#' @export

confint.cond_indirect_diff<- function(object, parm, level = .95, ...) {
    if (object$level != level) {
        stop("Requested level does not match stored level.")
      }
    full_output_attr <- attr(object$output, "full_output")[[1]]
    out <- data.frame(as.list(object$ci), check.names = FALSE)
    if (all(is.na(out))) out <- data.frame(ci.lower = NA, ci.upper = NA)
    rownames(out) <- paste0(full_output_attr$y,
                         "~",
                         paste0(rev(full_output_attr$m), collapse = "~"),
                         "~",
                         full_output_attr$x)
    out
  }

Try the manymome package in your browser

Any scripts or data that you put into this service are public.

manymome documentation built on Oct. 4, 2024, 5:10 p.m.