non-independent submodels

Posted on
No user picture. JWiley Joined: 03/25/2011
I am still getting used to OpenMx, so this may be a stupid question. Say I have a single factor ("G") measurement model, in a mxModel object called "mMeasure". Is it now possible to use the mMeasure object as part of a structural model? I imagine something like (pseudocode):

mStructure <- mxModel("Structure",
manifestVars = "Y", latentVars = "G",
mxPath(from = "G", to = "Y"),
## other model stuff)

mFull <- mxModel("Overall",
type = "RAM",
mxData(mydata[, c("all", "manifest", "variables", "for any submodel")], type = "raw"),
mMeasure,
mStructure)

My general hope being to work with one model, and once I have it in a form I want, use it in another model. I have tried several variants of the above putting data in the submodels and/or overall model without success. I am not looking for a worked example (though awesome if there is one lying around), just a "this can be done, keep looking" or "no *head smack* what were you thinking?". If I know its doable, once I get it worked out, I will post a working example for archive purposes.

Replied on Tue, 09/20/2011 - 11:42
Picture of user. Ryne Joined: 07/31/2009

There should be no problem adding your objects as submodels to existing models. There are two things to think about.

First, why are you using a multimodel structure? Using multiple models can be really great tool, but they add a great deal of complexity, as discussed in the next paragraph. Judging by your object names, it looks like you're trying to split a single model into measurement and structural components. The problem with this approach is that these two structures are, IMHO, never as separate as we would like, and always share the same data source. You seem to be trying to use mxModels to separate paths from one conceptual portion of your model from other portions. However, these parameters are all part of the same model, and the expected data covariance is not defined without all parts. You'd be better off updating a model, adding structural components to an existing measurement model. Feel free to give us more details and examples so we can help you build the best model you can.

Second, models to be analyzed need objective functions, and models analyzed as a non-independent set need interdependent objective functions. In the basic single model case, you provide some objective function either implicitly (type='RAM' builds an mxRAMObjective function for you) or explicitly. That is the function that is minimized, such that values of the free parameters are selected that make the minimum -2 log likelihood. If you have multiple models (for instance, a multiple group model), you'll have to specify objective functions for each group/model, and then an objective for the container model that holds them (usually an mxAlgebraObjective that minimizes an algebra like 'group1.objective + group2.objective'). This is part of why your model is failing; your separation of parameters across models yields one model with manifest-latent relationships, another model with latent-latent relationships, and a parent or container model with data and no relationships. When you specify multiple models, you have to specify how they interact with one another.

Hope this helps, and good luck with your models!

Replied on Tue, 09/20/2011 - 12:51
No user picture. JWiley Joined: 03/25/2011

In reply to by Ryne

Thanks, that helps. I was confused on what the independent argument was for. I was not looking at anything as complicated as a multimodel structure, just a lazy way of writing the syntax when I have a base model that does not change much, but I am trying things out with other aspects of it. Updating a model seems like it is the way to go (or I suppose I could put the stable part all in one mxPath object). Thanks!

An example would be:

psrVars <- c("X1", "X2", "X3", "X4")
DV <- "CESD"
manifests <- c(psrVars, DV)
latents <- "PSR"
mD <- mxData(observed = mydat[, manifests], type = "raw")

mPSR <- mxPath(from = "PSR",
to = psrVars,
values = c(1, 1, 1, 1),
labels = paste("b", psrVars, sep = ''))

mFVar <- mxPath(from = latents, arrows = 2,
free = FALSE, values = 1, labels = paste("v", latents, sep = ''))
mFMean <- mxPath(from = "one", to = latents,
free = FALSE, values = 0, labels = paste("m", latents, sep = ''))

mMean <- mxPath(from = "one", to = psrVars,
labels = paste("m", psrVars, sep = ''))

mVar <- mxPath(from = psrVars, arrows = 2,
labels = paste("v", psrVars, sep = ''))

mSV <- mxPath(from = DV, arrows = 2, labels = paste("v", DV, sep = ''))
mSM <- mxPath(from = "one", to = DV, labels = paste("m", DV, sep = ''))
mTheory <- mxPath(from = "PSR", to = DV,
arrows = 1, free = TRUE, values = 1, labels = "bPSR")

mFull <- mxModel(model = "PSR and CESD",
type = "RAM",
manifestVars = manifests, latentVars = latents,
mD,
mPSR, mFVar, mFMean,
mMean, mVar,
mSV, mSM, mTheory)

If the outcome (DV) changes, or I want to look at mediation, mPSR, mFVar, mFMean, mMean, and mVar all stay the same. I was basically hoping to somehow bundle them into a single object so the model looked more like:

mFull <- mxModel(model = "PSR and CESD",
type = "RAM",
manifestVars = manifests, latentVars = latents,
mD,
mMeasure, # contains all paths related just to the measurement
mSV, mSM, mTheory)

This is a simple example, but in more complex measurement models, it seemed like it would be nice. Though perhaps creating many small mxPath objects is not an optimal workflow. Conceptually, I was thinking of something like this in R:

## I want these two vectors
c("a", "b", "c", "d", "e", "g") #1
c("a", "b", "c", "d", "e", "h") #2

# always want a to e so simplify the above to
x <- c("a", "b", "c", "d", "e")
c(x, "g") #1 <-- the lazy way
c(x, "h") #2

Replied on Tue, 09/20/2011 - 13:11
Picture of user. mspiegel Joined: 07/31/2009

In reply to by JWiley

I am having trouble determining how the vector c("a", "b", "c", "d", "e", "g") fits into the example that you gave. But in any case, you may be able to improve your workflow by writing a function that accepts some form of input, and returns a new model on output. As at outline, it would look something like:

modelCreator <- function(input) {
   paths1 <- mxPath(....)
   paths2 <- mxPath(....)
   model <- mxModel("modelname", type = "RAM", paths1, paths2, ...)
   return(model)
}

And then you would call the modelCreator with different values for the input.

Replied on Thu, 09/22/2011 - 04:06
No user picture. JWiley Joined: 03/25/2011

In reply to by mspiegel

Thanks! That makes a lot of sense---a wrapper function is a good idea. Sorry for the confusing example with te letter vector---I was trying to convey that I wanted some way to store a set of objects created via mxPath together and then combine them. For some reason I had initially thought MxPath objects needed to be stored in MxRAMModel objects---I realized MxPath objects can just be in a list (don't know why that didn't occur to me sooner).

The more I play with it, the less I am sure it really improves workflow as much as I envisioned, but in any case for archival purposes, a clean way to do it is easy using lists and do.call:

mydat <- data.frame(X1 = rnorm(400), X2 = rnorm(400), X3 = rnorm(400),
X4 = rnorm(400), CESD = runif(400))

psrVars <- c("X1", "X2", "X3", "X4")
DV <- "CESD"
manifests <- c(psrVars, DV)
latents <- "PSR"
mD <- mxData(observed = mydat[, manifests], type = "raw")

mMeasure <- list(
mxPath(from = "PSR", to = psrVars,
values = c(1, 1, 1, 1), labels = paste("b", psrVars, sep = '')),
mxPath(from = latents, arrows = 2,
free = FALSE, values = 1, labels = paste("v", latents, sep = '')),
mxPath(from = "one", to = latents,
free = FALSE, values = 0, labels = paste("m", latents, sep = '')),
mxPath(from = "one", to = psrVars, labels = paste("m", psrVars, sep = '')),
mxPath(from = psrVars, arrows = 2, labels = paste("v", psrVars, sep = '')))

mSV <- mxPath(from = DV, arrows = 2, labels = paste("v", DV, sep = ''))
mSM <- mxPath(from = "one", to = DV, labels = paste("m", DV, sep = ''))
mTheory <- mxPath(from = "PSR", to = DV,
arrows = 1, free = TRUE, values = 1, labels = "bPSR")

mFull <- do.call("mxModel", c(mMeasure, list(
model = "PSR and CESD", type = "RAM",
manifestVars = manifests, latentVars = latents, mD,
mSV, mSM, mTheory)))

Replied on Mon, 09/26/2011 - 13:33
Picture of user. Ryne Joined: 07/31/2009

In reply to by JWiley

This looks good, but I share your concerns about workflow improvement.

In addition to the two styles we've already showed, you can update MxModel objects using the mxModel function as shown below. It would allow you to build incrementally, but save you the step of psuedo-compiling at the end. Each line takes the model, adds a new path and returns the appended model to the mMeasure object.

mMeasure <- mxModel("Model Name")
mMeasure <- mxModel(mMeasure, mxPath("x", "y", ....))
mMeasure <- mxModel(mMeasure, mxPath("a", "b", ....))