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

Posted on
Picture of user. pehkawn Joined: 05/24/2020
I've been trying to modify [this example](https://github.com/OpenMx/OpenMx/blob/master/inst/models/nightly/mplus-ex9.23.R) 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

Replied on Fri, 05/12/2023 - 10:03
Picture of user. mhunter Joined: 07/31/2009

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.
Replied on Fri, 05/12/2023 - 10:21
Picture of user. pehkawn Joined: 05/24/2020

In reply to by mhunter

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.
Replied on Sat, 05/13/2023 - 10:41
Picture of user. mhunter Joined: 07/31/2009

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?

Replied on Wed, 05/24/2023 - 18:36
Picture of user. pehkawn Joined: 05/24/2020

In reply to by mhunter

dates back to previous posts. [ML does not handle ordinal variables in multilevel model](https://openmx.ssri.psu.edu/comment/9615#comment-9615), 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()

Replied on Thu, 05/25/2023 - 13:24
Picture of user. AdminRobK Joined: 01/24/2014

In reply to by pehkawn

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.

Replied on Tue, 06/13/2023 - 17:39
Picture of user. mhunter Joined: 07/31/2009

OpenMx now throws an error message when someone tries to use multilevel with WLS. See: [41df8f2d](https://github.com/OpenMx/OpenMx/commit/41df8f2d84f1b862568f61dbba15668ad07e693e)
Replied on Tue, 06/13/2023 - 18:17
Picture of user. AdminRobK Joined: 01/24/2014

In reply to by mhunter

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.