I've been talking to Ryne about this, and I think we've put together a fairly reasonable proposal for the user experience for ordinal data.
We welcome comments and suggestions.
A) From the pathy perspective:
Models of type=RAM have an additional function mxThreshold(), similar to mxPath(). mxThreshold takes a list of data column names, and then lists of vectors of starting values, labels, and freeness/fixedness specifiers. Each of these lists has the same length as the list of data columns, and consists of vectors of numerics, strings, and logicals, respectively. These should probably repeat if you don't specify enough of them, so that an IRT model with 20 variables rated 1-6 can be entered as:
irtModel <- mxModel(irtModel, mxThresholds(names(data), value=c(-2,-1,0,1,2), labels=NA, free=T))
to have free independent thresholds for each of the variables in the data set. Names will have to line up with the names of data columns, or an error will be thrown, either now or at mxRun(). Any data elements that are not already R ordered factors will be coerced with ordered(). After all coercion happens, sanity checks will make sure the number of levels is consistent with the number of thresholds, etc.
B) From the matrix perspective:
Any objective function capable of dealing with objective data (right now, RAM and FIML) includes a slot "thresholds". This slot is filled with an mxMatrix in the same way that mxRAMObjective's A slot or mxFIMLObjective's cov slot is. (In implementation, I expect this will be similar to those implementations--the model will contain the mxMatrix object, and the objective function's slot will just hold the name of that matrix.)
The slot "thresholds" contains a rectangular matrix with n rows and m columns, where m is the number of ordinal data columns and n is the maximum number of thresholds needed for any of those columns. The elements of the threshold matrix are the values of the thresholds, specified like any other matrix. Elements where no threshold exists have their values set to NA [for example, if data column 1 needs only two thresholds and data column 2 requires 3, the values matrix might be
matrix(cbind(-1, 1, NA), cbind(-1, 0, 1))]. The dimnames() of this matrix must correspond to columns in the data.
Any data element whose name matches one of the dimnames of the threshold matrix is considered to be an ordinal variable. If it is not already an R ordered factor, it is coerced into one using ordered(). After all coercion happens, sanity checks will make sure the number of levels is consistent with the number of thresholds, etc.
C) Overall Notes:
1. This way of doing things allows anyone in a "standard case", where all levels of their factors are used, to not have to muck around with R's factors, but allows them to do so if they want to add levels that aren't used or do something else out of the ordinary. Users are protected from some accidents of automatic conversion because their number of thresholds is checked against the number of factor levels, and an error thrown if nothing is specified. We'll need to make note of the defaults of ordered(), so that we can tell our users how to specify their data columns, but it seems to work quite reasonably if given integers or numerics.
- It also keeps the thresholds with the model, but in a way that they can be easily transported from one model to another, or simply referenced from a sub- or supermodel.