---
title: Mendelian randomization using the twin design
author:
  - Luis Araujo
date: 2024 
vignette: >
  %\VignetteEngine{knitr::knitr}
  %\VignetteIndexEntry{Mendelian randomization using the twin design}
  %\usepackage[UTF-8]{inputenc}
---

```{r setup, include = F}
is_CRAN <- !identical(Sys.getenv("NOT_CRAN"), "true")
if (!is_CRAN) {
   options(mc.cores = parallel::detectCores())
} else {
  knitr::opts_chunk$set(eval = FALSE)
  knitr::knit_hooks$set(evaluate.inline = function(x, envir) x)
}
```


This script is a modified version of Minica & Neale 2018 AGES workshop
In this script we will test the (bidirectional) causal effect between two
phenotypes, think of BMI on SBP (systolic blood pressure) and vice-versa,
and polygenic scores for each.
In the interest of brevity we will always refer to BMI as variable X, and SBP
as variable Y, with the respective instruments iX and iY.

There are four main take away messages that you should focus:
  1. How to setup a simulation in OpenMX using mxGenerateData
  2. That a two-stages least squares test can be specified in SEM in OpenMX
  3. That MR-DoC recovers the same estimates from 2sls test
  4. That in the presence of horizontal pleiotropy estimates are biased
        in the 2sls test and unbiased in MR-DoC

The script contains four scenarios,
  - no pleiotropy between the instrument and the outcome (Scenario 1),
  - where there is pleiotropy (Scenario 2),
  - presence of a bidirectional causal effect (Scenario 3)
  - absence of a  causal effect (Scenario 4)
See presentation slides for path diagrams.
If you are using RStudio you can navigate using the outline dropdown menu
Setting the stage ------------------------------------------------------------


```r
rm(list=ls())  # Emptying the R environment, not recommended usually but useful
# for this workshop


# loading required packages
library(OpenMx)
library(MASS)
library(dplyr)

options(digits = 2, scipen = 999)  # we dont want scientific notation
mxOption(NULL, "Default optimizer", "SLSQP")
```

The models are specified in three objects, top (for common parts), MZ and DZ
spend some time recognizing the elements in the model, by now you should
have seen similar code. Notice two minor style changes, I am naming
the objects at the beginning and the matrices labels spelled out in the
positions they will end up in the matrix.
The final objects (mrdoc1, mrdoc2) will be reused throughout the script.
The matrix containing regressions for causal paths and instruments


```r
BE <- mxMatrix(name = "BE", type = "Full",nrow=3,  ncol = 3, byrow = TRUE,
               labels = c(NA,   "g2", "b1",
                          "g1", NA,   "b2",
                          NA,   NA,   NA),
               free = c(FALSE, FALSE, TRUE,
                        TRUE,  FALSE, TRUE,
                        FALSE, FALSE, FALSE),
               dimnames = list(c("X", "Y", "iX"),
                               c("X", "Y", "iX")))
```

ACE decomposition


```r
A <-  mxMatrix(name = 'A', type='Symm', nrow=3,ncol = 3,byrow = TRUE,
               labels=c("ax2", "covA", NA,
                        "covA","ay2",  NA,
                        NA,    NA,     "sigma_x"),
               free=c(TRUE, TRUE, FALSE,
                      TRUE, TRUE, FALSE,
                      FALSE,FALSE,TRUE),
               dimnames = list(c("X", "Y", "iX"),
                               c("X", "Y", "iX")))

C <-  mxMatrix(name = 'C', type='Symm',nrow=3, ncol = 3,byrow = TRUE,
               labels =c("cx2", "covC", NA,
                         "covC","cy2",  NA,
                         NA,    NA,     NA),
               free=c(TRUE, TRUE, FALSE,
                      TRUE, TRUE, FALSE,
                      FALSE,FALSE,FALSE),
               dimnames = list(c("X", "Y", "iX"),
                               c("X", "Y", "iX")))

E <-  mxMatrix(name = 'E', type='Symm', nrow=3, ncol = 3,byrow = TRUE,
               labels =c("ex2", "covE",NA,
                         "covE","ey2", NA,
                         NA,     NA,   NA),
               free= c(TRUE, FALSE,FALSE,
                       FALSE,TRUE, FALSE,
                       FALSE,FALSE,FALSE),
               dimnames = list(c("X", "Y", "iX"),
                               c("X", "Y", "iX")))
```

The filter matrix is used to remove the PRS from _T2 in MZs,
if we kept it the matrix would become redundant resulting
in the Hessian not positive


```r
filter <-  mxMatrix(name = 'filter', type='Full', nrow=5, ncol=6, free=FALSE,
                    byrow = TRUE,
                    values=c(1,0,0,0,0,0,
                             0,1,0,0,0,0,
                             0,0,1,0,0,0,
                             0,0,0,1,0,0,
                             0,0,0,0,1,0),
                    dimnames = list(c("X_T1", "Y_T1", "iX_T1","X_T2", "Y_T2"),
                                    c("X_T1", "Y_T1", "iX_T1","X_T2", "Y_T2", "iX_T2")))
```

This lambda (LY) matrix is fixing total variances to 1 for PRSs and
phenotypes


```r
LY <-  mxMatrix(name = 'LY', type='Full',nrow=3, ncol = 3, free = FALSE,
                values = diag(3), labels = NA,
                dimnames = list(c("X", "Y", "iX"),
                                c("X", "Y", "iX")))
```

Means objects


```r
mean_dz <-  mxMatrix(name = 'mean_dz', type='Full', nrow=1, ncol=6,
                     free=TRUE, values= 0, byrow = TRUE,
                     labels=c('mX1','mY2','miX1','mX1','mY2','miX1'))

mean_mz <-  mxAlgebra('mean_mz', expression = mean_dz%*%t(filter))
```

Identity matrix for the algebras


```r
I <-  mxMatrix(name = 'I', type='Iden', nrow= 3,ncol= 3)

algebras <- list(
  mxAlgebra('A_'  , expression = LY %&% solve(I - BE)%&%A),
  mxAlgebra('C_'  , expression = LY %&% solve(I - BE)%&%C),
  mxAlgebra('E_'  , expression = LY %&% solve(I - BE)%&%E),
  mxAlgebra('SPh' , expression = A_ + C_ + E_),
  mxAlgebra('variance_mz_', expression = rbind(
    cbind(SPh, A_+C_),
    cbind(A_+C_, SPh))),
  mxAlgebra('variance_dz', expression= rbind(
    cbind(SPh, .5%x%A_+C_),
    cbind(.5%x%A_+C_, SPh))),
  mxAlgebra('variance_mz', expression= filter%&%variance_mz_))

top_mr1 <- mxModel("top", BE, A, C, E, filter, LY,  mean_dz, mean_mz, I, algebras)
```

Preparing the objects for the multiple groups (MZ, DZ) analysis


```r
MZ_mr1 = mxModel("MZ",mxFitFunctionML(),
                 mxExpectationNormal(covariance = "top.variance_mz", means = "top.mean_mz",
                                     dimnames =  c("X_T1", "Y_T1", "iX_T1",
                                                   "X_T2", "Y_T2")))

DZ_mr1 = mxModel("DZ", mxFitFunctionML(),
                 mxExpectationNormal(covariance = "top.variance_dz", means = "top.mean_dz",
                                     dimnames =  c("X_T1", "Y_T1", "iX_T1",
                                                   "X_T2", "Y_T2", "iX_T2")))
```

Combining objects to generate the final model


```r
mrdoc1 = mxModel("mrdoc1", top_mr1, MZ_mr1, DZ_mr1,
                 mxFitFunctionMultigroup(c("MZ","DZ") ) )
```

Scenario 1: no pleiotropy  ---------------------------------------------------
Generate simulated data for mrdoc1
Fit the model in unrelateds using a structural equation model
Fit the model in twins - using the MR-doc
Compare the NCPs of the models in unrelateds and twins
Look at the 2 stage least squares results
Let's generate  simulated data where b2 (the pleiotropic path) is zero,
in other words, no pleiotropy present in the data.
As such we need to set the true values, this is a way of doing this:


```r
true_model <-  mrdoc1 |>
  omxSetParameters(labels =  c("g1","b1", "b2",
                               "ax2", "ay2", "cx2", "cy2", "ex2", "ey2",
                               "covA", "covC", "covE","sigma_x"),
                   values = c(0.316, 0.316, 0,
                              0.424, 0.671, 0.671, 0.424, 0.519, 0.519,
                              0.411,0.221,0, 1)) |>
  omxSetParameters(labels = c("b2"), free = FALSE) # remember, no pleiotropy
```

Simulating data according to the exact covariance matrix from mrdoc1 (above),
this is done using the switch empirical = T, this should be quick


