You are here

umxCP with two latent variables that loads in only part of the indicators

9 posts / 0 new
Last post
lf-araujo's picture
Joined: 11/25/2020 - 13:24
umxCP with two latent variables that loads in only part of the indicators
Image icon Fig 1100.7 KB
Image icon Fig 266.73 KB

Hi Tim and Mike,

This is the first time I am playing with real data, and since I understand lavaans syntax better than openMx's I decided to start with umx.

I am trying to specify a CP model with two latents, but my measured indicators represents parenting style and personality (big5), 10 variables in total. See fig 1. I think they are better conceptualized as two latents that can correlate, but only loads in part of the variables, the five personality traits and the five parenting styles.

Initially I thought that I could either use umx_make_Twins or remove the unwanted paths with umxModify( update = ). However, it seems that with umxModify I can only remove the paths, but I cant add the correlation between latents. I ended up with fig 2.

  • What should be the best approach to allow the two latents in fig 2 to correlate?

Below is a minimal working example to facilitate discussion:

dt <- ZA6701_en_add_scales_wid1_v4.0.0 %>%
  left_join(ZA6701_en_master_v4.0.0, by = "pid") %>%
  transmute(zygosity = zyg0102,
            parcontt1 = as.numeric(parcontt),
            parcontt2 = as.numeric(parcontt),
            parincot1 = as.numeric(parincot),
            parincot2 = as.numeric(parincot),
            parmonit1 = as.numeric(parmonit),
            parmonit2 = as.numeric(parmonit),
            parnegct1 = as.numeric(parnegct),
            parnegct2 = as.numeric(parnegct),
            parwarmt1 = as.numeric(parwarmt),
            parwarmt2 = as.numeric(parwarmt),
            peragre1 = as.numeric(peragre),
            peragre2 = as.numeric(peragre),
            percons1 = as.numeric(percons),
            percons2 = as.numeric(percons),
            perneur1 = as.numeric(perneur),
            perneur2 = as.numeric(perneur),
            perextr1 = as.numeric(perextr),
            perextr2 = as.numeric(perextr),
            peropen1 = as.numeric(peropen),
            peropen2 = as.numeric(peropen))
selDVs <- c("parcontt","parincot", "parmonit", "parnegct", "parwarmt",
          "peragre", "percons", "perneur", "perextr","peropen")
vupdate  <- c("cp_loadings_r6c1", "cp_loadings_r7c1", "cp_loadings_r8c1",
              "cp_loadings_r9c1", "cp_loadings_r10c1", "cp_loadings_r1c2",
              "cp_loadings_r2c2", "cp_loadings_r3c2", "cp_loadings_r4c2",
m2 <- umxCP(data = dt,
              mzData = "1: monozygotic",
              dzData = "2: dizygotic",
              selDVs = selDVs,
              sep = "",
              nFac = 2)
m3 <- umxModify(m2, update = vupdate, comparison = F)
AdminNeale's picture
Joined: 03/01/2013 - 14:09
tidyverse is broken

Hi Luis

I can't test because tidyverse doesn't exist for my version of R (4.0.3 which has been out ~2 months). But I can tell you how to do it. umxCP sets up OpenMx models with matrices. Most of these are in the 'top' model within the model that umxCP returns.
For example, the a paths are estimated in matrix a_cp, which we can see an example of by running the example from the ?umxCP help documentation.

DiagMatrix 'a_cp' 
     [,1]        [,2]        [,3]       
[1,] "a_cp_r1c1" NA          NA         
[2,] NA          "a_cp_r2c2" NA         
[3,] NA          NA          "a_cp_r3c3"
          [,1]      [,2]      [,3]
[1,] 0.3191584 0.0000000 0.0000000
[2,] 0.0000000 0.5930313 0.0000000
[3,] 0.0000000 0.0000000 0.6918959
      [,1]  [,2]  [,3]
     [,1] [,2] [,3]
[1,]    0   NA   NA
[2,]   NA    0   NA
[3,]   NA   NA    0

If you wish to allow the factors to correlate, you could use the Cholesky approach, which simply means freeing up the elements in the lower triangle of the a_cp, c_cp and e_cp matrices. If you only have 2 factors, this is easy because there's only one element in the lower triangle, so you could do something like this as Step 1:

model$top$a_cp$free[2,1] <- TRUE
model$top$c_cp$free[2,1] <- TRUE
model$top$e_cp$free[2,1] <- TRUE

Step 2 is to change the factor loading matrix, which is (cryptically) in matrix cp_loadings:

FullMatrix 'cp_loadings' 
     [,1]               [,2]               [,3]              
[1,] "cp_loadings_r1c1" "cp_loadings_r1c2" "cp_loadings_r1c3"
[2,] "cp_loadings_r2c1" "cp_loadings_r2c2" "cp_loadings_r2c3"
[3,] "cp_loadings_r3c1" "cp_loadings_r3c2" "cp_loadings_r3c3"
[4,] "cp_loadings_r4c1" "cp_loadings_r4c2" "cp_loadings_r4c3"
[5,] "cp_loadings_r5c1" "cp_loadings_r5c2" "cp_loadings_r5c3"
[6,] "cp_loadings_r6c1" "cp_loadings_r6c2" "cp_loadings_r6c3"
             [,1]       [,2]       [,3]
[1,]  0.234958126  0.7924620  3.0297019
[2,]  0.049709341 -0.2315207  1.5511085
[3,] -0.009447686  0.7429272  0.5365263
[4,]  3.435387889  2.2862029  1.6776101
[5,]  1.923986619  2.6755898  2.2131049
[6,] -0.701359035 -1.4013078 -1.6685230
     [,1] [,2] [,3]

You can fix values to zero with a umx function, or with base OpenMx it might be done with omxSetParameters():

omxSetParameters(model$top, labels=c("cp_loadings_r4c1","cp_loadings_r5c1","cp_loadings_r6c1"), values=0, free=FALSE)

There are various tricks for using wildcards for labels that umx has that could be handy way to find all the factor loadings in a column without having to list them all out.

Step 3 is to run the model again once it has been modified.

lf-araujo's picture
Joined: 11/25/2020 - 13:24
Thanks Mike,

Thanks Mike,

Seems easy enough, however I am stuck in Step 1, as umx models seems not to allow direct modifications. So with:

m2$submodels$top$e_cp$free[2,1] <- TRUE

I get:

You cannot directly set the matrices of a model.  To set objects in the model, use the mxModel() function.

I tried mxModel() with something like:

mxModel(m2$submodels$top, name = "a_cp", free = T)

But this is the wrong syntax.

  • How do I do this using mxModel?


AdminNeale's picture
Joined: 03/01/2013 - 14:09
umx generates OpenMx models

First, there should be no general difference between models auto-generated by umx and those specified manually by users. Both are OpenMx models.

Second, sometimes direct tweaking of matrices isn't allowed (and I'm not sure what conditions affect this). Possibly, omitting the $submodels bit would solve the issue.

