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(" \n CATEGORICAL = %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+
277295parTableToMplusModelConstraints <- 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+ }
0 commit comments