```r
sim_data <- mxGenerateData(true_model, nrows = 1000, empirical = TRUE)
```

The data does not comes with the instrument column for the second twin,
we have to duplicate the column. In your analysis, this step will not happen,
we are doing this because of how I coded the simulation for this workshop.


```r
sim_data$MZ <- mutate(sim_data$MZ, iX_T2 = iX_T1)

dnpmz <- sim_data$MZ
dnpdz <- sim_data$DZ

dim(dnpdz) #
```

```
## [1] 1000    6
```

```r
head(dnpdz)
```

```
##    X_T1  Y_T1 iX_T1   X_T2  Y_T2 iX_T2
## 1  1.17  1.63 -0.43  1.469  1.88 -0.28
## 2 -0.79 -0.79  0.39 -2.307 -2.29 -1.81
## 3  0.18  1.21  0.65  0.286  1.38  0.04
## 4 -1.74 -0.73  0.64 -0.908 -1.79  0.79
## 5  0.96  1.04 -0.36 -1.348  0.06 -1.50
## 6 -0.85 -0.56 -0.47 -0.074 -0.40 -1.21
```

```r
summary(dnpdz)
```

```
##       X_T1           Y_T1          iX_T1           X_T2           Y_T2          iX_T2      
##  Min.   :-5.0   Min.   :-4.4   Min.   :-3.4   Min.   :-4.9   Min.   :-4.8   Min.   :-3.05  
##  1st Qu.:-0.9   1st Qu.:-1.0   1st Qu.:-0.7   1st Qu.:-0.9   1st Qu.:-1.0   1st Qu.:-0.64  
##  Median :-0.1   Median : 0.0   Median : 0.0   Median : 0.0   Median : 0.0   Median : 0.07  
##  Mean   : 0.0   Mean   : 0.0   Mean   : 0.0   Mean   : 0.0   Mean   : 0.0   Mean   : 0.00  
##  3rd Qu.: 0.8   3rd Qu.: 1.0   3rd Qu.: 0.7   3rd Qu.: 0.9   3rd Qu.: 1.0   3rd Qu.: 0.64  
##  Max.   : 4.5   Max.   : 5.4   Max.   : 3.9   Max.   : 4.6   Max.   : 4.2   Max.   : 2.83
```

```r
dim(dnpmz) #
```

```
## [1] 1000    6
```

```r
head(dnpmz)
```

```
##     X_T1  Y_T1 iX_T1  X_T2   Y_T2 iX_T2
## 1 -0.064  1.70 -1.32 -0.39  1.160 -1.32
## 2 -1.285 -1.36 -0.60 -1.43  0.021 -0.60
## 3  0.299  0.44 -1.39  1.17  0.449 -1.39
## 4 -0.733 -2.93 -0.94 -0.61 -2.126 -0.94
## 5  1.016 -0.11  0.30  1.44  0.035  0.30
## 6 -1.071  0.18 -1.67 -1.21 -0.562 -1.67
```

```r
summary(dnpmz)
```

```
##       X_T1           Y_T1          iX_T1           X_T2           Y_T2          iX_T2     
##  Min.   :-4.2   Min.   :-5.5   Min.   :-3.4   Min.   :-5.4   Min.   :-6.7   Min.   :-3.4  
##  1st Qu.:-0.9   1st Qu.:-1.0   1st Qu.:-0.7   1st Qu.:-0.9   1st Qu.:-1.0   1st Qu.:-0.7  
##  Median : 0.0   Median : 0.0   Median : 0.0   Median : 0.0   Median : 0.0   Median : 0.0  
##  Mean   : 0.0   Mean   : 0.0   Mean   : 0.0   Mean   : 0.0   Mean   : 0.0   Mean   : 0.0  
##  3rd Qu.: 0.9   3rd Qu.: 1.0   3rd Qu.: 0.7   3rd Qu.: 0.8   3rd Qu.: 1.0   3rd Qu.: 0.7  
##  Max.   : 3.8   Max.   : 4.3   Max.   : 2.9   Max.   : 4.3   Max.   : 4.7   Max.   : 2.9
```

We now add the data to the model object created before.
Notice that the plus sign is overloaded in OpenMx as it is, for example,
in ggplot2. So you can combine objects with the
syntax below instead of `model = mxModel(model, mxData(data, type = "raw"))`
This syntax is optional, but helps reducing the lenght of the script


```r
mrdoc1$MZ <- mrdoc1$MZ +  mxData(dnpmz, type = "raw")
mrdoc1$DZ <- mrdoc1$DZ +  mxData(dnpdz, type = "raw")

m1 <- mrdoc1 |>
  # Remember, no b2 in data, no b2 in the model
  omxSetParameters(labels = "b2", free = FALSE) |>
  # The poing of examining the data a few lines above is to set sensible
  # starting values for the model. Here, in the interest of brevity, we ask
  # OpenMx to pick starting values for us.
  mxAutoStart()

m1 <- mxRun(m1)
```

```
## Running mrdoc1 with 14 parameters
```

```r
# In your analyses, make sure to test for local identification frequently:
# mxCheckIdentification(m1)$status
# Model is locally identified
# [1] TRUE

summary(m1)
```

```
## Summary of mrdoc1 
##  
## free parameters:
##       name      matrix row   col        Estimate Std.Error A
## 1       g1      top.BE   Y     X  0.316000000123     0.028  
## 2       b1      top.BE   X    iX  0.316000000040     0.021  
## 3      ax2       top.A   X     X  0.423575999118     0.077  
## 4     covA       top.A   X     Y  0.410588999655     0.064  
## 5      ay2       top.A   Y     Y  0.670328999037     0.089  
## 6  sigma_x       top.A  iX    iX  0.999000000056     0.026  
## 7      cx2       top.C   X     X  0.670329000971     0.074  
## 8     covC       top.C   X     Y  0.220779000217     0.054  
## 9      cy2       top.C   Y     Y  0.423576000776     0.080  
## 10     ex2       top.E   X     X  0.518481000185     0.023  
## 11     ey2       top.E   Y     Y  0.518481000190     0.023  
## 12     mX1 top.mean_dz   1  X_T1 -0.000000000056     0.026  
## 13     mY2 top.mean_dz   1  Y_T1 -0.000000000034     0.030  
## 14    miX1 top.mean_dz   1 iX_T1 -0.000000000032     0.021  
## 
## Model Statistics: 
##                |  Parameters  |  Degrees of Freedom  |  Fit (-2lnL units)
##        Model:             14                  10986                 32383
##    Saturated:             NA                     NA                    NA
## Independence:             NA                     NA                    NA
## Number of observations/statistics: 2000/11000
## 
## Information Criteria: 
##       |  df Penalty  |  Parameters Penalty  |  Sample-Size Adjusted
## AIC:          10411                  32411                    32411
## BIC:         -51120                  32490                    32445
## CFI: NA 
## TLI: 1   (also known as NNFI) 
## RMSEA:  0  [95% CI (NA, NA)]
## Prob(RMSEA <= 0.05): NA
## To get additional fit indices, see help(mxRefModels)
## timestamp: 2024-03-27 14:48:49 
## Wall clock time: 0.17 secs 
## optimizer:  SLSQP 
## OpenMx version number: 2.20 
## Need help?  See help(mxSummary)
```

One way of assessing whether the causal path is significant is by dropping it


```r
m2 <- omxSetParameters(m1, name = "nog1", labels="g1", free = FALSE,
                       values = 0)
m2 <- mxRun(m2)
```

```
## Running nog1 with 13 parameters
```

```r
# Comparing the two models
mxCompare(m1, m2)
```

```
##     base comparison ep minus2LL    df   AIC diffLL diffdf                              p
## 1 mrdoc1       <NA> 14    32383 10986 32411     NA     NA                             NA
## 2 mrdoc1       nog1 13    32499 10987 32525    115      1 0.0000000000000000000000000066
```

Q1: We dropped the causal path, what is the interpretation for the above
result?
ANSWER: The causal path is significant, model m2 was worse.
Can we specify a model for a 2-stage least squares test using SEM and OpenMX?


