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 a
G_list of length 1) when:
set.seed(12345)
# Generate exposure data with a single exposure (m = 1)
sim_data <- getX_multi_exposure(
N = 300,
J = 25,
nSparse = 10,
n_exposures = 1
)
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 the exposure has a linear causal effect:
outcome_data <- getY_multi_exposure(
sim_data,
XYmodels = "2", # Linear effect: beta(t) = 0.02*t
X_effects = TRUE,
outcome_type = "continuous"
)
cat("Outcome summary:\n")
#> Outcome summary:
summary(outcome_data$Y)
#> Min. 1st Qu. Median Mean 3rd Qu. Max.
#> -106.837 -30.302 -3.945 -3.029 24.274 111.770fpca1 <- FPCA(
sim_data$exposures[[1]]$Ly_sim,
sim_data$exposures[[1]]$Lt_sim,
list(dataType = 'Sparse', error = TRUE, verbose = FALSE)
)
cat("FPCA completed:\n")
#> FPCA completed:
cat(" Components selected:", fpca1$selectK, "\n")
#> Components selected: 2
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 a
G_list of length 1:
result <- mvfmr_separate(
G_list = list(sim_data$details$G),
fpca_results = list(fpca1),
Y = outcome_data$Y,
outcome_type = "continuous",
method = "gmm",
max_nPC = 4,
n_cores = 1,
true_effects = "2",
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.002314
#> Coverage: 0.745The solid colored line shows the estimated time-varying causal effect, with a shaded band representing the 95% confidence interval. The dashed red line (when present) indicates the true time-varying effect used in the simulation.
# Coefficients for basis functions
cat("Estimated coefficients:\n")
#> Estimated coefficients:
print(round(coef(result, exposure = 1), 4))
#> [1] -4.0138 1.1379
# Time-varying effect curve
cat("\nFirst 10 time points of beta(t):\n")
#>
#> First 10 time points of beta(t):
head(result$exposures[[1]]$effect, 10)
#> [1] 0.1332247 0.1355505 0.1394325 0.1448320 0.1517528 0.1602287 0.1702996
#> [8] 0.1819805 0.1952264 0.2098995cat("Performance:\n")
#> Performance:
cat(" MISE:", round(result$exposures[[1]]$performance$MISE, 6), "\n")
#> MISE: 0.002314
cat(" Coverage:", round(result$exposures[[1]]$performance$Coverage, 3), "\n")
#> Coverage: 0.745
cat(" Components used:", result$exposures[[1]]$nPC_used, "\n")
#> Components used: 2U-FMR also works with binary outcomes:
# Generate binary outcome
outcome_binary <- getY_multi_exposure(
sim_data,
XYmodels = "2",
X_effects = TRUE,
outcome_type = "binary"
)
# Estimate with control function
result_binary <- mvfmr_separate(
G_list = list(sim_data$details$G),
fpca_results = list(fpca1),
Y = outcome_binary$Y,
outcome_type = "binary",
method = "cf", # Control function for binary
max_nPC = 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 (beta = 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(
G_list = list(sim_data$details$G),
fpca_results = list(fpca1),
Y = outcome_data$Y,
outcome_type = "continuous",
bootstrap = TRUE,
n_bootstrap = 100,
max_nPC = 4,
verbose = FALSE
)
# Bootstrap CIs are stored in result_boot$exposures[[1]]$...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(
G_list = list(sim_data$details$G),
fpca_results = list(fpca1),
by_outcome_list = list(by_outcome),
sy_outcome_list = list(sy_outcome),
ny_outcome = 50000,
max_nPC = 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.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 26.04.1 LTS
#>
#> Matrix products: default
#> BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so; LAPACK version 3.12.0
#>
#> locale:
#> [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
#> [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
#> [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
#> [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
#> [9] LC_ADDRESS=C LC_TELEPHONE=C
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
#>
#> time zone: Etc/UTC
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] ggplot2_4.0.3 fdapace_0.6.0 mvfmr_0.2.0 rmarkdown_2.32
#>
#> loaded via a namespace (and not attached):
#> [1] gtable_0.3.6 shape_1.4.6.1 xfun_0.61
#> [4] bslib_0.12.0 htmlwidgets_1.6.4 lattice_0.23-1
#> [7] numDeriv_2016.8-1.1 vctrs_0.7.3 tools_4.6.1
#> [10] generics_0.1.4 parallel_4.6.1 tibble_3.3.1
#> [13] cluster_2.1.8.3 pkgconfig_2.0.3 Matrix_1.7-6
#> [16] data.table_1.18.6.1 checkmate_2.3.4 RColorBrewer_1.1-3
#> [19] S7_0.2.2 lifecycle_1.0.5 compiler_4.6.1
#> [22] farver_2.1.2 stringr_1.6.0 progress_1.2.3
#> [25] codetools_0.2-20 htmltools_0.5.9 sys_3.4.3
#> [28] buildtools_1.0.0 sass_0.4.10 yaml_2.3.12
#> [31] glmnet_5.0 htmlTable_2.5.0 Formula_1.2-6
#> [34] pracma_2.4.6 crayon_1.5.3 pillar_1.11.1
#> [37] jquerylib_0.1.4 MASS_7.3-66 cachem_1.1.0
#> [40] Hmisc_5.3-0 iterators_1.0.14 rpart_4.1.27
#> [43] foreach_1.5.2 tidyselect_1.2.1 digest_0.6.39
#> [46] stringi_1.8.9 dplyr_1.2.1 labeling_0.4.3
#> [49] maketools_1.3.2 splines_4.6.1 fastmap_1.2.0
#> [52] grid_4.6.1 colorspace_2.1-3 cli_3.6.6
#> [55] magrittr_2.0.5 base64enc_0.1-6 survival_3.8-12
#> [58] withr_3.0.3 foreign_0.8-91 prettyunits_1.2.0
#> [61] scales_1.4.0 backports_1.5.1 otel_0.2.0
#> [64] nnet_7.3-21 gridExtra_2.3.1 hms_1.1.4
#> [67] evaluate_1.0.5 knitr_1.52 doParallel_1.0.17
#> [70] rlang_1.3.0 Rcpp_1.1.2 glue_1.8.1
#> [73] pROC_1.19.1 rstudioapi_0.19.0 jsonlite_2.0.0
#> [76] R6_2.6.1