You are here

Understanding runHelper() error: MxExpectationRAM: latent exogenous variables are not supported

8 posts / 0 new
Last post
pehkawn's picture
Offline
Joined: 05/24/2020 - 19:45
Understanding runHelper() error: MxExpectationRAM: latent exogenous variables are not supported
AttachmentSize
File ex9.23.dat761.72 KB

I've been trying to modify this example from OpenMx' repos for testing. For this purpose, I am trying to fit the model using WLS. However, this returns the following error message:

Error in runHelper(model, frontendStart, intervals, silent, suppressWarnings,  : 
  MxExpectationRAM: latent exogenous variables are not supported (x -> sw)

Even without any other changes to the code, I get this error. I've fitted models with WLS before without issue, but I fail to identify what may differs that would cause this error. Furthermore, I am trying to understand why I get this error using WLS, but not with ML.

Code sample:

# MPlus: Three-level growth model with a continuous outcome and one covariate on each of the three levels
# https://www.statmodel.com/usersguide/chapter9.shtml
 
library(OpenMx)
 
#mxOption(NULL, "Number of Threads", 8L)
 
options(width=120)
 
ex923 <- suppressWarnings(try(read.table("ex9.23.dat")))
# if (is(ex923, "try-error")) ex923 <- read.table("data/ex9.23.dat")
colnames(ex923) <- c(paste0('y',1:4), 'x', 'w', 'z', 'level2', 'level3')
ex923$level2 <- as.integer(ex923$level2)
ex923$level3 <- as.integer(ex923$level3)
 
level3Model <- mxModel(
    'level3Model', type='RAM',
    latentVars=c(paste0('y',1:4), 'ib3', 'sb3', 'z'),
    mxData(ex923[!duplicated(ex923$level3),], 'raw', primaryKey='level3'),
    mxPath('ib3', paste0('y',1:4), free=FALSE, values=1),
    mxPath('sb3', paste0('y',1:4), free=FALSE, values=0:3),
    mxPath(c('ib3','sb3'), arrows=2, connect="unique.pairs", values=c(1,0,1)),
    mxPath('one', 'z', free=FALSE, labels="data.z"),
    mxPath('z', c('ib3','sb3')),
    mxPath('one', c('ib3','sb3')))
 
level2Model <- mxModel(
    'level2Model', type='RAM', level3Model,
    latentVars=c(paste0('y',1:4), 'ib2', 'sb2', 'w'),
    mxData(ex923[!duplicated(ex923$level2),], 'raw', primaryKey='level2'),
    mxPath('ib2', paste0('y',1:4), free=FALSE, values=1),
    mxPath('sb2', paste0('y',1:4), free=FALSE, values=0:3),
    mxPath(c('ib2','sb2'), arrows=2, connect="unique.pairs", values=c(1,0,1)),
    mxPath('one', 'w', free=FALSE, labels="data.w"),
    mxPath('w', c('ib2','sb2')),
    mxPath(paste0('y',1:4), arrows=2),
    mxPath(paste0('level3Model.y', 1:4), paste0('y',1:4), free=FALSE, values=1,
       joinKey="level3"))
 
withinModel <- NULL
withinModel <- mxModel(
    'withinModel', 
    type='RAM', 
    level2Model,
    manifestVars=c(paste0('y',1:4)), 
    latentVars=c('iw','sw', 'x'),
    mxData(ex923, 'raw'),
    mxPath('iw', paste0('y',1:4), free=FALSE, values=1),
    mxPath('sw', paste0('y',1:4), free=FALSE, values=0:3),
    mxPath(paste0('y',1:4), arrows=2),
    mxPath(c('iw','sw'), arrows=2, connect="unique.pairs", values=c(1,0,1)),
    mxPath('one', 'x', free=FALSE, labels="data.x"),
    mxPath('x', c('iw','sw')),
    mxPath(paste0('level2Model.y', 1:4), paste0('y',1:4), free=FALSE, values=1,
       joinKey="level2"),
    mxFitFunctionWLS() # Added alternative fit function
)
 
withinModel <- mxRun(withinModel)
 
omxCheckEquals(withinModel$expectation$debug$rampartUsage, c(6000))
 
omxCheckCloseEnough(logLik(withinModel), -56044.82, 1e-2)  # matches Mplus
mhunter's picture
Offline
Joined: 07/31/2009 - 15:26
No WLS with Multilevel

Although the error is complaining about "latent exogenous variables" the real problem is that OpenMx does not support WLS with multilevel models. WLS is based entirely on the summary statistics which do not exist with the multiple data sets being linked across levels in multilevel models. I'll create an issue on GitHub to catch this and improve the error message, but we have no plans to implement multilevel with WLS in OpenMx.

pehkawn's picture
Offline
Joined: 05/24/2020 - 19:45
I'm confused

I have designed and fitted multilevel models in OpenMx in the past. If this is not feasible, I suppose I should not be doing this, but there's no error message of the sort mentioned above.

mhunter's picture
Offline
Joined: 07/31/2009 - 15:26
Why WLS?

I do apologize for OpenMx not catching this situation and giving a more reasonable error message. Future versions of OpenMx will.

Is there a particular reason you want to run this model or a similar model with WLS?

pehkawn's picture
Offline
Joined: 05/24/2020 - 19:45
The reason I've been trying to to use WLS

dates back to previous posts. ML does not handle ordinal variables in multilevel model, and adding a high number of ordinal variables also makes ML computationally unfeasible. However, it was suggested WLS might work (at least on two-level models), which is why I've been attempting WLS model estimation.

Now, it's the first time I've seen the error message I posted above. Actually, OpenMx does not really produce any error messages when fitting a multilevel model with WLS, which is problematic if this is not possible. Below I've modified the example above to one that will not produce the aforementioned error message. (For the sake of demonstration, I've added an ordinal variable on level 1 and latent interactions on all levels.) It does not produce any estimates or standard errors for upper levels, however, which sort of makes sense now. There really should be a warning/error message prohibiting fitting multilevel models using WLS. As for fitting multilevel models, there's a real need to implement ways to model ordinal/categorical/dichotomous variables in such models.