You can always replace the entire matrix by extracting it to an object first, modify that object, then put the matrix back into the model again. Sometimes it may be necessary to use mxModel(), with syntax of the form:

revisedModel <- mxModel(oldMxModel,  matrixToBeReplaced)

where matrixToBeReplaced has the same name as the one it is replacing.


Leo's picture
Joined: 01/09/2020 - 14:36
This error occurs when

This error occurs when exactly? Because, at first, you're not touching OpenMx, just basically modifying a matrix with base r. I remember setting up an identical model with umxcp. However, I just let the factors correlate (correlated factors) instead of specifying Cholesky.

lf-araujo's picture
Joined: 11/25/2020 - 13:24
Thanks Leo,

Thanks Leo,

This happens if I just try to change the content of the R object within the model like:

m2 <- umxCP(data = dt,
              mzData = "1: monozygotic",
              dzData = "2: dizygotic",
              selDVs = selDVs,
              sep = "",
              nFac = 2)
m2$submodels$top$matrices$e_cp$free[2,1] <- TRUE

The same error happens. I am not sure if umx models are in any way different from the openMx ones. But I checked the structure and the above line was supposed to work. You mentioned a model with correlated factors, and I found an undocumented option in umx "correlatedA = T" that does what I wanted in terms of correlation (I think); the rest of Mike's instructions gets me where I wanted with this model.

Many thanks.

tbates's picture
Joined: 07/31/2009 - 14:25
umxCP, correlated causes

A 2 common pathway CP model with CP 1 loading one set of variables and CP 2loading the second set makes sense. The short answer to the question about why you can't manually add correlations after a model is built, is that the default umxCP uses a diag matrix which can't implement the off diagonal covariances you want. SO you need to say correlatedA=TRUE when creating the model. This should have been called something else but... history.

The install_github(tbates/umx") version now deprecates correlatedA in favour of correlatedACE which is more accurately named, and defaults to leaving the lower triangle fixed at zero, allowing you to free up the (few) paths you want.

To create a 2-factor CP model, just set nFac=2

To restrict what manifests each CP loads on, in the resultant model, delete the paths from CP1 to the set 2 manifests, and from CP2 to the set 1 manifests using labels.

Paths from cp1 are in column 1 of the cp_loadings matrix. and labeled as "cp_loadings_r1c1"

You can match the relevant pattern with regular expressions, or if you like more concrete or fool proof approach, make a list and drop that... e.g.:

tmp = umxCP(your setup here..., correlatedACE=TRUE, auto=FALSE)
pathsToDrop = c( "cp_loadings_r5c1", "cp_loadings_r6c1", ..., "cp_loadings_r1c2", ...)
m2 = umxModify(tmp, pathsToDrop, name = "style_and_personality")

you would then need to free the covariance of the a paths if you think that makes sense.

ida's picture
Joined: 01/24/2021 - 08:33
problems with correlatedACE

Dear Timothy,
I created a 2-factor CP model with correlatedA = TRUE a couple of weeks ago and it went well.

m1 = umxCP("2cp", selDVs = selDVs, sep = "_T", dzData = dzData, mzData = mzData, 
           nFac = 2, correlatedA = TRUE, tryHard = "yes")

Now, I reran the model and received the polite note of changing correlatedA to correlatedACE, as you just mentioned in your post. Unfortunately, in my case, using correlatedACE does not result in a model with correlated A,C, and E paths. The corresponding paths (a_cp_r2c1/c_cp_r2c1/e_cp_r2c1) do neither show up in the list of parameters nor are they plotted in the model. Do you have any idea to solve this problem?
Thank you in advance and best wishes,

AdminNeale's picture
Joined: 03/01/2013 - 14:09
drop matrices word?

Hi Luis

I don't usually have trouble changing objects within OpenMx models. One difference I notice is that you use:

m2$submodels$top$matrices$e_cp$free[2,1] <- TRUE

whereas I would use something like

m2$top$e_cp$free[2,1] <- TRUE

that is, refer to a matrix with model$matrix without either $submodels or $matrices in the string. I'm not sure that this is your issue though. Another option is to replace the whole matrix

e_cp <- m2$submodels$top$matrices$e_cp
m2$top <-mxModel(m2$top,tempMat)

although clumsier, the pass through mxModel has validation checks that can prevent runtime errors. HTH!