```r
IVModel = mxModel("MR", type = "RAM", manifestVars = c("X", "Y", "I"),
                  latentVars = c("eX", "eY"),
                  # Path from instrument to exposure
                  mxPath(from = "I" , to = "X", arrows = 1, label = "b1"),
                  # Path from exposure to outcome, g1
                  mxPath(from = "X", to = "Y", label = "g1"),
                  # Latent error+ setting up variance and means for variables
                  mxPath(from = c("I"), arrows = 2, label = "vI"),
                  mxPath(from = c("eX", "eY"), to = c("X","Y"), value = 1, free = FALSE),
                  # Variance of residual errors
                  mxPath(from = c("eX", "eY"), arrows =  2, free = TRUE,
                         labels = c("vX", "vY")),
                  mxPath(connect = "unique.bivariate", from =  c("eX", "eY"),   arrows = 2,
                         values = 0.2, labels = "re"), # Correlation among residuals
                  mxPath("one", to = c("X","Y", "I"), labels = c("mX", "mY", "mI")))

# The above specification closely follows the slides in the presentation
# If you have umx installed you can look at it:
# library(umx)
# plot(IVModel)
```

Let's generate a new data set by taking only twin 1 from mzs and dzs


```r
dat1 <- rbind(dnpmz[,1:3],dnpdz[,1:3]) |>
  # we need to rename the variables to match the dimension names set in the
  # IVmodel above
  rename(X = X_T1,
         Y = Y_T1,
         I = iX_T1)
```

Adding the data to the model


```r
unrel <- IVModel + mxData(dat1, type = "raw")
```

Know your data, check variable skewness, kurtosis and means, but
in the interest of brevity, let's autostart


```r
unrel <- mxAutoStart(unrel)
unrel <- mxRun(unrel)
```

```
## Running MR with 9 parameters
```

```r
summary(unrel)
```

```
## Summary of MR 
##  
## free parameters:
##   name matrix row col   Estimate Std.Error A
## 1   g1      A   Y   X  0.3159990     0.090  
## 2   b1      A   X   I  0.3159993     0.028  
## 3   vI      S   I   I  0.9990026     0.032  
## 4   vX      S  eX  eX  1.6123920     0.051 !
## 5   re      S  eX  eY  0.6313694     0.150  
## 6   vY      S  eY  eY  1.6123882     0.124  
## 7   mX      M   1   X  0.0000090     0.028 !
## 8   mY      M   1   Y -0.0000113     0.028 !
## 9   mI      M   1   I  0.0000033     0.022 !
## 
## Model Statistics: 
##                |  Parameters  |  Degrees of Freedom  |  Fit (-2lnL units)
##        Model:              9                   5991                 18603
##    Saturated:              9                   5991                    NA
## Independence:              6                   5994                    NA
## Number of observations/statistics: 2000/6000
## 
## Information Criteria: 
##       |  df Penalty  |  Parameters Penalty  |  Sample-Size Adjusted
## AIC:           6621                  18621                    18621
## BIC:         -26934                  18672                    18643
## CFI: NA 
## TLI: 1   (also known as NNFI) 
## RMSEA:  0  [95% CI (NA, NA)]
## Prob(RMSEA <= 0.05): NA
## To get additional fit indices, see help(mxRefModels)
## timestamp: 2024-03-27 14:48:50 
## Wall clock time: 0.038 secs 
## optimizer:  SLSQP 
## OpenMx version number: 2.20 
## Need help?  See help(mxSummary)
```

```r
unrel2 <- omxSetParameters(name = "no_causal", unrel, label="g1", free = FALSE,
                           values = 0)
unrel2 <- mxRun(unrel2)
```

```
## Running no_causal with 8 parameters
```

```r
mxCompare(unrel,unrel2)
```

```
##   base comparison ep minus2LL   df   AIC diffLL diffdf      p
## 1   MR       <NA>  9    18603 5991 18621     NA     NA     NA
## 2   MR  no_causal  8    18612 5992 18628    9.1      1 0.0025
```

Q2: The line above is a Likelihood ratio test with 1 degree of freedom, what is
the meaning of a significant p-value in this case?
ANSWER: The causal path is significant, dropping it made the model (no_causal)
 significantly worse


```r
# Now let's compare with a typical 2sls test
TSLS1=lm(X~I,data=dat1)
Xhat=predict(TSLS1)
TSLS2=lm(Y~Xhat,data=dat1)
summary(TSLS2)
```

```
## 
## Call:
## lm(formula = Y ~ Xhat, data = dat1)
## 
## Residuals:
##    Min     1Q Median     3Q    Max 
## -5.477 -1.017  0.004  1.018  5.287 
## 
## Coefficients:
##                           Estimate             Std. Error t value Pr(>|t|)   
## (Intercept) 0.00000000000000000429 0.03297416855661412793    0.00   1.0000   
## Xhat        0.31599999999999939249 0.10440084815354150338    3.03   0.0025 **
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.5 on 1998 degrees of freedom
## Multiple R-squared:  0.00456,	Adjusted R-squared:  0.00407 
## F-statistic: 9.16 on 1 and 1998 DF,  p-value: 0.0025
```

```r
# In the specialized ivreg package the syntax would be:
# library(ivreg)
# TSLS2=ivreg(Y~X|I,data=dat1)
```

Q3: Check out the estimate for Xhat in the previous summary and compare
to unrel estimate. Did MR-DoC estimated same values as 2sls?
ANSWER: Yes, the estimates are equal (0.316)
Now let's check the power to reject the hypothesis of g1=0 using the
non-centrality parameter for related and unrelated individuals.


```r
lambdam1=mxCompare(m1,m2)[2,7]
dfs=mxCompare(m1,m2)[2,8]
alpha=0.05
ca=qchisq(alpha,dfs,ncp=0,lower.tail=F)
powerm1=pchisq(ca,dfs,ncp=lambdam1,lower.tail=F)
powerm1
```

```
## [1] 1
```

```r
lambdaunrel=mxCompare(unrel,unrel2)[2,7]
dfs=mxCompare(unrel,unrel2)[2,8]
alpha=0.05 	# user specified: type I error prob.
ca=qchisq(alpha,dfs,ncp=0,lower.tail=F)
powerUnrel=pchisq(ca,dfs,ncp=lambdaunrel,lower.tail=F)
powerUnrel
```

```
## [1] 0.86
```

Q4: Which method had better power to detect the causal effect?
ANSWER: MR-DoC has a power of 0.99, 2sls has a power of 0.86
   Scenario 2: Pleiotropy -------------------------------------------------
Next consider the scenario with pleiotropy, assume re=0 and test g1 = 0
Check the results: does MR-DoC model recover correctly the parameters b2, g1,
and b1.
Do we detect a causal effect if we don't account for pleiotropy
(SEM, 2stage least squares, 2-sample MR)?


```r
sim_data2 <-  mrdoc1 |>
  omxSetParameters(labels =  c("g1","b1", "b2",
                               "ax2", "ay2", "cx2", "cy2", "ex2", "ey2",
                               "covA", "covC", "covE","sigma_x"),
                   values = c(0.143, 0.316, 0.127,
                              0.424, 0.67, 0.670, 0.424, 0.519, 0.519,
                              0.411,0.221,0, 1)) |>
  mxGenerateData( nrows = 1000, empirical = TRUE)

sim_data2$MZ <- mutate(sim_data2$MZ, iX_T2 = iX_T1)

dwpmz <- sim_data2$MZ
dwpdz <- sim_data2$DZ

dim(dwpdz) #
```

```
## [1] 1000    6
```

```r
head(dwpdz)
```

```
##     X_T1  Y_T1 iX_T1 X_T2   Y_T2 iX_T2
## 1 -0.035 -0.27  1.03 -1.1  1.461  1.16
## 2  0.481  1.56  1.29  1.5  0.156  2.26
## 3 -1.018 -1.55 -0.65 -1.4  0.011 -0.76
## 4  1.193 -0.93 -0.64  2.0 -0.079  0.68
## 5  1.600  2.11  0.62 -1.3 -0.314  1.37
## 6  0.050 -0.89 -0.50  1.4  0.766  0.80
```

```r
summary(dwpdz)
```

```
##       X_T1           Y_T1          iX_T1            X_T2           Y_T2          iX_T2      
##  Min.   :-3.9   Min.   :-4.2   Min.   :-3.06   Min.   :-4.1   Min.   :-4.2   Min.   :-3.01  
##  1st Qu.:-0.9   1st Qu.:-0.9   1st Qu.:-0.68   1st Qu.:-0.9   1st Qu.:-0.9   1st Qu.:-0.66  
##  Median : 0.0   Median : 0.0   Median : 0.00   Median : 0.0   Median : 0.0   Median :-0.05  
##  Mean   : 0.0   Mean   : 0.0   Mean   : 0.00   Mean   : 0.0   Mean   : 0.0   Mean   : 0.00  
##  3rd Qu.: 0.9   3rd Qu.: 1.0   3rd Qu.: 0.69   3rd Qu.: 0.9   3rd Qu.: 0.9   3rd Qu.: 0.68  
##  Max.   : 4.4   Max.   : 4.3   Max.   : 2.92   Max.   : 3.8   Max.   : 4.1   Max.   : 2.96
```

