Showing posts with label MplusAutomation package. Show all posts
Showing posts with label MplusAutomation package. Show all posts

Tuesday, February 15, 2011

Example 8.25: more latent class models (plus a graphical display)


In recent entries (here, here, here and here), we've been fitting a series of latent class models using SAS and R. One of the most commonly used and powerful package for latent class model estimation is Mplus. In this entry, we demonstrate how to use the MplusAutomation package to automate the process of fitting and interpreting a series of models using Mplus.

The first chunk of code needs to be run on a Windows computer with Mplus installed. We undertake the same analytic steps as before, then run the prepareMplusData() function to create the dataset, then createModels() to create the Mplus input files.

ds = read.csv("http://www.math.smith.edu/r/data/help.csv")
attach(ds)
library(MplusAutomation)
cesdcut = ifelse(cesd>20, 1, 0)
smallds = na.omit(data.frame(homeless, cesdcut,
satreat, linkstatus))
prepareMplusData(smallds, file="mplus.dat")
createModels("mplus.txt")

Once the preliminaries have been completed, we can run each of the models, then scarf up the results.

runModels()
summary=extractModelSummaries()
models=readModels()

To help create graphical summaries of the classes, I crafted some code that utilizes the Mplus output files. These include functions to calculate the antilogit (alogit()), determine class probabilities in terms of model parameters (calcclasses() and findprobs()), and a routine to plot the resulting values (plotres()). Unfortunately, these routines require tweaking for models with different number of predictors (as well as care if some of the predictors have more than 2 levels).

alogit = function(x) {
return(exp(x)/(1+exp(x)))
}

calcclasses = function(vals) {
numvals = length(vals)
classes = numeric(numvals+1)
for (i in 1:numvals) {
classes[i] = exp(vals[i])/
(1+sum(exp(vals[1:numvals])))
}
classes[numvals+1] = 1/(1+sum(exp(vals[1:numvals])))
return(classes)
}

findprobs = function(df) {
numclass = length(levels(as.factor(df$LatentClass)))-1
v1 = numeric(numclass)
v2 = numeric(numclass)
v3 = numeric(numclass)
v4 = numeric(numclass)
for (i in 1:numclass) {
v1[i] = alogit(-df$est[4*(i-1)+1])
v2[i] = alogit(-df$est[4*(i-1)+2])
v3[i] = alogit(-df$est[4*(i-1)+3])
v4[i] = alogit(-df$est[4*(i-1)+4])
}
if (numclass>1) {
classes=calcclasses(df$est[(4*numclass+1):(4*numclass+numclass-1)])
} else classes=1
return(list(prop=cbind(v1=v1, v2=v2, v3=v3, v4=v4),
classes=classes))
}

plotres = function(mylist, roundval=1, cexval=.75) {
# can only plot at most 4 classes!
reorder = order(mylist$classes)
probs = mylist$classes[reorder]
results = mylist$prop
dimen = dim(results)
cols = c(1,4,2,5) # black, blue, red, and turquoise
ltys = c(3,1,2,4) # dotted, solid, dash, dash-dot
ltyslines = ltys[rank(-mylist$classes)]
colslines = cols[rank(-mylist$classes)]
ltysrev = rev(ltys[1:dimen[1]])
colsrev = rev(cols[1:dimen[1]])
plot(c(0.9, dimen[2]), c(-0.08,1), xlab="",
ylab="estimated prevalence", xaxt="n", type="n")
abline(h=0, col="gray")
abline(h=1, col="gray")
for (i in 1:(dimen[1])) {
lines(1:(dimen[2]), results[i,], lty=ltyslines[i],
col=colslines[i], lwd=2)
points(1:(dimen[2]), results[i,], col=colslines[i])
}
text(1,0,"homeless", pos=1, cex=cexval)
text(2,0,"cesdcut", pos=1, cex=cexval)
text(3,0,"satreat", pos=1, cex=cexval)
text(4,0,"linkstat", pos=1, cex=cexval)
legendval = character(dimen[1])
for (i in 1:(dimen[1])) {
legendval[i] = paste("class ",i , " (", round(100*probs[i],
roundval), "%)", sep="")
}
legend(2, 0.4, legend=legendval,
lty=ltysrev, col=colsrev, cex=cexval, lwd=2)
}

files=list.files()
files=files[grep(".out",files)
]
par(mfrow=c(4,1), mar=c(1, 2, 2, 1) + 0.1)
for (i in 1:length(files)) {
  cat("file=",files[i],"\n")
  res = extractModelParameters(target=files[i])
  newres = findprobs(res$unstandardized)
  plotres(newres)
  title(substr(files[i], 1, nchar(files[i])-4))
}

Finally, by spelunking through the output files in the current directory (using list.files()) the results from each of the four models can be collated and displayed as seen in the Figure above.

