Conduct mediation analysis with time-varying mediators using the g-formula.
This function estimates total effect, natural direct effect, and natural indirect
effect for both natural mediation ("N") and interventional mediation ("I").
A data.frame summarizing the estimates will be returned.
Usage
mediation(
data,
id_var,
base_vars,
exposure,
outcome,
time_var,
models,
init_recode = NULL,
in_recode = NULL,
out_recode = NULL,
mc_sample = NULL,
mediation_type = c("I", "N"),
n_vw = 2L,
estimator = c("gcomp", "tmle"),
tmle_weight_trunc = 0.995,
return_fitted = FALSE,
return_data = FALSE,
R = 500,
quiet = FALSE,
seed = 12345L
)Arguments
- data
A
data.frame(long format): one row perid_varpertime_var.- id_var
Character scalar. Subject identifier column name.
- base_vars
Character vector of time-fixed baseline covariates (may be empty).
- exposure
Character scalar. Exposure variable to intervene on (must be in
data).- outcome
Character scalar. Name of the outcome variable in
data. Must match the response variable in the outcome or survival model.- time_var
Character scalar. Time variable column name (ordered; integer/numeric).
- models
A list of model specifications evaluated in temporal order. The order appeared in the list should reflect the temporal ordering of the variables, in another way data generation process. See
spec_modelfor a recommended constructor.- init_recode
Optional expression/function applied once at time 0 before the Monte Carlo loop (e.g., initializing baseline-derived variables). Should be defined with
recodes. See Details.- in_recode
Optional expression/function applied at the **start** of each time step (e.g., entry-time functional forms). Should be defined with
recodes. See Details.- out_recode
Optional expression/function applied at the **end** of each time step (e.g., create lags, cumulative counts). Should be defined with
recodes. See Details.- mc_sample
Integer. Size of the Monte Carlo cohort simulated under each intervention, counted in subjects. Defaults to
NULL, which resolves to 50 times the number of subjects indataand reports the value it chose unlessquiet = TRUE. Monte Carlo error falls as1/sqrt(mc_sample)while sampling error falls with the number of subjects, so the two stay in a fixed ratio whenmc_sampleis set as a multiple of the subject count; a larger multiple buys precision in the point estimate and does not change what is being estimated.- mediation_type
Character. Type of mediation effect:
"I"for interventional effect (default) or"N"for natural effect. The default was chosen because the interventional estimand remains identifiable in the general time-varying-confounder setting the package is designed for (see below). Identifiability. Natural direct and indirect effects ("N") are not identifiable from observational data when there exists a time-varying confounder of the mediator-outcome relationship that is itself affected by prior exposure (Avin, Shpitser & Pearl 2005; VanderWeele 2014; VanderWeele & Tchetgen Tchetgen 2017). For that setting VanderWeele & Tchetgen Tchetgen (2017) propose the randomized interventional analogues of the direct and indirect effects, whichmediation_type = "I"targets and which remain identifiable. A warning is emitted at runtime if"N"is chosen and any covariate model includes the exposure on its right-hand side — that is, a covariate the user has modelled as exposure-affected. The check reads the model formulas only: it does not establish that such a covariate also confounds the mediator-outcome relationship, nor does its silence establish that no intermediate confounder exists. Judging the causal structure remains the analyst's responsibility. The caveat is repeated byprint(). Interpretation of"I". Randomized interventional indirect effects buy identifiability at a price: they do not satisfy a sharp mediational null criterion, so a non-zero IIE does not by itself prove that the mediator transmits the effect for any individual (Miles 2023). The choice between"I"and"N"is therefore a substantive one, not merely computational.- n_vw
Integer. Number of independent permutation draws averaged for each intervention that draws its mediators from a permuted pool. Under
mediation_type = "I"that is every intervention in the decomposition — the referencesPhi00andPhi11just as much as the cross-worldPhi10andPhi1_k— but not the natural-course interventionsnat0/nat1, whose mediators come from their own fitted models and involve no permutation. The same averaging is applied within every bootstrap replicate. Default2Lto match the SASmGFORMULAmacro'sn_vw = 2. Set to1Lto disable averaging (faster, slightly noisier Monte Carlo). Has no effect whenmediation_type = "N"(the natural-effect mediator swap is not permutation-based).- estimator
Character.
"gcomp"(default) for the parametric g-formula plug-in (Monte Carlo simulation + bootstrap CIs), or"tmle"for the targeted maximum likelihood estimator of Zheng & van der Laan (2017, Section 4.3)."tmle"is available formediation_type = "N"only (single mediator, binary {0, 1} outcome or survival event indicator; an exposure model is required andvar_type = "custom"/spec_model(subset = )are not supported). Recode restriction: the targeted engine evaluates only lag-style recodes, soin_recodeentries must copy a single column (e.g.recodes(lag_A = A)), lags of the exposure must copy the exposure itself (chained exposure lags such asrecodes(lag2_A = lag_A)are rejected; deeper exposure history requiresestimator = "gcomp"),init_recodeentries must be a constant or a single column name, andout_recodeis not supported; derived recodes (splines, cumulative counts, carry-forward flags) requireestimator = "gcomp". Violations are rejected with an error rather than silently ignored. The TMLE uses backward iterated regressions with logistic fluctuations instead of forward simulation: it is multiply robust (consistent when specific subsets of the nuisance models are correct, not only when all are), reports Wald CIs from the efficient influence curve without bootstrapping (Ris ignored), and has no Monte Carlo simulation error (mc_sampleis ignored). Working-model form: the models you supply are used as written for the conditional densities that form the clever covariates, but the targeted sequential regressions are constructed as additive main-effects models in the variables appearing in those formulas. Transformations and interactions (poly(), splines,A:M) are therefore not carried into the sequential regressions. This is a property of this implementation, not of the estimator as published; the theoretical results of Zheng & van der Laan (2017) are stated for correctly specified nuisance models. Practical positivity violations (few subjects following an intervened regime) are handled by skipping the affected fluctuation steps and truncating extreme weights, with collected warnings — inspect these before trusting the affected functionals.- tmle_weight_trunc
Numeric in (0, 1]. Quantile at which each clever-covariate weight vector is truncated in the TMLE fluctuation steps (positivity protection). Default
0.995;1disables truncation. Ignored forestimator = "gcomp".- return_fitted
Logical. If
TRUE, return full fitted model objects; otherwise, a light-weight summary (call and coefficients). DefaultFALSE.- return_data
Logical. If
TRUE, return the stacked simulated data (all interventions) including predicted outcomes; may be large. DefaultFALSE.- R
Number of bootstrap replicates. If
R > 1, computation usesfuture.apply::future_lapplyand runs sequentially unless a parallel plan is set; seefuture::plan. Useplan(multisession)on Windows andplan(multicore)on Unix-alikes to enable parallel bootstrap. Default500.- quiet
Logical. If
TRUE, suppress progress messages/bars. DefaultFALSE.- seed
Integer random seed for reproducibility. Default
12345L. Settingseedfixes the RNG stream used for the original-data Monte Carlo simulation and the bootstrap replicates (the latter via thefuture_seedinterface of future.apply); the caller's global RNG state is saved and restored on exit, so a seeded call does not disturb the ambient random stream. PassNULLto disable seeding entirely: noset.seed()is called, the Monte Carlo draws consume and advance the ambient RNG stream, and repeated calls therefore give different results (reproducible only via an outerset.seed()). Useseed = NULLinside simulation loops that manage their own seeds. Users running multiple seeded analyses in the same session should set distinct seeds to ensure independent bootstrap draws across analyses.
Value
An object of class "gformula" with components:
call: the matched function call.all.args: a named list of evaluated arguments for reproducibility.effect_size: adata.tablewith one row per simulated intervention (columnsInterventionandEst). Formediation_type = "I"the mediator(s) in every decomposition intervention are drawn from independently-permuted marginal pools (stochastic draws \(G\)):Phi00(= \(E[Y_{0,G_0}]\)) andPhi11(= \(E[Y_{1,G_1}]\)) are the interventional reference interventions,Phi10(= \(E[Y_{1,G_0}]\)) is the cross-world intervention, and for \(N \ge 2\) mediatorsPhi1_k(k = 1, …, N-1) capture the sequential per-mediator transition. Two additional natural-course interventionsnat0(= \(E[Y_0]\)) andnat1(= \(E[Y_1]\)) give the plug-in total effect. Formediation_type = "N"the interventionsPhi00/Phi11are the natural never-/always-treat interventions (no permutation). WithR > 1the table also carriesSd,perct_lcl/perct_ucl, andnorm_lcl/norm_ucl.estimate: adata.tablesummarizing the decomposition of effects. For a single mediator undermediation_type = "I"the rows are:"Indirect effect"= \(E[Y_{1,G_1}] - E[Y_{1,G_0}]\)"Direct effect"= \(E[Y_{1,G_0}] - E[Y_{0,G_0}]\)"Total effect"= \(E[Y_1] - E[Y_0]\) (natural plug-in g-formula; not the sum of the components)"TE - (Direct + Indirect)"= the mediated-interaction residual \(TE - (IDE + IIE)\) = \(TE\) minus the interventional overall effect \(E[Y_{1,G_1}] - E[Y_{0,G_0}]\) (generally non-zero for interventional effects; this row is absent formediation_type = "N", where the decomposition sums exactly to \(TE\))."Mediation Proportion"= \((TE - IDE) / TE \times 100\%\) (= \((IIE + residual)/TE\); Yamamuro et al. 2021)"Mediation Proportion (multiplicative)"= \(RR_{IDE}\,(RR_{IIE}-1)/(RR_{OE}-1) \times 100\%\) on the interventional overall scale (Lin et al. 2017, Table 2). Undermediation_type = "N"the same formula is reported on the total-effect scale as an analogue; Zheng & van der Laan (2017) do not define a proportion mediated.
For multiple mediators each indirect effect is labelled
"Indirect effect (<mediator>)"; the additive proportion is \((TE - IDE)/TE\) and the multiplicative is \(RR_{IDE}\,(\prod_k RR_{IIE_k} - 1)/(RR_{OE}-1)\) (Yamamuro et al. 2021). Columns:RD,RR; withR > 1alsoSd, percentile CIs (perct_lcl/perct_ucl), and normal CIs (norm_lcl/norm_ucl) for RD; andSd_RR,perct_lcl_RR/perct_ucl_RR,norm_lcl_RR/norm_ucl_RRfor RR.RRisNAfor the Mediation Proportion rows.sim_data: ifreturn_data = TRUE, the simulated Monte Carlo dataset used internally (can be large), stacked across interventions with anInterventioncolumn. It is the end-of-follow-up snapshot — one row per Monte Carlo subject per intervention, holding each variable at its final simulated time step alongside the accumulatedPred_Y— not a row-per-time-point panel. Withn_vw > 1the pool-drawing interventions are simulatedn_vwtimes and only the last permutation is retained here, whereaseffect_size$Estaverages alln_vwof them. Recomputingmean(Pred_Y)fromsim_datawill therefore not reproduceEstexactly for those interventions (it does fornat0/nat1); usen_vw = 1if you need the two to agree.fitted_models: a named list of fitted models keyed by outcome, exposure, and mediator variables. Ifreturn_fitted = TRUE, returns full model objects plus attributes (recodes,subset,var_type,mod_type); otherwise, a compact list withcallandcoeff.boot_estimates: whenR > 1, a list of per-replicate bootstrap estimates:$interventions(columnsreplicate,Intervention,Est) and$effects(columnsreplicate,Effect,RD,RR; includes the per-replicate Mediation Proportion draws). Useful for diagnostics such as counting non-finite replicates or computing custom intervals. These are scalar summaries whose size is independent of the input data.NULLwhenR <= 1.data_summary: list with the number of individuals (n_id), observations (n_obs), and time points (n_times,t_min,t_max) of the input data.observed: list with the observed nonparametric benchmark of the outcome (value,label): the mean outcome at the last time point, or the product-limit cumulative incidence for survival outcomes. Printed next to the simulated intervention means as an informal benchmark.tmle_diag: forestimator = "tmle"only, a list with the subject-level efficient-influence-curve matrix (eic, one column per functional), its column means (eic_mean, near zero for well-supported functionals at the targeted fit), and the number of subjects (n).NULLforestimator = "gcomp".intermediate_confounders: formediation_type = "N", a character vector of covariate names whose model includes the exposure (exposure-affected/intermediate confounders that make the natural effects non-identifiable); empty when none. Theprintmethod re-surfaces a short identifiability caveat when this is non-empty.
Details
**Data requirements** The input dataset must be in longitudinal ("long") format: one record per subject per time period. In survival settings, all records after the event must be removed.
The exposure variable must be **binary, coded as 0 (reference/untreated) and 1 (active/treated)**. Methods implemented here (Lin et al. 2017; Zheng & van der Laan 2017) are defined for binary exposures only.
**Model specification**
Each element of models is created by spec_model and must specify:
(i) a formula, (ii) mod_type ("exposure", "covariate",
"mediator", "outcome", "survival", or "censor"), and
(iii) var_type ("binary", "normal", "categorical", or
"custom"). At least one "mediator" model is required.
Multiple mediator models are supported under mediation_type = "I"
(Yamamuro et al. 2021); list them in temporal order. Multi-mediator
analyses are not supported under mediation_type = "N".
The list order determines the simulation sequence at each time step and must match your assumed data-generating process. A common ordering is A(t) -> L(t) -> M(t) -> S(t) (confounders not affected by mediator) or A(t) -> M(t) -> L(t) -> S(t) (confounders affected by both exposure and mediator). The function checks that the mediator precedes the outcome and that exposure precedes the mediator, and warns if these are violated.
For non-standard covariate distributions (bounded, zero-inflated, truncated), use
var_type = "custom" with custom_fit and custom_sim arguments to
spec_model. See the custom covariate distributions section of
vignette("causalMed-03-gformula").
**Mediator pool (interventional effects)**
For mediation_type = "I", a natural-course pass under each treatment
level \(a^*\) stores every simulated individual's full mediator trajectory
\(M(1{:}T)\) in a pool. Each decomposition intervention that fixes a
mediator to its \(a^*\) value — including the reference interventions
Phi00 (\(a^* = 0\)) and Phi11 (\(a^* = 1\)) — permutes that
pool once and assigns subject \(i\) the entire trajectory of
pool individual \(\pi(i)\): a joint, stochastic draw \(G_{a^*}\) from the
simulated distribution of \(M(1{:}T)\), independent of \(i\)'s own
confounder history (and permuted independently across mediators). This makes
all interventions in the decomposition — references and cross-world alike — use the
randomized interventional mediator distribution, as prescribed by Lin et al.
(2017, Eq. 4), VanderWeele & Tchetgen Tchetgen (2017), and Yamamuro et al.
(2021, Figure 3 step 3), and as implemented by the SAS mGFORMULA macro. The
natural plug-in total effect is obtained from separate natural-course interventions
(nat0, nat1), so \(TE\) need not equal the sum of the
interventional direct and indirect effects; their difference is reported as
the mediated-interaction residual. The pool is not survival-weighted; the
full reference cohort is used, mirroring the reference SAS implementations.
**Warnings** Warnings from model fitting (e.g., convergence, near-separation) are collected during the run and printed as a deduplicated summary at function exit, including a repeat count when the same message fires across bootstrap replicates.
**Re-coding hooks**
init_recode: executed once at time 0 before simulation (initialize baselines).in_recode: executed at the start of each time step (e.g., entry-time logic).out_recode: executed at the end of each time step (e.g., cumulative counts, lags).
References
Lin, S. H., Young, J. G., Logan, R., & VanderWeele, T. J. (2017). Mediation analysis for a survival outcome with time-varying exposures, mediators, and confounders. Statistics in Medicine, 36(26), 4153–4166. doi:10.1002/sim.7426
VanderWeele, T. J., & Tchetgen Tchetgen, E. J. (2017). Mediation analysis with time varying exposures and mediators. Journal of the Royal Statistical Society: Series B, 79(3), 917–938. doi:10.1111/rssb.12194
Yamamuro, S., Shinozaki, T., Iimuro, S., & Matsuyama, Y. (2021). Mediational g-formula for time-varying treatment and repeated-measured multiple mediators: Application to atorvastatin's effect on cardiovascular disease via cholesterol lowering and anti-inflammatory actions in elderly type 2 diabetics. Statistical Methods in Medical Research, 30(8), 1782–1799. doi:10.1177/09622802211025988
Zheng, W., & van der Laan, M. (2017). Longitudinal mediation analysis with time-varying mediators and exposures, with application to survival outcomes. Journal of Causal Inference, 5(2). doi:10.1515/jci-2016-0006
Miles, C. H. (2023). On the causal interpretation of randomised interventional indirect effects. Journal of the Royal Statistical Society: Series B, 85(4), 1154–1172. doi:10.1093/jrsssb/qkad066
Examples
if (FALSE) { # \dontrun{
data(nonsurvivaldata)
models <- list(
cov_model = spec_model(L ~ V+L_lag1+A+time,var_type= "normal",mod_type = "covariate"),
mediator_model = spec_model(M ~ V + A + L + M_lag1 + time,
var_type = "normal", mod_type = "mediator"),
outcome_model = spec_model(Y ~ V+A+M+A * M+L, var_type= "binary",mod_type ="outcome")
)
fit <- mediation(
data = nonsurvivaldata,
id_var = "id",
base_vars = "V",
exposure = "A",
outcome = "Y",
time_var = "time",
models = models,
init_recode = recodes(M_lag1=0,L_lag1=0),
in_recode = recodes(M_lag1=M,L_lag1=L),
mediation_type = "I",
mc_sample = 100000,
R = 500,
return_data = FALSE,
return_fitted = FALSE
)
print(fit)
} # }