```r
dim(dwpmz) #
```

```
## [1] 1000    6
```

```r
head(dwpmz)
```

```
##    X_T1    Y_T1  iX_T1    X_T2   Y_T2  iX_T2
## 1 -0.89 -1.5548 -0.490 -0.1428 -3.586 -0.490
## 2 -0.14  1.3320 -0.553  0.7621  0.332 -0.553
## 3 -1.22 -0.0653 -0.034 -0.0076  1.245 -0.034
## 4  0.72 -0.0053  0.166  1.6579  0.067  0.166
## 5 -0.92 -0.8504  0.171 -1.3165 -1.570  0.171
## 6 -1.78  0.9096 -1.363 -2.7041 -1.469 -1.363
```

```r
summary(dwpmz)
```

```
##       X_T1           Y_T1          iX_T1           X_T2           Y_T2          iX_T2     
##  Min.   :-3.7   Min.   :-4.6   Min.   :-3.0   Min.   :-4.0   Min.   :-4.1   Min.   :-3.0  
##  1st Qu.:-0.9   1st Qu.:-1.0   1st Qu.:-0.6   1st Qu.:-0.9   1st Qu.:-1.0   1st Qu.:-0.6  
##  Median :-0.1   Median : 0.0   Median : 0.0   Median : 0.0   Median : 0.0   Median : 0.0  
##  Mean   : 0.0   Mean   : 0.0   Mean   : 0.0   Mean   : 0.0   Mean   : 0.0   Mean   : 0.0  
##  3rd Qu.: 0.9   3rd Qu.: 0.9   3rd Qu.: 0.6   3rd Qu.: 0.9   3rd Qu.: 0.9   3rd Qu.: 0.6  
##  Max.   : 4.6   Max.   : 4.4   Max.   : 3.7   Max.   : 4.7   Max.   : 4.2   Max.   : 3.7
```

```r
pleio = mrdoc1
pleio$MZ <- mrdoc1$MZ + mxData(dwpmz, type = "raw")
pleio$DZ <- mrdoc1$DZ + mxData(dwpdz, type = "raw")

pleio <- mxRun(pleio)
```

```
## Running mrdoc1 with 15 parameters
```

```r
summary(pleio)
```

```
## Summary of mrdoc1 
##  
## free parameters:
##       name      matrix row   col    Estimate Std.Error A
## 1       g1      top.BE   Y     X  0.14300289     0.031  
## 2       b1      top.BE   X    iX  0.31599910     0.022  
## 3       b2      top.BE   Y    iX  0.12700095     0.025  
## 4      ax2       top.A   X     X  0.42359100     0.077 !
## 5     covA       top.A   X     Y  0.41058422     0.067 !
## 6      ay2       top.A   Y     Y  0.66931525     0.090 !
## 7  sigma_x       top.A  iX    iX  0.99900149     0.026  
## 8      cx2       top.C   X     X  0.66932329     0.074 !
## 9     covC       top.C   X     Y  0.22077728     0.054 !
## 10     cy2       top.C   Y     Y  0.42358333     0.080 !
## 11     ex2       top.E   X     X  0.51847605     0.023  
## 12     ey2       top.E   Y     Y  0.51848682     0.023 !
## 13     mX1 top.mean_dz   1  X_T1  0.00000085     0.026  
## 14     mY2 top.mean_dz   1  Y_T1 -0.00000048     0.027  
## 15    miX1 top.mean_dz   1 iX_T1 -0.00000751     0.021 !
## 
## Model Statistics: 
##                |  Parameters  |  Degrees of Freedom  |  Fit (-2lnL units)
##        Model:             15                  10985                 32379
##    Saturated:             NA                     NA                    NA
## Independence:             NA                     NA                    NA
## Number of observations/statistics: 2000/11000
## 
## Information Criteria: 
##       |  df Penalty  |  Parameters Penalty  |  Sample-Size Adjusted
## AIC:          10409                  32409                    32409
## BIC:         -51117                  32493                    32445
## CFI: NA 
## TLI: 1   (also known as NNFI) 
## RMSEA:  0  [95% CI (NA, NA)]
## Prob(RMSEA <= 0.05): NA
## To get additional fit indices, see help(mxRefModels)
## timestamp: 2024-03-27 14:48:51 
## Wall clock time: 0.14 secs 
## optimizer:  SLSQP 
## OpenMx version number: 2.20 
## Need help?  See help(mxSummary)
```

parameters used for simulation
g1 = 0.143
b1 = 0.316
b2 = 0.127
Q5: check the results: does MR-DoC model recover correctly the parameters b2,
g1, and b1? Hint: look at the true values used for simulation
ANSWER: Yes.
 MR-DoC: Test g1=0 using a likelihood ratio test   #########


```r
pleio2 <- omxSetParameters(name = "no_causal", pleio, label="g1", free = FALSE,
                           values = 0)
pleio2 <- mxRun(pleio2)
```

```
## Running no_causal with 14 parameters
```

```r
mxCompare(pleio, pleio2)
```

```
##     base comparison ep minus2LL    df   AIC diffLL diffdf         p
## 1 mrdoc1       <NA> 15    32379 10985 32409     NA     NA        NA
## 2 mrdoc1  no_causal 14    32400 10986 32428     21      1 0.0000043
```

Let's run in unrelateds: ---------------------------------------------------


```r
# take twin 1 from mz and dz
dat2 <- rbind(dwpmz[,1:3],dwpdz[,1:3])|>
  rename(X = X_T1,
         Y = Y_T1,
         I = iX_T1)


unrel3 <- IVModel + mxData(dat2, type = "raw")
# in the interest of brevity, let's autostart the model
unrel3 <- mxAutoStart(unrel3)
unrel3 <- mxRun(unrel3)
```

```
## Running MR with 9 parameters
```

```r
summary(unrel3)
```

```
## Summary of MR 
##  
## free parameters:
##   name matrix row col    Estimate Std.Error A
## 1   g1      A   Y   X  0.54484392     0.083 !
## 2   b1      A   X   I  0.31600496     0.028 !
## 3   vI      S   I   I  0.99903761     0.032 !
## 4   vX      S  eX  eX  1.61131910     0.051 !
## 5   re      S  eX  eY -0.01613821     0.137 !
## 6   vY      S  eY  eY  1.36412244     0.043 !
## 7   mX      M   1   X -0.00000021     0.028  
## 8   mY      M   1   Y -0.00000017     0.026  
## 9   mI      M   1   I  0.00000305     0.022 !
## 
## Model Statistics: 
##                |  Parameters  |  Degrees of Freedom  |  Fit (-2lnL units)
##        Model:              9                   5991                 18600
##    Saturated:              9                   5991                    NA
## Independence:              6                   5994                    NA
## Number of observations/statistics: 2000/6000
## 
## Information Criteria: 
##       |  df Penalty  |  Parameters Penalty  |  Sample-Size Adjusted
## AIC:           6618                  18618                    18618
## BIC:         -26937                  18669                    18640
## CFI: NA 
## TLI: 1   (also known as NNFI) 
## RMSEA:  0  [95% CI (NA, NA)]
## Prob(RMSEA <= 0.05): NA
## To get additional fit indices, see help(mxRefModels)
## timestamp: 2024-03-27 14:48:52 
## Wall clock time: 0.053 secs 
## optimizer:  SLSQP 
## OpenMx version number: 2.20 
## Need help?  See help(mxSummary)
```

parameters used for simulation
g1 = 0.143
b1 = 0.316
b2 = 0.127
Q6: Does the 2sls model recover correctly the parameters used for simulation?
ANSWER: No, it overestimated g1


```r
unrel4 <- omxSetParameters(name = "no_causal", unrel3, label="g1", free = FALSE,
                           values = 0)
unrel4 <- mxRun(unrel4)
```

```
## Running no_causal with 8 parameters
```

```r
mxCompare(unrel3,unrel4)
```

```
##   base comparison ep minus2LL   df   AIC diffLL diffdf           p
## 1   MR       <NA>  9    18600 5991 18618     NA     NA          NA
## 2   MR  no_causal  8    18633 5992 18649     32      1 0.000000014
```

```r
## Which model has highest power? (look at chisq difference) -----------------

## MR-DoC in Twins
chisq_Twins=mxCompare(pleio,pleio2)[2,7]
chisq_Twins
```

```
## [1] 21
```

```r
## MR-SEM in unrelateds
chisq_Unrel=mxCompare(unrel3, unrel4)[2,7]
chisq_Unrel
```

```
## [1] 32
```

Q7: Conclusion? Larger diff, higher power
ANSWER: Power in the model with unrelated was  higher.


