Thanks Terrence, that was very informative despite the fact that I left you fishing. I definitely erred on the side of terseness. Here is a script that illustrates what I had in mind.
Having further experimented with this, I now suspect that it is just incorrect to omit the thresholds despite the fact that the model goes through without a warning. Nonetheless, I am not confident that I am thinking about this correctly.
The below script fits two single factor CFA's with four ordinal indicators. The first model omits the threshold for x1 from the model. The second model restores this threshold. As you can see from the below summary table, the chi-square values are the same but I pick up a df with one less parameter when I omit the threshold from the model syntax. Of course, this possibility relies on
auto.th = FALSE.
> summaryTable(noThresholdFit, thresholdFit, c('No Threshold', 'Threshold'))
model moments free stat df pvalue
1 No Threshold 10 7 1.314769 3 0.7256291
2 Threshold 10 8 1.314769 2 0.5182049
>
I am counting sufficient statistics as 6 polychoric correlations plus 4 thresholds = 10. (However, in the table it is just the sum of free parameters plus df.)
Is there ever a good reason to fit a model like the first one, or with all thresholds omitted?
# Counting DF with ordinal data
require(lavaan)
set.seed(54321)
# Simulated data
populationModel <- '
F1 =~ .6*x1 + .6*x2 + .6*x3 + .6*x4
F1 ~~ 1*F1
x1 | -.2*t1
x2 | -.1*t1
x3 | 0.1*t1
x4 | 0.2*t1
' # end model
lavaanify(populationModel)
rm(myData)
myData <- simulateData(model=populationModel, sample.nobs=1000)
summary(myData)
# Fit model with x1 threshold omitted
noThresholdModel <- '
F1 =~ x1 + x2 + x3 + x4
F1 ~~ 1*F1
x2 | t1
x3 | t1
x4 | t1
' # end model
noThresholdFit <- lavaan(model=noThresholdModel,
data=myData,
ordered=TRUE)
summary(noThresholdFit)
lavInspect(noThresholdFit, what='free')
lavInspect(noThresholdFit, what='sampstat')
lavaanify(noThresholdModel)
# fit model with x1 threshold restored
thresholdModel <- '
F1 =~ x1 + x2 + x3 + x4
F1 ~~ 1*F1
x1 | t1
x2 | t1
x3 | t1
x4 | t1
' # end model
thresholdFit <- lavaan(model=thresholdModel,
data=myData,
ordered=TRUE)
summary(thresholdFit)
lavInspect(thresholdFit, what='free')
lavInspect(thresholdFit, what='est')
lavInspect(thresholdFit, what='fitted')
lavInspect(thresholdFit, what='sampstat')
lavaanify(thresholdModel)
# summary comparison
summaryTable <- function(fit1, fit2, names, ...){
fit1Test <- lavInspect(fit1, what='test')
fit2Test <- lavInspect(fit2, what='test')
model <- names
stat <- c(fit1Test$standard$stat, fit2Test$standard$stat)
df <- c(fit1Test$standard$df, fit2Test$standard$df)
pvalue <- c(fit1Test$standard$pvalue, fit2Test$standard$pvalue)
free <- c(max(unlist(lavInspect(fit1))),
max(unlist(lavInspect(fit2))))
moments <- free + df
myTable <- data.frame(model, moments, free, stat, df, pvalue)
return(myTable)
} # end function
summaryTable(noThresholdFit, thresholdFit, c('No Threshold', 'Threshold'))