## ---------------------------------------------------------------------------------------------------------------------
Sys.setenv(OMP_NUM_THREADS=parallel::detectCores())
library(OpenMx)
library(dplyr)
library(magrittr)
#mxOption(NULL, "Number of Threads", 8L)
options(width=120)
 
 
## ---------------------------------------------------------------------------------------------------------------------
ex923 <- suppressWarnings(try(read.table("ex9.23.dat")))
colnames(ex923) <- c(paste0('y',1:4), 'x', 'w', 'z', 'level2', 'level3')
 
ex923 %<>% 
mutate(across( x:z, ~ { round(. - min(.) + 1) } ) )
 
ex923$x %<>% as.ordered()
 
## ---------------------------------------------------------------------------------------------------------------------
# Level 3 model
level3Model <- mxModel(
    # Model name
    'level3Model', 
    # Model type
    type = 'RAM',
    # Variables. Manifest, latent and product variables, respectively.
    manifestVars = 'z',
    latentVars=c(paste0('y',1:4), 'ib3', 'sb3'),
    productVars=c("ib3×sb3"),
    # residual manifest variances
    mxPath(
        from = 'z',
        arrows = 2,
        free = FALSE,
        values = 1
    ),
    # latent variances & covariances
    mxPath(
        from = paste0('y',3:4),
        connect = "unique.bivariate",
        arrows = 2,
        free = TRUE,
        values = 0.1,
        labels = "cov_y3_y4"
    ),
    mxPath(
        from = c('ib3','sb3'),
        connect = "unique.pairs",
        arrows = 2,
        free = TRUE,
        values = 0.4,
        labels = c("e_ib3", "cov_ib3_sb3", "e_sb3")
    ),
    # pattern coefficients
    mxPath(
        from = "ib3",
        to = paste0('y',1:2),
        arrows = 1,
        free = c(FALSE, TRUE),
        values = 1,
        lbound = 1e-6,
        labels = paste0('ib3_y',1:2)
    ),
    mxPath(
        from = "sb3",
        to = paste0('y',3:4),
        arrows = 1,
        free = c(FALSE, TRUE),
        values = 1,
        lbound = 1e-6,
        labels = paste0('sb3_y', 3:4)
    ),
    mxPath(
        from = c("ib3", "sb3"),
        to = "z",
        arrows = 1,
        free = TRUE,
        values = 1,
        lbound = 1e-6,
        labels = paste0(c("ib3", "sb3"), "_z")
    ),
    # Latent interactions
    mxPath(
        from = c("ib3","sb3"),
        to = "ib3×sb3",
        arrows = 1,
        free = FALSE,
        values = 1
    ),
    mxPath(
        from = "ib3×sb3",
        to = "z",
        arrows = 1,
        free = TRUE,
        values = .5,
        labels = "ib3×sb3_z"
    ),
    # means
    means = mxPath(
        from = "one",
        to = c(paste0('y',1:4), 'z', 'ib3', 'sb3'),
        arrows = 1,
        free = TRUE,
        values = 0,
        labels = paste0("μ_", c(paste0('y',1:4), 'z', 'ib3', 'sb3'))
    ),
    # # thresholds
    # mxThreshold( 
    #     vars = "z", 
    #     nThresh = max(as.double(ex923$z)) - 1,
    #     free = TRUE,
    #     values = mxNormalQuantiles(max(as.double(ex923$z)) - 1),
    #     labels = paste0("z_thresh_", 1:(max(as.double(ex923$z)) - 1)),
    # ),
    mxData(ex923[!duplicated(ex923$level3),], 'raw', primaryKey='level3')
)
 