```r
##  Fit the model using two stage least squares     ####################
TSLS1=lm(X~I,data=dat2)
Xhat=predict(TSLS1)
TSLS2=lm(Y~Xhat,data=dat2)
summary(TSLS2)
```

```
## 
## Call:
## lm(formula = Y ~ Xhat, data = dat2)
## 
## Residuals:
##    Min     1Q Median     3Q    Max 
## -4.622 -0.922 -0.037  0.920  4.710 
## 
## Coefficients:
##                          Estimate            Std. Error t value    Pr(>|t|)    
## (Intercept) 0.0000000000000000135 0.0302219807176829502    0.00           1    
## Xhat        0.5448987341772134618 0.0956870349706873818    5.69 0.000000014 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.4 on 1998 degrees of freedom
## Multiple R-squared:  0.016,	Adjusted R-squared:  0.0155 
## F-statistic: 32.4 on 1 and 1998 DF,  p-value: 0.0000000142
```

Check above that the 2sls test using either glm or SEM still finds
same estimates.
Q8: Do we detect a causal effect if we account for pleiotropy (SEM, 2stage
least squares)?
ANSWER: The models using unrelated detects a causal effect but not
accounting for pleiotropy the estimate was biased.
Scenario 3. Bidirectional causation -------------------------------------------
Specifying the mrdoc2 model. This is similar to mrdoc1 except
larger matrices (one more instrument)
Matrix for causal paths


```r
BE <-  mxMatrix(name = "BE", type = "Full",nrow=4,  ncol = 4,byrow = TRUE,
                labels = c(NA,   "g2", "b1", "b4",
                           "g1", NA,   "b2", "b3",
                           NA,   NA,   NA,   NA,
                           NA,   NA,   NA,   NA),
                free = c(FALSE, TRUE, TRUE, FALSE,
                         TRUE, FALSE, FALSE, TRUE,
                         FALSE, FALSE, FALSE, FALSE,
                         FALSE, FALSE, FALSE, FALSE),
                dimnames = list(c("X", "Y", "iX", "iY"),
                                c("X", "Y", "iX", "iY")))
```

A, C and E decomposition


```r
A <- mxMatrix(name = 'A', type='Symm', nrow=4, ncol = 4,byrow = TRUE,
              labels=c("ax2","covA",NA,NA,
                       "covA","ay2",NA,NA,
                       NA,NA,"x2"  ,"rf",
                       NA,NA,"rf","y2"),
              free=c(TRUE,TRUE,FALSE,FALSE,
                     TRUE,TRUE,FALSE,FALSE,
                     FALSE,FALSE,TRUE,TRUE,
                     FALSE,FALSE,TRUE,TRUE),
              dimnames = list(c("X", "Y", "iX", "iY"),
                              c("X", "Y", "iX", "iY")))

C <-  mxMatrix(name = 'C', type='Symm',nrow=4, ncol = 4,byrow = TRUE,
               labels =c("cx2", "covC",NA,NA,
                         "covC","cy2" ,NA,NA,
                         NA,    NA,    NA,NA,
                         NA,    NA,    NA,NA),
               free=c(TRUE,TRUE,  FALSE,FALSE,
                      TRUE,TRUE,  FALSE,FALSE,
                      FALSE,FALSE,FALSE,FALSE,
                      FALSE,FALSE,FALSE,FALSE),
               dimnames = list(c("X", "Y", "iX", "iY"),
                               c("X", "Y", "iX", "iY")))

E <-  mxMatrix(name = 'E', type='Symm', nrow=4, ncol = 4,byrow = TRUE,
               labels =c("ex2", "covE",NA,NA,
                         "covE","ey2" ,NA,NA,
                         NA,    NA,    NA,NA,
                         NA,    NA,    NA,NA),
               free= c(TRUE,TRUE,  FALSE,FALSE,
                       TRUE,TRUE,  FALSE,FALSE,
                       FALSE,FALSE,FALSE,FALSE,
                       FALSE,FALSE,FALSE,FALSE),
               dimnames = list(c("X", "Y", "iX", "iY"),
                               c("X", "Y", "iX", "iY")))
```

A filter matrix, as we need to remove the PRSs from twin 2


```r
filter <-  mxMatrix(name = 'filter', type='Full', nrow=6, ncol=8, free=FALSE,
                    byrow = TRUE,
                    values=c(1,0,0,0,0,0,0,0,
                             0,1,0,0,0,0,0,0,
                             0,0,1,0,0,0,0,0,
                             0,0,0,1,0,0,0,0,
                             0,0,0,0,1,0,0,0,
                             0,0,0,0,0,1,0,0),
                    dimnames = list(c("X_T1", "Y_T1", "iX_T1","iY_T1",
                                      "X_T2", "Y_T2"),
                                    c("X_T1", "Y_T1", "iX_T1","iY_T1","X_T2",
                                      "Y_T2", "iX_T2","iY_T2")))

LY <-  mxMatrix(name = 'LY', type='Full',nrow=4, ncol = 4, free = FALSE,
                values = diag(4), labels = NA,
                dimnames = list(c("X", "Y", "iX", "iY"),
                                c("X", "Y", "iX", "iY")))
```

The object with the means


```r
mean_dz <-  mxMatrix(name = 'mean_dz', type='Full', nrow=1, ncol=8, free=TRUE,
                     byrow = TRUE, values = 0, labels=c('mX1','mY2','miX1','miY2',
                                                        'mX1','mY2','miX1','miY2') )
```

Removing the PRS from the MZ means


```r
algebras <- list(  mxAlgebra('mean_mz', expression = mean_dz %*% t(filter)),
                   # Identity matrix to algebra calculations
                   mxMatrix(name = 'I', type='Iden', nrow= 4,ncol= 4 ),
                   # The needed matrices for calculating the variances
                   mxAlgebra('A_'  , expression =  LY %&% solve(I - BE) %&% A),
                   mxAlgebra('C_'  , expression =  LY %&%solve(I - BE) %&% C),
                   mxAlgebra('E_'  , expression =  LY %&%solve(I - BE) %&% E),
                   mxAlgebra('full_variance' , expression= A_ + C_ + E_),
                   mxAlgebra('variance_mz_', expression=rbind(
                     cbind(full_variance, A_ + C_),
                     cbind(A_ + C_, full_variance))),
                   mxAlgebra('variance_dz', expression=rbind(
                     cbind(full_variance, 0.5%x%A_ + C_),
                     cbind(0.5%x%A_ + C_, full_variance))),
                   mxAlgebra('variance_mz', expression= filter%&%variance_mz_))

top_mr2 <- mxModel("top", BE, A, C, E, filter, LY, mean_dz,  algebras)
```

Preparing the objects for the multiple groups (MZ, DZ) analysis


```r
MZ_mr2 = mxModel("MZ", mxFitFunctionML(),
                 mxExpectationNormal(covariance = "top.variance_mz",means = "top.mean_mz",
                                     dimnames =  c("X_T1", "Y_T1", "iX_T1", "iY_T1",
                                                   "X_T2", "Y_T2")))

DZ_mr2 = mxModel("DZ", mxFitFunctionML(),
                 mxExpectationNormal(covariance = "top.variance_dz", means = "top.mean_dz",
                                     dimnames =  c("X_T1", "Y_T1", "iX_T1", "iY_T1",
                                                   "X_T2", "Y_T2", "iX_T2", "iY_T2")))
```

Combining objects to generate the final model


```r
mrdoc2 = mxModel("mrdoc2", top_mr2, MZ_mr2, DZ_mr2,
                 mxFitFunctionMultigroup(c("MZ","DZ") ) )
```

Simulating using mrdoc2, now we are simulating data so that there is an
effect of X on Y and vice-versa. g1, g2 !=0


```r
sim_data3 <- mrdoc2 |>
  omxSetParameters(labels =  c("g1","g2", "b1", "b3",
                               "ax2", "ay2", "cx2", "cy2", "ex2", "ey2",
                               "covA", "covC", "covE","rf", "x2", "y2"),
                   values = c(0.184, 0.111, 0.411, 0.519,
                              0.424, 0.670, 0.670, 0.424, 0.519, 0.519,
                              0.411,0.221,0.221,0.111, 1,1)) |>
  mxGenerateData( nrows = 1000, empirical = TRUE)
```

Remember, we need to duplicate the columns for the second twin


```r
sim_data3$MZ <- mutate(sim_data3$MZ, iX_T2 = iX_T1, iY_T2 = iY_T1)

dg2mz <- sim_data3$MZ
dg2dz <- sim_data3$DZ


dim(dg2dz) #
```

```
## [1] 1000    8
```

```r
head(dg2dz)
```

