parameter constraints

23 replies [Last post]
manu's picture
Offline
Joined: 10/08/2009

Dear all,

sorry for this simple question. What I want to do is to impose a simple parameter constraint. If I want to set two parameters equal, I simply use the same label. However, what if I want to set parameter a = 2*b, so that actually only one parameter is freely estimated?

After searching the archives, the only cumbersome way I came up with is to define two new matrices with a single element but new names, use the mxAlgebra function to multiply by two and then use the mxConstraint function to set the matrices equal. So I did something like that

mxMatrix(type = "Full", nrow=1, ncol=1, free=F, values = 0, label="a", name="con_a"),
mxMatrix(type = "Full", nrow=1, ncol=1, free=T, values = 1, label="b", name="con_b"),
mxAlgebra(expression= 2*con_b, name="con"),
mxConstraint("con", "=", "con_a"),

But then something goes wrong, and I am not sure why. Do I have to define the elements of both matrices as free? fixed? Is not there a simpler way? This seems overly complicated.

Thank you very much!

mhermans's picture
Offline
Joined: 12/08/2009
I am trying to learn OpenMx

I am trying to learn OpenMx while replicating a SEM-model, i.e. the example-model in Raykov & Shrout (2002) "Reliability of Scales With General Structure".

I think the pathspecification & basic constraints are more or less ok (see syntax below), but I'm not sure how to specify the contraints on the parameters between eta1/eta2 and eta4 (see image: http://i.imgur.com/7VGbA.png).

The constraint is that the parameter should be equal to the sum of the different loadings on eta1, eta2. Is this possible using the path-specification? Is there an appropriate example where I can start from?

thanks,

Maarten

# Raykov & Shrout (2002) example
# ==============================
require(OpenMx)
fn <- 'http://kanut.studentenweb.org/OpenMx/raykov_table1.cm' # covariance matrix, lower
n <- max(count.fields(fn)) # 6 observed vars
X <- data.matrix(read.table(fn, fill = TRUE, col.names = 1:n)) 
X[upper.tri(X)] <- t(X)[upper.tri(t(X))] # fill upper tri (full matrix)
rownames(X) <- colnames(X)
X <- as.matrix(X) # otherwise error, known bug
 
manifests <- rownames(X)
latents <- c('eta1', 'eta2', 'eta3', 'eta4')
 
raykov.rel.model.constr <- mxModel(name="Raykov reliability model, constrained",
    type="RAM",
    manifestVars = manifests ,
    latentVars = latents,
    mxPath(from='eta1', to=manifests[1:4]),
    mxPath(from='eta2', to=manifests[3:6]),
    mxPath(from=c('eta1', 'eta2'), to='eta4'),
    mxPath(from=manifests, arrows=2),
    mxPath(from=manifests, to='eta3', free=FALSE, values=1.0),
    mxPath(from=c('eta1', 'eta2'), arrows=2, free=FALSE, values=1.0),
    mxPath(from=c('eta3', 'eta4'), arrows=2, free=TRUE),
    mxPath(from='eta1', to='eta2', arrows=2),
    mxData(observed=X, type="cov", numObs=300)
)

neale's picture
Offline
Joined: 07/31/2009
I think it can be done as

I think it can be done as mspiegel suggests. However, I have to say that as it stands, the model is a bit weird from an SEM point of view. The two latent variables eta4 and eta3 have no "indicators" - that is, they are not a cause (neither direct nor indirect) of any measured observation. Therefore, their existence in the model will have no effect on the fit of the model, and the same fit could be achieved by omitting them. I have only skimmed the article in question, but in essence I don't see any need to set up the model this way in OpenMx. Rather, simply set up the two factor model, and the algebras per mspiegel. Thus there is no need to specify the additional latent variables eta3 and eta4. A great feature of OpenMx is that it is possible to request likelihood-based confidence intervals on any parameter or function of parameters. So 95% CI's on the composites, e.g., lambda11+lambda21 etc. could be readily obtained using the mxCI function and the argument (model,Intervals=T) to the mxRun command.

mspiegel's picture
Offline
Joined: 07/31/2009
Hi. Thanks for including the

