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.
This vignette introduces Multivariable Functional Mendelian Randomization (MV-FMR), a method to estimate time-varying causal effects of multiple correlated longitudinal exposures on health outcomes.
Use joint multivariable estimation (mvfmr()) when:
# Install from CRAN
install.packages("mvfmr")library(mvfmr)
library(fdapace)
library(ggplot2)
#> Warning: package 'ggplot2' was built under R version 4.3.3We generate genetic instruments and two longitudinal exposures:
set.seed(473920)
# Generate exposure data
sim_data <- getX_multi_exposure(
N = 300, # Sample size
J = 25, # Number of genetic instruments
nSparse = 10, # Observations per subject
n_exposures = 2 # Number of exposures (m)
)
# Check dimensions
cat("Sample size:", nrow(sim_data$details$G), "\n")
#> Sample size: 300
cat("Number of instruments:", ncol(sim_data$details$G), "\n")
#> Number of instruments: 25We create an outcome with linear effect from exposure 1 and exposure 2:
outcome_data <- getY_multi_exposure(
sim_data,
XYmodels = c("2", "2"), # Exposure 1/2: linear beta(t) = 0.02*t
X_effects = c(TRUE, TRUE),
outcome_type = "continuous"
)
cat("Outcome summary:\n")
#> Outcome summary:
summary(outcome_data$Y)
#> Min. 1st Qu. Median Mean 3rd Qu. Max.
#> -236.50 -71.40 -26.03 -23.83 28.53 182.65We apply FPCA to each exposure to extract principal components:
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)
)
})
cat("FPCA completed:\n")
#> FPCA completed:
for (k in seq_along(fpca_results)) {
cat(" Exposure", k, ":", fpca_results[[k]]$selectK, "components selected\n")
}
#> Exposure 1 : 3 components selected
#> Exposure 2 : 3 components selectedNow we perform joint estimation using mvfmr(). All exposure-related arguments (fpca_results, max_nPC, true_effects, X_true, …) are lists or vectors of length m, one entry per exposure:
result_joint <- mvfmr(
G = sim_data$details$G,
fpca_results = fpca_results,
Y = outcome_data$Y,
outcome_type = "continuous",
method = "gmm",
max_nPC = c(4, 4),
improvement_threshold = 0.001,
bootstrap = FALSE,
n_cores = 1,
true_effects = c("2", "2"),
X_true = sim_data$details$X_list,
verbose = FALSE
)
# View results
print(result_joint)
#>
#> Functional Multivariable MR Result
#> ==================================
#> Exposures: 2
#> Sample size: 300
#> Outcome: continuous
#> Method: gmm
#> Components selected: nPC1 = 2, nPC2 = 2
#>
#> Performance Metrics:
#> Exposure 1 - MISE: 0.002458 , Coverage: 1
#> Exposure 2 - MISE: 0.003881 , Coverage: 0.745The estimated time-varying causal effects can be visualized using the built-in plot method:
# Plot every exposure's effect
plot(result_joint)The solid colored lines show the estimated time-varying causal effects, with shaded bands representing 95% confidence intervals. The dashed red lines (when present) indicate the true time-varying effects used in the simulation.
# Estimated beta coefficients for basis functions
coef(result_joint)
#> [1] -4.035692 -1.647212 -3.634343 -1.451868
# Time-varying effects at each time point (one entry per exposure)
head(result_joint$effects[[1]])
#> [1] 0.06182589 0.07321675 0.08677954 0.10220947 0.11906258 0.13675702
head(result_joint$effects[[2]])
#> [1] 0.02302308 0.02248962 0.02740122 0.03681268 0.04977623 0.06537926Since we simulated data with known truth, we can evaluate performance:
cat("Performance Metrics:\n")
#> Performance Metrics:
for (k in seq_along(result_joint$effects)) {
cat("\nExposure", k, ":\n")
cat(" MISE:", round(result_joint$performance$MISE[[k]], 6), "\n")
cat(" Coverage:", round(result_joint$performance$Coverage[[k]], 3), "\n")
}
#>
#> Exposure 1 :
#> MISE: 0.002458
#> Coverage: 1
#>
#> Exposure 2 :
#> MISE: 0.003881
#> Coverage: 0.745We can compare joint (multivariable) with separate (univariable) estimation. 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):
result_separate <- mvfmr_separate(
G_list = list(sim_data$details$G, sim_data$details$G),
fpca_results = fpca_results,
Y = outcome_data$Y,
outcome_type = "continuous",
method = "gmm",
max_nPC = c(4, 4),
n_cores = 1,
true_effects = c("2", "2"),
verbose = FALSE
)
#> [1] "Processing X1"
#> [1] "Processing X2"
print(result_separate)
#>
#> Separate Univariable MR Results
#> ================================
#> Exposures: 2
#> Separate instruments: TRUE
#> Outcome: continuous
#> Method: gmm
#>
#> Exposure 1 :
#> Components: 2
#> MSE: 0.178885
#> Coverage: 0
#>
#> Exposure 2 :
#> Components: 2
#> MSE: 0.252422
#> Coverage: 0.157comparison <- data.frame(
Method = rep(c("Joint (MV-FMR)", "Separate (U-FMR)"), each = 2),
Exposure = rep(c("X1", "X2"), times = 2),
MISE = c(
result_joint$performance$MISE[[1]],
result_joint$performance$MISE[[2]],
result_separate$exposures[[1]]$performance$MISE,
result_separate$exposures[[2]]$performance$MISE
),
Coverage = c(
result_joint$performance$Coverage[[1]],
result_joint$performance$Coverage[[2]],
result_separate$exposures[[1]]$performance$Coverage,
result_separate$exposures[[2]]$performance$Coverage
)
)
print(comparison)
#> Method Exposure MISE Coverage
#> 1 Joint (MV-FMR) X1 0.002458118 1.0000000
#> 2 Joint (MV-FMR) X2 0.003881328 0.7450980
#> 3 Separate (U-FMR) X1 0.178885369 0.0000000
#> 4 Separate (U-FMR) X2 0.252422123 0.1568627Check instrument strength using F-statistics. IS() is already generic in the number of exposures/components K:
# Calculate F-statistics
K_total <- sum(result_joint$nPC_used)
PC_stacked <- do.call(cbind, lapply(seq_along(fpca_results), function(k) {
fpca_results[[k]]$xiEst[, 1:result_joint$nPC_used[k]]
}))
fstats <- IS(
J = ncol(sim_data$details$G),
K = K_total,
PC = 1:K_total,
datafull = cbind(sim_data$details$G, PC_stacked),
Y = outcome_data$Y
)
fstats_df <- cbind(
"Exposure" = unlist(lapply(seq_along(result_joint$nPC_used), function(k) {
rep(paste0("X", k), result_joint$nPC_used[k])
})),
as.data.frame(fstats)
)
print(fstats_df[, c("Exposure", "PC", "cFF")])
#> Exposure PC cFF
#> 1 X1 1 0.3913644
#> 2 X1 2 1.1379411
#> 3 X2 3 0.3986037
#> 4 X2 4 0.9953631Rule of thumb: cFF > 10 indicates strong instruments.
MV-FMR also supports binary outcomes using the control function approach:
# Generate binary outcome
outcome_binary <- getY_multi_exposure(
sim_data,
XYmodels = c("2", "2"),
outcome_type = "binary"
)
# Estimate with control function
result_binary <- mvfmr(
G = sim_data$details$G,
fpca_results = fpca_results,
Y = outcome_binary$Y,
outcome_type = "binary",
method = "cf", # Control function for binary
max_nPC = c(3, 3),
n_cores = 1,
verbose = FALSE
)
print(result_binary)Nothing 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(163918)#281046
sim_data3 <- getX_multi_exposure(N = 500, J = 50, nSparse = 10, n_exposures = 3)
outcome_data3 <- getY_multi_exposure(
sim_data3,
XYmodels = c("2", "2", "2"),
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))
})
result_joint3 <- mvfmr(
G = sim_data3$details$G,
fpca_results = fpca_results3,
Y = outcome_data3$Y,
outcome_type = "continuous",
method = "gmm",
max_nPC = c(4, 4, 4),
n_cores = 1,
true_effects = c("2", "2", "2"),
X_true = sim_data3$details$X_list,
verbose = FALSE
)
print(result_joint3)
#>
#> Functional Multivariable MR Result
#> ==================================
#> Exposures: 3
#> Sample size: 500
#> Outcome: continuous
#> Method: gmm
#> Components selected: nPC1 = 2, nPC2 = 2, nPC3 = 2
#>
#> Performance Metrics:
#> Exposure 1 - MISE: 0.001228 , Coverage: 1
#> Exposure 2 - MISE: 0.004026 , Coverage: 0.706
#> Exposure 3 - MISE: 0.006513 , Coverage: 0.588plot(result_joint3)vignette("univariable-fmr") for single exposure analysisinst/examples/test_MV-FMR.R for complete scenariosIf 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 preprint arXiv:2512.19064.
sessionInfo()
#> R version 4.3.0 (2023-04-21)
#> Platform: aarch64-apple-darwin20 (64-bit)
#> Running under: macOS Ventura 13.6
#>
#> Matrix products: default
#> BLAS: /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRblas.0.dylib
#> LAPACK: /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.11.0
#>
#> locale:
#> [1] C/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
#>
#> time zone: Europe/Rome
#> tzcode source: internal
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] ggplot2_3.5.2 fdapace_0.5.9 mvfmr_0.2.0
#>
#> loaded via a namespace (and not attached):
#> [1] gtable_0.3.6 shape_1.4.6 xfun_0.41
#> [4] bslib_0.6.1 htmlwidgets_1.6.4 lattice_0.21-8
#> [7] numDeriv_2016.8-1.1 vctrs_0.6.5 tools_4.3.0
#> [10] generics_0.1.3 parallel_4.3.0 tibble_3.2.1
#> [13] fansi_1.0.6 highr_0.10 cluster_2.1.4
#> [16] pkgconfig_2.0.3 Matrix_1.6-4 data.table_1.14.10
#> [19] checkmate_2.3.1 lifecycle_1.0.4 farver_2.1.1
#> [22] compiler_4.3.0 stringr_1.5.1 progress_1.2.3
#> [25] munsell_0.5.0 codetools_0.2-19 htmltools_0.5.7
#> [28] sass_0.4.8 yaml_2.3.8 glmnet_4.1-8
#> [31] htmlTable_2.4.2 Formula_1.2-5 pracma_2.4.4
#> [34] pillar_1.9.0 crayon_1.5.3 jquerylib_0.1.4
#> [37] MASS_7.3-58.4 cachem_1.0.8 Hmisc_5.1-1
#> [40] iterators_1.0.14 rpart_4.1.19 foreach_1.5.2
#> [43] tidyselect_1.2.0 digest_0.6.33 stringi_1.8.3
#> [46] dplyr_1.1.4 labeling_0.4.3 splines_4.3.0
#> [49] fastmap_1.1.1 grid_4.3.0 colorspace_2.1-0
#> [52] cli_3.6.2 magrittr_2.0.3 base64enc_0.1-3
#> [55] survival_3.5-7 utf8_1.2.4 withr_2.5.2
#> [58] foreign_0.8-84 prettyunits_1.2.0 scales_1.3.0
#> [61] backports_1.5.0 rmarkdown_2.25 nnet_7.3-18
#> [64] gridExtra_2.3 hms_1.1.3 evaluate_0.23
#> [67] knitr_1.45 doParallel_1.0.17 rlang_1.1.6
#> [70] Rcpp_1.1.0 glue_1.6.2 pROC_1.18.5
#> [73] rstudioapi_0.15.0 jsonlite_1.8.8 R6_2.5.1
#> [76] plyr_1.8.9These 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.