vQTLs as exposures

Published

April 10, 2026

vQTLs as exposures

vQTLs represent genetic variants that induce greater sensitivity of a particular trait to other genetic or environmental factors. This could also manifest as GxG or GxE interactions.

What happens when a vQTL and mean-QTL are used jointly in an MVMR analysis?

Data generating model 1

Suppose that the interacting environmental factor (E) influences Y, depending on the value of X

E -> Y | X

and that it also influences X with its effect depending on the genetic factor for X (Gx1)

E -> X | Gx1

but that there is another genetic factor for X that doesn’t interact with E

Gx2 -> X
library(dplyr)
library(ggplot2)
library(simulateGP)

test_drm <- function(g, y) {
  y.i <- tapply(y, g, median, na.rm=T)  
  z.ij <- abs(y - y.i[g+1])
  fast_assoc(z.ij, g) %>%
    as_tibble() %>%
    mutate(method="drm")
}

dgm1 <- function(n, bxy, bey, bex, bintx, binty) {
    e <- rnorm(n)
    g1 <- rbinom(n, 2, 0.4)
    g2 <- rbinom(n, 2, 0.4)
    x <- rnorm(n) + e * bex + e * g1 * bintx + g2
    y <- rnorm(n) + e * bey + x * bxy + x * e * binty
    return(tibble(e, g1, g2, y, x))
}
dat <- dgm1(10000, bxy=1, bey=1, bex=1, bintx=1, binty=1)
bind_rows(
    test_drm(dat$g1, dat$x) %>% mutate(out="x", g="g1"),
    test_drm(dat$g1, dat$y) %>% mutate(out="y", g="g1"),
    test_drm(dat$g2, dat$x) %>% mutate(out="x", g="g2"),
    test_drm(dat$g2, dat$y) %>% mutate(out="y", g="g2")
)
A tibble: 4 × 9
ahat bhat se fval pval n method out g
<dbl> <dbl> <dbl> <dbl> <dbl> <int> <chr> <chr> <chr>
1.195706 0.689784798 0.01964462 1.232934e+03 8.281668e-255 10000 drm x g1
2.579266 0.807983342 0.05686533 2.018876e+02 2.233561e-45 10000 drm y g1
1.651814 -0.003847114 0.01988447 3.743192e-02 8.465922e-01 10000 drm x g2
2.682251 0.629851581 0.05656551 1.239861e+02 1.249735e-28 10000 drm y g2

Implement the MVMR analysis

mvmr <- function(g1, g2, x, y) {
    mod1 <- test_drm(dat$g1, dat$x)
    xvar <- g1 * mod1$bhat
    mod2 <- lm(x ~ g2)
    xmean <- predict(mod2)
    mvmr <- summary(lm(y ~ 0 + xvar + xmean))$coefficients %>%
        as_tibble(rownames="term") %>%
        select(bhat=Estimate, se=`Std. Error`, pval=`Pr(>|t|)`) %>%
        mutate(method="mvmr", term=c("xvar", "xmean"))
    return(mvmr)
}

Scenario 1:

  • X causally influences Y
  • E interacts with G to influence X
  • E interacts with X to influence Y

This leads to a strong mean and strong variance effect of X

dat <- dgm1(10000, bxy=1, bey=1, bex=1, bintx=1, binty=1)
mvmr(dat$g1, dat$g2, dat$x, dat$y)
A tibble: 2 × 5
bhat se pval method term
<dbl> <dbl> <dbl> <chr> <chr>
2.292121 0.08775757 1.577425e-145 mvmr xvar
1.397728 0.05615492 9.779455e-133 mvmr xmean

Scenario 2:

  • X has no main causal influence on Y
  • E interacts with G to influence X
  • E interacts with X to influence Y

This leads to the mean X effect strongly attenuating

dat <- dgm1(10000, bxy=0, bey=1, bex=1, bintx=1, binty=1)
mvmr(dat$g1, dat$g2, dat$x, dat$y)
A tibble: 2 × 5
bhat se pval method term
<dbl> <dbl> <dbl> <chr> <chr>
2.2227606 0.06156952 2.198433e-268 mvmr xvar
0.3755239 0.04369793 9.686354e-18 mvmr xmean

Other scenarios…

dat <- dgm1(10000, bxy=0, bey=1, bex=1, bintx=0, binty=1)
mvmr(dat$g1, dat$g2, dat$x, dat$y)
A tibble: 2 × 5
bhat se pval method term
<dbl> <dbl> <dbl> <chr> <chr>
-90.2625837 6.31075754 5.946832e-46 mvmr xvar
0.4710726 0.03275921 2.008269e-46 mvmr xmean
dat <- dgm1(10000, bxy=0, bey=1, bex=1, bintx=1, binty=0)
mvmr(dat$g1, dat$g2, dat$x, dat$y)
A tibble: 2 × 5
bhat se pval method term
<dbl> <dbl> <dbl> <chr> <chr>
-0.013102141 0.02523419 0.6036176 mvmr xvar
0.006687282 0.01628023 0.6812567 mvmr xmean
dat <- dgm1(10000, bxy=1, bey=1, bex=1, bintx=0, binty=0)
mvmr(dat$g1, dat$g2, dat$x, dat$y)
A tibble: 2 × 5
bhat se pval method term
<dbl> <dbl> <dbl> <chr> <chr>
0.3694879 0.75363857 6.239522e-01 mvmr xvar
1.0248249 0.02768784 3.647040e-281 mvmr xmean