# Level 2 model
level2Model <- mxModel(
    # Model name
    'level2Model', 
    # Model type
    type = 'RAM',
    # Higher level model to insert
    level3Model,
    # Variables. Manifest, latent and product variables, respectively.
    manifestVars = 'w',
    latentVars=c(paste0('y',1:4), 'ib2', 'sb2'),
    productVars=c("ib2×sb2"),
    # residual manifest variances
    mxPath(
        from = 'w',
        arrows = 2,
        free = FALSE,
        values = 1
    ),
    # latent variance & covariance
    mxPath(
        from = paste0('y',3:4),
        connect = "unique.bivariate",
        arrows = 2,
        free = TRUE,
        values = 0.1,
        labels = "cov_y3_y4"
    ),
    mxPath(
        from = c('ib2','sb2'),
        connect = "unique.pairs",
        arrows = 2,
        free = TRUE,
        values = 0.4,
        labels = c("e_ib2", "cov_ib2_sb2", "e_sb2")
    ),
    # pattern coefficients
    mxPath(
        from = "ib2",
        to = paste0('y',1:2),
        arrows = 1,
        free = c(FALSE, TRUE),
        values = 1,
        lbound = 1e-6,
        labels = paste0('ib2_y',1:2)
    ),
    mxPath(
        from = "sb2",
        to = paste0('y',3:4),
        arrows = 1,
        free = c(FALSE, TRUE),
        values = 1,
        lbound = 1e-6,
        labels = paste0('sb2_y', 3:4)
    ),
    mxPath(
        from = c("ib2", "sb2"),
        to = "w",
        arrows = 1,
        free = TRUE,
        values = 1,
        lbound = 1e-6,
        labels = paste0(c("ib2", "sb2"), "_w")
    ),
    # Latent interactions
    mxPath(
        from = c("ib2","sb2"),
        to = "ib2×sb2",
        arrows = 1,
        free = FALSE,
        values = 1
    ),
    mxPath(
        from = "ib2×sb2",
        to = "w",
        arrows = 1,
        free = TRUE,
        values = .5,
        labels = "ib2×sb2_w"
    ),
    # means
    means = mxPath(
        from = "one",
        to = c(paste0('y',1:4), 'w', 'ib2', 'sb2'),
        arrows = 1,
        free = TRUE,
        values = 0,
        labels = paste0("μ_", c(paste0('y',1:4), 'w', 'ib2', 'sb2'))
    ),
    # # thresholds
    # mxThreshold( 
    #     vars = "w", 
    #     nThresh = max(as.double(ex923$w)) - 1,
    #     free = TRUE,
    #     values = mxNormalQuantiles(max(as.double(ex923$w)) - 1),
    #     labels = paste0("w_thresh_", 1:(max(as.double(ex923$w)) - 1)),
    # ),
    # Cross-level
    mxPath(
        from = paste0('level3Model.y', 1:4), 
        to = paste0('y',1:4), 
        free = FALSE, 
        values = 1, 
        joinKey = "level3"
    ),
    # Data
    mxData(ex923[!duplicated(ex923$level2),], 'raw', primaryKey='level2')
)
 
