| Title: | Infrastructure for Mediation Model Fitting and Extraction |
|---|---|
| Description: | Provides S7-based infrastructure for fitting mediation models, extracting path coefficients, and performing bootstrap inference. Designed as a foundation package for the mediation analysis ecosystem, supporting 'probmed', 'RMediation', and 'medrobust' packages. Implements unified interfaces for model fitting across different engines (generalized linear models, linear mixed models for cluster-randomized designs, and an experimental maximum-likelihood structural equation engine; Bayesian methods are planned), standardized extraction of mediation paths from various model types, and robust bootstrap inference methods. Mediation methods are described in MacKinnon, Lockwood and Williams (2004) <doi:10.1207/s15327906mbr3901_4>, Preacher and Hayes (2008) <doi:10.3758/brm.40.3.879>, Tofighi and MacKinnon (2011) <doi:10.3758/s13428-011-0076-x>, and VanderWeele (2014) <doi:10.1097/EDE.0000000000000121>. |
| Authors: | Davood Tofighi [aut, cre, cph] (ORCID: <https://orcid.org/0000-0001-8523-7776>) |
| Maintainer: | Davood Tofighi <[email protected]> |
| License: | GPL (>= 3) |
| Version: | 0.10.0 |
| Built: | 2026-10-14 18:51:18 UTC |
| Source: | https://github.com/Data-Wise/medfit |
Conduct bootstrap inference to compute confidence intervals for mediation statistics. Supports parametric, nonparametric, and plugin methods.
bootstrap_mediation( statistic_fn, method = c("parametric", "nonparametric", "plugin"), mediation_data = NULL, data = NULL, n_boot = 1000L, ci_level = 0.95, parallel = FALSE, ncores = NULL, seed = NULL, cluster = NULL, ... )bootstrap_mediation( statistic_fn, method = c("parametric", "nonparametric", "plugin"), mediation_data = NULL, data = NULL, n_boot = 1000L, ci_level = 0.95, parallel = FALSE, ncores = NULL, seed = NULL, cluster = NULL, ... )
statistic_fn |
Function that computes the statistic of interest.
|
method |
Character string: bootstrap method. Options:
|
mediation_data |
A mediation data object (required for parametric/plugin):
MediationData, SerialMediationData, ParallelMediationData,
InteractionMediationData, or JointMediationData. |
data |
Data frame (required for nonparametric bootstrap) |
n_boot |
Integer: number of bootstrap samples (default: 1000) |
ci_level |
Numeric: confidence level between 0 and 1 (default: 0.95) |
parallel |
Logical: use parallel processing? (default: FALSE) |
ncores |
Integer: number of cores for parallel processing.
If NULL, uses |
seed |
Integer: random seed for reproducibility (optional but recommended) |
cluster |
Character string: name of a cluster variable in |
... |
Additional arguments (reserved for future use) |
Parametric Bootstrap (method = "parametric"):
Samples parameter vectors from
Fast and efficient
Assumes asymptotic normality of parameters
Recommended for most applications with n > 50
Requires mediation_data argument
Nonparametric Bootstrap (method = "nonparametric"):
Resamples observations with replacement
Refits models for each bootstrap sample
More robust, no normality assumption
Computationally intensive
Use when normality is questionable or n is small
Requires data argument
Plugin Estimator (method = "plugin"):
Computes point estimate only
No confidence interval
Fastest method
Use for quick checks or when CI not needed
Requires mediation_data argument
The statistic_fn should be a function that:
For parametric/plugin: Takes a named numeric vector of parameters
For nonparametric: Takes a data frame
Returns a single numeric value
Common statistic functions for indirect effect:
# Using parameter names from MediationData
indirect_fn <- function(theta) {
theta["m_X"] * theta["y_M"]
}
Set parallel = TRUE to use multiple cores:
Uses parallel::mclapply() on Unix systems
Falls back to sequential on Windows
Automatically detects available cores
Always set a seed for reproducible results:
bootstrap_mediation(..., seed = 12345)
A BootstrapResult object containing:
Point estimate
Percentile confidence interval bounds (the and
quantiles of the bootstrap distribution)
Bootstrap distribution (for parametric and nonparametric)
Method used
BootstrapResult, MediationData, fit_mediation()
# Fit mediation model med_data <- fit_mediation( formula_y = outcome ~ treatment + mediator1 + covariate1 + covariate2, formula_m = mediator1 ~ treatment + covariate1 + covariate2, data = mediation_demo, treatment = "treatment", mediator = "mediator1" ) # Define indirect effect function indirect_fn <- function(theta) theta["m_treatment"] * theta["y_mediator1"] # Plugin estimator (point estimate only, fastest) result_plugin <- bootstrap_mediation( statistic_fn = indirect_fn, method = "plugin", mediation_data = med_data ) print(result_plugin) # Parametric bootstrap (recommended for most applications) result <- bootstrap_mediation( statistic_fn = indirect_fn, method = "parametric", mediation_data = med_data, n_boot = 1000, ci_level = 0.95, seed = 12345 ) print(result) # Nonparametric bootstrap (slower but more robust) refit_fn <- function(boot_data) { fit_m <- lm(mediator1 ~ treatment + covariate1 + covariate2, data = boot_data) fit_y <- lm(outcome ~ treatment + mediator1 + covariate1 + covariate2, data = boot_data) unname(coef(fit_m)["treatment"] * coef(fit_y)["mediator1"]) } result_np <- bootstrap_mediation( statistic_fn = refit_fn, method = "nonparametric", data = mediation_demo, n_boot = 500, seed = 12345 ) print(result_np) if (requireNamespace("lme4", quietly = TRUE)) { # Treatment assigned to whole clusters (needs lme4); formulas hold fixed # effects only, the engine adds the random cluster intercept set.seed(1) J <- 30 id <- rep(seq_len(J), each = 6) cdat <- data.frame(school = factor(id), X = sample(rep(0:1, J / 2))[id]) cdat$M <- 0.5 * cdat$X + rnorm(J, sd = 0.5)[id] + rnorm(J * 6) cdat$Y <- 0.2 * cdat$X + 0.4 * cdat$M + rnorm(J, sd = 0.5)[id] + rnorm(J * 6) # Resample whole clusters. A refit that is singular or does not converge is # dropped, and the warning counts how many were nie_of <- function(d) { unname(nie(fit_mediation(Y ~ X + M, M ~ X, data = d, treatment = "X", mediator = "M", engine = "lmer", cluster = "school"))) } result_cluster <- bootstrap_mediation( nie_of, method = "nonparametric", data = cdat, cluster = "school", n_boot = 20, seed = 1 ) print(result_cluster) }# Fit mediation model med_data <- fit_mediation( formula_y = outcome ~ treatment + mediator1 + covariate1 + covariate2, formula_m = mediator1 ~ treatment + covariate1 + covariate2, data = mediation_demo, treatment = "treatment", mediator = "mediator1" ) # Define indirect effect function indirect_fn <- function(theta) theta["m_treatment"] * theta["y_mediator1"] # Plugin estimator (point estimate only, fastest) result_plugin <- bootstrap_mediation( statistic_fn = indirect_fn, method = "plugin", mediation_data = med_data ) print(result_plugin) # Parametric bootstrap (recommended for most applications) result <- bootstrap_mediation( statistic_fn = indirect_fn, method = "parametric", mediation_data = med_data, n_boot = 1000, ci_level = 0.95, seed = 12345 ) print(result) # Nonparametric bootstrap (slower but more robust) refit_fn <- function(boot_data) { fit_m <- lm(mediator1 ~ treatment + covariate1 + covariate2, data = boot_data) fit_y <- lm(outcome ~ treatment + mediator1 + covariate1 + covariate2, data = boot_data) unname(coef(fit_m)["treatment"] * coef(fit_y)["mediator1"]) } result_np <- bootstrap_mediation( statistic_fn = refit_fn, method = "nonparametric", data = mediation_demo, n_boot = 500, seed = 12345 ) print(result_np) if (requireNamespace("lme4", quietly = TRUE)) { # Treatment assigned to whole clusters (needs lme4); formulas hold fixed # effects only, the engine adds the random cluster intercept set.seed(1) J <- 30 id <- rep(seq_len(J), each = 6) cdat <- data.frame(school = factor(id), X = sample(rep(0:1, J / 2))[id]) cdat$M <- 0.5 * cdat$X + rnorm(J, sd = 0.5)[id] + rnorm(J * 6) cdat$Y <- 0.2 * cdat$X + 0.4 * cdat$M + rnorm(J, sd = 0.5)[id] + rnorm(J * 6) # Resample whole clusters. A refit that is singular or does not converge is # dropped, and the warning counts how many were nie_of <- function(d) { unname(nie(fit_mediation(Y ~ X + M, M ~ X, data = d, treatment = "X", mediator = "M", engine = "lmer", cluster = "school"))) } result_cluster <- bootstrap_mediation( nie_of, method = "nonparametric", data = cdat, cluster = "school", n_boot = 20, seed = 1 ) print(result_cluster) }
S7 class containing results from bootstrap inference, including point estimates, confidence intervals, and bootstrap distribution.
BootstrapResult(estimate, ci_lower, ci_upper, ci_level, boot_estimates, n_boot, method, call)BootstrapResult(estimate, ci_lower, ci_upper, ci_level, boot_estimates, n_boot, method, call)
estimate |
Numeric scalar: point estimate of the statistic |
ci_lower |
Numeric scalar: lower bound of confidence interval |
ci_upper |
Numeric scalar: upper bound of confidence interval |
ci_level |
Numeric scalar: confidence level (e.g., 0.95 for 95% CI) |
boot_estimates |
Numeric vector: bootstrap distribution of estimates |
n_boot |
Integer scalar: number of bootstrap samples |
method |
Character scalar: bootstrap method ("parametric", "nonparametric", or "plugin") |
call |
Call object or NULL: original function call |
This class standardizes bootstrap inference results across different bootstrap methods (parametric, nonparametric, plugin).
The class includes validation to ensure consistency between method type and required fields.
A BootstrapResult S7 object
# Parametric bootstrap result result <- BootstrapResult( estimate = 0.15, ci_lower = 0.10, ci_upper = 0.20, ci_level = 0.95, boot_estimates = rnorm(1000, 0.15, 0.02), n_boot = 1000L, method = "parametric", call = NULL )# Parametric bootstrap result result <- BootstrapResult( estimate = 0.15, ci_lower = 0.10, ci_upper = 0.20, ci_level = 0.95, boot_estimates = rnorm(1000, 0.15, 0.02), n_boot = 1000L, method = "parametric", call = NULL )
S7 class for mediation with a treatment assigned to whole clusters (such as
schools) and a mediator and outcome measured on the individuals in them. It
holds the paths of a two-level linear mixed model pair: the mediator model
M ~ X + ... + (1 | cluster) and the outcome model with the mediator split
into its within-cluster deviation and the observed cluster mean. medfit
computes the effects; causal interpretation is the user's responsibility.
ClusterMediationData(a_path, b_within, b_between, c_prime, estimates, vcov, kr_df, treatment, mediator, outcome, cluster, n_obs, n_clusters, cluster_sizes, parameterization, covariates_centered, se_type, reml, converged, sigma_m, sigma_y, tau_m, tau_y, data, source_package)ClusterMediationData(a_path, b_within, b_between, c_prime, estimates, vcov, kr_df, treatment, mediator, outcome, cluster, n_obs, n_clusters, cluster_sizes, parameterization, covariates_centered, se_type, reml, converged, sigma_m, sigma_y, tau_m, tau_y, data, source_package)
a_path |
Numeric scalar: effect of the treatment on the mediator. |
b_within |
Numeric scalar: within-cluster mediator effect on the outcome. |
b_between |
Numeric scalar: between-cluster (cluster-mean) mediator effect on the outcome. |
c_prime |
Numeric scalar: direct effect of the treatment. |
estimates |
Named numeric vector of parameter estimates: the four alias rows plus each model's fixed effects. |
vcov |
Square variance-covariance matrix of |
kr_df |
Named numeric vector of Kenward-Roger degrees of freedom, one
per path, when |
treatment, mediator, outcome
|
Character names of the variables. |
cluster |
Character name of the cluster variable. |
n_obs |
Integer number of observations. |
n_clusters |
Integer number of clusters. |
cluster_sizes |
Integer vector with one size per cluster. |
parameterization |
|
covariates_centered |
Logical: whether every level-1 covariate in the outcome model has a cluster-mean companion. |
se_type |
|
reml |
Logical: whether the models were fitted by REML. |
converged |
Logical convergence flag. |
sigma_m, sigma_y
|
Numeric scalars: residual standard deviations of the mediator and outcome models. |
tau_m, tau_y
|
Numeric scalars: random-intercept standard deviations of the mediator and outcome models. |
data |
Optional data frame, or NULL. |
source_package |
Character name of the originating package. |
With the within parameterization
and the effect of the treatment on the mediator, the natural
indirect effect is . It splits into an own-mediator part
and a spillover part .
The split is the large-cluster, cluster-average approximation. With the
observed cluster mean, moving only member 's mediator by
also moves the mean by , so the exact own-mediator effect is
and the exact
spillover is ;
they still sum to . decompose() warns when the
approximation error exceeds half the own effect's standard error.
The estimand assumes randomized treatment, intact clusters, no interference
between clusters, linear models without mediator-by-treatment or
mediator-by-covariate products, and interference through the observed
cluster mean only; all members of a cluster must be in the analysis rows.
The own effect also needs no unmeasured lower-level mediator-outcome
confounding. The spillover, NIE and NDE need no unmeasured upper-level
mediator-outcome confounding either; under it only the sum of the direct
effect and is identified. print() and
summary() print this block. The validator ties the alias rows a, c_prime, b_within and
b_between of estimates and vcov to the path properties, so an object
with inconsistent numbers cannot be built.
kr_df holds the Kenward-Roger degrees of freedom of each path when
se_type = "kr", and is empty otherwise.
A ClusterMediationData S7 object.
Talloen, W., Moerkerke, B., Loeys, T., De Naeghel, J., Van Keer, H., & Vansteelandt, S. (2016). Estimation of indirect effects in the presence of unmeasured confounding for the mediator-outcome relationship in a multilevel 2-1-1 mediation model. Journal of Educational and Behavioral Statistics, 41(4), 359-391. doi:10.3102/1076998616636855
VanderWeele, T. J. (2010). Direct and indirect effects for neighborhood-based clustered and longitudinal data. Sociological Methods & Research, 38(4), 515-544. doi:10.1177/0049124110366236
# Hand-built object (the paths and their alias rows must agree) est <- c(a = 0.5, c_prime = 0.2, b_within = 0.3, b_between = 0.6) vc <- diag(0.01, 4) dimnames(vc) <- list(names(est), names(est)) cmd <- ClusterMediationData( a_path = 0.5, b_within = 0.3, b_between = 0.6, c_prime = 0.2, estimates = est, vcov = vc, treatment = "X", mediator = "M", outcome = "Y", cluster = "school", n_obs = 400L, n_clusters = 40L, cluster_sizes = rep(10L, 40), parameterization = "within", covariates_centered = TRUE, se_type = "model", reml = TRUE, converged = TRUE, sigma_m = 1, sigma_y = 1, tau_m = 0.5, tau_y = 0.5, source_package = "medfit" ) cmd@a_path * cmd@b_between # the NIE, 0.5 * 0.6# Hand-built object (the paths and their alias rows must agree) est <- c(a = 0.5, c_prime = 0.2, b_within = 0.3, b_between = 0.6) vc <- diag(0.01, 4) dimnames(vc) <- list(names(est), names(est)) cmd <- ClusterMediationData( a_path = 0.5, b_within = 0.3, b_between = 0.6, c_prime = 0.2, estimates = est, vcov = vc, treatment = "X", mediator = "M", outcome = "Y", cluster = "school", n_obs = 400L, n_clusters = 40L, cluster_sizes = rep(10L, 40), parameterization = "within", covariates_centered = TRUE, se_type = "model", reml = TRUE, converged = TRUE, sigma_m = 1, sigma_y = 1, tau_m = 0.5, tau_y = 0.5, source_package = "medfit" ) cmd@a_path * cmd@b_between # the NIE, 0.5 * 0.6
Return the components of the total effect for an object whose outcome model has treatment-by-mediator products.
For an InteractionMediationData object this is VanderWeele's (2014) four-way decomposition: controlled direct effect (CDE), reference interaction (INTref), mediated interaction (INTmed), and pure indirect effect (PIE), together with the derived natural direct and indirect effects and the total effect. For a JointMediationData object it is the CDE and the joint natural direct and indirect effects (VanderWeele and Vansteelandt 2014).
decompose(x, ...)decompose(x, ...)
x |
An InteractionMediationData or JointMediationData object. |
... |
Additional arguments (ignored). |
For a 0/1 treatment, outcome model
,
mediator model ,
and reference mediator level :
with ,
, and
.
is the mediator model's prediction at no treatment and the covariate means.
A named numeric vector: c(cde, int_ref, int_med, pie, nde, nie, total) for InteractionMediationData, c(cde, nde, nie, total) for
JointMediationData.
VanderWeele, T. J. (2014). A unification of mediation and interaction: A 4-way decomposition. Epidemiology, 25(5), 749–761.
VanderWeele, T. J., & Vansteelandt, S. (2014). Mediation analysis with multiple mediators. Epidemiologic Methods, 2(1), 95–115. doi:10.1515/em-2012-0010
fit_m <- lm(mediator1 ~ treatment + covariate1 + covariate2, data = mediation_demo) fit_y <- lm(outcome ~ treatment * mediator1 + covariate1 + covariate2, data = mediation_demo) med_int <- extract_mediation(fit_m, model_y = fit_y, treatment = "treatment", mediator = "mediator1") decompose(med_int)fit_m <- lm(mediator1 ~ treatment + covariate1 + covariate2, data = mediation_demo) fit_y <- lm(outcome ~ treatment * mediator1 + covariate1 + covariate2, data = mediation_demo) med_int <- extract_mediation(fit_m, model_y = fit_y, treatment = "treatment", mediator = "mediator1") decompose(med_int)
Generic function to extract mediation structure (a, b, c' paths and variance-covariance matrices) from fitted models. This function provides a unified interface for extracting mediation information from various model types (lm, glm, lavaan, lmer, and a native SEMFit).
extract_mediation(object, ...)extract_mediation(object, ...)
object |
Fitted model object (lm, glm, lavaan, lmerMod, or a SEMFit) |
... |
Additional arguments passed to methods. Common arguments include:
|
The extract_mediation() generic provides methods for different model types:
lm/glm: Extract from linear and generalized linear models
lavaan: Extract from structural equation models
lmerMod: Extract from two linear mixed models for a treatment assigned
to whole clusters, as a ClusterMediationData: the mediator model is
object, the outcome model is model_y, and cluster names the cluster
variable. Both models must be fitted to the same rows; the cluster means
must be computed on those rows, and a pair of fits that kept different
individuals is an error (best effort: it compares the data row names the
fits kept, so it cannot see a mismatch between frames whose row names were
both reset)
SEMFit: Extract from a model fitted by fit_sem() (the experimental
native maximum-likelihood engine), with the same arguments and structures as
the lavaan method. A treatment-by-mediator product written x:m (or m:x)
in the outcome equation gives the four-way decomposition, stored under the
canonical name m:x; without it decomposition = "four_way" is refused
by name. interaction = takes that product written with :.
brmsfit: Extract from Bayesian models (future)
Note: OpenMx extraction is planned for a future release.
The returned objects share one interface (nie(), nde(), te(),
confint(), bootstrap_mediation(), ...) for use by other medfit functions
and dependent packages (probmed, RMediation, medrobust).
The class returned depends on the structure. A single mediator gives a
MediationData object, or an InteractionMediationData object when the
outcome model has a treatment-by-mediator term. A mediator vector of
length two or more gives a SerialMediationData or ParallelMediationData
object; for lm/glm fits whose outcome model has a treatment-by-mediator term
written with : or *, it gives a JointMediationData object with the
joint natural effects of the mediators. Any other product term in a
multi-mediator fit errors.
With object the fitted mediator model (the first mediator's model for
several mediators), the lm/glm method takes:
model_y: the fitted outcome model. With several mediators include every
mediator, not only the last: omitting one that also affects the outcome
biases the others' coefficients.
treatment: name of the treatment variable.
mediator: name of the mediator, or an ordered character vector of two or
more mediator names.
mediator_models: list of the fitted models for mediators 2 to k, in
order; required whenever mediator has length two or more.
structure: "auto" (default), "serial", or "parallel"; "auto"
classifies the mediators from the mediator models.
decomposition: "auto" (default) uses the four-way decomposition when
a single mediator's outcome model has a treatment-by-mediator term;
"four_way" requires that term; "two_way" ignores it and returns a
MediationData object.
m_star: the reference mediator level for the controlled direct effect
(default 0; for JointMediationData a scalar or a vector named by the
interacting mediators).
vcov_fun: function returning each model's covariance matrix (default
stats::vcov(); for example sandwich::vcovHC). Not supported for
JointMediationData.
outcome, data: optional; detected from the models when omitted.
The lavaan method takes a fitted lavaan model and the variable names; see
?extract_mediation_lavaan for its arguments.
For lm/glm fits, each equation's block of @vcov is that model's own
covariance. The blocks for different equations are stored as zero. This is
exact for simple mediation, serial chains, and the four-way model, whose
later equations contain every regressor of the earlier ones, but for
parallel mediators it leaves out the covariance between their a paths.
JointMediationData stores the full stacked least-squares covariance, and a
lavaan fit estimates all equations jointly, so for the same data the
intervals can differ between engines.
The simple single-mediator MediationData from lm/glm fits names three
coefficients m_<treatment>, y_<mediator> and y_<treatment>
(m_X, y_M and y_X for variables X and M), and code that looks
coefficients up by name, such as the parametric bootstrap in probmed, relies on
them. The lavaan method adds the same three names to @estimates and
@vcov for an observed, continuous treatment, mediator and outcome. Each is
a second name for the a, b or c_prime path, with the same estimate and
covariances, appended after the existing rows. A lavaan fit with a latent or
ordered treatment, mediator or outcome, and any extraction with
standardized = TRUE, gets none of the three and works with the plugin
estimators only. Serial, parallel and four-way objects are not
affected.
An S7 object whose class depends on the mediation structure (see Details): MediationData, InteractionMediationData, SerialMediationData, ParallelMediationData, JointMediationData, or ClusterMediationData. Each carries the path coefficients, the full parameter vector and its variance-covariance matrix, residual standard deviations (for Gaussian models), variable names, and the original data when available.
MediationData, fit_mediation(), bootstrap_mediation()
# Extract the mediation structure from fitted lm models, using the # simulated mediation_demo data bundled with medfit fit_m <- lm(mediator1 ~ treatment + covariate1 + covariate2, data = mediation_demo) fit_y <- lm(outcome ~ treatment + mediator1 + covariate1 + covariate2, data = mediation_demo) med_data <- extract_mediation(fit_m, model_y = fit_y, treatment = "treatment", mediator = "mediator1") if (requireNamespace("lme4", quietly = TRUE)) { # Treatment assigned to whole clusters (needs lme4); formulas hold fixed # effects only, the engine adds the random cluster intercept set.seed(1) J <- 30 id <- rep(seq_len(J), each = 6) cdat <- data.frame(school = factor(id), X = sample(rep(0:1, J / 2))[id]) cdat$M <- 0.5 * cdat$X + rnorm(J, sd = 0.5)[id] + rnorm(J * 6) cdat$Y <- 0.2 * cdat$X + 0.4 * cdat$M + rnorm(J, sd = 0.5)[id] + rnorm(J * 6) # The outcome model carries the mediator as its within-cluster deviation # plus the observed cluster mean cdat$M_bar <- ave(cdat$M, cdat$school) cdat$M_w <- cdat$M - cdat$M_bar fit_m <- lme4::lmer(M ~ X + (1 | school), data = cdat) fit_y <- lme4::lmer(Y ~ X + M_w + M_bar + (1 | school), data = cdat) cluster_med <- extract_mediation(fit_m, model_y = fit_y, treatment = "X", mediator = "M", cluster = "school") nie(cluster_med) }# Extract the mediation structure from fitted lm models, using the # simulated mediation_demo data bundled with medfit fit_m <- lm(mediator1 ~ treatment + covariate1 + covariate2, data = mediation_demo) fit_y <- lm(outcome ~ treatment + mediator1 + covariate1 + covariate2, data = mediation_demo) med_data <- extract_mediation(fit_m, model_y = fit_y, treatment = "treatment", mediator = "mediator1") if (requireNamespace("lme4", quietly = TRUE)) { # Treatment assigned to whole clusters (needs lme4); formulas hold fixed # effects only, the engine adds the random cluster intercept set.seed(1) J <- 30 id <- rep(seq_len(J), each = 6) cdat <- data.frame(school = factor(id), X = sample(rep(0:1, J / 2))[id]) cdat$M <- 0.5 * cdat$X + rnorm(J, sd = 0.5)[id] + rnorm(J * 6) cdat$Y <- 0.2 * cdat$X + 0.4 * cdat$M + rnorm(J, sd = 0.5)[id] + rnorm(J * 6) # The outcome model carries the mediator as its within-cluster deviation # plus the observed cluster mean cdat$M_bar <- ave(cdat$M, cdat$school) cdat$M_w <- cdat$M - cdat$M_bar fit_m <- lme4::lmer(M ~ X + (1 | school), data = cdat) fit_y <- lme4::lmer(Y ~ X + M_w + M_bar + (1 | school), data = cdat) cluster_med <- extract_mediation(fit_m, model_y = fit_y, treatment = "X", mediator = "M", cluster = "school") nie(cluster_med) }
Fit mediation models using a specified modeling engine. This function provides a convenient formula-based interface for fitting both the mediator and outcome models simultaneously.
fit_mediation( formula_y, formula_m, data, treatment, mediator, engine = "glm", family_y = stats::gaussian(), family_m = stats::gaussian(), weights = NULL, se_type = c("model", "sandwich", "kr"), engine_args = list(), m_star = 0, cluster = NULL, model = NULL, ... )fit_mediation( formula_y, formula_m, data, treatment, mediator, engine = "glm", family_y = stats::gaussian(), family_m = stats::gaussian(), weights = NULL, se_type = c("model", "sandwich", "kr"), engine_args = list(), m_star = 0, cluster = NULL, model = NULL, ... )
formula_y |
Formula for outcome model (e.g., |
formula_m |
Formula for mediator model (e.g., |
data |
Data frame containing all variables. With the |
treatment |
Character string: name of treatment variable |
mediator |
Character string: name of mediator variable |
engine |
Character string: modeling engine to use. Currently supports:
|
family_y |
Family object for outcome model (default: |
family_m |
Family object for mediator model (default: |
weights |
Optional numeric vector of case weights (length |
se_type |
Variance-covariance estimator for |
engine_args |
Named list of engine-specific overrides (default:
|
m_star |
Numeric scalar: reference mediator level |
cluster |
Character string: name of the cluster variable in |
model |
Character string: model syntax for |
... |
Additional arguments passed to the fitting function. With the
|
The function fits two models:
Mediator model: formula_m (e.g., M ~ X + C1 + C2)
Outcome model: formula_y (e.g., Y ~ X + M + C1 + C2)
The treatment variable must appear in both formulas. The mediator variable must appear in the outcome formula but NOT in the mediator formula (as it is the response).
When engine = "glm" (default):
Models are fit using stats::glm()
Supports all GLM families (gaussian, binomial, poisson, etc.)
With a non-identity link the path coefficients are on the link
scale, where the product is not a natural indirect
effect on the outcome scale; interpret it with care or use
engine = "regmedint" for closed-form effects of a logistic outcome
A treatment-by-mediator term in formula_y requires Gaussian
identity-link models (the four-way formulas are linear); other families
error
For Gaussian models, residual standard deviations are extracted
Non-Gaussian outcomes have sigma_y = NULL
gaussian(): Continuous outcomes (default)
binomial(): Binary outcomes
poisson(): Count outcomes
Gamma(): Positive continuous outcomes
When engine = "regmedint":
Delegates to regmedint::regmedint() (suggested package) for closed-form
natural (in)direct effects, with or without a treatment-mediator interaction
Returns MediationData or InteractionMediationData depending on whether
formula_y contains a treatment-by-mediator interaction term; use
engine_args to override the derived regmedint arguments
When the fit yields an InteractionMediationData, m_star fixes the level
at which the controlled direct effect is read off:
and
.
The two shift in compensating directions, so nde(), nie(), te(), and
pm() are invariant to m_star; only the CDE/INTref split moves.
The engines reach the same result by different routes. engine = "glm"
applies m_star at extraction time, after the coefficients are fit;
engine = "regmedint" passes it to regmedint::regmedint() as m_cde,
where it is consumed by that package's closed-form estimator at fitting
time. Supplying m_star for a fit with no treatment-by-mediator term is an
error, not a silent no-op.
A MediationData object containing the fitted mediation structure,
or an InteractionMediationData object when formula_y contains a
treatment-by-mediator interaction term.
VanderWeele, T. J. (2014). A unification of mediation and interaction: A 4-way decomposition. Epidemiology, 25(5), 749–761.
MediationData, extract_mediation(), bootstrap_mediation()
# mediation_demo is simulated data bundled with medfit; its covariates # confound the mediator-outcome relation, so both models adjust for them med_data <- fit_mediation( formula_y = outcome ~ treatment + mediator1 + covariate1 + covariate2, formula_m = mediator1 ~ treatment + covariate1 + covariate2, data = mediation_demo, treatment = "treatment", mediator = "mediator1" ) print(med_data) # Binary outcome (takes longer to fit): dichotomize the outcome at its median demo <- mediation_demo demo$outcome_bin <- as.integer(demo$outcome > stats::median(demo$outcome)) med_data_bin <- fit_mediation( formula_y = outcome_bin ~ treatment + mediator1 + covariate1 + covariate2, formula_m = mediator1 ~ treatment + covariate1 + covariate2, data = demo, treatment = "treatment", mediator = "mediator1", family_y = binomial() ) if (requireNamespace("lme4", quietly = TRUE)) { # Treatment assigned to whole clusters (needs lme4); formulas hold fixed # effects only, the engine adds the random cluster intercept set.seed(1) J <- 30 id <- rep(seq_len(J), each = 6) cdat <- data.frame(school = factor(id), X = sample(rep(0:1, J / 2))[id]) cdat$M <- 0.5 * cdat$X + rnorm(J, sd = 0.5)[id] + rnorm(J * 6) cdat$Y <- 0.2 * cdat$X + 0.4 * cdat$M + rnorm(J, sd = 0.5)[id] + rnorm(J * 6) cluster_fit <- fit_mediation( Y ~ X + M, M ~ X, data = cdat, treatment = "X", mediator = "M", engine = "lmer", cluster = "school" ) nie(cluster_fit) # a * b_between decompose(cluster_fit) # own-mediator and spillover parts }# mediation_demo is simulated data bundled with medfit; its covariates # confound the mediator-outcome relation, so both models adjust for them med_data <- fit_mediation( formula_y = outcome ~ treatment + mediator1 + covariate1 + covariate2, formula_m = mediator1 ~ treatment + covariate1 + covariate2, data = mediation_demo, treatment = "treatment", mediator = "mediator1" ) print(med_data) # Binary outcome (takes longer to fit): dichotomize the outcome at its median demo <- mediation_demo demo$outcome_bin <- as.integer(demo$outcome > stats::median(demo$outcome)) med_data_bin <- fit_mediation( formula_y = outcome_bin ~ treatment + mediator1 + covariate1 + covariate2, formula_m = mediator1 ~ treatment + covariate1 + covariate2, data = demo, treatment = "treatment", mediator = "mediator1", family_y = binomial() ) if (requireNamespace("lme4", quietly = TRUE)) { # Treatment assigned to whole clusters (needs lme4); formulas hold fixed # effects only, the engine adds the random cluster intercept set.seed(1) J <- 30 id <- rep(seq_len(J), each = 6) cdat <- data.frame(school = factor(id), X = sample(rep(0:1, J / 2))[id]) cdat$M <- 0.5 * cdat$X + rnorm(J, sd = 0.5)[id] + rnorm(J * 6) cdat$Y <- 0.2 * cdat$X + 0.4 * cdat$M + rnorm(J, sd = 0.5)[id] + rnorm(J * 6) cluster_fit <- fit_mediation( Y ~ X + M, M ~ X, data = cdat, treatment = "X", mediator = "M", engine = "lmer", cluster = "school" ) nie(cluster_fit) # a * b_between decompose(cluster_fit) # own-mediator and spillover parts }
Fits a model written in lavaan-style syntax to raw data with medfit's native
maximum-likelihood engine, and returns a SEMFit object. The engine handles
observed and latent variables, user labels, fixed values, starts and bounds,
equality and inequality constraints (==, <, >, linear or nonlinear), and defined
parameters (:=) with delta-method standard errors.
fit_sem( model, data, information = c("observed", "expected"), n_starts = NULL, control = list(), weights = NULL, se_type = c("model", "sandwich"), meanstructure = NULL, fixed_x = FALSE, sampling_weights = lifecycle::deprecated() )fit_sem( model, data, information = c("observed", "expected"), n_starts = NULL, control = list(), weights = NULL, se_type = c("model", "sandwich"), meanstructure = NULL, fixed_x = FALSE, sampling_weights = lifecycle::deprecated() )
model |
Character: model syntax. Lines may use |
data |
A data frame with the model's observed variables as numeric
columns. Rows with missing values in those columns are dropped once
(listwise deletion) and counted in |
information |
Character: |
n_starts |
Positive integer or |
control |
List: options passed to the optimizer ( |
weights |
Optional numeric vector of non-negative case weights,
one per row of |
se_type |
Character: |
meanstructure |
Logical or |
fixed_x |
|
sampling_weights |
|
A SEMFit object. With a product term whose factor is a regression target (x:m with
m regressed on x), glance() reports the chi-square, df and p-value as NA with a note, and
anova() and mbco_sem() refuse; see SEMFit-methods.
fit <- fit_sem( "mediator1 ~ a*treatment\noutcome ~ b*mediator1 + treatment\nab := a*b", data = mediation_demo ) fit@defined # A treatment-by-mediator product: the four-way decomposition fit4 <- fit_sem( "mediator1 ~ treatment\noutcome_int ~ treatment + mediator1 + treatment:mediator1", data = mediation_demo, se_type = "sandwich" ) decompose(extract_mediation(fit4, treatment = "treatment", mediator = "mediator1", outcome = "outcome_int"))fit <- fit_sem( "mediator1 ~ a*treatment\noutcome ~ b*mediator1 + treatment\nab := a*b", data = mediation_demo ) fit@defined # A treatment-by-mediator product: the four-way decomposition fit4 <- fit_sem( "mediator1 ~ treatment\noutcome_int ~ treatment + mediator1 + treatment:mediator1", data = mediation_demo, se_type = "sandwich" ) decompose(extract_mediation(fit4, treatment = "treatment", mediator = "mediator1", outcome = "outcome_int"))
S7 class for simple mediation with a treatment-by-mediator interaction
( where the outcome model
contains an term). It carries VanderWeele's (2014)
four-way decomposition of the total effect into controlled direct effect
(CDE), reference interaction (INTref), mediated interaction (INTmed), and
pure indirect effect (PIE):
with and
. medfit computes the
decomposition; causal interpretation is the user's responsibility (it requires
the four no-unmeasured-confounding assumptions of VanderWeele 2014).
InteractionMediationData(a_path, b_path, c_prime, interaction, cde, int_ref, int_med, pie, nde, nie, total_effect, m_star, estimates, vcov, sigma_m, sigma_y, treatment, mediator, outcome, mediator_predictors, outcome_predictors, data, n_obs, converged, source_package)InteractionMediationData(a_path, b_path, c_prime, interaction, cde, int_ref, int_med, pie, nde, nie, total_effect, m_star, estimates, vcov, sigma_m, sigma_y, treatment, mediator, outcome, mediator_predictors, outcome_predictors, data, n_obs, converged, source_package)
a_path |
Numeric scalar: treatment -> mediator effect ( |
b_path |
Numeric scalar: mediator -> outcome main effect ( |
c_prime |
Numeric scalar: treatment -> outcome main effect ( |
interaction |
Numeric scalar: treatment x mediator coefficient ( |
cde, int_ref, int_med, pie
|
Numeric scalars: the four-way components (controlled direct, reference interaction, mediated interaction, pure indirect). |
nde, nie, total_effect
|
Numeric scalars: derived natural direct effect (CDE + INTref), natural indirect effect (INTmed + PIE), and total effect (the sum of all four components). |
m_star |
Numeric scalar: reference mediator level for the decomposition (default 0). |
estimates |
Numeric vector of all parameter estimates. |
vcov |
Square variance-covariance matrix of |
sigma_m |
Optional numeric scalar mediator residual SD, or NULL. |
sigma_y |
Optional numeric scalar outcome residual SD, or NULL. |
treatment, mediator, outcome
|
Single character strings naming the treatment / mediator / outcome. |
mediator_predictors, outcome_predictors
|
Character vectors of predictor names for the mediator and outcome models. |
data |
Optional data frame, or NULL. |
n_obs |
Integer number of observations. |
converged |
Logical convergence flag. |
source_package |
Character name of the originating package. |
Path coefficients follow the outcome model
and mediator model :
a_path = , b_path = ,
c_prime = , interaction = . With
reference level m_star () the components are
,
,
, and
. When the
decomposition collapses to standard simple mediation (CDE = NDE = ;
INTref = INTmed = 0; NIE = PIE = ).
is the mediator model's prediction at no treatment and the covariate means
. The effects are for the treatment contrast 0 to 1, and
the lm/glm extractor requires Gaussian identity-link models. From
fit_mediation(engine = "regmedint"), @estimates and @vcov also carry
regmedint's own delta-method covariance of the four components and the
derived effects.
An InteractionMediationData S7 object.
VanderWeele, T. J. (2014). A unification of mediation and interaction: A 4-way decomposition. Epidemiology, 25(5), 749–761.
# Hand-built object (theta3 = 0.2 interaction, m* = 0) imd <- InteractionMediationData( a_path = 0.5, b_path = 0.3, c_prime = 0.1, interaction = 0.2, cde = 0.1, int_ref = 0.04, int_med = 0.10, pie = 0.15, nde = 0.14, nie = 0.25, total_effect = 0.39, m_star = 0, estimates = c(a = 0.5, b = 0.3, c_prime = 0.1, theta3 = 0.2), vcov = diag(0.01, 4), treatment = "X", mediator = "M", outcome = "Y", mediator_predictors = "X", outcome_predictors = c("X", "M", "X:M"), n_obs = 200L, converged = TRUE, source_package = "medfit" ) nie(imd) # INTmed + PIE = 0.25 decompose(imd) # all four components + derived effects# Hand-built object (theta3 = 0.2 interaction, m* = 0) imd <- InteractionMediationData( a_path = 0.5, b_path = 0.3, c_prime = 0.1, interaction = 0.2, cde = 0.1, int_ref = 0.04, int_med = 0.10, pie = 0.15, nde = 0.14, nie = 0.25, total_effect = 0.39, m_star = 0, estimates = c(a = 0.5, b = 0.3, c_prime = 0.1, theta3 = 0.2), vcov = diag(0.01, 4), treatment = "X", mediator = "M", outcome = "Y", mediator_predictors = "X", outcome_predictors = c("X", "M", "X:M"), n_obs = 200L, converged = TRUE, source_package = "medfit" ) nie(imd) # INTmed + PIE = 0.25 decompose(imd) # all four components + derived effects
Recomputes the joint CDE, NDE, NIE and total effect of a
JointMediationData object from a named parameter vector, holding the
object's structure, reference levels (m_star) and sample covariate means
fixed. With the default estimates = object@estimates it returns the stored
effects. Its main use is as the statistic of a parametric bootstrap: the
draws bootstrap_mediation() passes to statistic_fn are named like
@estimates, and the NDE needs the prefixed intercept and covariate rows
(m1_, ..., y_) and the covariate means, not only the path aliases.
joint_effects(object, estimates = object@estimates)joint_effects(object, estimates = object@estimates)
object |
A JointMediationData object. |
estimates |
Named numeric vector containing every prefixed source row
of |
Named numeric vector: cde, nde, nie, te.
d <- mediation_demo fit <- extract_mediation( lm(mediator1 ~ treatment + covariate1 + covariate2, d), model_y = lm(outcome_int ~ treatment * mediator1 + mediator2 + covariate1 + covariate2, d), treatment = "treatment", mediator = c("mediator1", "mediator2"), mediator_models = list(lm(mediator2 ~ treatment + mediator1 + covariate1 + covariate2, d)) ) joint_effects(fit) # Parametric bootstrap of the joint NIE boot <- bootstrap_mediation( function(theta) joint_effects(fit, theta)[["nie"]], method = "parametric", mediation_data = fit, n_boot = 500, seed = 1 ) boot@ci_lower boot@ci_upperd <- mediation_demo fit <- extract_mediation( lm(mediator1 ~ treatment + covariate1 + covariate2, d), model_y = lm(outcome_int ~ treatment * mediator1 + mediator2 + covariate1 + covariate2, d), treatment = "treatment", mediator = c("mediator1", "mediator2"), mediator_models = list(lm(mediator2 ~ treatment + mediator1 + covariate1 + covariate2, d)) ) joint_effects(fit) # Parametric bootstrap of the joint NIE boot <- bootstrap_mediation( function(theta) joint_effects(fit, theta)[["nie"]], method = "parametric", mediation_data = fit, n_boot = 500, seed = 1 ) boot@ci_lower boot@ci_upper
S7 class for the joint natural effects of two or more mediators, serial or parallel, when the outcome model may contain treatment-by-mediator product terms (VanderWeele and Vansteelandt 2014). The mediators are treated as one block: the natural indirect effect (NIE) runs through every mediator and every path among them, and the natural direct effect (NDE) is the remainder. medfit computes the effects; causal interpretation is the user's responsibility.
JointMediationData(structure, mediators, treatment, outcome, interactions, a_total, b_paths, theta3, c_prime, cde, nde, nie, total_effect, m_star, estimates, vcov, data, n_obs, converged, source_package)JointMediationData(structure, mediators, treatment, outcome, interactions, a_total, b_paths, theta3, c_prime, cde, nde, nie, total_effect, m_star, estimates, vcov, data, n_obs, converged, source_package)
structure |
Single string, |
mediators |
Character vector of mediator names, in causal order. |
treatment, outcome
|
Single character strings. |
interactions |
Character vector naming the mediators that carry a treatment-by-mediator product in the outcome model (may be empty). |
a_total |
Numeric vector named by |
b_paths |
Numeric vector named by |
theta3 |
Numeric vector named by |
c_prime |
Numeric scalar: treatment main effect on the outcome
( |
cde, nde, nie, total_effect
|
Numeric scalars: controlled direct, natural direct, natural indirect and total effects. |
m_star |
Numeric vector named by |
estimates |
Named numeric vector of parameter estimates. |
vcov |
Square variance-covariance matrix of |
data |
Optional data frame, or NULL. |
n_obs |
Integer number of observations. |
converged |
Logical convergence flag. |
source_package |
Character name of the originating package. |
With outcome model
and the total (propagated) effect of the treatment on
mediator (for a serial chain this includes the paths through earlier
mediators), the unit-contrast effects are
and . is zero for a
mediator without a product term, and is the mean of
mediator under no treatment at the covariate means (propagated down a
serial chain like ). The validator enforces the NIE and CDE
identities, so an object with inconsistent numbers cannot be built.
The NIE is the effect through the mediators as a block: every path from
the treatment through any mediator, including paths among the mediators. It
is not split into per-mediator or per-path pieces, and none of its parts
should be reported as the effect "through M1". For a serial chain it
therefore differs from the default nie() of a SerialMediationData
(a * d * b, the effect through the full chain only): with no product term, the
joint NIE of M1 -> M2 is ,
not . The effects use the unit contrast of a 0/1
treatment (0 to 1).
The joint effects are identified under no-unmeasured-confounding assumptions stated for the whole mediator vector: none for the treatment and outcome, none for the mediators and the outcome, none for the treatment and the mediators, and no mediator-outcome confounder affected by the treatment outside the mediator vector (VanderWeele and Vansteelandt 2014). In a serial chain the earlier mediators are affected by the treatment and confound the later ones; that is allowed precisely because they are part of the vector. Mediator-outcome and treatment-mediator confounders must be controlled for every mediator.
Every model (each mediator model and the outcome model) must carry the same covariates. This is a medfit limitation that keeps the serial-chain algebra and the covariance exact, not a requirement of the method; differing sets error, naming the terms.
The models must be Gaussian with the identity link, unweighted, with an
intercept, fit to the same rows; every mediator must appear in the outcome
model; mediator lists the mediators in causal order.
A product must be written in the outcome formula with : or *. A product
precomputed as a data column (for example XM <- X * M1 added to the
data and then used as Y ~ X + M1 + M2 + XM) cannot be recognized from the
formula: medfit treats it as an ordinary covariate and the effects ignore
the interaction, with no error. Write X * M1 in the formula instead.
The NDE depends on the covariates; it is evaluated at their sample means.
The effects are linear in the covariates, so this equals the sample average
of the per-observation effects. Standard errors use the delta method with
analytic gradients over a stacked-OLS covariance that includes the
correlation between parallel mediator equations. They treat the covariate
means as fixed, so they are conditional on the observed covariates and
slightly understate the uncertainty of a population-average effect. For a
parametric bootstrap use joint_effects() as the statistic.
A JointMediationData S7 object.
VanderWeele, T. J., & Vansteelandt, S. (2014). Mediation analysis with multiple mediators. Epidemiologic Methods, 2(1), 95–115. doi:10.1515/em-2012-0010
# Hand-built parallel object with an X x M2 product (m2* = 0) jmd <- JointMediationData( structure = "parallel", mediators = c("M1", "M2"), treatment = "X", outcome = "Y", interactions = "M2", a_total = c(M1 = 0.5, M2 = 0.3), b_paths = c(M1 = 0.3, M2 = 0.4), theta3 = c(M2 = 0.2), c_prime = 0.1, cde = 0.1, nde = 0.12, nie = 0.33, total_effect = 0.45, m_star = c(M2 = 0), estimates = c(a1 = 0.5, a2 = 0.3, b1 = 0.3, b2 = 0.4, theta3_M2 = 0.2, c_prime = 0.1), vcov = diag(0.01, 6), n_obs = 200L, converged = TRUE, source_package = "medfit" ) jmd@nie # (0.3 + 0) * 0.5 + (0.4 + 0.2) * 0.3 = 0.33# Hand-built parallel object with an X x M2 product (m2* = 0) jmd <- JointMediationData( structure = "parallel", mediators = c("M1", "M2"), treatment = "X", outcome = "Y", interactions = "M2", a_total = c(M1 = 0.5, M2 = 0.3), b_paths = c(M1 = 0.3, M2 = 0.4), theta3 = c(M2 = 0.2), c_prime = 0.1, cde = 0.1, nde = 0.12, nie = 0.33, total_effect = 0.45, m_star = c(M2 = 0), estimates = c(a1 = 0.5, a2 = 0.3, b1 = 0.3, b2 = 0.4, theta3_M2 = 0.2, c_prime = 0.1), vcov = diag(0.01, 6), n_obs = 200L, converged = TRUE, source_package = "medfit" ) jmd@nie # (0.3 + 0) * 0.5 + (0.4 + 0.2) * 0.3 = 0.33
Tests the null hypothesis that an indirect effect, the product of two or more
labeled paths (, or a serial ), is zero in a
model fitted by fit_sem(). The product is zero exactly when one factor is,
so the model is refitted once per path with that path set to zero; each is a
linear equality constraint, so each refit is a single solve. The statistic is
the smallest of the likelihood-ratio statistics, referred to a chi-square
distribution with one degree of freedom (the minimum-of-k LRTs; see the
Methods and Formulas article). The nonlinear constraint
is never solved, because a nonlinear solver can stop at the branch with the
larger statistic.
mbco_sem( object, a, b, n_starts = NULL, control = NULL, fit = lifecycle::deprecated(), paths = NULL )mbco_sem( object, a, b, n_starts = NULL, control = NULL, fit = lifecycle::deprecated(), paths = NULL )
object |
|
a, b
|
Character strings: the labels of the two free regression paths
whose product is the indirect effect (for example |
n_starts, control
|
Passed to the optimizer for the two refits, as in
|
fit |
|
paths |
Character vector of two or more labels: the paths whose product
is the indirect effect ( |
A fit made with weights is refused: the weighted discrepancy is
not a likelihood, so its difference is not chi-square. So is a fit that did
not converge, since its discrepancy is the reference both refits are
measured against. A refit that fails the acceptance gate of the optimizer is
an error rather than a statistic.
Inequality constraints in the model matter only when they put the null on
the boundary of the parameter space. If a bound or an inequality that
involves the tested parameter is active at its refit (a > 0 with the null
a = 0), the function warns that the statistic is a mixture of chi-squares,
not chi-square(1), and that the p-value is not valid. A bound the null sits
inside (a < 0.2), an inactive inequality and one on another parameter are
not mentioned. A null the constraints rule out (a > 0.3 with the null
a = 0) is an error that says so.
Only a product of labeled paths is tested; a sum of products (several
parallel indirect effects together) is not. If two of the paths are tied by
an equality (a == b), their refits impose the same restriction and their
diff_ll values coincide. The test is conservative: under a null where only
one factor is zero its size is at most the nominal level, and with more than
one zero factor it is smaller still.
An object of class c("mbco_sem", "htest"): statistic (the
smallest diffLL), parameter (df = 1), p.value, method,
data.name, n_obs, and tests, a data frame with one row per refit
(constraint, the minimized discrepancy f, diff_ll equal to
n_obs * (f - fit@f), and converged, always TRUE because a refit
that does not converge is an error). It prints with the standard htest
layout.
fit <- fit_sem( "mediator1 ~ a*treatment\noutcome ~ b*mediator1 + treatment", data = mediation_demo ) mbco_sem(fit, "a", "b") # a serial indirect effect, the product of three paths serial <- fit_sem( paste0("mediator1 ~ a*treatment\nmediator2 ~ d*mediator1 + treatment\n", "outcome ~ b*mediator2 + treatment"), data = mediation_demo ) mbco_sem(serial, paths = c("a", "d", "b"))fit <- fit_sem( "mediator1 ~ a*treatment\noutcome ~ b*mediator1 + treatment", data = mediation_demo ) mbco_sem(fit, "a", "b") # a serial indirect effect, the product of three paths serial <- fit_sem( paste0("mediator1 ~ a*treatment\nmediator2 ~ d*mediator1 + treatment\n", "outcome ~ b*mediator2 + treatment"), data = mediation_demo ) mbco_sem(serial, paths = c("a", "d", "b"))
A simplified entry point for mediation analysis. Specify the data and variable names, and get results with minimal configuration.
This is the recommended starting point for most mediation analyses.
For more control over model specifications, use fit_mediation() directly.
med( data, treatment, mediator, outcome, covariates = NULL, boot = FALSE, n_boot = 1000L, seed = NULL, ... )med( data, treatment, mediator, outcome, covariates = NULL, boot = FALSE, n_boot = 1000L, seed = NULL, ... )
data |
A data frame containing all variables |
treatment |
Character: name of treatment (exposure) variable |
mediator |
Character: name of mediator variable |
outcome |
Character: name of outcome variable |
covariates |
Character vector: names of covariates to include (optional, default: none) |
boot |
Logical: compute a bootstrap confidence interval for the
indirect effect? (default: FALSE for speed). Uses a parametric bootstrap
of |
n_boot |
Integer: number of bootstrap samples (default: 1000) |
seed |
Integer: random seed for reproducibility (optional) |
... |
Additional arguments passed to |
med() is designed to be the simplest way to run a mediation analysis.
It constructs the model formulas automatically from variable names.
Fits Gaussian (continuous) mediator and outcome models
No covariates unless specified
No bootstrap unless requested (use boot = TRUE)
After running med(), use:
nie(result): Natural indirect effect
nde(result): Natural direct effect
te(result): Total effect
pm(result): Proportion mediated
quick(result): One-line summary
summary(result): Detailed summary
A MediationData object with mediation results
fit_mediation() for full control, quick() for instant summary,
nie(), nde(), te(), pm() for extracting effects
# mediation_demo is simulated data bundled with medfit; its covariates # confound the mediator-outcome relation, so adjust for them result <- med( data = mediation_demo, treatment = "treatment", mediator = "mediator1", outcome = "outcome", covariates = c("covariate1", "covariate2") ) print(result) # Quick summary quick(result) # With bootstrap CI (slower) result_boot <- med( data = mediation_demo, treatment = "treatment", mediator = "mediator1", outcome = "outcome", covariates = c("covariate1", "covariate2"), boot = TRUE, n_boot = 1000, seed = 42 )# mediation_demo is simulated data bundled with medfit; its covariates # confound the mediator-outcome relation, so adjust for them result <- med( data = mediation_demo, treatment = "treatment", mediator = "mediator1", outcome = "outcome", covariates = c("covariate1", "covariate2") ) print(result) # Quick summary quick(result) # With bootstrap CI (slower) result_boot <- med( data = mediation_demo, treatment = "treatment", mediator = "mediator1", outcome = "outcome", covariates = c("covariate1", "covariate2"), boot = TRUE, n_boot = 1000, seed = 42 )
The model syntax read by fit_sem() and by fit_mediation(engine = "native"). It is a lavaan-style core plus two extensions, each marked
"medfit extension" below. One statement per line (or separated by ;).
| Statement | Meaning |
y ~ x1 + x2 |
regression of y on x1 and x2 |
f =~ y1 + y2 + y3 |
latent variable f measured by the indicators |
y1 ~~ y2 |
covariance; y1 ~~ y1 a variance |
y ~ x + m + x:m |
product term: the observed variable x * m as a regressor (see Product terms) |
y ~ a*x |
label the path a; equal labels are one parameter |
y ~ 0.5*x |
fix the path at 0.5 |
y ~ start(0.3)*x, lower(0)*, upper(1)* |
starting value, lower and upper bound |
a == b, a + b == 1, cp == a*b |
equality constraint on labels; may be nonlinear |
a < b, a > 0 |
inequality constraint; label < number is a bound |
ab := a*b |
defined parameter, with a delta-method standard error |
Constraints may be nonlinear in the labels (cp == a*b, a*b == 0.1,
a*b > 0.1, ab > 0 with ab := a*b), solved with the Jacobian recomputed at
every point; a model with one tries every start and keeps the best accepted
solution, warning when the starts disagree. A product of labels set to zero
(a*b == 0, b1*b2*b3 == 0) is instead solved exactly: one linear fit per
factor (a == 0, b == 0), keeping the smallest discrepancy. A sum of products
(a1*b1 + a2*b2 == 0) has no exact reduction, so it is best-effort: the fit is
the best of the starts tried, a disagreement warning means a better optimum may
exist, and n_starts raises the number of starts. Labels used in a constraint
or a definition must be defined in the model. A constraint with no free
parameter is an error. Exogenous variables are given free
variances and covariances, as lavaan does with fixed.x = FALSE; fixed_x = TRUE
(or a vector of names) in fit_sem() fixes the moments of the selected ones at the
sample values instead, as fixed.x = TRUE does. Unlike lavaan, which keeps an
explicit x ~~ x or x ~ 1 free under fixed.x = TRUE and lets the degrees of
freedom move, medfit stops with an error when the model has a statement on a
selected variable's moments. Any
intercept statement (y ~ 1, or 1 as a term: y ~ 1 + x) turns the mean structure
on and frees every observed intercept the model does not write, as lavaan does;
y ~ 0*1 then restricts the mean of y to 0 (one degree of freedom), and
meanstructure = TRUE in fit_sem() turns it on without any statement, and a product term
(x:m, see Product terms) always turns it on. A
latent variable's mean is fixed at 0; a free latent ~1 is an error. A repeated 1 for one
variable is an error.
A variable cannot appear on both sides of ~ or =~ (y ~ y), and a path,
a (co)variance or an intercept takes one row: a repeated y ~ x is an error,
not a second row that competes for the label.
x:m is the product of two observed variables, written with no spaces, on the
right side of a regression (y ~ x + m + x:m). As in lavaan, the product is an
observed exogenous variable: the engine builds the column x * m from data
after listwise deletion, with a free variance, free covariances with the other
exogenous variables and (the mean structure is on) a free mean. The likelihood
does not change. x:m and m:x are one term, stored and printed under the
canonical name, the two names sorted in the C locale (m:x); the coefficient is
named y~m:x, or by its label (b*x:m labels the whole product).
extract_mediation() finds the treatment-by-mediator product through the same
name and returns the four-way decomposition; centering the mediator moves the
split between the controlled direct effect and the reference interaction, not
the natural effects.
A column in data named m:x or x:m is used only if it equals x * m on the
rows kept; otherwise the fit stops. A product term turns the mean structure on,
and meanstructure = FALSE is then an error. A coefficient equals the
equation-wise least-squares estimate inside the class below, and the sandwich
covariance (se_type = "sandwich") is the standard-error choice, because a
product is not normal; the engine warns once when it is not used. fixed_x
includes the product columns.
| Limit | Message (starts with, or contains) |
A space around : |
"a product ':' needs a variable name on each side, written without spaces (x:m)" |
x:x |
"of a variable with itself is not supported" |
x:m:w |
"a three-way or higher product" |
Product as a left side (x:m ~ y, x:m ~~ y) |
"cannot be a left side" |
Product in =~ |
"is only valid on the right side of a regression" |
Product inside a constraint or := |
"not inside the expression" |
| A latent variable in a product | "products with latent variables are not supported in this version" |
y ~ x:m + m:x (one product twice in an equation) |
"are the same product (canonical name" |
A factor that is the target or downstream of it (y ~ x + x:y) |
"(rule 8: no outcome-derived regressors)" |
| A loop of regressions | "The model is not recursive" |
A residual covariance between outcomes (m1 ~~ m2) |
"cannot have a residual covariance (rule 9)" |
| A label shared by two rows | "is used on more than one parameter" |
A fixed coefficient (0.3*x, 0*x) |
"has a fixed coefficient (rule 9)" |
A bound (lower(0)*x) |
"has a bound (rule 9" |
==, < or > naming a coefficient label |
"names the coefficient label" |
| A fixed, shared or constrained intercept | "(rule 9, intercept clause" |
meanstructure = FALSE with a product |
"meanstructure = FALSE, but the model has a product term" |
A column m:x in data that is not x * m |
"is not the product of" |
| A non-numeric factor | "needs numeric variables" |
information = "expected", endogenous factor |
"with a product of an endogenous variable" |
anova() or mbco_sem() on such a fit |
"is not available for ... a model with a product term" |
Another product on the treatment or mediator, in extract_mediation() |
"does not support the product" |
Rules 8 and 9 are checked on the parsed table, so they need no data. Rule 8
rejects a regressor built from the outcome (it is correlated with the equation's
error); rule 9 names what is not yet supported with products, and the same
model works without the product. The covariance that completion would add
between two pure outcomes is fixed at 0 in a product model, with one note.
Products of observed variables only: a product with a latent variable, x:x and
three-way products are not supported.
The global chi-square is not valid when a factor of a product is itself a
regression target (the model treats the product as exogenous although it is a
function of that variable) or when the exogenous (co)variance block is
restricted. glance() then reports chi-square, df and p-value as NA with a
note, anova() and mbco_sem() refuse, and expected information is refused for
an endogenous product; logLik(), AIC and BIC stay defined. A product of
exogenous variables with the block free keeps the ordinary test.
Comma shorthand (medfit extension). A comma-separated left side
expands to one statement per name: y1, y2 ~ x means y1 ~ x and
y2 ~ x. Commas on the right side are not supported; use +.
lavaan reads y1, y2 ~ x differently: it silently keeps only y2.
A model copied between the two packages can therefore change meaning
without an error.
CONSTRAINT(...) (medfit extension). A constraint line may be
written CONSTRAINT(a == b), equivalent to the bare a == b. The keyword
is case-sensitive and starts the statement, one pair of parentheses
encloses the whole constraint, it holds exactly one comparison, and no
text follows the closing parenthesis.
Both sides of a constraint and the right side of a definition are
arithmetic expressions in the labels, with + - * / ^, parentheses, finite
numbers, and the functions exp, log, log10, sqrt, abs, min, max,
sin, cos, tan, pnorm and qnorm. Anything else is
rejected with the construct named.
# Labeled paths and a defined parameter fit_sem( "mediator1 ~ a*treatment\noutcome ~ b*mediator1 + cp*treatment\nab := a*b", data = mediation_demo )@defined # Comma shorthand: one left-hand side per name coef(fit_sem( "mediator1, mediator2 ~ treatment\noutcome ~ mediator1 + mediator2 + treatment", data = mediation_demo ))[1:3] # An equality constraint, written with the CONSTRAINT() keyword coef(fit_sem( "mediator1 ~ a*treatment\noutcome ~ b*mediator1 + treatment\nCONSTRAINT(a == b)", data = mediation_demo ))[c("a", "b")]# Labeled paths and a defined parameter fit_sem( "mediator1 ~ a*treatment\noutcome ~ b*mediator1 + cp*treatment\nab := a*b", data = mediation_demo )@defined # Comma shorthand: one left-hand side per name coef(fit_sem( "mediator1, mediator2 ~ treatment\noutcome ~ mediator1 + mediator2 + treatment", data = mediation_demo ))[1:3] # An equality constraint, written with the CONSTRAINT() keyword coef(fit_sem( "mediator1 ~ a*treatment\noutcome ~ b*mediator1 + treatment\nCONSTRAINT(a == b)", data = mediation_demo ))[c("a", "b")]
A simulated dataset for demonstrating medfit's simple, serial, parallel, and treatment-by-mediator interaction workflows with one running example. The data are simulated: they describe no real study, population, or finding.
mediation_demomediation_demo
A data frame with 400 rows and 8 variables:
Integer, 0/1. Randomized treatment assignment.
Numeric. First mediator; affected by treatment.
Numeric. Serial mediator; affected by treatment and
mediator1.
Numeric. Parallel mediator; affected by treatment only.
Numeric. Continuous confounder of the mediator-outcome relations.
Integer, 0/1. Binary confounder of the mediator-outcome relations.
Numeric. Outcome with no product terms; use it for the simple, serial, and parallel models.
Numeric. outcome plus a treatment by mediator1
interaction; use it only for models with that interaction: the four-way
decomposition with a single mediator, or joint effects with several
mediators (JointMediationData).
The variables follow a fictional workplace-training story, used only so the
examples read concretely: randomized assignment to a training program
(treatment) raises skill confidence (mediator1), which builds task
mastery (mediator2); the assignment also triggers supervisor check-ins
(mediator3); outcome is job performance.
All errors are independent standard normal. The generating equations are
and outcome_int adds .
Because the covariates confound the mediator-outcome relations, every model
should adjust for both covariate1 and covariate2. A model that omits a
downstream mediator estimates reduced-form coefficients rather than the
structural ones above: for example, the simple model outcome ~ treatment + mediator1 + covariates has limiting coefficients 0.55 for mediator1 and
0.41 for treatment. For a serial model, include mediator1 in the outcome
model as well; the serial indirect effect
is then the effect through the chain mediator1 to mediator2 only.
Simulated for package demonstration; not drawn from or representing
any real study. The generating script is data-raw/mediation_demo.R in
the package source repository.
data(mediation_demo) str(mediation_demo) # Simple mediation, adjusting for both covariates fit <- fit_mediation( formula_y = outcome ~ treatment + mediator1 + covariate1 + covariate2, formula_m = mediator1 ~ treatment + covariate1 + covariate2, data = mediation_demo, treatment = "treatment", mediator = "mediator1" ) nie(fit)data(mediation_demo) str(mediation_demo) # Simple mediation, adjusting for both covariates fit <- fit_mediation( formula_y = outcome ~ treatment + mediator1 + covariate1 + covariate2, formula_m = mediator1 ~ treatment + covariate1 + covariate2, data = mediation_demo, treatment = "treatment", mediator = "mediator1" ) nie(fit)
S7 class containing standardized mediation model structure, including path coefficients, parameter estimates, variance-covariance matrix, and metadata.
MediationData(a_path, b_path, c_prime, estimates, vcov, sigma_m, sigma_y, family_m, family_y, treatment, mediator, outcome, mediator_predictors, outcome_predictors, data, n_obs, converged, source_package)MediationData(a_path, b_path, c_prime, estimates, vcov, sigma_m, sigma_y, family_m, family_y, treatment, mediator, outcome, mediator_predictors, outcome_predictors, data, n_obs, converged, source_package)
a_path |
Numeric scalar: effect of treatment on mediator (a path) |
b_path |
Numeric scalar: effect of mediator on outcome (b path) |
c_prime |
Numeric scalar: direct effect of treatment on outcome (c' path) |
estimates |
Numeric vector: all parameter estimates |
vcov |
Numeric matrix: variance-covariance matrix of estimates |
sigma_m |
Numeric scalar or NULL: residual SD for mediator model |
sigma_y |
Numeric scalar or NULL: residual SD for outcome model |
family_m |
GLM |
family_y |
GLM |
treatment |
Character scalar: name of treatment variable |
mediator |
Character scalar: name of mediator variable |
outcome |
Character scalar: name of outcome variable |
mediator_predictors |
Character vector: predictor names in mediator model |
outcome_predictors |
Character vector: predictor names in outcome model |
data |
Data frame or NULL: original data |
n_obs |
Integer scalar: number of observations |
converged |
Logical scalar: whether models converged |
source_package |
Character scalar: package/engine used for fitting |
This class provides a unified container for mediation model information extracted from various model types (lm, glm, lavaan, etc.). It ensures consistency across the mediation analysis ecosystem.
The class includes comprehensive validation to ensure data integrity.
A MediationData S7 object
# Create a MediationData object med_data <- MediationData( a_path = 0.5, b_path = 0.3, c_prime = 0.2, estimates = c(0.5, 0.3, 0.2), vcov = diag(3) * 0.01, sigma_m = 1.0, sigma_y = 1.2, treatment = "X", mediator = "M", outcome = "Y", mediator_predictors = "X", outcome_predictors = c("X", "M"), data = NULL, n_obs = 100L, converged = TRUE, source_package = "stats" )# Create a MediationData object med_data <- MediationData( a_path = 0.5, b_path = 0.3, c_prime = 0.2, estimates = c(0.5, 0.3, 0.2), vcov = diag(3) * 0.01, sigma_m = 1.0, sigma_y = 1.2, treatment = "X", mediator = "M", outcome = "Y", mediator_predictors = "X", outcome_predictors = c("X", "M"), data = NULL, n_obs = 100L, converged = TRUE, source_package = "stats" )
Extract the natural direct effect from a mediation analysis result. The NDE represents the effect of treatment on outcome that does NOT operate through the mediator(s).
nde(x, ...)nde(x, ...)
x |
A MediationData, SerialMediationData, ParallelMediationData, InteractionMediationData, or JointMediationData object. |
... |
Additional arguments passed to methods |
Without a treatment-by-mediator product (MediationData,
SerialMediationData, ParallelMediationData) the NDE is the direct-path
coefficient, . With a product it also depends on the
mediator's mean under no treatment:
InteractionMediationData: .
JointMediationData: , with
the mean of mediator under no treatment at the covariate means.
A numeric scalar of class mediation_effect carrying a type
attribute.
nie(), te(), pm(), paths(), decompose()
med_data <- fit_mediation( formula_y = outcome ~ treatment + mediator1 + covariate1 + covariate2, formula_m = mediator1 ~ treatment + covariate1 + covariate2, data = mediation_demo, treatment = "treatment", mediator = "mediator1" ) nde(med_data)med_data <- fit_mediation( formula_y = outcome ~ treatment + mediator1 + covariate1 + covariate2, formula_m = mediator1 ~ treatment + covariate1 + covariate2, data = mediation_demo, treatment = "treatment", mediator = "mediator1" ) nde(med_data)
Extract the natural indirect effect from a mediation analysis result. The NIE represents the effect of treatment on outcome that operates through the mediator(s).
nie(x, ...)nie(x, ...)
x |
A MediationData, SerialMediationData, ParallelMediationData,
InteractionMediationData, JointMediationData, or BootstrapResult
object. For a |
... |
Additional arguments passed to methods. For a
|
The effects are for a unit contrast of the treatment. By class:
MediationData: .
SerialMediationData: with type = "chain" (the default), the
effect through the full chain only,
;
with type = "total", the total indirect effect, the sum over every
treatment-to-outcome path through at least one mediator (including paths
that skip a mediator, such as X -> M2 -> Y), which equals
te(x) - nde(x).
ParallelMediationData: .
InteractionMediationData: .
JointMediationData: the joint NIE through all the mediators,
.
The "Methods and Formulas" article on the package website gives the full formulas.
A numeric scalar of class mediation_effect carrying a type
attribute. For intervals use confint() or bootstrap_mediation().
nde(), te(), pm(), paths(), decompose()
med_data <- fit_mediation( formula_y = outcome ~ treatment + mediator1 + covariate1 + covariate2, formula_m = mediator1 ~ treatment + covariate1 + covariate2, data = mediation_demo, treatment = "treatment", mediator = "mediator1" ) nie(med_data)med_data <- fit_mediation( formula_y = outcome ~ treatment + mediator1 + covariate1 + covariate2, formula_m = mediator1 ~ treatment + covariate1 + covariate2, data = mediation_demo, treatment = "treatment", mediator = "mediator1" ) nie(med_data)
S7 class for parallel mediation, where a treatment affects an outcome
through two or more independent mediators operating in parallel
( for
). The total indirect effect is the sum
of the per-mediator products, .
This complements MediationData (simple) and SerialMediationData
(serial chains).
ParallelMediationData(a_paths, b_paths, c_prime, estimates, vcov, sigma_mediators, sigma_y, treatment, mediators, outcome, mediator_predictors, outcome_predictors, data, n_obs, converged, source_package)ParallelMediationData(a_paths, b_paths, c_prime, estimates, vcov, sigma_mediators, sigma_y, treatment, mediators, outcome, mediator_predictors, outcome_predictors, data, n_obs, converged, source_package)
a_paths |
Numeric vector: treatment -> mediator effects
|
b_paths |
Numeric vector: mediator -> outcome effects
|
c_prime |
Numeric scalar: direct effect |
estimates |
Numeric vector of all parameter estimates. |
vcov |
Square variance-covariance matrix of |
sigma_mediators |
Optional numeric vector of mediator residual SDs (length k), or NULL. |
sigma_y |
Optional numeric scalar outcome residual SD, or NULL. |
treatment, outcome
|
Single character strings naming the treatment / outcome. |
mediators |
Character vector of mediator names (length k, unique). |
mediator_predictors |
List of predictor-name vectors, one per mediator. |
outcome_predictors |
Character vector of outcome-model predictor names. |
data |
Optional data frame, or NULL. |
n_obs |
Integer number of observations. |
converged |
Logical convergence flag. |
source_package |
Character name of the originating package. |
A ParallelMediationData S7 object.
pmd <- ParallelMediationData( a_paths = c(0.5, 0.4), b_paths = c(0.6, 0.3), c_prime = 0.2, estimates = c(0.5, 0.4, 0.6, 0.3, 0.2), vcov = diag(0.01, 5), treatment = "X", mediators = c("M1", "M2"), outcome = "Y", mediator_predictors = list("X", "X"), outcome_predictors = c("X", "M1", "M2"), n_obs = 200L, converged = TRUE, source_package = "medfit" ) nie(pmd) # total indirect effect: sum(a_j * b_j) = 0.42 paths(pmd) # a1, b1, a2, b2, c_primepmd <- ParallelMediationData( a_paths = c(0.5, 0.4), b_paths = c(0.6, 0.3), c_prime = 0.2, estimates = c(0.5, 0.4, 0.6, 0.3, 0.2), vcov = diag(0.01, 5), treatment = "X", mediators = c("M1", "M2"), outcome = "Y", mediator_predictors = list("X", "X"), outcome_predictors = c("X", "M1", "M2"), n_obs = 200L, converged = TRUE, source_package = "medfit" ) nie(pmd) # total indirect effect: sum(a_j * b_j) = 0.42 paths(pmd) # a1, b1, a2, b2, c_prime
Extract all path coefficients from a mediation analysis result.
paths(x, ...)paths(x, ...)
x |
A MediationData, SerialMediationData, ParallelMediationData, InteractionMediationData, or JointMediationData object. |
... |
Additional arguments passed to methods |
For simple mediation (MediationData):
a: Treatment -> Mediator (X -> M)
b: Mediator -> Outcome (M -> Y | X)
c_prime: Direct effect (X -> Y | M)
For serial mediation (SerialMediationData):
a: Treatment -> First mediator
d (two mediators) or d21, d32, ...: Mediator-to-mediator paths
b: Last mediator -> Outcome
c_prime: Direct effect
For parallel mediation (ParallelMediationData): a1, b1, a2, b2,
..., c_prime.
For InteractionMediationData: a, b, c_prime, and theta3 (the
treatment-by-mediator coefficient).
For JointMediationData: the raw coefficients a1..aK, dij, b1..bK,
theta3_<mediator>, and c_prime. The a paths here are the raw
coefficients, not the propagated a* values used by the effects.
A named numeric vector of path coefficients
med_data <- fit_mediation( formula_y = outcome ~ treatment + mediator1 + covariate1 + covariate2, formula_m = mediator1 ~ treatment + covariate1 + covariate2, data = mediation_demo, treatment = "treatment", mediator = "mediator1" ) paths(med_data)med_data <- fit_mediation( formula_y = outcome ~ treatment + mediator1 + covariate1 + covariate2, formula_m = mediator1 ~ treatment + covariate1 + covariate2, data = mediation_demo, treatment = "treatment", mediator = "mediator1" ) paths(med_data)
Extract the proportion of the total effect that is mediated (operates through the mediator(s)).
pm(x, ...)pm(x, ...)
x |
A MediationData, SerialMediationData, ParallelMediationData, InteractionMediationData, or JointMediationData object. |
... |
Additional arguments passed to methods |
For serial mediation (SerialMediationData) the numerator is the total
indirect effect, nie(x, type = "total"), and the denominator the full
total effect from te().
The proportion mediated can be:
Between 0 and 1: Normal mediation
Greater than 1: Suppression (direct and indirect effects have opposite signs)
Negative: Inconsistent mediation
The ratio has no delta-method standard error in medfit; bootstrap it with
bootstrap_mediation().
A numeric scalar of class mediation_effect, usually between 0 and
1 (negative or greater than 1 in cases of suppression effects), or
NA with a warning when the total effect is numerically zero.
med_data <- fit_mediation( formula_y = outcome ~ treatment + mediator1 + covariate1 + covariate2, formula_m = mediator1 ~ treatment + covariate1 + covariate2, data = mediation_demo, treatment = "treatment", mediator = "mediator1" ) pm(med_data)med_data <- fit_mediation( formula_y = outcome ~ treatment + mediator1 + covariate1 + covariate2, formula_m = mediator1 ~ treatment + covariate1 + covariate2, data = mediation_demo, treatment = "treatment", mediator = "mediator1" ) pm(med_data)
Print a one-line summary of mediation results, perfect for quick checks or ADHD-friendly workflows.
quick(x, digits = 3, ...)quick(x, digits = 3, ...)
x |
A MediationData or SerialMediationData object (or result from
|
digits |
Integer: number of significant digits (default: 3) |
... |
Additional arguments (ignored) |
Prints a compact one-line summary showing:
NIE (Natural Indirect Effect) with CI if available
NDE (Natural Direct Effect)
Proportion Mediated (PM)
If bootstrap results are available (from med(..., boot = TRUE)),
confidence intervals are shown for NIE.
Invisibly returns x
result <- med( data = mediation_demo, treatment = "treatment", mediator = "mediator1", outcome = "outcome", covariates = c("covariate1", "covariate2") ) # One-line summary quick(result)result <- med( data = mediation_demo, treatment = "treatment", mediator = "mediator1", outcome = "outcome", covariates = c("covariate1", "covariate2") ) # One-line summary quick(result)
S7 class holding a structural equation model fitted by the native
maximum-likelihood engine (see fit_sem()): estimates, their covariance,
the parameter table, defined parameters (:=) with delta-method standard
errors, and the diagnostics of the optimizer.
SEMFit(theta, vcov, table, defined, f, n_obs, n_dropped, df, information, converged, data, model, diagnostics, internals, call)SEMFit(theta, vcov, table, defined, f, n_obs, n_dropped, df, information, converged, data, model, diagnostics, internals, call)
theta |
Named numeric vector: free-parameter estimates |
vcov |
Numeric matrix: covariance of |
table |
Data frame: one row per parameter-table row with |
defined |
Data frame of |
f |
Numeric scalar: minimized maximum-likelihood discrepancy |
n_obs, n_dropped
|
Integer scalars: rows used and rows dropped for missing values (listwise deletion) |
df |
Numeric scalar: degrees of freedom |
information |
Character scalar: |
converged |
Logical scalar: the acceptance gate passed. |
data |
Data frame: the rows used, on the model's observed variables |
model |
Character scalar: the model syntax |
diagnostics |
List: optimizer diagnostics ( |
internals |
List: the internal index objects the engine needs
( |
call |
Call object or NULL: the call that produced the fit |
A SEMFit S7 object
fit <- fit_sem( "mediator1 ~ a*treatment\noutcome ~ b*mediator1 + treatment\nab := a*b", data = mediation_demo ) fit@table[fit@table$op == "~", c("lhs", "rhs", "est", "se")] fit@defined fit@convergedfit <- fit_sem( "mediator1 ~ a*treatment\noutcome ~ b*mediator1 + treatment\nab := a*b", data = mediation_demo ) fit@table[fit@table$op == "~", c("lhs", "rhs", "est", "se")] fit@defined fit@converged
Accessors and display for a SEMFit returned by fit_sem().
object, x
|
A SEMFit object |
... |
Additional arguments (ignored) |
coef() returns the free-parameter estimates, named as lavaan names its
coefficients: the label when the row has one, else lhs op rhs without
spaces ("M~X", "X~~X", "eta=~m2"). vcov(), tidy() and the
accessors extract_mediation() reads use the same names.
vcov() returns their covariance, projected onto the active constraints
and bounds; rows for a pinned parameter are zero.
nobs() returns the number of rows used after listwise deletion.
logLik() returns the log likelihood with the means free; its df
attribute counts the free parameters plus one mean per observed variable
that is not fixed. With fixed_x it is conditional on the fixed variables
(lavaan's value), so AIC() and BIC() of fits with different fixed sets
or data stop instead of ranking them; the same holds for anova() and
mbco_sem(). A fixed_x fit also lists its fixed moments after the free
parameters in tidy() (fixed_x column TRUE, no standard error) and in
summary(); coef() and vcov() leave them out. print() and summary()
say what the log likelihood is conditional on.
Model test with a product term. The global chi-square is not valid when
a factor of a product term (x:m) is itself a regression target, because
the model then treats the product as exogenous although it is a function of
m; nor when the variances and covariances of the exogenous variables
(every product included) are restricted (fixed, labeled, bounded, or left
out because a ~~ statement names one of them; a fixed_x moment is not a
restriction). fit_sem() then warns once, glance() reports chisq, df
and p_value as NA (with the reason in attr(, "note")), summary()
prints the reason, and anova() and mbco_sem() stop naming the product.
The coefficients equal equation-wise least squares and the sandwich
standard errors are unaffected, but only with observed information:
fit_sem(information = "expected") stops for a product with an endogenous
factor, because its standard errors (the sandwich built on them included)
are wrong there. fit_sem() also warns once for any product term fitted
without se_type = "sandwich". logLik(), AIC() and BIC() stay
defined.
anova() tests nested fits by the likelihood-ratio statistic
on the difference in model degrees
of freedom: anova(fit_full, fit_restricted), or several fits, each tested
against the one with fewer degrees of freedom. It compares fits that were
already made and never refits. The fits must use the same observed
variables and rows, and neither may have sampling weights or have failed to
converge. Nesting is checked by parameter-table inclusion: the restricted
fit may free no parameter the other leaves out and must have more degrees
of freedom. That is necessary for nesting, not sufficient; whether two
models are nested in substance is the analyst's responsibility. A
restricted fit with a smaller discrepancy than the model it restricts is an
error (one fit is not at its global optimum). When a bound or an
inequality on a tested parameter is active at the restricted fit, the null
lies on the boundary of the parameter space, the statistic is a mixture of
chi-squares, and the function warns that the p-value is not valid, as
mbco_sem() does. Returns an object of class "anova" with columns Df,
Chisq (n times the discrepancy), Chisq diff, Df diff and
Pr(>Chisq). Name rows by naming the arguments after the first.
confint() returns Wald (normal) intervals for the free parameters and
the defined parameters, confint(fit, parm, level = 0.95); parm takes
names or positions. A parameter pinned by a constraint or sitting on a
bound has a zero standard error and a point interval.
tidy() returns one row per free parameter, then the moments fixed_x fixed (a fixed_x
column, only when something is fixed), then per defined parameter:
term (the name coef() uses), op (the operator of its row in the
parameter table, := for a defined parameter), estimate, std_error,
statistic (the z value, NA when the standard error is zero),
p_value (two-sided), and with conf_int = TRUE the interval
confint() returns (conf_low, conf_high, at conf_level). type
is "all" (default), "parameters" or "defined".
glance() returns one row: nobs, npar (free parameters after the
equality constraints), df, chisq (n times the minimized discrepancy,
the test of the model against the saturated one), p_value (NA when
df is 0), logLik, AIC and BIC (with the means free, as logLik()
defines them) and fixed_x, the names the fit holds at their sample moments,
comma-joined ("" when none). A fit with sampling weights has no likelihood, so its
chisq, p_value, logLik, AIC and BIC are NA. There is no
converged column: fit_sem() returns a fit only for a solution that
passed the acceptance gate and errors otherwise, so @converged is TRUE
for every SEMFit and would carry no information.
summary() collects the estimates, standard errors and z statistics, the
defined parameters, and the diagnostics: rows dropped, information type,
degrees of freedom, retries, stationarity, active bounds and constraints,
improper-solution flags and the multiplier sign check.
coef() a named numeric vector; vcov() a matrix; nobs() an
integer; logLik() an object of class "logLik"; summary() an object of
class "summary_SEMFit"; print() invisibly returns its argument.
fit <- fit_sem( "mediator1 ~ a*treatment\noutcome ~ b*mediator1 + treatment\nab := a*b", data = mediation_demo ) coef(fit) vcov(fit)[c("a", "b"), c("a", "b")] nobs(fit) summary(fit) generics::tidy(fit, conf_int = TRUE) generics::glance(fit) # a likelihood-ratio test of b = 0 against the fitted model fit0 <- fit_sem(paste0(fit@model, "\nb == 0"), data = mediation_demo) anova(fit, fit0)fit <- fit_sem( "mediator1 ~ a*treatment\noutcome ~ b*mediator1 + treatment\nab := a*b", data = mediation_demo ) coef(fit) vcov(fit)[c("a", "b"), c("a", "b")] nobs(fit) summary(fit) generics::tidy(fit, conf_int = TRUE) generics::glance(fit) # a likelihood-ratio test of b = 0 against the fitted model fit0 <- fit_sem(paste0(fit@model, "\nb == 0"), data = mediation_demo) anova(fit, fit0)
S7 class for serial mediation models where the effect flows through multiple mediators in sequence: X -> M1 -> M2 -> ... -> Mk -> Y.
This class supports serial mediation chains of any length, from simple two-mediator models (product-of-three: a * d * b) to complex chains with many mediators (product-of-k).
SerialMediationData(a_path, d_path, b_path, c_prime, estimates, vcov, sigma_mediators, sigma_y, treatment, mediators, outcome, mediator_predictors, outcome_predictors, data, n_obs, converged, source_package)SerialMediationData(a_path, d_path, b_path, c_prime, estimates, vcov, sigma_mediators, sigma_y, treatment, mediators, outcome, mediator_predictors, outcome_predictors, data, n_obs, converged, source_package)
a_path |
Numeric scalar: effect of treatment on first mediator (X -> M1) |
d_path |
Numeric vector: sequential mediator-to-mediator effects
|
b_path |
Numeric scalar: effect of last mediator on outcome (Mk -> Y) |
c_prime |
Numeric scalar: direct effect of treatment on outcome (X -> Y) |
estimates |
Numeric vector: all parameter estimates |
vcov |
Numeric matrix: variance-covariance matrix of estimates |
sigma_mediators |
Numeric vector or NULL: residual SDs for mediator models. Length should match number of mediators. First element is residual SD for M1 model, second element for M2 model, etc. |
sigma_y |
Numeric scalar or NULL: residual SD for outcome model |
treatment |
Character scalar: name of treatment variable |
mediators |
Character vector: names of mediators in sequential order. First element is M1, second element is M2, etc. |
outcome |
Character scalar: name of outcome variable |
mediator_predictors |
List of character vectors: predictor names for each mediator model. First list element contains predictors for M1 (typically just "X"), second element contains predictors for M2 (typically c("X", "M1")), etc. |
outcome_predictors |
Character vector: predictor names in outcome model |
data |
Data frame or NULL: original data |
n_obs |
Integer scalar: number of observations |
converged |
Logical scalar: whether all models converged |
source_package |
Character scalar: package/engine used for fitting |
Serial mediation models the indirect effect flowing through a sequence of mediators. The indirect effect through the full chain is the product of the chain's path coefficients:
2 mediators (product-of-three): Indirect = a * d * b
3 mediators (product-of-four): Indirect = a * d21 * d32 * b
k mediators (product-of-k+1): Indirect = a * d21 * d32 * ... * d(k,k-1) * b
This is the effect through the chain only, and what nie() returns by
default. Paths that skip a mediator (X -> M2, M1 -> Y, ...) are not part
of it; nie(x, type = "total") adds them, and te() is the full total
effect over every path, so pm() is the total indirect share of it. With
treatment-by-mediator products in the outcome model, extract_mediation()
returns a JointMediationData object with the effect through all the
mediators together.
a: Treatment -> First mediator (X -> M1)
d21: First -> Second mediator (M1 -> M2)
d32: Second -> Third mediator (M2 -> M3)
dji: Previous mediator -> Current mediator
b: Last mediator -> Outcome (Mk -> Y)
c': Direct effect (X -> Y, controlling for all mediators)
This class is designed to handle serial chains of any length:
Minimal case: 2 mediators (length(d_path) = 1)
No upper limit on chain length
Validator ensures consistency between mediators and paths
A SerialMediationData S7 object
# Two-mediator serial mediation (X -> M1 -> M2 -> Y) # Product-of-three: a * d * b serial_data <- SerialMediationData( a_path = 0.5, # X -> M1 d_path = 0.4, # M1 -> M2 (scalar for 2 mediators) b_path = 0.3, # M2 -> Y c_prime = 0.1, # X -> Y (direct) estimates = c(0.5, 0.4, 0.3, 0.1), vcov = diag(4) * 0.01, sigma_mediators = c(1.0, 1.1), # SD for M1, M2 models sigma_y = 1.2, treatment = "X", mediators = c("M1", "M2"), outcome = "Y", mediator_predictors = list( c("X"), # M1 ~ X c("X", "M1") # M2 ~ X + M1 ), outcome_predictors = c("X", "M1", "M2"), # Y ~ X + M1 + M2 data = NULL, n_obs = 100L, converged = TRUE, source_package = "lavaan" ) # Three-mediator serial mediation (X -> M1 -> M2 -> M3 -> Y) # Product-of-four: a * d21 * d32 * b serial_data_3 <- SerialMediationData( a_path = 0.5, # X -> M1 d_path = c(0.4, 0.35), # M1 -> M2, M2 -> M3 (vector for 3 mediators) b_path = 0.3, # M3 -> Y c_prime = 0.1, estimates = c(0.5, 0.4, 0.35, 0.3, 0.1), vcov = diag(5) * 0.01, sigma_mediators = c(1.0, 1.1, 1.05), # SD for M1, M2, M3 models sigma_y = 1.2, treatment = "X", mediators = c("M1", "M2", "M3"), outcome = "Y", mediator_predictors = list( c("X"), # M1 ~ X c("X", "M1"), # M2 ~ X + M1 c("X", "M1", "M2") # M3 ~ X + M1 + M2 ), outcome_predictors = c("X", "M1", "M2", "M3"), data = NULL, n_obs = 100L, converged = TRUE, source_package = "lavaan" )# Two-mediator serial mediation (X -> M1 -> M2 -> Y) # Product-of-three: a * d * b serial_data <- SerialMediationData( a_path = 0.5, # X -> M1 d_path = 0.4, # M1 -> M2 (scalar for 2 mediators) b_path = 0.3, # M2 -> Y c_prime = 0.1, # X -> Y (direct) estimates = c(0.5, 0.4, 0.3, 0.1), vcov = diag(4) * 0.01, sigma_mediators = c(1.0, 1.1), # SD for M1, M2 models sigma_y = 1.2, treatment = "X", mediators = c("M1", "M2"), outcome = "Y", mediator_predictors = list( c("X"), # M1 ~ X c("X", "M1") # M2 ~ X + M1 ), outcome_predictors = c("X", "M1", "M2"), # Y ~ X + M1 + M2 data = NULL, n_obs = 100L, converged = TRUE, source_package = "lavaan" ) # Three-mediator serial mediation (X -> M1 -> M2 -> M3 -> Y) # Product-of-four: a * d21 * d32 * b serial_data_3 <- SerialMediationData( a_path = 0.5, # X -> M1 d_path = c(0.4, 0.35), # M1 -> M2, M2 -> M3 (vector for 3 mediators) b_path = 0.3, # M3 -> Y c_prime = 0.1, estimates = c(0.5, 0.4, 0.35, 0.3, 0.1), vcov = diag(5) * 0.01, sigma_mediators = c(1.0, 1.1, 1.05), # SD for M1, M2, M3 models sigma_y = 1.2, treatment = "X", mediators = c("M1", "M2", "M3"), outcome = "Y", mediator_predictors = list( c("X"), # M1 ~ X c("X", "M1"), # M2 ~ X + M1 c("X", "M1", "M2") # M3 ~ X + M1 + M2 ), outcome_predictors = c("X", "M1", "M2", "M3"), data = NULL, n_obs = 100L, converged = TRUE, source_package = "lavaan" )
Extract the total effect from a mediation analysis result: the sum of the indirect and direct effects.
te(x, ...)te(x, ...)
x |
A MediationData, SerialMediationData, ParallelMediationData, InteractionMediationData, or JointMediationData object. |
... |
Additional arguments passed to methods |
For a SerialMediationData object the total effect is the sum over
every directed X-to-Y path in the fitted models: the direct path, the full
chain, and every path that skips a mediator (for two mediators,
).
With the same covariates in every equation and linear models this equals
the treatment coefficient of the outcome regressed on the treatment and
covariates alone. A path missing from its model counts as zero; when a path
is in a model but its coefficient was not recorded (a hand-built object),
te() returns NA with a warning. For glm fits with a non-identity link
the sum of path products is on the linear-predictor scale, as for
MediationData.
A numeric scalar of class mediation_effect carrying a type
attribute.
nie(), nde(), pm(), paths(), decompose()
med_data <- fit_mediation( formula_y = outcome ~ treatment + mediator1 + covariate1 + covariate2, formula_m = mediator1 ~ treatment + covariate1 + covariate2, data = mediation_demo, treatment = "treatment", mediator = "mediator1" ) te(med_data) # Verify: TE = NIE + NDE nie(med_data) + nde(med_data)med_data <- fit_mediation( formula_y = outcome ~ treatment + mediator1 + covariate1 + covariate2, formula_m = mediator1 ~ treatment + covariate1 + covariate2, data = mediation_demo, treatment = "treatment", mediator = "mediator1" ) te(med_data) # Verify: TE = NIE + NDE nie(med_data) + nde(med_data)
tidy() converts a mediation data object or a BootstrapResult into a
tidy tibble, one row per path coefficient or effect. glance() returns a
one-row summary. The base generics stats::coef(), stats::vcov(),
stats::confint(), and stats::nobs() also have methods for every
mediation class; see Details.
tidy_s7_object(x, ...) glance_s7_object(x, ...)tidy_s7_object(x, ...) glance_s7_object(x, ...)
x |
A MediationData, SerialMediationData, ParallelMediationData,
InteractionMediationData, JointMediationData, ClusterMediationData,
BootstrapResult, or SEMFit object (a |
... |
Passed to the class method: |
Path standard errors are the square roots of the diagonal of @vcov.
Effect standard errors (NIE, NDE, TE, and for interaction objects the
four-way components) use the delta method over the full @vcov, the same
computation as confint(parm = "effects"), so tidy(conf_int = TRUE)
reproduces confint() exactly. Intervals are normal approximations
(); the sampling
distribution of a product of coefficients is skewed, so for inference on
indirect effects prefer bootstrap_mediation(). tidy() raises no warning
about this; confint() does.
For a serial chain fitted as separate lm/glm regressions, @vcov has zero
covariances between equations, and the effect standard errors inherit that.
The proportion mediated (reported by glance()) has no standard error: it
is a ratio whose delta-method standard error is unstable when the total
effect is near zero, so bootstrap it instead.
coef(object, type = "paths"): the path coefficients; type = "effects"
gives the effects, "all" both, and for interaction objects
"components" gives the four-way components.
vcov(object): the stored @vcov, covering every entry of @estimates.
confint(object, parm = "paths", level = 0.95): normal intervals for the
paths, or with parm = "effects" for the effects using the delta-method
standard errors above (interaction objects also accept
parm = "components"). With parm = "effects" it warns that the normal
approximation may be inaccurate for an indirect effect.
nobs(object): the number of observations.
tidy(): a tibble (a data frame if tibble is not installed) with
columns term, estimate, std_error, and, when conf_int = TRUE,
conf_low and conf_high.
glance(): a one-row tibble with nie, nde, te, pm, nobs, and
converged; interaction objects add interaction and m_star, and
joint objects add cde, structure, n_mediators, interactions, and
m_star.
bootstrap_mediation(), MediationData
med_data <- fit_mediation( formula_y = outcome ~ treatment + mediator1 + covariate1 + covariate2, formula_m = mediator1 ~ treatment + covariate1 + covariate2, data = mediation_demo, treatment = "treatment", mediator = "mediator1" ) # tidy() is the generic from the generics package (also re-exported by broom) generics::tidy(med_data) generics::tidy(med_data, type = "effects", conf_int = TRUE)med_data <- fit_mediation( formula_y = outcome ~ treatment + mediator1 + covariate1 + covariate2, formula_m = mediator1 ~ treatment + covariate1 + covariate2, data = mediation_demo, treatment = "treatment", mediator = "mediator1" ) # tidy() is the generic from the generics package (also re-exported by broom) generics::tidy(med_data) generics::tidy(med_data, type = "effects", conf_int = TRUE)