```
##   X_T1  Y_T1 iX_T1 iY_T1  X_T2   Y_T2 iX_T2 iY_T2
## 1 1.09  0.83  0.49 -0.84  2.06  1.510  0.51  -1.2
## 2 0.75 -0.86  1.13 -1.56 -0.40 -1.701 -0.11  -1.6
## 3 1.24  1.41 -0.85 -0.65 -2.17  1.506 -1.24   1.1
## 4 2.16  2.24 -1.12  0.26  1.53  1.849 -0.55   1.2
## 5 0.29  0.40 -0.45  0.68 -0.65  0.032 -1.81   1.0
## 6 2.65  2.96 -0.69 -0.67  2.66  1.472  0.78  -1.6
```

```r
summary(dg2dz)
```

```
##       X_T1           Y_T1          iX_T1          iY_T1           X_T2           Y_T2          iX_T2     
##  Min.   :-5.6   Min.   :-5.0   Min.   :-3.5   Min.   :-2.8   Min.   :-5.0   Min.   :-4.5   Min.   :-2.9  
##  1st Qu.:-1.0   1st Qu.:-1.0   1st Qu.:-0.6   1st Qu.:-0.7   1st Qu.:-1.0   1st Qu.:-1.1   1st Qu.:-0.7  
##  Median : 0.0   Median :-0.1   Median : 0.0   Median : 0.0   Median : 0.0   Median : 0.0   Median : 0.0  
##  Mean   : 0.0   Mean   : 0.0   Mean   : 0.0   Mean   : 0.0   Mean   : 0.0   Mean   : 0.0   Mean   : 0.0  
##  3rd Qu.: 1.0   3rd Qu.: 1.1   3rd Qu.: 0.7   3rd Qu.: 0.6   3rd Qu.: 0.9   3rd Qu.: 1.1   3rd Qu.: 0.7  
##  Max.   : 4.3   Max.   : 4.5   Max.   : 3.0   Max.   : 3.4   Max.   : 4.7   Max.   : 4.5   Max.   : 3.2  
##      iY_T2      
##  Min.   :-3.12  
##  1st Qu.:-0.64  
##  Median : 0.00  
##  Mean   : 0.00  
##  3rd Qu.: 0.67  
##  Max.   : 3.08
```

```r
dim(dg2mz) #
```

```
## [1] 1000    8
```

```r
head(dg2mz)
```

```
##    X_T1   Y_T1  iX_T1  iY_T1  X_T2   Y_T2  iX_T2  iY_T2
## 1  1.59  1.517 -0.498  0.330  2.19  1.234 -0.498  0.330
## 2  0.40 -0.608 -0.982 -0.358  0.71 -0.108 -0.982 -0.358
## 3 -0.39 -1.330  0.519 -0.828 -2.66 -3.425  0.519 -0.828
## 4  1.14  0.557  0.686  0.709 -0.17  0.517  0.686  0.709
## 5  0.60  0.081  2.273 -0.552  0.37  0.406  2.273 -0.552
## 6 -0.28 -1.163  0.085  0.033  0.17 -0.097  0.085  0.033
```

```r
summary(dg2mz)
```

```
##       X_T1           Y_T1          iX_T1           iY_T1           X_T2           Y_T2          iX_T2      
##  Min.   :-4.9   Min.   :-5.3   Min.   :-2.72   Min.   :-4.0   Min.   :-4.6   Min.   :-5.2   Min.   :-2.72  
##  1st Qu.:-0.9   1st Qu.:-1.1   1st Qu.:-0.72   1st Qu.:-0.6   1st Qu.:-0.9   1st Qu.:-1.1   1st Qu.:-0.72  
##  Median : 0.0   Median : 0.0   Median :-0.04   Median : 0.0   Median : 0.0   Median : 0.0   Median :-0.04  
##  Mean   : 0.0   Mean   : 0.0   Mean   : 0.00   Mean   : 0.0   Mean   : 0.0   Mean   : 0.0   Mean   : 0.00  
##  3rd Qu.: 1.0   3rd Qu.: 1.0   3rd Qu.: 0.70   3rd Qu.: 0.7   3rd Qu.: 1.0   3rd Qu.: 1.0   3rd Qu.: 0.70  
##  Max.   : 5.3   Max.   : 5.0   Max.   : 2.97   Max.   : 2.9   Max.   : 5.6   Max.   : 4.3   Max.   : 2.97  
##      iY_T2     
##  Min.   :-4.0  
##  1st Qu.:-0.6  
##  Median : 0.0  
##  Mean   : 0.0  
##  3rd Qu.: 0.7  
##  Max.   : 2.9
```

```r
bidir <- mrdoc2
bidir$MZ <- bidir$MZ + mxData(dg2mz, type = "raw")
bidir$DZ <- bidir$DZ + mxData(dg2dz, type = "raw")

bidir <- mxAutoStart(bidir)
bidir <- mxRun(bidir)
```

```
## Running mrdoc2 with 20 parameters
```

You can check for local identification: (this is slow, do this at home)
mxCheckIdentification(bidir)$status


```r
summary(bidir)
```

```
## Summary of mrdoc2 
##  
## free parameters:
##    name      matrix row   col    Estimate Std.Error A
## 1    g1      top.BE   Y     X  0.18404298     0.054 !
## 2    g2      top.BE   X     Y  0.11099381     0.042 !
## 3    b1      top.BE   X    iX  0.41099670     0.023 !
## 4    b3      top.BE   Y    iY  0.51899828     0.023 !
## 5   ax2       top.A   X     X  0.42357732     0.088 !
## 6  covA       top.A   X     Y  0.41058220     0.076 !
## 7   ay2       top.A   Y     Y  0.66934882     0.101 !
## 8    x2       top.A  iX    iX  0.99894780     0.026 !
## 9    rf       top.A  iX    iY  0.11087160     0.018 !
## 10   y2       top.A  iY    iY  0.99894780     0.026 !
## 11  cx2       top.C   X     X  0.66931127     0.080 !
## 12 covC       top.C   X     Y  0.22074242     0.074 !
## 13  cy2       top.C   Y     Y  0.42355736     0.085 !
## 14  ex2       top.E   X     X  0.51847349     0.036 !
## 15 covE       top.E   X     Y  0.22076944     0.041  
## 16  ey2       top.E   Y     Y  0.51848643     0.038 !
## 17  mX1 top.mean_dz   1  X_T1 -0.00000019     0.029  
## 18  mY2 top.mean_dz   1  Y_T1  0.00000018     0.031  
## 19 miX1 top.mean_dz   1 iX_T1  0.00000012     0.021  
## 20 miY2 top.mean_dz   1 iY_T1 -0.00000015     0.021  
## 
## Model Statistics: 
##                |  Parameters  |  Degrees of Freedom  |  Fit (-2lnL units)
##        Model:             20                  13980                 40029
##    Saturated:             NA                     NA                    NA
## Independence:             NA                     NA                    NA
## Number of observations/statistics: 2000/14000
## 
## Information Criteria: 
##       |  df Penalty  |  Parameters Penalty  |  Sample-Size Adjusted
## AIC:          12069                  40069                    40070
## BIC:         -66231                  40181                    40118
## CFI: NA 
## TLI: 1   (also known as NNFI) 
## RMSEA:  0  [95% CI (NA, NA)]
## Prob(RMSEA <= 0.05): NA
## To get additional fit indices, see help(mxRefModels)
## timestamp: 2024-03-27 14:48:53 
## Wall clock time: 0.15 secs 
## optimizer:  SLSQP 
## OpenMx version number: 2.20 
## Need help?  See help(mxSummary)
```

simulated parameters
g1 = 0.184
g2 = 0.111
b1 = 0.411
b3 = 0.519
Q9: Check the results: does MR-DoC model recover correctly the parameters
g1, g2, b1, and b3
ANSWER: Yes, the estimates matches the true values set for the data.
 MR-DoC: Test g1=0 using a likelihood ratio test


```r
bidir2 <- omxSetParameters(name = "drop_g1g2", bidir, label=c("g1", "g2"),
                           free = FALSE, values = 0)
bidir2 <-  mxRun(bidir2)
```

```
## Running drop_g1g2 with 18 parameters
```

```r
mxCompare(bidir, bidir2)
```

```
##     base comparison ep minus2LL    df   AIC diffLL diffdf       p
## 1 mrdoc2       <NA> 20    40029 13980 40069     NA     NA      NA
## 2 mrdoc2  drop_g1g2 18    40045 13982 40081     16      2 0.00032
```

Q10: How many degrees of freedom for this LRT?
ANSWER: Two degrees of freedom, we dropped both g1 and g2.
Q11: Are the causal paths significant?
ANSWER: Dropping g1 and g2 resulted in a significantly worse model, therefore
the bidirectional relationship is significant.


