Skip to content

Commit a965745

Browse files
committed
Add categorical argument to modsem_mplus()
1 parent f82dd81 commit a965745

File tree

2 files changed

+125
-26
lines changed

2 files changed

+125
-26
lines changed

R/modsem_mplus.R

Lines changed: 122 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#' @param data dataset
55
#' @param estimator estimator argument passed to \code{Mplus}.
66
#' @param cluster cluster argument passed to \code{Mplus}.
7+
#' @param categorical categorical argument passed to \code{Mplus}.
78
#' @param type type argument passed to \code{Mplus}.
89
#' @param algorithm algorithm argument passed to \code{Mplus}.
910
#' @param processors processors argument passed to \code{Mplus}.
@@ -62,6 +63,7 @@ modsem_mplus <- function(model.syntax,
6263
rcs.choose = NULL,
6364
rcs.scale.corrected = TRUE,
6465
output.std = TRUE,
66+
categorical = NULL,
6567
...) {
6668
if (rcs) { # use reliability-correct single items?
6769
corrected <- relcorr_single_item(
@@ -109,8 +111,14 @@ modsem_mplus <- function(model.syntax,
109111

110112
# Fix names in data
111113
data <- as.data.frame(data)
114+
catCols <- colnames(data)[sapply(data, is.factor)]
115+
categorical <- intersect(union(catCols, categorical), colnames(data))
116+
112117
dmask <- colnames(data) %in% names(abbreviated)
118+
dmaskCat <- categorical %in% names(abbreviated)
119+
113120
colnames(data)[dmask] <- abbreviated[colnames(data)[dmask]]
121+
categorical[dmaskCat] <- abbreviated[categorical[dmaskCat]]
114122

115123
indicators <- unique(parTable[parTable$op == "=~", "rhs", drop = TRUE])
116124
intTerms <- unique(getIntTermRows(parTable)$rhs)
@@ -132,6 +140,15 @@ modsem_mplus <- function(model.syntax,
132140

133141
} else VARIABLE <- NULL
134142

143+
# Categorical variables
144+
categorical <- intersect(categorical, indicators)
145+
if (length(categorical)) {
146+
VARIABLE <- paste0(
147+
VARIABLE, sprintf("\nCATEGORICAL = %s;",
148+
paste0(categorical, collapse = " "))
149+
)
150+
}
151+
135152
usevariables <- intersect(c(cluster, indicators), colnames(data))
136153

137154
# Estimate model
@@ -274,6 +291,7 @@ parTableToMplusModel <- function(parTable, ignoreConstraints = FALSE) {
274291
stringr::str_remove_all(out, ":")
275292
}
276293

294+
277295
parTableToMplusModelConstraints <- function(parTable) {
278296
constraints <- parTable[parTable$op %in% c(":=", "==", "<", ">"),
279297
, drop = FALSE]
@@ -320,14 +338,15 @@ mplusTableToParTable <- function(coefsTable,
320338
intTerms,
321339
intTermsMplus,
322340
parTable.in = NULL) {
341+
323342
coefsTable <- rename(coefsTable, Label = "label",
324343
se = "std.error", pval = "p.value")
325344
coefsTable$label <- stringr::str_remove_all(coefsTable$label, pattern = " ")
326345

327346
indicatorsCaps <- stringr::str_to_upper(indicators)
328347
patternMeas <-
329348
paste0("(", stringr::str_c(indicatorsCaps, collapse = "|"), ")") |>
330-
paste0("<-(?!>|Intercept)")
349+
paste0("<-(?!>|(Intercept|Thresholds))")
331350
measCoefNames <- grepl(patternMeas, coefsTable$label, perl = TRUE)
332351

333352
# Mplus has lhs/rhs in reversed order for the measurement model,
@@ -336,20 +355,20 @@ mplusTableToParTable <- function(coefsTable,
336355
"<-", i = 1)
337356
measLhs <- stringr::str_split_i(coefsTable$label[measCoefNames],
338357
"<-", i = 2)
339-
measModel <- data.frame(lhs = measLhs, op = "=~", rhs = measRhs) |>
340-
cbind(coefsTable[measCoefNames, ])
358+
measModel <- data.frame0(lhs = measLhs, op = "=~", rhs = measRhs) |>
359+
cbind0(coefsTable[measCoefNames, ])
341360

342361
# Structural Model
343-
measrRemoved <- coefsTable[!measCoefNames, , drop = FALSE]
344-
patternStruct <- "<-(?!>|Intercept)"
345-
structCoefNames <- grepl(patternStruct, measrRemoved$label, perl = TRUE)
362+
sub <- coefsTable[!measCoefNames, , drop = FALSE]
363+
patternStruct <- "<-(?!>|(Intercept|Thresholds))"
364+
structCoefNames <- grepl(patternStruct, sub$label, perl = TRUE)
346365

347-
structLhs <- stringr::str_split_i(measrRemoved$label[structCoefNames],
366+
structLhs <- stringr::str_split_i(sub$label[structCoefNames],
348367
"<-", i = 1)
349-
structRhs <- stringr::str_split_i(measrRemoved$label[structCoefNames],
368+
structRhs <- stringr::str_split_i(sub$label[structCoefNames],
350369
"<-", i = 2)
351-
structModel <- data.frame(lhs = structLhs, op = "~", rhs = structRhs) |>
352-
cbind(measrRemoved[structCoefNames, ])
370+
structModel <- data.frame0(lhs = structLhs, op = "~", rhs = structRhs) |>
371+
cbind0(sub[structCoefNames, ])
353372

354373
maxCharMplus <- maxchar(c(structModel$rhs, structModel$lhs))
355374
for (i in seq_along(intTerms)) {
@@ -361,31 +380,44 @@ mplusTableToParTable <- function(coefsTable,
361380
}
362381

363382
# Variances and Covariances
364-
structMeasrRemoved <- measrRemoved[!structCoefNames, , drop = FALSE]
383+
sub <- sub[!structCoefNames, , drop = FALSE]
365384
patternCovVar <- "<->"
366-
covVarCoefNames <- grepl(patternCovVar, structMeasrRemoved$label, perl = TRUE)
367-
covVarLhs <- stringr::str_split_i(structMeasrRemoved$label[covVarCoefNames],
385+
covVarCoefNames <- grepl(patternCovVar, sub$label, perl = TRUE)
386+
covVarLhs <- stringr::str_split_i(sub$label[covVarCoefNames],
368387
"<->", i = 1)
369-
covVarRhs <- stringr::str_split_i(structMeasrRemoved$label[covVarCoefNames],
388+
covVarRhs <- stringr::str_split_i(sub$label[covVarCoefNames],
370389
"<->", i = 2)
371-
covVarModel <- data.frame(lhs = covVarLhs, op = "~~", rhs = covVarRhs) |>
372-
cbind(structMeasrRemoved[covVarCoefNames, ])
390+
covVarModel <- data.frame0(lhs = covVarLhs, op = "~~", rhs = covVarRhs) |>
391+
cbind0(sub[covVarCoefNames, ])
373392

374393
# Intercepts
375-
covStructMeasrRemoved <- structMeasrRemoved[!covVarCoefNames, , drop = FALSE]
394+
sub <- sub[!covVarCoefNames, , drop = FALSE]
376395
patternIntercept <- "<-Intercept"
377-
interceptNames <- grepl(patternIntercept, covStructMeasrRemoved$label, perl = TRUE)
378-
interceptLhs <- stringr::str_split_i(covStructMeasrRemoved$label[interceptNames],
396+
interceptNames <- grepl(patternIntercept, sub$label, perl = TRUE)
397+
interceptLhs <- stringr::str_split_i(sub$label[interceptNames],
379398
"<-", i = 1)
380-
interceptModel <- data.frame(lhs = interceptLhs, op = "~1", rhs = "") |>
381-
cbind(covStructMeasrRemoved[interceptNames, ])
399+
interceptModel <- data.frame0(lhs = interceptLhs, op = "~1", rhs = "") |>
400+
cbind0(sub[interceptNames, ])
401+
402+
# Thresholds
403+
sub <- sub[!interceptNames, , drop = FALSE]
404+
patternThreshold <- "<-Thresholds"
405+
thresholdNames <- grepl(patternThreshold, sub$label, perl = TRUE)
406+
thresholdLhsPart <- stringr::str_split_i(sub$label[thresholdNames],
407+
"<-", i = 1)
408+
409+
thresholdLhsSplit <- stringr::str_split_fixed(thresholdLhsPart, pattern = "\\$", n = 2)
410+
thresholdLhs <- thresholdLhsSplit[,1L]
411+
thresholdRhs <- paste0("t", thresholdLhsSplit[,2L])
412+
413+
thresholdModel <- data.frame0(lhs = thresholdLhs, op = "|", rhs = thresholdRhs) |>
414+
cbind0(sub[thresholdNames, ])
382415

383416
# Custom / Remaining
384-
intCovStructMeasrRemoved <- covStructMeasrRemoved[!interceptNames, , drop = FALSE]
417+
sub <- sub[!thresholdNames, , drop = FALSE]
385418

386-
if (NROW(intCovStructMeasrRemoved)) {
387-
customModel <- data.frame(lhs = intCovStructMeasrRemoved$label, op = ":=", rhs = "") |>
388-
cbind(intCovStructMeasrRemoved)
419+
if (NROW(sub)) {
420+
customModel <- cbind0(data.frame0(lhs = sub$label, op = ":=", rhs = ""), sub)
389421

390422
if (!is.null(parTable.in)) {
391423
lrCustom <- parTable.in[parTable.in$op == ":=", c("lhs", "rhs"), drop = FALSE]
@@ -401,7 +433,7 @@ mplusTableToParTable <- function(coefsTable,
401433

402434
# Combine
403435
mplusParTable <- rbind(measModel, structModel, covVarModel, interceptModel,
404-
customModel)
436+
thresholdModel, customModel)
405437
mplusParTable [c("lhs", "rhs")] <-
406438
lapplyDf(mplusParTable[c("lhs", "rhs")], function(x)
407439
stringr::str_remove_all(x, " "))
@@ -444,6 +476,7 @@ getOrderedParameterLabelsMplus <- function(parTable, TECH1, intTerms, intTermsMp
444476
if ("X" %in% names(spec)) specX <- spec$X
445477
else specX <- spec
446478

479+
tau <- specX$tau
447480
nu <- specX$nu
448481
alpha <- specX$alpha
449482
lambda <- specX$lambda
@@ -463,6 +496,9 @@ getOrderedParameterLabelsMplus <- function(parTable, TECH1, intTerms, intTermsMp
463496
}
464497

465498
getReg <- function(M, row.lhs = TRUE, op = "~", try.op = NULL) {
499+
if (is.null(M))
500+
return(NULL)
501+
466502
out <- c()
467503

468504
rows <- rownames(M)
@@ -499,7 +535,39 @@ getOrderedParameterLabelsMplus <- function(parTable, TECH1, intTerms, intTermsMp
499535
out
500536
}
501537

538+
getThreshold <- function(T) {
539+
if (is.null(T))
540+
return(NULL)
541+
542+
vars <- colnames(T)
543+
split <- stringr::str_split_fixed(vars, pattern = "\\$", n = 2)
544+
545+
T <- c(T)
546+
out <- c()
547+
548+
for (i in seq_along(T)) {
549+
id <- as.integer(T[i])
550+
551+
if (is.na(id) || id <= 0L)
552+
next
553+
554+
lhs <- split[i, 1]
555+
rhs <- paste0("t", split[i, 2])
556+
557+
label <- parTable[parTable$op == "|" &
558+
parTable$lhs == lhs &
559+
parTable$rhs == rhs, "label"]
560+
561+
out <- setLabel(out = out, label = label, id = id)
562+
}
563+
564+
out
565+
}
566+
502567
getIntercept <- function(T) {
568+
if (is.null(T))
569+
return(NULL)
570+
503571
vars <- colnames(T)
504572
T <- c(T)
505573
out <- c()
@@ -523,6 +591,9 @@ getOrderedParameterLabelsMplus <- function(parTable, TECH1, intTerms, intTermsMp
523591

524592

525593
getCovariance <- function(M, op = "~~") {
594+
if (is.null(M))
595+
return(NULL)
596+
526597
out <- c()
527598

528599
rows <- rownames(M)
@@ -582,6 +653,7 @@ getOrderedParameterLabelsMplus <- function(parTable, TECH1, intTerms, intTermsMp
582653
}
583654

584655
out <- c(
656+
getThreshold(tau),
585657
getIntercept(nu),
586658
getIntercept(alpha),
587659
getReg(lambda, row.lhs = FALSE, op = "=~"),
@@ -595,3 +667,27 @@ getOrderedParameterLabelsMplus <- function(parTable, TECH1, intTerms, intTermsMp
595667
sort(out)
596668
}
597669

670+
671+
any0 <- function(x) {
672+
any(vapply(x, FUN.VALUE = logical(1L), FUN = \(x) length(x) <= 0))
673+
}
674+
675+
676+
data.frame0 <- function(...) {
677+
args <- list(...)
678+
679+
if (any0(args))
680+
return(NULL)
681+
682+
data.frame(...)
683+
}
684+
685+
686+
cbind0 <- function(...) {
687+
args <- list(...)
688+
689+
if (any0(args))
690+
return(NULL)
691+
692+
cbind(...)
693+
}

man/modsem_mplus.Rd

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)