# Level 1 model. (Full model.)
withinModel <- mxModel(
    # Model name
    'withinModel',
    # Model type
    type='RAM',
    # Higher level model to insert
    level2Model,
    # Variables. Manifest, latent and product variables, respectively.
    manifestVars=c(paste0('y',1:4), 'x'),
    latentVars=c('iw','sw'),
    productVars=c("iw×sw"),
    # residual variances
    mxPath(
        from = c(paste0('y',1:4), 'x'),
        arrows = 2,
        free = FALSE,
        values = 1
    ),
    # residual covariances
    mxPath(
        from = paste0('y',1:2),
        connect = "unique.bivariate",
        arrows = 2,
        free = TRUE,
        values = 0.1,
        labels = "cov_y1_y2"
    ),
    mxPath(
        from = paste0('y',3:4),
        connect = "unique.bivariate",
        arrows = 2,
        free = TRUE,
        values = 0.1,
        labels = "cov_y3_y4"
    ),
    # latent variance & covariance
    mxPath(
        from = c('iw','sw'),
        connect = "unique.pairs",
        arrows = 2,
        free = TRUE,
        values = 0.4,
        labels = c("e_iw", "cov_iw_sw", "e_sw")
    ),
    # pattern coefficients
    mxPath(
        from = "iw",
        to = paste0('y',1:2),
        arrows = 1,
        free = c(FALSE, TRUE),
        values = 1,
        lbound = 1e-6,
        labels = paste0('iw_y',1:2)
    ),
    mxPath(
        from = "sw",
        to = paste0('y',3:4),
        arrows = 1,
        free = c(FALSE, TRUE),
        values = 1,
        lbound = 1e-6,
        labels = paste0('sw_y', 3:4)
    ),
    mxPath(
        from = c("iw", "sw"),
        to = "x",
        arrows = 1,
        free = TRUE,
        values = 1,
        lbound = 1e-6,
        labels = paste0(c("iw", "sw"), "_x")
    ),
    # Latent interactions
    mxPath(
        from = c("iw","sw"),
        to = "iw×sw",
        arrows = 1,
        free = FALSE,
        values = 1
    ),
    mxPath(
        from = "iw×sw",
        to = "x",
        arrows = 1,
        free = TRUE,
        values = .5,
        labels = "iw×sw_x"
    ),
    # means
    means = mxPath(
        from = "one",
        to = c(paste0('y',1:4), 'x', 'iw', 'sw'),
        arrows = 1,
        free = TRUE,
        values = 0,
        labels = paste0("μ_", c(paste0('y',1:4), 'x', 'iw', 'sw'))
    ),
    # thresholds
    mxThreshold( 
        vars = "x", 
        nThresh = max(as.double(ex923$x)) - 1,
        free = TRUE,
        values = mxNormalQuantiles(max(as.double(ex923$x)) - 1),
        labels = paste0("x_thresh_", 1:(max(as.double(ex923$x)) - 1)),
    ),
    # Cross-level
    mxPath(
        from = paste0('level2Model.y', 1:4),
        to = paste0('y',1:4),
        free = FALSE,
        values = 1, joinKey = "level2"
    ),
    # Data
    mxData(ex923, 'raw'),
    # Fit function: WLS
    mxFitFunctionWLS(
        # allContinuousMethod = 'marginals'
    )    
)
 
 
## ---------------------------------------------------------------------------------------------------------------------
withinModel %>%
mxRun() %>%
summary()
AdminRobK's picture
Offline
Joined: 01/24/2014 - 12:15
Go Bayes or go home
As for fitting multilevel models, there's a real need to implement ways to model ordinal/categorical/dichotomous variables in such models.

OpenMx is not the right tool for the job, though. I am not aware of any non-Bayesian general method for fitting multilevel models to such variables. Look into stan.

There really should be a warning/error message prohibiting fitting multilevel models using WLS.

Agreed.

mhunter's picture
Offline
Joined: 07/31/2009 - 15:26
Now throw error

OpenMx now throws an error message when someone tries to use multilevel with WLS. See: 41df8f2d

AdminRobK's picture
Offline
Joined: 01/24/2014 - 12:15
next release
OpenMx now throws an error message when someone tries to use multilevel with WLS. See: 41df8f2d

Or, at least it will in its next stable-version release.