```r
## Let's run in unrelateds X -> Y: ---------------------------------------------
```

We are proceeding to check if running 2sls in each direction matches the true
values we set in the simulation. Remember the model includes indirect
pleiotropy of type PS2 -> rf -> PS1 -> X


```r
# take twin 1 from mz and dz
dat4=rbind(dg2mz[,1:4],dg2dz[,1:4]) |>
  rename(X = X_T1,
         Y = Y_T1,
         I = iX_T1)

unrel7 <- IVModel + mxData(dat4, type = "raw")
# in the interest of brevity, let's autostart the model
unrel7 <- mxAutoStart(unrel7)
unrel7 <- mxRun(unrel7)
```

```
## Running MR with 9 parameters
```

```r
summary(unrel7)
```

```
## Summary of MR 
##  
## free parameters:
##   name matrix row col    Estimate Std.Error A
## 1   g1      A   Y   X  0.31920390     0.067  
## 2   b1      A   X   I  0.42609788     0.031  
## 3   vI      S   I   I  0.99899914     0.032  
## 4   vX      S  eX  eX  1.90053222     0.060  
## 5   re      S  eX  eY  0.82566402     0.134  
## 6   vY      S  eY  eY  1.61915635     0.122  
## 7   mX      M   1   X -0.00000031     0.031  
## 8   mY      M   1   Y -0.00000026     0.028  
## 9   mI      M   1   I -0.00000001     0.022  
## 
## Model Statistics: 
##                |  Parameters  |  Degrees of Freedom  |  Fit (-2lnL units)
##        Model:              9                   5991                 18772
##    Saturated:              9                   5991                    NA
## Independence:              6                   5994                    NA
## Number of observations/statistics: 2000/6000
## 
## Information Criteria: 
##       |  df Penalty  |  Parameters Penalty  |  Sample-Size Adjusted
## AIC:           6790                  18790                    18791
## BIC:         -26765                  18841                    18812
## CFI: NA 
## TLI: 1   (also known as NNFI) 
## RMSEA:  0  [95% CI (NA, NA)]
## Prob(RMSEA <= 0.05): NA
## To get additional fit indices, see help(mxRefModels)
## timestamp: 2024-03-27 14:48:54 
## Wall clock time: 0.039 secs 
## optimizer:  SLSQP 
## OpenMx version number: 2.20 
## Need help?  See help(mxSummary)
```

```r
## Let's run in unrelateds Y -> X: ---------------------------------------------

# take twin 1 from mz and dz
dat5=rbind(dg2mz[,1:4],dg2dz[,1:4]) |>
  rename(Y = X_T1,  # notice the inversion here
         X = Y_T1,  # notice the inversion here
         I = iY_T1)
```

The inversion above is only necessary so we don't have to rewrite the model
from scratch.
Bear with me as I rename the parameters in the base model
This will help interpreting results


```r
IVModelYX = omxSetParameters(name = "IVModelYX", IVModel, label = c("g1","b1", "vX", "vY", "mX", "mY"),
                             newlabel = c("g2","b3", "vY", "vX", "mY", "mX"))

unrel8 <- IVModelYX + mxData(dat5, type = "raw")
# in the interest of brevity, let's autostart the model
unrel8 <- mxAutoStart(unrel8)
unrel8 <- mxRun(unrel8)
```

```
## Running IVModelYX with 9 parameters
```

```r
summary(unrel8)
```

```
## Summary of IVModelYX 
##  
## free parameters:
##   name matrix row col     Estimate Std.Error A
## 1   g2      A   Y   X 0.1957359896     0.052  
## 2   b3      A   X   I 0.5383903493     0.032  
## 3   vI      S   I   I 0.9990000291     0.032  
## 4   vY      S  eX  eX 2.0688193334     0.065  
## 5   re      S  eX  eY 1.0285950507     0.118  
## 6   vX      S  eY  eY 1.5888873870     0.119  
## 7   mY      M   1   X 0.0000005475     0.032  
## 8   mX      M   1   Y 0.0000004254     0.028  
## 9   mI      M   1   I 0.0000000055     0.022  
## 
## Model Statistics: 
##                |  Parameters  |  Degrees of Freedom  |  Fit (-2lnL units)
##        Model:              9                   5991                 18628
##    Saturated:              9                   5991                    NA
## Independence:              6                   5994                    NA
## Number of observations/statistics: 2000/6000
## 
## Information Criteria: 
##       |  df Penalty  |  Parameters Penalty  |  Sample-Size Adjusted
## AIC:           6646                  18646                    18647
## BIC:         -26909                  18697                    18668
## CFI: NA 
## TLI: 1   (also known as NNFI) 
## RMSEA:  0  [95% CI (NA, NA)]
## Prob(RMSEA <= 0.05): NA
## To get additional fit indices, see help(mxRefModels)
## timestamp: 2024-03-27 14:48:54 
## Wall clock time: 0.04 secs 
## optimizer:  SLSQP 
## OpenMx version number: 2.20 
## Need help?  See help(mxSummary)
```

The result of the summary immediately above is of the relationship of
Y to X, in other words g2.
simulated parameters
g1 = 0.184
g2 = 0.111
b1 = 0.411
b3 = 0.519
Q12: Does MR-SEM models (unrel7 or X->Y, and model unrel8 Y <- X) recover correctly the parameters used for simulation?
What happened to the estimates?
ANSWER: Both g1 and g2 were overestimated in the 2sls solution.
Scenario 4: Pleiotropy & no causal effect STRETCH GOAL!  -------------------
We are back to MR-DoC1 and we will be simulating data without
the causal effect
Compare the results obtained with MR-DoC, unrelateds SEM, 2SLS, 2-sample MR.


```r
sim_data4 <-  mrdoc1 |>
  omxSetParameters(labels =  c("g1","b1", "b2",
                               "ax2", "ay2", "cx2", "cy2", "ex2", "ey2",
                               "covA", "covC", "covE","sigma_x"),
                   values = c(0, 0.316, 0.316,
                              0.424, 0.670, 0.670, 0.424, 0.519, 0.519,
                              0.411,0.221,0, 1)) |>
  mxGenerateData( nrows = 1000, empirical = TRUE)

sim_data4$MZ <-  mutate(sim_data4$MZ, iX_T2 = iX_T1)

dg1mz <- sim_data4$MZ
dg1dz <- sim_data4$DZ

dim(dg1dz) #
```

```
## [1] 1000    6
```

```r
head(dg1dz)
```

```
##      X_T1  Y_T1 iX_T1  X_T2  Y_T2 iX_T2
## 1  1.4663  3.46 -0.58  0.94  1.08 -0.34
## 2  1.1069  0.95 -0.82  0.27 -0.72 -0.30
## 3  0.0035 -0.99 -1.19  0.13 -0.92 -0.77
## 4  1.2180  2.96  2.56  0.69 -0.87 -0.13
## 5  0.2032  1.14 -0.32  1.35  0.17 -0.18
## 6 -0.5218 -0.32  0.42 -1.66 -0.60 -1.00
```

```r
summary(dg1dz)
```

```
##       X_T1           Y_T1          iX_T1            X_T2           Y_T2          iX_T2     
##  Min.   :-4.4   Min.   :-3.9   Min.   :-3.05   Min.   :-4.3   Min.   :-4.1   Min.   :-3.2  
##  1st Qu.:-0.9   1st Qu.:-0.9   1st Qu.:-0.69   1st Qu.:-0.9   1st Qu.:-0.9   1st Qu.:-0.7  
##  Median : 0.0   Median : 0.0   Median : 0.03   Median : 0.0   Median : 0.0   Median : 0.0  
##  Mean   : 0.0   Mean   : 0.0   Mean   : 0.00   Mean   : 0.0   Mean   : 0.0   Mean   : 0.0  
##  3rd Qu.: 0.9   3rd Qu.: 0.9   3rd Qu.: 0.68   3rd Qu.: 0.9   3rd Qu.: 0.9   3rd Qu.: 0.7  
##  Max.   : 5.0   Max.   : 3.5   Max.   : 2.79   Max.   : 4.0   Max.   : 4.1   Max.   : 3.2
```

```r
dim(dg1mz) #
```

```
## [1] 1000    6
```

```r
head(dg1mz)
```

```
##    X_T1  Y_T1 iX_T1  X_T2   Y_T2 iX_T2
## 1  1.22  0.95  0.12  1.50  1.355  0.12
## 2  1.12  1.18 -0.78  0.70  0.932 -0.78
## 3  0.68  0.56  0.32  1.88  0.046  0.32
## 4  0.23 -1.34  0.50  0.45 -1.950  0.50
## 5 -0.37 -0.20  0.33  1.70 -2.129  0.33
## 6 -0.23 -1.57  1.10 -0.43 -0.576  1.10
```