The single class model simply reproduces the prevalences of each of the predictors. The two class solution primarily separates those not receiving substance abuse treatment from those that do. The three class solution further splits along substance abuse treatment, as well as homeless status. The four class solution is somewhat jumbled, with a group of homeless subjects comprising the largest class. None of the classes distinguish linkstatus (the primary outcome of the RCT).

Monday, February 7, 2011

Example 8.24: MplusAutomation and Mplus

In recent entries (here, here, and here), we've been fitting a series of latent class models using SAS and R. One of the most commonly used and powerful software package for latent class model estimation is Mplus. This commercial software includes support for many features that are not presently available in R or SAS. As an example, while the randomLCA package supports data with clustering, and the poLCA package supports polytomous variables, neither package supports clustering and polytomous variables.

In this entry, we demonstrate how to use the R package MplusAutomation to automate the process of fitting and interpreting a series of models using Mplus.

The key to all this magic is the template file which is used to create the Mplus input files. Here we demonstrate automating the creation of 4 models with 1, 2, 3, and 4 latent classes, using a template file called mplus.txt.

[[init]]
iterators = classes;
classes = 1:4;
dir = "Z:/field/blog";
filename = "mplus-[[classes]]-class-.inp";
outputDirectory = [[dir]];
[[/init]]
TITLE: [[classes]]-class
DATA: FILE IS mplus.dat;
VARIABLE: NAMES ARE homeless cesdcut satreat linkstatus;
CLASSES = c ([[classes]]);
CATEGORICAL = all;
ANALYSIS: TYPE = MIXTURE;
STARTS = 2000 200;
STITERATIONS=1000;
OUTPUT: TECH1 TECH10;
SAVEDATA: FILE IS "mplus-[[classes]]-class.cprob";
SAVE IS CPROB;

The package's createModels() function will loop through the four possible numbers of classes (1 through 4) and create separate Mplus input files. Multiple iterators are supported, and they can be referenced numerically or symbolically. This can be very helpful if there are different variables being used in each of the models, or other variations in the model.

When the createModels() function is run for this example, it generates 4 files. The file mplus-1-class-.inp looks like:

TITLE: 1-class
DATA: FILE IS mplus.dat;
VARIABLE: NAMES ARE homeless cesdcut satreat linkstatus;
CLASSES = c (1);
CATEGORICAL = all;
ANALYSIS: TYPE = MIXTURE;
STARTS = 2000 200;
STITERATIONS=1000;
OUTPUT: TECH1 TECH10;
SAVEDATA: FILE IS "mplus-1-class.cprob";
SAVE IS CPROB;

We call Mplus using the runModels() function after reading in the data and writing out a dataset in Mplus format (with prepareMplusData). Then the results can be collated and displayed.

ds = read.csv("http://www.math.smith.edu/r/data/help.csv")
attach(ds)
library(MplusAutomation)
cesdcut = ifelse(cesd>20, 1, 0)
smallds = na.omit(data.frame(homeless, cesdcut,
satreat, linkstatus))
prepareMplusData(smallds, file="mplus.dat")
createModels("mplus.txt")
runModels()
summary=extractModelSummaries()
models=readModels()

We see that the three class solution has the lowest AICC, while the one class solution has the lowest aBIC.

> summary
Title AnalysisType
1 1-class MIXTURE; STARTS = 2000 200; STITERATIONS=1000
2 2-class MIXTURE; STARTS = 2000 200; STITERATIONS=1000
3 3-class MIXTURE; STARTS = 2000 200; STITERATIONS=1000
4 4-class MIXTURE; STARTS = 2000 200; STITERATIONS=1000
DataType Estimator Observations Parameters LL
1 INDIVIDUAL MLR 431 4 -1045.656
2 INDIVIDUAL MLR 431 9 -1040.513
3 INDIVIDUAL MLR 431 14 -1032.484
4 INDIVIDUAL MLR 431 19 -1032.067
LLCorrectionFactor AIC BIC aBIC Entropy
1 1.000 2099.313 2115.577 2102.883 NA
2 1.019 2099.026 2135.621 2107.060 0.349
3 1.000 2092.967 2149.893 2105.465 0.941
4 1.000 2102.134 2179.390 2119.095 0.832
AICC Filename
1 2099.407 mplus-1-class-.out
2 2099.454 mplus-2-class-.out
3 2093.977 mplus-3-class-.out
4 2103.983 mplus-4-class-.out

Additional results for each of the specific models can be found in the returned objects.

> names(models)
[1] "mplus.1.class..out" "mplus.2.class..out"
[3] "mplus.3.class..out" "mplus.4.class..out"
> names(models$mplus.1.class..out)
[1] "parameters" "savedata" "summaries"

In a future entry, we'll explore more ways to utilize the information in the Mplus output, including displaying the prevalences in each group in a graphical manner.