Hi. Thanks for including the link to the path diagram. OK, sadly my background is computer science and I didn't sit through enough of the SEM class to determine the desired constraint. But what you want to introduce is the argument 'labels = ' on those mxPath() functions that you would like to give labels to the free parameters. In OpenMx, a free parameter can either by anonymous (its label is NA) or have a unique label.

For example, lets say we have assigned labels 'a', 'b' and 'c' to 3 free parameters. Now assume we have a fourth parameter, 'd' such that a + b + c + d = 1. Note that 'd' is not a free parameter, because it is uniquely determined by the free parameters 'a', 'b', 'and 'c'. We would declare 'd' as an algebra: mxAlgebra(a + b + c, name = 'd'). You may find the following information helpful: in the algebra specification, the free parameters 'a', 'b', and 'c' are secretly converted into 1 x 1 matrices that consist of a single cell with the free parameter. You may ignore this information if it just confuses the matter.

Now you want to use 'd' in a mxPath() statement. You should use the special square-bracket notation in a label= argument. So the label of the cell will be "d[1,1]". Make sure that free=FALSE for that cell (it should throw an error otherwise).

mhermans's picture
Offline
Joined: 12/08/2009
Thanks for your comments. I

Thanks for your comments.

I have updated (see highlighted part: http://pastebin.org/447417) the model specification based on your suggestions. mxModel() returns no errors, but mxRun() gives

Error: The label '`reliability model.llambda14`[1, 1]' of matrix 'A' in model 'reliability model' generated the error message: object 'model' not found

neale: ... there is no need to specify the additional latent variables eta3 and eta4

The idea is that in such a "strange" SEM model, the values for the variances of eta3 and eta4 will allow you to calculate the reliability of a scale based on the items X1-X6. I'm not SEM-savvy enough to figure out how to do this without including eta3 & eta4, but suggestions are welcome.

neale: A great feature of OpenMx is that it is possible to request likelihood-based confidence intervals ...

That is indeed a nice feature, it'll save me from implementing a bootstrap procedure as described in the paper.

neale's picture
Offline
Joined: 07/31/2009
Hi mhermans I have tweaked

Hi mhermans

I have tweaked your script so that it runs (but have not compared results with those of Raykov & Shrout). There was an error in that this line:

mxPath(from=c('eta3', 'eta4'), arrows=2, free=TRUE),

specifies residual error on the eta3 and eta4 latent variables, whereas I believe they are neither required nor identified. This issue may stem from rather sloppy conventions commonly encountered in path diagrams. That is, a mathematically sufficient diagram should be drawn with double-headed variance terms throughout (on all latent and observed variables), and these should be omitted ONLY when they are fixed at zero. Many path diagrams leave out variance paths on all latent variables - in many cases these are all fixed at 1.0 and this is assumed (but without explicitly saying so in the Figure caption for example). Such tacit assumptions/conventions are dangerous because they lead to ambiguity about the model specification, something that should be avoided. A lesson I learned from Jack McArdle & Steve Boker many moons ago.

Note in the script that the equality constrained paths do not appear in the parameter list of the output. However, as I requested CIs on these algebras they are immediately visible in the summary().

Good luck!

AttachmentSize
raykovShroutExample.R 1.51 KB
mspiegel's picture
Offline
Joined: 07/31/2009
Try renaming your model

Try renaming your model "reliabilityModel" and changing all the appropriate references from "reliability model" to "reliabilityModel". We try to escape whitespace as best we can, but this could be a bug.

mhermans's picture
Offline
Joined: 12/08/2009
That unfortunately gives the

That unfortunately gives the same error...

AttachmentSize
openmx_worked_example.R 1.56 KB
mspiegel's picture
Offline
Joined: 07/31/2009
Oops. This was a bug in our

Oops. This was a bug in our library. I've committed a patch to our source code repository. We will have a binary release by the end of this week, including this fix. Does the following output look correct? If it is working correctly, I'll add this model to our test suite.

free parameters:
       name matrix  row  col  Estimate    Std.Error
1  lambda11      A   X1 eta1 0.4607447 4.574634e-02
2  lambda21      A   X2 eta1 0.7092546 5.791039e-02
3  lambda31      A   X3 eta1 0.5988003 5.441698e-02
4  lambda41      A   X4 eta1 0.4147812 5.044377e-02
5  lambda32      A   X3 eta2 0.2547224 5.528213e-02
6  lambda42      A   X4 eta2 0.3246762 5.535732e-02
7  lambda52      A   X5 eta2 0.5299765 5.263664e-02
8  lambda62      A   X6 eta2 0.8139780 5.943604e-02
9      <NA>      S   X1   X1 0.3677141 3.663752e-02
10     <NA>      S   X2   X2 0.4469578 5.774198e-02
11     <NA>      S   X3   X3 0.3198523 4.757342e-02
12     <NA>      S   X4   X4 0.3659904 3.536257e-02
13     <NA>      S   X5   X5 0.4991246 4.808476e-02
14     <NA>      S   X6   X6 0.2674381 6.173348e-02
15     <NA>      S eta1 eta2 0.2842163 7.710995e-02
16     <NA>      S eta3 eta3 0.1000000 4.325877e+22
17     <NA>      S eta4 eta4 0.1000000 4.325877e+22
 
observed statistics:  21 
estimated parameters:  17 
degrees of freedom:  4 
-2 log likelihood:  880.4232 
saturated -2 log likelihood:  872.6369 
number of observations:  300 
chi-square:  7.786316 
p:  0.09972689 
AIC (Mx):  -0.2136844 
BIC (Mx):  -7.514407 

mhermans's picture
Offline
Joined: 12/08/2009
The error is indeed gone in

The error is indeed gone in the svn-version, thanks for the quick fix.

If it is working correctly, I'll add this model to our test suite.

I have not yet---even including the suggestions from neale, thanks---been able to reproduce the values from the worked example from the paper, but I'm guessing it has to do with my specification, not OpenMx. I'll post an update if I get closer.

pdeboeck's picture
Offline
Joined: 08/04/2009
Another topic --- right now a

Another topic --- right now a constraint must be of the form: 'exp1 [<, ==, >] exp2'

Any chance of allowing for >= or <=?

mspiegel's picture
Offline
Joined: 07/31/2009
Equality constraints are kind

Equality constraints are kind of a hack in any numeric optimizer. Since we use floating point representations of real numbers, a == b is really implemented as abs(a - b) < epsilon. So, the difference between A < B and A <= B is the epsilon threshold, which is supposed to be a very small value. We haven't seen a need for A <= B, but we could be wrong.

pdeboeck's picture
Offline
Joined: 08/04/2009
Ahhh --- I should have

Ahhh --- I should have guessed. I'm getting by without it. I just have a situation where A>=B would be technically more accurate than A>B. However, on the practical side: When I use conditions where A=B in the data but specify A>B, it seems to be fine. I typically get estimates of A and B that differ by an inconsequential amount. So it is probably not worth the time to make this edit. Perhaps someone will find a more compelling reason to include >=.

mspiegel's picture
Offline
Joined: 07/31/2009
This specification would also

This specification would also work:

mxMatrix(type = "Full", nrow=1, ncol=1, free=T, values = 0, label="a", name="con_a"),
mxMatrix(type = "Full", nrow=1, ncol=1, free=T, values = 1, label="b", name="con_b"),
mxAlgebra(2 * b, name="twob"),
mxConstraint("con_a", "=", "twob"),

WRT your question, both the matrices have to include free parameters. Note that the mxAlgebra expression "twob" uses the name of the free parameter (b) instead of the name of the matrix (con_b). Either way would work, I find this easier to read. We are toying around with a mxConstraint() specification such that the left-hand and right-hand side are specified explicitly as algebras inside the constraint declaration. However this has not been implemented.

Also your constraint is not satisfied by the starting values. You should change the starting values.

tbates's picture
Offline
Joined: 07/31/2009
It would be very nice, and

It would be very nice, and very natural to be able to say

mxConstraint("con_a", "=", "2*con_b")

You would need some trigger to let the machine know that "a" was not the name of an algebra, but rather an algebra itself: maybe the presence of a dot, or similarly to "data." specifying a definition variable, using "alg." as a prefix
mxConstraint("alg.con_a.a", "=", alg.con_b.b*2")

mspiegel's picture
Offline
Joined: 07/31/2009
We were thinking more

We were thinking more like:

mxConstraint(con_a == 2*con_b, name ="constraint1")

The constraint gets formatted like a MxAlgebra. Note the lack of quotes around the first argument. If the outermost operator in the MxConstraint is neither <, ==, nor >, then an error would be thrown.

tbates's picture
Offline
Joined: 07/31/2009
Excellent - and very

Excellent - and very readable.

People will make the mistake of putting the algebra in quotes out of habit a few times, but the logic is very memorable: names in quotes, algebras are algebras... nice!

Should return a nice error when only two parameters are provided and the first is in quotes. Something like "you passed in an algebra to mxConstraint , but put it in quotes: remove the quotes for this to work."

tbrick's picture
Offline
Joined: 07/31/2009
I like this formatting. If

I like this formatting.

If this is being interpreted like an algebra, then people could actually use their free parameters directly. For example, to constrain the parameters "a" and "b" in manu's example above, the code would just be:
 mxConstraint(a == 2*b, name="constraint1")
without the need to build the con_a and con_b matrices at all.

mspiegel's picture
Offline
Joined: 07/31/2009
Well, yes provided that the

Well, yes provided that the free parameters 'a' and 'b' were defined in some other matrix. There is currently no mechanism for declaring free or fixed parameters outside of a matrix declaration. Yes, it would be cool if there was a mechanism. In fact, there is: write a function that builds a matrix for you!

Ryne's picture
Offline
Joined: 07/31/2009
That's not a problem. If 'b'

That's not a problem. If 'b' isn't used elsewhere in the model, then the constraint is worthless anyway.

manu's picture
Offline
Joined: 10/08/2009
thanks for the fantastic

thanks for the fantastic responses!!! I think having a simple constraint
mxConstraint(a == 2*b, name="constraint1") would be great!
However, I do have a quick follow-up question/comment: While I am now able to impose the constraints using the approach described above, it appears that OpenMx counts the two additional 1x1 matrices as free parameters, i.e. the number of estimated parameters and degrees of freedom in the summary() function are incorrect. I.e., in the upper example the number of estimated parameters would be two, while in fact I am only estimating one parameter, or am I missing something?

mspiegel's picture
Offline
Joined: 07/31/2009
Does the mxVersion() function

Does the mxVersion() function return 0.2.10-1172? The summary() function has been reimplemented.

manu's picture
Offline
Joined: 10/08/2009
ah, that's it - in the

ah, that's it - in the updated version the constraint is explicity noted and the df are correct - thank you!

Ryne's picture
Offline
Joined: 07/31/2009
This is a great question.

This is a great question. Don't apologize for it!

Both parameters should be free in the specification you gave above. The code you gave above fixes the "a" parameter to a value of zero.

You have two alternatives, one new and OpenMx-y, and one old school and LISREL-y. The novel way would be to define the "b" parameter not in a matrix, but in an algebra. OpenMx can treat the output of an mxAlgebra as a matrix. So you could define your "b" parameter as:

mxMatrix("Full", 1, 1, T, 0, name="a")
mxAlgebra(a * 2, name="b")

Technically, "a" and "b" aren't parameters in what I have you above; they're 1 x 1 matrices with unnamed parameters in them. Feel free to adjust the names as needed, remembering that it's the matrix name that appears in the algebra. You can then use square bracket substitution to throw the "b" parameter into other matrices.

http://openmx.psyc.virginia.edu/thread/193

The historical way of dealing with this is a dummy latent variable. Whatever path is supposed to get a value of "b" gets interrupted by a mediating latent variable with zero variance. The regression from the exogenous variable to the dummy gets a value of "a", while the regression from the dummy to the endogenous variable gets a 2 (or visa versa). Unless your model takes a form very conducive to the dummy variable trick and you want all of the constraints to be explicitly in the model, I'd avoid this, but it's good to know its there.

ryne