MR estimates with binary exposures

Author

Gibran Hemani

Published

September 28, 2026

Background

If we perform 2 sample MR analysis where exposure and outcome are both binary, and both are estimated on the log odds scale, the effect estimate will be in different units when comparing against observational logistic regression. In the latter case, the outcome is on the logistic scale but the exposure is on the risk difference scale.

So the solution is to convert the MR estimate by transforming the SNP-exposure estimate on the risk difference scale, or to convert the observational estimate with the inverse.

Example

set.seed(1234)

# Sample size
n <- 1000000

# Simulate a genotype
g <- rbinom(n, 2, 0.5)

# Try effect of SNP on the exposure on the liability scale (not the risk difference scale)
b_gx <- 0.8

# Generate liability for X
x_liability <- g * b_gx

# Generate X using logistic transformation
x <- rbinom(n, 1, plogis(x_liability))

Note that the risk difference scale gives a different estimate to the logistic scale. Here’s the risk difference scale:

summary(lm(x ~ g))$coef[2]
[1] 0.1655918

Here’s the logistic scale, which matches the simulated beta:

summary(glm(x ~ g, family = "binomial"))$coef[2]
[1] 0.7983852

Now make X causal for Y. The observational logistic regression estimate matches the simulation.

b_xy <- 0.1
y <- rbinom(n, 1, plogis(x * b_xy))
# y <- as.integer(x * b_xy + rlogis(n)>0) # note this is equivalent to above
glm(y ~ x, family = "binomial")$coef[2]
         x 
0.09324593 

Let’s make the MR estimate

bgx <- glm(x ~ g, family = "binomial")$coef[2]
bgy <- glm(y ~ g, family = "binomial")$coef[2]
bgy/bgx
         g 
0.02376076 

This doesn’t match the simulated beta. That’s because we’re asking “how much does Y change per liability change in X”, rather than “how much does Y change per unit change in X”. So we need to change bgx to be in unit change scale (risk difference scale).

px <- mean(x)
scale_factor <- px * (1-px)
bgx_transform <- bgx * scale_factor
bgy / bgx_transform
     g 
0.1089 

Or we can convert the observational estimate onto the same scale as the naive MR estimate (effect on log-odds of Y per unit change in log-odds of X), which should match bgy/bgx:

obs_risk_diff <- glm(y ~ x, family = "binomial")$coef[2]
obs_liability_scale <- obs_risk_diff * scale_factor
obs_liability_scale
         x 
0.02034522 

Note that the transformation assumes that the disease isn’t rare and the effect sizes are small.


sessionInfo()
R version 4.6.1 (2026-06-24)
Platform: aarch64-apple-darwin23
Running under: macOS Sequoia 15.2

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Europe/London
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
 [1] htmlwidgets_1.6.4 compiler_4.6.1    fastmap_1.2.0     cli_3.6.6        
 [5] tools_4.6.1       htmltools_0.5.9   otel_0.2.0        yaml_2.3.12      
 [9] rmarkdown_2.31    knitr_1.51        jsonlite_2.0.0    xfun_0.57        
[13] digest_0.6.39     rlang_1.3.0       evaluate_1.0.5