```r
summary(dg1mz)
```

```
##       X_T1           Y_T1          iX_T1           X_T2           Y_T2          iX_T2     
##  Min.   :-4.5   Min.   :-4.7   Min.   :-3.1   Min.   :-3.9   Min.   :-4.4   Min.   :-3.1  
##  1st Qu.:-0.9   1st Qu.:-0.9   1st Qu.:-0.7   1st Qu.:-0.9   1st Qu.:-0.8   1st Qu.:-0.7  
##  Median : 0.1   Median : 0.0   Median : 0.0   Median : 0.0   Median : 0.0   Median : 0.0  
##  Mean   : 0.0   Mean   : 0.0   Mean   : 0.0   Mean   : 0.0   Mean   : 0.0   Mean   : 0.0  
##  3rd Qu.: 0.9   3rd Qu.: 0.9   3rd Qu.: 0.7   3rd Qu.: 0.9   3rd Qu.: 0.9   3rd Qu.: 0.7  
##  Max.   : 4.1   Max.   : 4.0   Max.   : 3.5   Max.   : 4.6   Max.   : 4.2   Max.   : 3.5
```

```r
no_causal <- mrdoc1
no_causal$MZ <- mrdoc1$MZ + mxData(dg1mz, type = "raw")
no_causal$DZ <- mrdoc1$DZ + mxData(dg1dz, type = "raw")

no_causal <- mxRun(no_causal)
```

```
## Running mrdoc1 with 15 parameters
```

```r
summary(no_causal)
```

```
## Summary of mrdoc1 
##  
## free parameters:
##       name      matrix row   col    Estimate Std.Error A
## 1       g1      top.BE   Y     X -0.00000169     0.031  
## 2       b1      top.BE   X    iX  0.31600043     0.022  
## 3       b2      top.BE   Y    iX  0.31600035     0.025  
## 4      ax2       top.A   X     X  0.42356291     0.077  
## 5     covA       top.A   X     Y  0.41059142     0.067  
## 6      ay2       top.A   Y     Y  0.66932379     0.090 !
## 7  sigma_x       top.A  iX    iX  0.99899888     0.026  
## 8      cx2       top.C   X     X  0.66934112     0.074  
## 9     covC       top.C   X     Y  0.22078101     0.054  
## 10     cy2       top.C   Y     Y  0.42358392     0.080  
## 11     ex2       top.E   X     X  0.51848479     0.023  
## 12     ey2       top.E   Y     Y  0.51848004     0.023  
## 13     mX1 top.mean_dz   1  X_T1  0.00000296     0.026  
## 14     mY2 top.mean_dz   1  Y_T1  0.00000083     0.026  
## 15    miX1 top.mean_dz   1 iX_T1  0.00000136     0.021  
## 
## Model Statistics: 
##                |  Parameters  |  Degrees of Freedom  |  Fit (-2lnL units)
##        Model:             15                  10985                 32379
##    Saturated:             NA                     NA                    NA
## Independence:             NA                     NA                    NA
## Number of observations/statistics: 2000/11000
## 
## Information Criteria: 
##       |  df Penalty  |  Parameters Penalty  |  Sample-Size Adjusted
## AIC:          10409                  32409                    32409
## BIC:         -51117                  32493                    32445
## CFI: NA 
## TLI: 1   (also known as NNFI) 
## RMSEA:  0  [95% CI (NA, NA)]
## Prob(RMSEA <= 0.05): NA
## To get additional fit indices, see help(mxRefModels)
## timestamp: 2024-03-27 14:48:55 
## Wall clock time: 0.15 secs 
## optimizer:  SLSQP 
## OpenMx version number: 2.20 
## Need help?  See help(mxSummary)
```

Q13: Check the results: does MR-DoC model recover correctly the parameters b2,
g1, and b1?
ANSWER: Yes, it does.


```r
##  MR-DoC: Test g1=0 using a likelihood ratio test   #########

no_causal2 <- omxSetParameters(name = "drop_g1", no_causal, label="g1",
                               free = FALSE, values = 0)
no_causal2 <- mxRun(no_causal2)
```

```
## Running drop_g1 with 14 parameters
```

```r
mxCompare(no_causal, no_causal2)
```

```
##     base comparison ep minus2LL    df   AIC       diffLL diffdf  p
## 1 mrdoc1       <NA> 15    32379 10985 32409           NA     NA NA
## 2 mrdoc1    drop_g1 14    32379 10986 32407 -0.000000047      1  1
```

Q14: Do we detect a causal effect with a true g1 value set to zero?
ANSWER: As expected, no.


```r
## Let's run in unrelateds: ---------------------------------------------------

# take twin 1 from mz and dz
dat3 <- rbind(dg1mz[,1:3],dg1dz[,1:3])|>
  rename(X = X_T1,
         Y = Y_T1,
         I = iX_T1)

unrel5 <- IVModel + mxData(dat3, type = "raw")
# in the interest of brevity, let's autostart the model
unrel5 <- mxAutoStart(unrel5)
unrel5 <- mxRun(unrel5)
```

```
## Running MR with 9 parameters
```

parameters used for simulation
g1 = 0
b1 = 0.316
b2 = 0.316


```r
summary(unrel5)
```

```
## Summary of MR 
##  
## free parameters:
##   name matrix row col         Estimate Std.Error A
## 1   g1      A   Y   X  1.0000224830708     0.099  
## 2   b1      A   X   I  0.3160040126595     0.028 !
## 3   vI      S   I   I  0.9990013057179     0.032  
## 4   vX      S  eX  eX  1.6114040656051     0.051 !
## 5   re      S  eX  eY -0.9800670433984     0.166  
## 6   vY      S  eY  eY  1.9600849023070     0.204  
## 7   mX      M   1   X -0.0000046579304     0.028 !
## 8   mY      M   1   Y -0.0000042512894     0.031 !
## 9   mI      M   1   I -0.0000000000029     0.022  
## 
## Model Statistics: 
##                |  Parameters  |  Degrees of Freedom  |  Fit (-2lnL units)
##        Model:              9                   5991                 18600
##    Saturated:              9                   5991                    NA
## Independence:              6                   5994                    NA
## Number of observations/statistics: 2000/6000
## 
## Information Criteria: 
##       |  df Penalty  |  Parameters Penalty  |  Sample-Size Adjusted
## AIC:           6618                  18618                    18618
## BIC:         -26937                  18669                    18640
## CFI: NA 
## TLI: 1   (also known as NNFI) 
## RMSEA:  0  [95% CI (NA, NA)]
## Prob(RMSEA <= 0.05): NA
## To get additional fit indices, see help(mxRefModels)
## timestamp: 2024-03-27 14:48:55 
## Wall clock time: 0.04 secs 
## optimizer:  SLSQP 
## OpenMx version number: 2.20 
## Need help?  See help(mxSummary)
```

Q15: Does MR-SEM model recover correctly the parameters used for simulation?
ANSWER: No, g1 = 0 + pleiotropy severily biased the causal path in the models
that used unrelated data.


```r
unrel6 <- omxSetParameters(name = "no_causal", unrel5, label="g1", free = FALSE,
                           values = 0)
unrel6 <- mxRun(unrel6)
```

```
## Running no_causal with 8 parameters
```

```r
mxCompare(unrel5,unrel6)
```

```
##   base comparison ep minus2LL   df   AIC diffLL diffdf                               p
## 1   MR       <NA>  9    18600 5991 18618     NA     NA                              NA
## 2   MR  no_causal  8    18720 5992 18736    120      1 0.00000000000000000000000000059
```

```r
##  Fit the model using two stage least squares     ####################
TSLS1=lm(X~I,data=dat3)
Xhat=predict(TSLS1)
TSLS2=lm(Y~Xhat,data=dat3)
summary(TSLS2)
```

```
## 
## Call:
## lm(formula = Y ~ Xhat, data = dat3)
## 
## Residuals:
##    Min     1Q Median     3Q    Max 
## -4.725 -0.880  0.026  0.831  3.737 
## 
## Coefficients:
##                           Estimate             Std. Error t value            Pr(>|t|)    
## (Intercept) 0.00000000000000000517 0.02839894364232584470     0.0                   1    
## Xhat        1.00000000000000177636 0.08991504358428256682    11.1 <0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.3 on 1998 degrees of freedom
## Multiple R-squared:  0.0583,	Adjusted R-squared:  0.0578 
## F-statistic:  124 on 1 and 1998 DF,  p-value: <0.0000000000000002
```

The 2sls regression again recover the same biased estimates as MR-SEM. In
the presence of pleiotropy MR-DoC outperforms the other methods.
