This vignette demonstrates Univariable Functional Mendelian Randomization (U-FMR) for estimating the time-varying causal effect of a single longitudinal exposure on a health outcome.
Use univariable estimation (mvfmr_separate() with G2 = NULL) when:
devtools::install_github("NicoleFontana/mvfmr")library(mvfmr)
library(fdapace)
library(ggplot2)set.seed(12345)
# Generate exposure data (we'll only use X1)
sim_data <- getX_multi_exposure(
N = 300,
J = 25,
nSparse = 10
)
cat("Data simulated:\n")
#> Data simulated:
cat(" Sample size:", nrow(sim_data$details$G), "\n")
#> Sample size: 300
cat(" Instruments:", ncol(sim_data$details$G), "\n")
#> Instruments: 25We create an outcome where only X1 has a causal effect:
outcome_data <- getY_multi_exposure(
sim_data,
X1Ymodel = "2", # Linear effect: β(t) = 0.02×t
X2Ymodel = "0", # No effect from X2
X1_effect = TRUE,
X2_effect = FALSE, # X2 does not affect Y
outcome_type = "continuous"
)
cat("Outcome summary:\n")
#> Outcome summary:
summary(outcome_data$Y)
#> Min. 1st Qu. Median Mean 3rd Qu. Max.
#> -101.272 -30.169 -5.085 -2.914 23.769 103.874# FPCA for exposure 1 only
fpca1 <- FPCA(
sim_data$X1$Ly_sim,
sim_data$X1$Lt_sim,
list(dataType = 'Sparse', error = TRUE, verbose = FALSE)
)
cat("FPCA completed:\n")
#> FPCA completed:
cat(" Components selected:", fpca1$selectK, "\n")
#> Components selected: 3
cat(" Variance explained:",
round(sum(fpca1$lambda[1:fpca1$selectK]) / sum(fpca1$lambda) * 100, 1), "%\n")
#> Variance explained: 100 %Estimate the causal effect using mvfmr_separate() with only one exposure:
result <- mvfmr_separate(
G1 = sim_data$details$G, # Instruments for X1
G2 = NULL, # No second exposure
fpca_results = list(fpca1),
Y = outcome_data$Y,
outcome_type = "continuous",
method = "gmm",
max_nPC1 = 4,
max_nPC2 = 4, # Not used when G2 = NULL
n_cores = 1,
true_effects = list(model1 = "2", model2 = "0"),
verbose = FALSE
)
#> [1] "Processing X1"
print(result)
#>
#> Separate Univariable MR Results
#> ================================
#> Exposures: 1
#> Separate instruments: TRUE
#> Outcome: continuous
#> Method: gmm
#>
#> Exposure 1:
#> Components: 2
#> MSE: 0.001915
#> Coverage: 0.804plot(result)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.
# Coefficients for basis functions
cat("Estimated coefficients:\n")
#> Estimated coefficients:
print(round(coef(result, exposure = 1), 4))
#> [1] -3.8088 1.5315
# Time-varying effect curve
cat("\nFirst 10 time points of β(t):\n")
#>
#> First 10 time points of β(t):
head(result$exposure1$effect, 10)
#> [1] 0.02639840 0.03866777 0.05175991 0.06578036 0.08081493 0.09688697
#> [7] 0.11391916 0.13170830 0.14992295 0.16813235cat("Performance:\n")
#> Performance:
cat(" MISE:", round(result$exposure1$performance$MISE, 6), "\n")
#> MISE: 0.001915
cat(" Coverage:", round(result$exposure1$performance$Coverage, 3), "\n")
#> Coverage: 0.804
cat(" Components used:", result$exposure1$nPC_used, "\n")
#> Components used: 2U-FMR also works with binary outcomes:
# Generate binary outcome
outcome_binary <- getY_multi_exposure(
sim_data,
X1Ymodel = "2",
X2Ymodel = "0",
X1_effect = TRUE,
X2_effect = FALSE,
outcome_type = "binary"
)
# Estimate with control function
result_binary <- mvfmr_separate(
G1 = sim_data$details$G,
G2 = NULL,
fpca_results = list(fpca1),
Y = outcome_binary$Y,
outcome_type = "binary",
method = "cf", # Control function for binary
max_nPC1 = 3,
n_cores = 1,
verbose = FALSE
)
print(result_binary)
cat("Cases:", sum(outcome_binary$Y == 1), "\n")
cat("Controls:", sum(outcome_binary$Y == 0), "\n")The package includes 10 pre-defined time-varying effect shapes:
"0": No effect (null)"1": Constant (β = 0.1)"2": Linear increasing"3": Linear decreasing"4": Early life effect"5": Late life effect"6": Early decreasing"7": Late increasing"8": Quadratic (U-shape)"9": Cubic# Get robust confidence intervals via bootstrap
result_boot <- mvfmr_separate(
G1 = sim_data$details$G,
G2 = NULL,
fpca_results = list(fpca1),
Y = outcome_data$Y,
outcome_type = "continuous",
bootstrap = TRUE,
n_bootstrap = 100,
max_nPC1 = 4,
verbose = FALSE
)
# Bootstrap CIs are stored in result_boot$exposure1$...If you have GWAS summary statistics instead of individual-level outcome data:
# Simulate GWAS summary statistics
by_outcome <- rnorm(25, 0.02, 0.01)
sy_outcome <- runif(25, 0.005, 0.015)
result_2sample <- fmvmr_separate_twosample(
G1_exposure = sim_data$details$G,
G2_exposure = NULL,
fpca_results = list(fpca1),
by_outcome1 = by_outcome,
by_outcome2 = NULL,
sy_outcome1 = sy_outcome,
sy_outcome2 = NULL,
ny_outcome = 50000,
max_nPC1 = 3,
verbose = FALSE
)
print(result_2sample)vignette("multivariable-fmr") for joint estimationinst/examples/test_U-FMR.R for all 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.1.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.4.1 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.0.11 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.9