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.

Univariable Functional Mendelian Randomization

Nicole Fontana

2026-02-05

Overview

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.

When to Use U-FMR

Use univariable estimation (mvfmr_separate() with G2 = NULL) when:

Installation

devtools::install_github("NicoleFontana/mvfmr")
library(mvfmr)
library(fdapace)
library(ggplot2)

Example: Single Exposure Analysis

Step 1: Simulate Data

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: 25

Step 2: Generate Outcome

We 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

Step 3: FPCA for Single Exposure

# 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 %

Step 4: Univariable Estimation

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.804

Step 5: Visualize Time-Varying Effect

plot(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.

Step 6: Extract Results

# 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.16813235

Step 7: Performance Metrics

cat("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: 2

Binary Outcomes

U-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")

Advanced Topics

Available Effect Models

The package includes 10 pre-defined time-varying effect shapes:

Bootstrap Inference

# 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$...

Two-Sample Design

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)

Next Steps

Learn More

Citation

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 preprint arXiv:2512.19064.

Session Info

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

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.