The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
If you use this package, please cite:
Fontana, N., Ieva, F., Zuccolo, L., Di Angelantonio, E., & Secchi, P. (2025). Unraveling time-varying causal effects of multiple exposures: Integrating functional data analysis with multivariable Mendelian randomization. arXiv. https://arxiv.org/abs/2512.19064
@misc{fontana2025unravelingtimevaryingcausaleffects,
title={Unraveling time-varying causal effects of multiple exposures: integrating Functional Data Analysis with Multivariable Mendelian Randomization},
author={Nicole Fontana and Francesca Ieva and Luisa Zuccolo and Emanuele Di Angelantonio and Piercesare Secchi},
year={2025},
eprint={2512.19064},
archivePrefix={arXiv},
primaryClass={stat.AP},
url={https://arxiv.org/abs/2512.19064},
}The mvfmr package implements Multivariable Functional
Mendelian Randomization methods to estimate time-varying causal effects
of longitudinal exposures on health outcomes. The package supports:
Install the released version from CRAN:
install.packages("mvfmr")Or install the development version from GitHub:
install.packages("devtools") # Install devtools if not already installed
devtools::install_github("NicoleFontana/mvfmr")The package ships with test scripts demonstrating different use
cases. Once installed, they live inside the package itself, so you can
run or open them with demo() / system.file() —
no download needed:
demo/tests_manuscript.R)Reproduces the main simulation scenarios from the manuscript: - Scenario 1-3: pleiotropy, null effects, mediation - Exposure effects: linear and quadratic - Performance comparison: MV-FMR vs U-FMR across scenarios - Evaluation: MISE, coverage rates
# Run the manuscript simulations
demo("tests_manuscript", package = "mvfmr")inst/examples/test_MV-FMR.R)Complete tutorial for using joint multivariable
estimation: - Data simulation with two exposures - FPCA and component
selection - Joint estimation with mvfmr() - Instrument
diagnostics - Performance metrics and visualization - Binary outcome
analysis - Comparison with univariable estimation
# Run the MV-FMR tutorial
source(system.file("examples", "test_MV-FMR.R", package = "mvfmr"))
# Or just locate it to open/copy/adapt it:
system.file("examples", "test_MV-FMR.R", package = "mvfmr")inst/examples/test_U-FMR.R)Complete tutorial for single exposure analysis: -
Single exposure simulation - Univariable estimation with
mvfmr_separate() - Instrument diagnostics - Performance
metrics and visualization - Comparison of different exposure effects -
Binary outcome analysis
# Run the U-FMR tutorial
source(system.file("examples", "test_U-FMR.R", package = "mvfmr"))Note: These scripts serve as templates for your own
analyses. Open them (via system.file()), modify the
parameters, and adapt to your specific research questions.
Exposure-related arguments (fpca_results,
max_nPC, true_effects, X_true, …)
are lists or vectors of length m, one entry per
exposure.
library(mvfmr)
library(fdapace)
# Step 1: Simulate exposure data (m = 2 exposures)
set.seed(12345)
sim_data <- getX_multi_exposure(
N = 1000, # Sample size
J = 50, # Number of genetic instruments
nSparse = 10, # Sparse observations per subject
n_exposures = 2 # Number of exposures (m)
)
# Step 2: Generate outcome
outcome_data <- getY_multi_exposure(
sim_data,
XYmodels = c("2", "8"), # Linear effect for exposure 1, quadratic for exposure 2
X_effects = c(TRUE, TRUE),
outcome_type = "continuous"
)
# Step 3: Functional PCA for each exposure
fpca_results <- lapply(sim_data$exposures, function(exp_k) {
FPCA(
exp_k$Ly_sim,
exp_k$Lt_sim,
list(dataType = 'Sparse', error = TRUE, verbose = FALSE)
)
})
# Step 4: Joint estimation with MV-FMR
result <- mvfmr(
G = sim_data$details$G,
fpca_results = fpca_results,
Y = outcome_data$Y,
outcome_type = "continuous",
method = "gmm",
max_nPC = c(10, 10),
bootstrap = TRUE,
n_bootstrap = 100
)
# View results
print(result)
summary(result)
plot(result) # Visualize time-varying effects for every exposure
# Extract coefficients and effects
coef(result)
result$effects[[1]] # Time-varying effect for exposure 1
result$effects[[2]] # Time-varying effect for exposure 2Continuing from Example 1: compare joint vs. separate (univariable)
estimation for the same two exposures. For
mvfmr_separate(), instruments are passed as
G_list, a list of length m (here the same
shared instrument matrix is reused for both exposures):
# Separate estimation (U-FMR for each exposure independently), reusing
# sim_data / outcome_data / fpca_results simulated in Example 1
result_separate <- mvfmr_separate(
G_list = list(sim_data$details$G, sim_data$details$G),
fpca_results = fpca_results,
Y = outcome_data$Y
)
# Compare performance: joint (`result`, from Example 1) vs. separate
result$performance
result_separate$exposures[[1]]$performance
result_separate$exposures[[2]]$performanceThe package can also be used for single exposure analysis (U-FMR), by
simulating and passing a single exposure (n_exposures = 1,
G_list of length 1):
library(mvfmr)
library(fdapace)
# Step 1: Simulate a single exposure
set.seed(12345)
sim_data <- getX_multi_exposure(
N = 1000,
J = 50,
nSparse = 10,
n_exposures = 1
)
# Step 2: Generate outcome
outcome_data <- getY_multi_exposure(
sim_data,
XYmodels = "2", # Linear effect
X_effects = TRUE,
outcome_type = "continuous"
)
# Step 3: FPCA for the (only) exposure
fpca1 <- FPCA(
sim_data$exposures[[1]]$Ly_sim,
sim_data$exposures[[1]]$Lt_sim,
list(dataType = 'Sparse', error = TRUE, verbose = FALSE)
)
# Step 4: Univariable estimation
result <- mvfmr_separate(
G_list = list(sim_data$details$G), # A list of length 1
fpca_results = list(fpca1),
Y = outcome_data$Y,
outcome_type = "continuous",
method = "gmm",
max_nPC = 10
)
# View results
print(result)
coef(result, exposure = 1)
result$exposures[[1]]$effect # Time-varying effectNothing changes in the API when moving from 2 to m
exposures: fpca_results, max_nPC,
true_effects and X_true simply grow to length
m.
set.seed(2026)
sim_data3 <- getX_multi_exposure(N = 1000, J = 50, nSparse = 10, n_exposures = 3)
outcome_data3 <- getY_multi_exposure(
sim_data3,
XYmodels = c("2", "5", "8"),
outcome_type = "continuous"
)
fpca_results3 <- lapply(sim_data3$exposures, function(exp_k) {
FPCA(exp_k$Ly_sim, exp_k$Lt_sim, list(dataType = 'Sparse', error = TRUE, verbose = FALSE))
})
# Joint estimation across all 3 exposures
result_joint3 <- mvfmr(
G = sim_data3$details$G,
fpca_results = fpca_results3,
Y = outcome_data3$Y,
outcome_type = "continuous",
method = "gmm",
max_nPC = c(10, 10, 10),
true_effects = c("2", "5", "8"),
X_true = sim_data3$details$X_list
)
print(result_joint3)
plot(result_joint3) # One panel per exposure
# Separate estimation across all 3 exposures
result_separate3 <- mvfmr_separate(
G_list = list(sim_data3$details$G, sim_data3$details$G, sim_data3$details$G),
fpca_results = fpca_results3,
Y = outcome_data3$Y,
max_nPC = c(10, 10, 10),
true_effects = c("2", "5", "8")
)
# Access any exposure by index (1..m)
result_joint3$effects[[3]]
coef(result_separate3, exposure = 3)Use outcome GWAS summary statistics instead of individual-level outcome data.
library(mvfmr)
library(fdapace)
# Step 1: Simulate exposure data (individual-level)
set.seed(12345)
sim_data <- getX_multi_exposure(
N = 5000, # Exposure sample size
J = 30, # Number of genetic instruments (SNPs)
nSparse = 10,
n_exposures = 2
)
# Perform FPCA on longitudinal exposures
fpca_results <- lapply(sim_data$exposures, function(exp_k) {
FPCA(exp_k$Ly_sim, exp_k$Lt_sim, list(dataType = 'Sparse', error = TRUE, verbose = FALSE))
})
# Step 2: Get outcome GWAS summary statistics (from a separate study)
# Simulate obtaining summary statistics from a separate GWAS
# (this mimics what you'd get from a published GWAS)
by_outcome <- rnorm(30, mean = 0.02, sd = 0.01) # SNP-outcome associations
sy_outcome <- runif(30, 0.005, 0.015) # Standard errors
ny_outcome <- 100000 # GWAS sample size
# Step 3: Two-sample MV-FMR estimation
result_twosample <- fmvmr_twosample(
G_exposure = sim_data$details$G, # Genotypes from the exposure sample
fpca_results = fpca_results, # FPCA from the exposures
by_outcome = by_outcome, # GWAS betas (from the outcome study)
sy_outcome = sy_outcome, # GWAS standard errors
ny_outcome = ny_outcome, # GWAS sample size
max_nPC = c(3, 3),
verbose = TRUE
)
# Step 4: View results
print(result_twosample)
# Extract time-varying effects
result_twosample$effects[[1]]
result_twosample$effects[[2]]getX_multi_exposure() - Generate
genetic instruments and exposure data for m exposures
getX_multi_exposure(
N = 1000, # Sample size
J = 50, # Number of genetic instruments
nSparse = 10, # Observations per subject
n_exposures = 2, # Number of exposures (m)
shared_effect = TRUE, # Whether all exposures share the same time-varying confounding
separate_G = FALSE, # Whether to use separate instruments per exposure
shared_G_proportion = 0.15 # Proportion of shared instruments (0-1, if separate_G = TRUE)
)getX_multi_exposure_mediation() -
Generate data with mediation pathways between exposures
getX_multi_exposure_mediation(
N = 1000, # Sample size
J = 50, # Number of genetic instruments
nSparse = 10, # Observations per subject
n_exposures = 2, # Number of exposures (m)
mediation_strength = NULL, # m x m matrix: entry [j, k] (j < k) is the strength
# with which exposure j mediates its effect onto
# exposure k. Default: NULL = no mediation.
mediation_type = "linear" # "linear", "nonlinear", "time_varying" (scalar or
# m x m matrix mirroring mediation_strength)
)getY_multi_exposure() - Generate
outcome with time-varying effects
getY_multi_exposure(
RES, # Output from getX_multi_exposure() or getX_multi_exposure_mediation()
XYmodels = NULL, # Length-m vector of effect models, one per exposure (see below); default '1' for all
X_effects = NULL, # Length-m logical vector: include each exposure's effect?; default TRUE for all
outcome_type = "continuous" # "continuous" or "binary"
)Available effect models: - "0" - No
effect (null) - "1" - Constant effect (β = 0.1) -
"2" - Linear increasing (β(t) = 0.02×t) - "3"
- Linear decreasing (β(t) = 0.5 - 0.02×t) - "4" - Early
life effect (β(t) = 0.1 for t < 20) - "5" - Late life
effect (β(t) = 0.1 for t > 30) - "6" - Early decreasing
(β(t) = 0.05×(20-t) for t < 20) - "7" - Late increasing
(β(t) = 0.05×(t-30) for t > 30) - "8" - Quadratic (β(t)
= 0.002×t² - 0.11×t + 0.5) - "9" - Cubic (β(t) =
-0.00002×t³ + 0.004×t² - 0.2×t + 1)
mvfmr() - Joint multivariable
estimation
mvfmr(
G, # Genetic instrument matrix (N x J)
fpca_results, # List of length m of FPCA objects, one per exposure
Y, # Outcome vector
outcome_type = "continuous", # "continuous" or "binary"
method = "gmm", # "gmm", "cf" (control function), or "cf-lasso"
nPC = NA, # Fixed number of components per exposure (length 1 or m; NA = select automatically)
max_nPC = NA, # Maximum number of components per exposure (length 1 or m)
improvement_threshold = 0.001, # Minimum CV improvement required to add a component
bootstrap = FALSE, # Whether to compute bootstrap confidence intervals
n_bootstrap = 100, # Number of bootstrap replicates
n_cores = parallel::detectCores() - 1, # Number of CPU cores for parallel computations
true_effects = NULL, # Length-m vector of true effect model codes (simulation only)
X_true = NULL, # Length-m list of true X curves (simulation only)
verbose = FALSE # Print progress and diagnostic messages
)mvfmr_separate() - Separate univariable
estimation
mvfmr_separate(
G_list, # List of length m of genetic instrument matrices, one per exposure
# (use a list of length 1 to analyze a single exposure)
fpca_results, # List of length m of FPCA objects, same length as G_list
Y, # Outcome vector
outcome_type = "continuous",
method = "gmm",
nPC = NA,
max_nPC = NA,
improvement_threshold = 0.001,
bootstrap = FALSE,
n_bootstrap = 100,
n_cores = parallel::detectCores() - 1,
true_effects = NULL,
X_true = NULL,
verbose = FALSE
)fmvmr_twosample() - Two-sample joint
multivariable estimation
fmvmr_twosample(
G_exposure, # Genetic instrument matrix from the exposure sample (N x J)
fpca_results, # List of length m of FPCA objects
by_outcome, # Vector of SNP-outcome betas from the outcome GWAS, length J
sy_outcome, # Vector of standard errors for SNP-outcome effects, length J
ny_outcome, # Sample size of the outcome GWAS
max_nPC = NA, # Maximum number of components per exposure (length 1 or m)
true_effects = NULL, # Length-m vector of true effect model codes (simulation only)
verbose = TRUE
)fmvmr_separate_twosample() - Two-sample
separate univariable estimation
fmvmr_separate_twosample(
G_list, # List of length m of genetic instrument matrices
fpca_results, # List of length m of FPCA objects
by_outcome_list, # List of length m of SNP-outcome beta vectors
sy_outcome_list, # List of length m of SNP-outcome standard error vectors
ny_outcome, # Outcome GWAS sample size
max_nPC = NA,
true_effects = NULL,
verbose = TRUE
)IS() - Calculate instrument strength
(F-statistics)
IS(
J, # Number of genetic instruments
K, # Number of exposures/components
PC, # Vector of indices indicating which columns in datafull correspond to the principal components
datafull, # Data frame containing instruments (first J columns) and principal components (subsequent columns) [G, X]
Y # Optional outcome vector; if provided, Q-statistic for overidentification is calculated
)The package supports three estimation methods:
mvfmr object (from
mvfmr())result <- mvfmr(...)
names(result)Components: - coefficients - Estimated β coefficients
for basis functions (stacked across all exposures) - vcov -
Variance-covariance matrix - effects - List of length m,
one time-varying effect curve per exposure -
confidence_intervals -
lower/upper, each a list of length m -
nPC_used - Vector of length m: components selected per
exposure - performance - MISE and
Coverage (lists of length m), only for simulations -
plots - effects (list of m ggplot2 objects)
and plot_beta (combined coefficient plot)
Methods: - print(), summary() - Display
results - plot() - Visualize time-varying effects for every
exposure - coef() - Extract coefficients -
vcov() - Extract variance-covariance matrix
mvfmr_separate
object (from mvfmr_separate())result <- mvfmr_separate(...)
names(result)Components: - exposures - List of length m; each entry
has coefficients, vcov, effect,
nPC_used, performance - plots -
effects, a list of m ggplot2 objects
Methods: - coef(result, exposure = k) - Extract
coefficients for exposure k (1..m) -
vcov(result, exposure = k) - Extract variance-covariance
matrix for exposure k
For binary outcomes, use method = "cf" or
method = "cf-lasso":
# Generate binary outcome
outcome_binary <- getY_multi_exposure(
sim_data,
XYmodels = c("2", "8"),
outcome_type = "binary"
)
# Estimate with control function
result <- mvfmr(
G = sim_data$details$G,
fpca_results = list(fpca1, fpca2),
Y = outcome_binary$Y,
outcome_type = "binary",
method = "cf"
)Automatic selection via cross-validation:
result <- mvfmr(
G = G,
fpca_results = list(fpca1, fpca2),
Y = Y,
max_nPC = c(10, 10), # Search up to 10 components per exposure
improvement_threshold = 0.01 # Stop if improvement < 1%
)
# View selected components
result$nPC_usedresult <- mvfmr(
G = G,
fpca_results = list(fpca1, fpca2),
Y = Y,
bootstrap = TRUE,
n_bootstrap = 200 # Number of bootstrap replicates
)
# Bootstrap confidence intervals available in:
result$confidence_intervalsresult <- mvfmr(
G = G,
fpca_results = list(fpca1, fpca2),
Y = Y,
n_cores = 4 # Use 4 cores for cross-validation
)mediation_strength is an m x m matrix: entry
[j, k] (with j < k) is the strength with
which exposure j mediates its effect onto exposure
k. Any exposure can mediate onto any later one, each with
its own strength, so mediation chains with more than two exposures
(e.g. X1 -> X2, X1 -> X3, X2 -> X3) are supported directly.
# Generate data where exposure 1 mediates onto exposure 2
mediation_strength <- matrix(0, 2, 2)
mediation_strength[1, 2] <- 0.5
sim_mediation <- getX_multi_exposure_mediation(
N = 1000,
J = 50,
n_exposures = 2,
mediation_strength = mediation_strength,
mediation_type = "linear"
)
outcome <- getY_multi_exposure(
sim_mediation,
XYmodels = c("2", "1"), # Direct effect of exposure 1; effect of exposure 2 (mediator)
outcome_type = "continuous"
)
fpca_results <- lapply(sim_mediation$exposures, function(exp_k) {
FPCA(exp_k$Ly_sim, exp_k$Lt_sim, list(dataType = 'Sparse', error = TRUE, verbose = FALSE))
})
# Estimate with MV-FMR to capture mediation
result <- mvfmr(
G = sim_mediation$details$G,
fpca_results = fpca_results,
Y = outcome$Y
)Check instrument strength with F-statistics (IS() is
generic in the number of exposures/components K):
# After FPCA
K_total <- sum(sapply(fpca_results, function(f) f$selectK))
PC_stacked <- do.call(cbind, lapply(fpca_results, function(f) f$xiEst[, 1:f$selectK]))
fstats <- IS(
J = ncol(G),
K = K_total,
PC = 1:K_total,
datafull = cbind(G, PC_stacked)
)
# View conditional F-statistics (cFF)
print(fstats)When true effects are provided (simulations):
This package extends the univariable functional Mendelian Randomization framework to the multivariable setting. Key related work:
Our implementation builds upon and extends the TVMR package by Tian et al.:
Tian, H., Mason, A. M., Liu, C., & Burgess, S. (2024). Estimating time‐varying exposure effects through continuous‐time modelling in Mendelian randomization. Statistics in Medicine, 43(26), 5006-5024. https://doi.org/10.1002/sim.10222
GitHub: https://github.com/HDTian/TVMR
Nicole Fontana
MIT — see the LICENSE file for details.
For questions and issues: - Open an issue on GitHub - Email: nicole.fontana@polimi.it
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.