From cd9ea83a591f4c512b68e88e82ef3e7269456025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20=C5=BB=C3=B3=C5=82tak?= Date: Tue, 7 Feb 2023 00:22:55 +0100 Subject: [PATCH] Functions making Mplus syntaxes and corrections to documentation. --- DESCRIPTION | 7 +- NAMESPACE | 6 + NEWS.md | 24 +- R/compute_item_expected_scores.R | 22 +- R/expand_responses.R | 9 +- R/generate_item_parameters.R | 63 +- R/generate_responses.R | 62 +- R/log_normal_pars.R | 4 +- R/make_item.R | 26 +- R/make_test.R | 36 +- R/resp_styles_to_mplus.R | 685 ++++++++++++++++++ R/scoring_matrices.R | 63 +- R/scoring_on_previous_responses.R | 8 +- R/thresholds_and_intercepts.R | 12 +- README.md | 84 ++- man/as.MplusSyntaxElements.Rd | 17 + man/compute_item_expected_scores.Rd | 16 +- man/expand_responses.Rd | 6 +- man/generate_intercepts.Rd | 44 +- man/generate_item_responeses_gpcm.Rd | 8 +- man/generate_item_responses_sml.Rd | 18 +- man/generate_item_responses_sqn.Rd | 20 +- man/generate_slopes.Rd | 19 +- man/generate_test_responses.Rd | 8 +- man/log-normal.Rd | 4 +- man/make_item.Rd | 20 +- man/make_mplus_gpcm_model_syntax.Rd | 98 +++ man/make_mplus_gpcm_nrm_syntax.Rd | 34 + man/make_mplus_gpcm_vmmc_syntax.Rd | 90 +++ man/make_mplus_irtree_model_syntax.Rd | 98 +++ man/make_mplus_irtree_vmmc_syntax.Rd | 76 ++ ...e_mplus_latent_traits_orthogonal_syntax.Rd | 18 + man/make_mplus_structural_model.Rd | 39 + man/make_scoring_matrix_aem.Rd | 36 +- man/make_scoring_matrix_rt.Rd | 4 +- man/make_scoring_matrix_stz.Rd | 4 +- man/make_scoring_matrix_trivial.Rd | 9 +- man/make_test.Rd | 36 +- man/print.MplusSyntaxElements.Rd | 23 + man/score_on_previous_answers.Rd | 8 +- man/thresholds_and_intercepts.Rd | 12 +- tests/testthat/test_careless_inattentive.R | 52 +- tests/testthat/test_scoring_matrices.R | 2 +- tests/testthat/test_sequential_mae5.R | 4 +- tests/testthat/test_sequential_mae6.R | 4 +- tests/testthat/test_simultaneous_aem.R | 6 +- 46 files changed, 1597 insertions(+), 347 deletions(-) create mode 100644 R/resp_styles_to_mplus.R create mode 100644 man/as.MplusSyntaxElements.Rd create mode 100644 man/make_mplus_gpcm_model_syntax.Rd create mode 100644 man/make_mplus_gpcm_nrm_syntax.Rd create mode 100644 man/make_mplus_gpcm_vmmc_syntax.Rd create mode 100644 man/make_mplus_irtree_model_syntax.Rd create mode 100644 man/make_mplus_irtree_vmmc_syntax.Rd create mode 100644 man/make_mplus_latent_traits_orthogonal_syntax.Rd create mode 100644 man/make_mplus_structural_model.Rd create mode 100644 man/print.MplusSyntaxElements.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 2a9b582..6bc6a3b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: rstyles Type: Package Title: Generating Simulated Data Mimicking Response Styles to Survey Questions -Version: 0.6.0 +Version: 0.7.0 Authors@R: c(person("Tomasz", "Zoltak", email = "tomek@zozlak.org", role = c("aut", "cre"), @@ -36,8 +36,9 @@ Suggests: covr, mnormt, mirt, - truncnorm + truncnorm, + MplusAutomation Encoding: UTF-8 Language: en-US -RoxygenNote: 7.1.2 +RoxygenNote: 7.2.3 Config/testthat/edition: 3 diff --git a/NAMESPACE b/NAMESPACE index f5e4653..d32fc80 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,6 +5,7 @@ S3method(compute_item_expected_scores,rstylesTest) S3method(intercepts2thresholds,data.frame) S3method(intercepts2thresholds,default) S3method(intercepts2thresholds,matrix) +S3method(print,MplusSyntaxElements) S3method(thresholds2intercepts,data.frame) S3method(thresholds2intercepts,default) S3method(thresholds2intercepts,matrix) @@ -18,6 +19,8 @@ export(intercepts2thresholds) export(lnorm_mean) export(lnorm_sd) export(make_item) +export(make_mplus_gpcm_model_syntax) +export(make_mplus_irtree_model_syntax) export(make_scoring_matrix_aem) export(make_scoring_matrix_rt) export(make_scoring_matrix_stz) @@ -28,3 +31,6 @@ export(score_on_last_answer_previous) export(score_on_last_answer_straight) export(score_on_previous_answers_bounce) export(thresholds2intercepts) +importFrom(stats,na.omit) +importFrom(stats,setNames) +importFrom(utils,head) diff --git a/NEWS.md b/NEWS.md index 8f0b5af..d056693 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,8 +1,22 @@ +# rstyles 0.7.0 (6.02.2023) + +## New features + +- New functions `make_mplus_irtree_model_syntax()` and `make_mplus_gpcm_model_syntax()` enabling convenient preparing Mplus syntax specifying response-style models. + +## Bug fixes + +- `expand_responses()` deals with data with responses being a *tibble*. + +## Other changes + +- Considering remarks of Plieninger (2020) regarding the interpretation of IRTree models the distinction between *sequential* and *simultaneous* response processes was (almost completely) removed from the package parameter names and descriptions in documentation in favor of labeling them as *IRTree* and *GPCM*. + # rstyles 0.6.0 (23.12.2021) ## New features -- New functions `make_scoring_matrix_rt()` and `make_scoring_matrix_stz()` enabling convienient construction of scoring matrices using *random thresholds* and *sum to zero* approaches. +- New functions `make_scoring_matrix_rt()` and `make_scoring_matrix_stz()` enabling convenient construction of scoring matrices using *random thresholds* and *sum to zero* approaches. # rstyles 0.5.0 (20.12.2021) @@ -10,13 +24,13 @@ - New function `generate_item_expected_scores()` that allows to generate (by numerical integration) expected probabilities of responses (response categories) given an item object or test object and covariance matrix of latent traits. - Dependency from the package *mvtnorm* was added to be able to compute weights for quadrature points. -- New utility functions `thresholds2intercepts()` and `intercepts2thresholds()` allowing conversion between *thresholds* and *intercepts* parametrisations of *simultaneous* GPCM items. +- New utility functions `thresholds2intercepts()` and `intercepts2thresholds()` allowing conversion between *thresholds* and *intercepts* parameterizations of GPCM items. ## Bug fixes - `expand_responses()` do not take into account missing values in the data while performing assertion that all values in the data are members of the set of values defined by rownames of the `scoringMatrix`. -- `generate_intercepts()` correctly transforms parameters generated in a parametrisation involving item difficulty and thresholds relative to his difficulty into parametrisation of intercepts in case of *simultaneous* items (i.e. it cumulatively sums up thresholds and subtracts difficulty instead of adding difficulty to thresholds). - - Documentation was corrected to explain, that in case of *simultaneous* mode arguments define how values of *thresholds* should be generated but that function returns accordingly computed *intercepts*. +- `generate_intercepts()` correctly transforms parameters generated in a parameterization involving item difficulty and thresholds relative to his difficulty into parametrization of intercepts in case of GPCM items (i.e. it cumulatively sums up thresholds and subtracts difficulty instead of adding difficulty to thresholds). + - Documentation was corrected to explain, that in case of GPCM mode arguments define how values of *thresholds* should be generated but that function returns accordingly computed *intercepts*. ## Other changes @@ -32,7 +46,7 @@ - `make_test()` assigns names to the created items by default and provides additional `names` argument if user wants to provide names himself/herself. - `generate_test_responses()` uses items' names (if there are any) to name columns of the returned matrix. -- `generate_test_responses()` converts matrix it returns to numeric one (if only this is possible without loss of information); it also provides additional argument `tryConvertToNumeric` that allows to bring back its former behaviour (i.e. returning a character matrix). +- `generate_test_responses()` converts matrix it returns to numeric one (if only this is possible without loss of information); it also provides additional argument `tryConvertToNumeric` that allows to bring back its former behavior (i.e. returning a character matrix). - `generate_intercepts_sml()`, and consequently `generate_intercepts()` when called with `FUNt` argument, returns intercepts matrix with additional first columns of zeros to make it compatible with the format that uses function `simdata()` from *mirt* package (`generate_test_responses()` was, and still is, able to deal with providing it intercepts either with or without such additional zeros). ## Documentation diff --git a/R/compute_item_expected_scores.R b/R/compute_item_expected_scores.R index 737abb0..a8b37c5 100644 --- a/R/compute_item_expected_scores.R +++ b/R/compute_item_expected_scores.R @@ -3,16 +3,16 @@ #' item object constructed using \code{\link{make_item}} or test (i.e. list of #' items) object constructed using \code{\link{make_test}} and covariance matrix #' of latent traits, assuming multivariate-normal distribution of latent traits. -#' @param x object of class \emph{rstylesItem} or \emph{rstylesTest} -#' @param vcov covariance matrix of latent traits (in line with item's scoring +#' @param x an object of class \emph{rstylesItem} or \emph{rstylesTest} +#' @param vcov a covariance matrix of latent traits (in line with item's scoring #' matrix); if not provided uncorrelated standard normal is used -#' @returns table +#' @return A table #' @seealso \code{\link{make_item}} #' @examples -#' itemGPCM <- make_item(scoringMatrix = make_scoring_matrix_aem(1:5, "simultaneous")[, -4], +#' itemGPCM <- make_item(scoringMatrix = make_scoring_matrix_aem(1:5, "gpcm")[, -4], #' slopes = c(i = 1, m = 2, e = 3), #' intercepts = cumsum(c(0, seq(-0.5, 0.5, length.out = 4))), -#' mode = "simultaneous") +#' mode = "gpcm") #' vcov <- matrix(c( 1, 0.5, -0.5, #' 0.5, 1, -0.25, #' -0.5, -0.25, 1), @@ -23,13 +23,13 @@ #' itemIRTree <- make_item(scoringMatrix = make_scoring_matrix_aem(1:5, "mae"), #' slopes = c(m = 1, a = 1, e = 1), #' intercepts = c(m1 = 0, a1 = 0, e1 = 0), -#' mode = "sequential") +#' mode = "irtree") #' vcovIRTree <- vcov #' colnames(vcovIRTree) <- rownames(vcovIRTree) <- c("a", "m", "e") #' compute_item_expected_scores(itemIRTree) # orthogonal, standard-normal latent traits #' compute_item_expected_scores(itemIRTree, vcovIRTree) #' -#' sM <- make_scoring_matrix_aem(1:5, "simultaneous")[, -4] +#' sM <- make_scoring_matrix_aem(1:5, "gpcm")[, -4] #' test <- make_test(sM, #' generate_slopes(11, sM, c(1, 2, 3)), #' generate_intercepts(11, sM, @@ -39,7 +39,7 @@ #' FUNt = seq, #' argst = list(from = -1.5, to = 1.5, #' length.out = 4)), -#' "simultaneous") +#' "gpcm") #' sapply(compute_item_expected_scores(test, vcov), identity) #' @name compute_item_expected_scores #' @export @@ -100,7 +100,7 @@ compute_item_expected_scores.rstylesItem <- function(x, w <- mvtnorm::dmvnorm(theta, sigma = vcov) w <- w / sum(w) - if (x$mode == "simultaneous") { + if (x$mode == "gpcm") { x$slopes <- x$slopes[sapply(colnames(x$scoringMatrix), match, table = names(x$slopes)), drop = FALSE] probs <- compute_item_expected_scores_gpcm( @@ -108,7 +108,7 @@ compute_item_expected_scores.rstylesItem <- function(x, x$scoringMatrix * rep(x$slopes, each = nrow(x$scoringMatrix)), x$intercepts) - } else if (x$mode == "sequential") { + } else if (x$mode == "irtree") { nodes <- vector(mode = "list", length = ncol(x$scoringMatrix)) names(nodes) <- colnames(x$scoringMatrix) for (i in 1L:length(nodes)) { @@ -119,7 +119,7 @@ compute_item_expected_scores.rstylesItem <- function(x, x$slopes[which(names(x$slopes) == names(nodes)[i])], x$intercepts[grep(paste0("^", names(nodes)[i], "_?[[:digit:]]+"), names(x$intercepts))], - "simultaneous") + "gpcm") nodes[[i]] <- compute_item_expected_scores_gpcm( theta[, names(nodes)[i], drop = FALSE], nodes[[i]]$scoringMatrix * rep(nodes[[i]]$slopes, diff --git a/R/expand_responses.R b/R/expand_responses.R index 25b5e74..572da47 100644 --- a/R/expand_responses.R +++ b/R/expand_responses.R @@ -2,10 +2,10 @@ #' @description Using scoring matrix provided as its second argument function #' \emph{expands} (recodes) responses to each item (column) in a provided matrix #' of responses into a set of variables that may be put into model estimation. -#' @param responses matrix of responses with items in columns and observations +#' @param responses a matrix of responses with items in columns and observations #' in rows -#' @param scoringMatrix matrix describing scoring patterns on each latent trait -#' @return matrix (of integers) +#' @param scoringMatrix a matrix describing scoring patterns on each latent trait +#' @return A matrix (of integers) #' @examples #' sM <- make_scoring_matrix_aem(5, "mae") #' responses <- matrix(sample(1L:5L, 20, replace = TRUE), ncol = 4, @@ -26,7 +26,8 @@ expand_responses <- function(responses, scoringMatrix) { all(setdiff(unique(unlist(sapply(responses, unique))), NA) %in% rownames(scoringMatrix))) - respWide <- matrix(NA, nrow = nrow(responses), + responses <- as.matrix(responses) + respWide <- matrix(NA_real_, nrow = nrow(responses), ncol = ncol(scoringMatrix) * ncol(responses), dimnames = list(NULL, paste0(rep(colnames(scoringMatrix), diff --git a/R/generate_item_parameters.R b/R/generate_item_parameters.R index ac70e69..faa8b7b 100644 --- a/R/generate_item_parameters.R +++ b/R/generate_item_parameters.R @@ -1,24 +1,23 @@ #' @title Generating parameters of items - slopes (discrimination) #' @description Function generates a matrix of items' slope (discrimination) #' parameters. -#' @param nItems number of items for which slopes will be generated -#' @param scoringMatrix \emph{scoring matrix} that will be used for the +#' @param nItems the number of items for which slopes will be generated +#' @param scoringMatrix a \emph{scoring matrix} that will be used for the #' generated items, specifically generated with #' \code{\link{make_scoring_matrix_aem}} or #' \code{\link{make_scoring_matrix_trivial}} #' @param ... arguments that will be passed to \code{FUN}; should rather be #' named and in typical applications should be numeric vectors of the length of #' one or of the number of columns of the \code{scoringMatrix} -#' @param FUN function that will be used to generate slopes, typically -#' @param nReversed number of \emph{reversed} (\emph{revers-keyed}) items, i.e. -#' items that -#' @param reverseTraits character vector containing names of traits for which +#' @param FUN a function that will be used to generate slopes, typically +#' @param nReversed the number of \emph{reversed} (\emph{revers-keyed}) items +#' @param reverseTraits a character vector containing names of traits for which #' items should be \emph{reversed} (if \code{nReversed > 0}); default value is #' "i", that is the name assigned to the trait that is assumed to describe #' \emph{the trait that scale is supposed to measure} (i.e. trait that is not #' related with response styles) by \code{\link{make_scoring_matrix_aem}} when -#' called with argument \code{sequence = "simultaneous"} -#' @param reverseIndep logical value indicating whether sampling items that are +#' called with argument \code{sequence = "gpcm"} +#' @param reverseIndep a logical value indicating whether sampling items that are #' \emph{reversed} should be performed independently for each trait (given by #' \code{reverseTraits}) or for all the traits simultaneously (this argument #' is only considered if \code{nReversed > 0} and there is more than one trait @@ -50,7 +49,7 @@ #' way to change this (but one may still use \code{\link{cbind}} to collapse #' results of several separate \code{generate_slopes} calls to achieve this #' goal). -#' @returns Matrix of \code{nItems} rows and number of columns equal to the +#' @return A matrix of \code{nItems} rows and number of columns equal to the #' length of vectors provided by \code{...}. #' @seealso \code{\link{generate_intercepts}}, \code{\link{make_test}} #' @examples @@ -85,7 +84,7 @@ #' # 10 items with slopes generated from a normal distributions with mean of 1 #' # and standard deviation of 0.2 with half of the items "reverse-keyed" on #' # the trait "i" -#' sM <- make_scoring_matrix_aem(5, sequence = "simultaneous") +#' sM <- make_scoring_matrix_aem(5, sequence = "gpcm") #' generate_slopes(10, sM, FUN = rnorm, mean = 1, sd = 0.2, #' nReversed = 5, reverseTraits = "i") #' @export @@ -177,25 +176,25 @@ generate_slopes <- function(nItems, scoringMatrix, ..., FUN = identity, #' @title Generating parameters of items - intercepts (thresholds/difficulties) #' @description Function generates a matrix of items' intercept #' (thresholds/difficulties) parameters. -#' @param nItems number of items for which intercepts will be generated -#' @param scoringMatrix \emph{scoring matrix} that will be used for the +#' @param nItems the number of items for which intercepts will be generated +#' @param scoringMatrix a \emph{scoring matrix} that will be used for the #' generated items, specifically generated with #' \code{\link{make_scoring_matrix_aem}} or #' \code{\link{make_scoring_matrix_trivial}} -#' @param FUNd function that will be used to generate item difficulties,h +#' @param FUNd a function that will be used to generate item difficulties,h #' typically \code{\link[stats]{Uniform}}, \code{\link[stats]{Normal}} or #' \code{\link[truncnorm]{rtruncnorm}} -#' @param argsd list of arguments to be passed to \code{FUNd} -#' @param FUNt optionally function that will be used to generate item +#' @param argsd a list of arguments to be passed to \code{FUNd} +#' @param FUNt optionally a function that will be used to generate item #' thresholds (i.e. difficulties of categories relative to difficulty of the -#' whole item), assuming \emph{simultaneous} item responding process; +#' whole item), assuming \emph{gpcm} item responding process; #' typically \code{\link[stats]{Uniform}}, \code{\link[stats]{Normal}} or #' \code{\link[truncnorm]{rtruncnorm}}; if not set item responding process is -#' assumed to be a \emph{sequential} one; -#' @param argst optionally list of arguments to be passed to \code{FUNt} -#' @details \strong{Assuming \emph{sequential} response process:} +#' assumed to be a \emph{irtree} one; +#' @param argst optionally a list of arguments to be passed to \code{FUNt} +#' @details \strong{Assuming \emph{irtree} response process:} #' -#' Assuming \emph{sequential} response process test item must be characterized +#' Assuming \emph{irtree} response process test item must be characterized #' by a set of intercept parameters describing individual thresholds of binary #' \emph{pseudo-items} modeling consecutive decisions in the assumed sequence #' of responding process. In such a case: @@ -212,9 +211,9 @@ generate_slopes <- function(nItems, scoringMatrix, ..., FUN = identity, #' \emph{pseudo-items} across all the items;} #' \item{arguments \code{FUNt} and \code{argst} are not used.} #' } -#' \strong{Assuming \emph{simultaneous} response process:} +#' \strong{Assuming \emph{GPCM} response process:} #' -#' Assuming \emph{simultaneous} response process test item must be characterized +#' Assuming \emph{GPCM} response process test item must be characterized #' by a set of \emph{intercept} parameters describing relative frequency of #' specific categories of the response scale. However, it is convenient to #' define model parameters in another parameterisation, in which item @@ -250,7 +249,7 @@ generate_slopes <- function(nItems, scoringMatrix, ..., FUN = identity, #' Returned \emph{intercepts} are computed by summing general item #' \emph{difficulty} and values of the relative thresholds generated for #' a given item and then computing cumulative sum of this vector. -#' @returns Matrix of \code{nItems} rows and number of columns equal to the +#' @return A matrix of \code{nItems} rows and number of columns equal to the #' number of intercepts. \strong{Be aware that these are \emph{intercepts} and #' not \emph{thresholds} what are returned} (and intercept for a category #' \emph{g} is a sum of \emph{thresholds} from the first category up to the @@ -258,17 +257,17 @@ generate_slopes <- function(nItems, scoringMatrix, ..., FUN = identity, #' @seealso \code{\link{generate_slopes}}, \code{\link{make_test}}, #' \code{\link{thresholds2intercepts}}, \code{\link{intercepts2thresholds}} #' @examples -#' # 5 items with 5-point response scale assuming "sequential" item response +#' # 5 items with 5-point response scale assuming IRTree item response #' # process with "pseudo-items" intercepts sampled from a uniform distribution #' # with limits +-1.5 #' sM <- make_scoring_matrix_aem(5, sequence = "mae") #' generate_intercepts(5, sM, runif, list(min = -1.5, max = 1.5)) -#' # 10 items with 5-point response scale assuming "sequential" item response +#' # 10 items with 5-point response scale assuming IRTree item response #' # process with "pseudo-items" intercepts sampled from a normal distribution #' # with the mean of 0 and the standard deviation of 1.5 #' sM <- make_scoring_matrix_aem(5, sequence = "mae") #' generate_intercepts(5, sM, rnorm, list(mean = 0, sd = 1)) -#' # 10 items with 5-point response scale assuming "sequential" item response +#' # 10 items with 5-point response scale assuming IRTree item response #' # process with "pseudo-items" intercepts sampled from a uniform distribution #' # with limits set to: #' # trait 'm' (i.e. the first column in the scoring matrix): from -3 to -1 @@ -279,32 +278,32 @@ generate_slopes <- function(nItems, scoringMatrix, ..., FUN = identity, #' list(min = c(-3, -1, 1), #' max = c(-1, 1, 3))) #' -#' # 10 items with 6-point response scale assuming "simultaneous" item response +#' # 10 items with 6-point response scale assuming GPCM item response #' # process with items difficulties sampled from a normal distribution with #' # the mean of 0 and the standard deviation of 1.5 and thresholds relative #' # to the items difficulties sampled from a uniform distribution with #' # the limits of +-2 -#' sM <- make_scoring_matrix_aem(6, sequence = "simultaneous") +#' sM <- make_scoring_matrix_aem(6, sequence = "gpcm") #' generate_intercepts(10, sM, #' FUNd = rnorm, argsd = list(mean = 0, sd = 1.5), #' FUNt = runif, argst = list(min = -2, max = 2)) -#' # 5 items with 6-point response scale assuming "simultaneous" item response +#' # 5 items with 6-point response scale assuming GPCM item response #' # process with items difficulties sampled from a uniform distribution with #' # the limits of +-2 and thresholds relative to the items difficulties sampled #' # from a normal distribution with the mean of 0 and the standard deviation #' # defined individually for each item #' # the limits of +-2 -#' sM <- make_scoring_matrix_aem(6, sequence = "simultaneous") +#' sM <- make_scoring_matrix_aem(6, sequence = "gpcm") #' generate_intercepts(5, sM, #' FUNd = runif, argsd = list(min = -2, max = 2), #' FUNt = rnorm, argst = list(mean = 0, #' sd = c(1, 1.2, 1.4, 1.6, 1.9))) -#' # 20 items with 5-point response scale assuming "simultaneous" item response +#' # 20 items with 5-point response scale assuming GPCM item response #' # process with items difficulties sampled from a uniform distribution with #' # the limits of +-2 and thresholds relative to the items difficulties #' # generated deterministically as a sequence of 4 regularly spaced values #' # from 0.9 to -0.9 -#' sM <- make_scoring_matrix_aem(5, sequence = "simultaneous") +#' sM <- make_scoring_matrix_aem(5, sequence = "gpcm") #' generate_intercepts(20, sM, #' FUNd = runif, argsd = list(min = -2, max = 2), #' FUNt = seq, argst = list(from = 0.9, diff --git a/R/generate_responses.R b/R/generate_responses.R index 0f47c0b..9b55e4b 100644 --- a/R/generate_responses.R +++ b/R/generate_responses.R @@ -1,13 +1,13 @@ #' @title Generate simulated data #' @description Generates simulated responses to a test given informations #' about items values of latent traits. -#' @param theta matrix (or data frame) of already generated latent traits' +#' @param theta a matrix (or data frame) of already generated latent traits' #' values -#' @param items list with test items' specification -#' @param performAssertions logical value indicating whether function should +#' @param items a list with test items' specification +#' @param performAssertions a logical value indicating whether function should #' perform assertions of the other arguments (\code{TRUE} by default, may be #' changed to \code{FALSE} for a little performance gain) -#' @param tryConvertToNumeric logical value indicating whether function should +#' @param tryConvertToNumeric a logical value indicating whether function should #' try to convert generated responses to numeric values (technically responses #' are get from rownames of the items' scoring matrices that are character #' vectors) @@ -63,7 +63,7 @@ generate_test_responses <- function(theta, items, performAssertions = TRUE, whichRows <- colSums(t(responses[, 1L:(i - 1L), drop = FALSE]) == responses[j, 1L:(i - 1L)]) == (i - 1L) - if (items[[i]]$mode == 'sequential') { + if (items[[i]]$mode == 'irtree') { responses[whichRows, i] <- generate_item_responses_sqn(theta[whichRows, , drop = FALSE], scoringMatrixTemp, @@ -71,7 +71,7 @@ generate_test_responses <- function(theta, items, performAssertions = TRUE, items[[i]]$intercepts, items[[i]]$editResponse, TRUE) - } else {#simultaneous + } else {#gpcm responses[whichRows, i] <- generate_item_responses_sml(theta[whichRows, , drop = FALSE], scoringMatrixTemp, @@ -80,12 +80,12 @@ generate_test_responses <- function(theta, items, performAssertions = TRUE, } } } else {# if there is no scoring on previous responses, there is no need for looping through combinations of previuos responses - if (items[[i]]$mode == 'sequential') { + if (items[[i]]$mode == 'irtree') { responses[, i] <- generate_item_responses_sqn(theta, scoringMatrix, items[[i]]$slopes, items[[i]]$intercepts, items[[i]]$editResponse) - } else {#simultaneous + } else {#gpcm responses[, i] <- generate_item_responses_sml(theta, scoringMatrix, items[[i]]$slopes, items[[i]]$intercepts) @@ -102,9 +102,9 @@ generate_test_responses <- function(theta, items, performAssertions = TRUE, } return(responses) } -#' @title Internals: simulating responding to item in a 'sequential' (IRTree) way -#' @description Function generates responses 'sequentially`, i.e. using IRTree -#' approach. It goes through consecutive columns of a scoring matrix, +#' @title Internals: simulating responding to item in the IRTree way +#' @description Function generates responses using IRTree approach, i.e. +#' 'sequentially`. It goes through consecutive columns of a scoring matrix, #' calling \code{\link{generate_item_responeses_gpcm}} to get responses at #' a given node of a tree and the recursively calls itself on subsets of #' observations with a given response with reduced scoring matrix. @@ -112,17 +112,17 @@ generate_test_responses <- function(theta, items, performAssertions = TRUE, #' Because function internally relies on calling #' \code{\link{generate_item_responeses_gpcm}}, no normal ogive models can be #' used (this may be changed in the future versions). -#' @param theta matrix of latent traits' values -#' @param scoringMatrix matrix describing scoring patterns on each latent trait -#' @param slopes vector of slope parameters of each trait -#' @param intercepts intercept parameters -#' @param editResponse optional function returning scoring matrix that should be -#' used instead that provided by \code{scoringMatrix}; this should be function +#' @param theta a matrix of latent traits' values +#' @param scoringMatrix a matrix describing scoring patterns on each latent trait +#' @param slopes a vector of slope parameters for each trait +#' @param intercepts a vector of intercept parameters +#' @param editResponse an optional function returning scoring matrix that should +#' be used instead that provided by \code{scoringMatrix}; this should be function #' accepting two arguments: \code{response} - generated response (by the model #' described with the first column of the \code{scoringMatrix}) that is supposed #' to be \emph{edited} and \code{scoringMatrix} - current scoring matrix (to be #' replaced) -#' @param decidingOnPreviousResponse logical value indicating whether first +#' @param decidingOnPreviousResponse a logical value indicating whether first #' column of provided scoring matrix describes making decision whether to #' respond on the basis of responses to previous items or not (in this first #' case \emph{negative} choice shouldn't reduce number of rows in a response @@ -202,17 +202,17 @@ generate_item_responses_sqn <- function(theta, scoringMatrix, slopes, } return(responses) } -#' @title Internals: simulating responding to item in a 'simultaneous' way -#' @description Function generates responses 'simultaneously` with a whole -#' scoring matrix used at once. Only (G)PCM approach is suitable in such a case, -#' because with complicated scoring matrices there is no guarantee that +#' @title Internals: simulating responding to item in the GPCM way +#' @description Function generates responses in the GPCM way with a whole +#' scoring matrix used at once. Only (G)PCM/NRM approach is suitable in such +#' a case, because with complicated scoring matrices there is no guarantee that #' probabilities of responses are increasing along with order of responses #' (rows) in a scoring matrix. Consequently, no normal ogive models can be used. -#' @param theta matrix of latent traits' values -#' @param scoringMatrix matrix describing scoring patterns on each latent trait -#' @param slopes vector of slope parameters of each trait -#' @param intercepts intercept parameters -#' @return vector of responses on item +#' @param theta a matrix of latent traits' values +#' @param scoringMatrix a matrix describing scoring patterns on each latent trait +#' @param slopes a vector of slope parameters of each trait +#' @param intercepts a vector of intercept parameters +#' @return a vector of responses on item #' @seealso \code{link{generate_test_responses}}, #' \code{\link{generate_item_responses_sqn}}, #' \code{\link{generate_item_responeses_gpcm}} @@ -236,11 +236,11 @@ generate_item_responses_sml <- function(theta, scoringMatrix, slopes, #' \code{\link{generate_item_responses_sml}} that are used to match theirs #' arguments \code{theta}, \code{scoringMatrix} and \code{slopes} before #' passing them to \code{generate_item_responeses_gpcm}). -#' @param theta matrix of latent traits' values -#' @param weightsMatrix matrix of discrimination parameters (being +#' @param theta a matrix of latent traits' values +#' @param weightsMatrix a matrix of slope parameters (being #' multiplication of a design matrix by discriminations of factors) -#' @param intercepts intercept parameters -#' @return vector of responses on item +#' @param intercepts a vector of intercept parameters +#' @return a vector of responses on item generate_item_responeses_gpcm <- function(theta, weightsMatrix, intercepts) { probs <- matrix(NA_real_, nrow = nrow(theta), ncol = nrow(weightsMatrix), diff --git a/R/log_normal_pars.R b/R/log_normal_pars.R index 1a7d2cc..931ad26 100644 --- a/R/log_normal_pars.R +++ b/R/log_normal_pars.R @@ -4,12 +4,12 @@ #' exponentiated scale (\code{lnorm_mean}, \code{lnorm_sd}) or values of #' parameters on the log scale that will result in given values of parameters #' on the exponentiated scale (\code{find_pars_lnorm}). -#' @param meanlog mean (expected value)of the distribution on the log scale +#' @param meanlog mean (expected value) of the distribution on the log scale #' @param sdlog standard deviation of the distribution on the log scale #' @param m mean (expected value) of the distribution on the exponential scale #' @param sd standard deviation of the distribution on the exponential scale #' @return \code{lnorm_mean()} and \code{lnorm_sd()} return numeric vectors; -#' \code{find_pars_lnorm()} returns named numeric vector with elements named +#' \code{find_pars_lnorm()} returns a named numeric vector with elements named #' \emph{meanlog} describing means and elements named \emph{sdlog} describing #' standard deviations. #' @examples diff --git a/R/make_item.R b/R/make_item.R index 3c60837..fee6724 100644 --- a/R/make_item.R +++ b/R/make_item.R @@ -1,36 +1,36 @@ #' @title Creating object representing an item #' @description Function mostly performs checks whether provided arguments are #' reasonable and match each other. -#' @param scoringMatrix matrix describing how responses (described in rownames +#' @param scoringMatrix a matrix describing how responses (described in rownames #' of the matrix) map on \emph{scores} of latent traits (described in columns of #' the matrix) -#' @param slopes \strong{named} numeric vector of slope (discrimination) +#' @param slopes a \strong{named} numeric vector of slope (discrimination) #' parameters with names describing latent variables matching each slope #' (must contain at least all the names occurring in column \emph{names} of the #' \code{scoringMatrix} but may also contain additional slopes matching latent #' traits scoring patterns that will be returned by functions provided with #' arguments \code{scoringOnPreviousResponses} or \code{editResponse}) -#' @param intercepts numeric vector of intercept parameters (must be shorter +#' @param intercepts a numeric vector of intercept parameters (must be shorter #' of one than number of rows in the \\code{scoringMatrix} or the same length #' but with the first element being 0) -#' @param mode a way the item should be answered - see -#' \code{\link{generate_item_responses_sqn}}, -#' \code{\link{generate_item_responses_sml}} -#' @param scoringOnPreviousResponses optional function returning a column vector +#' @param mode the way the item should be answered - see +#' \code{\link{generate_item_responses_sqn}} for "irtree" and +#' \code{\link{generate_item_responses_sml}} for "gpcm" +#' @param scoringOnPreviousResponses an optional function returning a column vector #' that will be put before first column of the \code{scoringMatrix} -#' @param editResponse only if \code{mode='sequential'}: optional function +#' @param editResponse only if \code{mode='irtree'}: an optional function #' returning scoring matrix that should replace that provided by #' \code{scoringMatrix} after \emph{response is made} at the first \emph{node}; #' this should be function accepting two arguments: \code{response} - generated #' response (by the model described with the first column of the #' \code{scoringMatrix}) that is supposed to be \emph{edited} and #' \code{scoringMatrix} - current scoring matrix (to be replaced) -#' @returns Object of class \emph{rstylesItem} representing an item. List of +#' @return An object of class \emph{rstylesItem} representing an item. List of #' such objects is passed as a test specification to #' \code{\link{generate_test_responses}}. #' @export make_item <- function(scoringMatrix, slopes, intercepts, - mode = c('sequential', 'simultaneous'), + mode = c('irtree', 'gpcm'), scoringOnPreviousResponses = NULL, editResponse = NULL) { mode <- match.arg(mode) stopifnot("Argument `scoringMatrix` must be a numeric matrix." = @@ -43,8 +43,8 @@ make_item <- function(scoringMatrix, slopes, intercepts, nrow(scoringMatrix) > 1L, "Argument `scoringMatrix` must have unique rownames." = length(unique(rownames(scoringMatrix))) == nrow(scoringMatrix), - "With argument `mode` set to 'simultaneous` argument `scoringMatrix` can't contain NAs." = - mode == 'sequential' || !anyNA(scoringMatrix), + "With argument `mode` set to 'gpcm` argument `scoringMatrix` can't contain NAs." = + mode == 'irtree' || !anyNA(scoringMatrix), "Argument `scoringMatrix` can't contain NAs in the first column." = !anyNA(scoringMatrix[, 1L]), "Argument 'scoringMatrix` can't contain duplicated rows." = @@ -68,7 +68,7 @@ make_item <- function(scoringMatrix, slopes, intercepts, is.vector(intercepts), "Argument `intercepts` can't contain NAs." = !anyNA(intercepts)) - if (mode == "sequential") { + if (mode == "irtree") { for (i in 1L:ncol(scoringMatrix)) { if (length(setdiff(unique(scoringMatrix[, i]), NA_integer_)) != (length(grep(paste0("^", colnames(scoringMatrix)[i], diff --git a/R/make_test.R b/R/make_test.R index 7276128..795e3c1 100644 --- a/R/make_test.R +++ b/R/make_test.R @@ -2,26 +2,26 @@ #' @description Function makes a list object representing test items given #' matrices of slopes and intercepts/thresholds parameters. Result may be used #' within a call to \code{\link{generate_test_responses}}. -#' @param scoringMatrix \emph{scoring matrix} that should be used for items, +#' @param scoringMatrix a \emph{scoring matrix} that should be used for items, #' especially one generated with \code{\link{make_scoring_matrix_aem}} -#' @param slopes matrix of slope parameters (items in rows, traits in cols), +#' @param slopes a matrix of slope parameters (items in rows, traits in cols), #' especially generated with \code{\link{generate_slopes}} -#' @param intercepts matrix of intercept parameters (items in rows, +#' @param intercepts a matrix of intercept parameters (items in rows, #' intercepts/thresholds in cols), especially generated with #' \code{\link{generate_intercepts}} -#' @param mode a way the item should be answered - see +#' @param mode the way the item should be answered - see #' \code{\link{generate_item_responses_sqn}}, #' \code{\link{generate_item_responses_sml}} -#' @param scoringOnPreviousResponses optional function returning a column vector -#' that will be put before first column of the \code{scoringMatrix} -#' @param editResponse only if \code{mode='sequential'}: optional function +#' @param scoringOnPreviousResponses an optional function returning a column +#' vector that will be put before first column of the \code{scoringMatrix} +#' @param editResponse only if \code{mode='irtree'}: an optional function #' returning scoring matrix that should replace that provided by #' \code{scoringMatrix} after \emph{response is made} at the first \emph{node}; #' this should be function accepting two arguments: \code{response} - generated #' response (by the model described with the first column of the #' \code{scoringMatrix}) that is supposed to be \emph{edited} and #' \code{scoringMatrix} - current scoring matrix (to be replaced) -#' @param names optional character vector providing names of the items (by +#' @param names an optional character vector providing names of the items (by #' default names will be created as concatenation of the letter "i" - like #' "item" - and consecutive integers) #' @details Function is actually a simple wrapper around \code{\link{make_item}} @@ -30,11 +30,11 @@ #' \strong{Column names of the \code{intercepts} matrix:} #' #' \itemize{ -#' \item{If \code{mode = "simultaneous"} names should be of the form: +#' \item{If \code{mode = "gpcm"} names should be of the form: #' \emph{dN} where \emph{d} stands for itself and \emph{N} are #' consecutive integers from 1 to one less than the number of rows of #' the the \emph{scoring matrix}.} -#' \item{If \code{mode = "sequential"} names should be of the form: +#' \item{If \code{mode = "irtree"} names should be of the form: #' \emph{traitN} where \emph{trait} are names of traits - the same as #' those in columns of the \emph{scoring matrix} - and \emph{N} are #' integers describing consecutive thresholds of a \emph{pseudo-item} @@ -44,11 +44,11 @@ #' by the number of columns less by one than the number of possible #' responses, with \emph{N} being consecutive integers.} #' } -#' @returns List of objects of the \emph{rstylesItem} class. +#' @return A list of objects of the \emph{rstylesItem} class. #' @examples #' ################################################################################ #' # responses to 10 items using 5-point Likert scale -#' # with respect to the Bockenholt's IRTree (i.e. "sequential") "MAE" model +#' # with respect to the Bockenholt's IRTree "MAE" model #' # 1) make scoring matrix #' sM <- make_scoring_matrix_aem(5, "mae") #' # 2) generate items' slopes: @@ -68,14 +68,14 @@ #' list(min = -1.5, #' max = 1.5)) #' # 4) call `make_test()` -#' # (for IRTree mode must be set to "sequential") -#' test <- make_test(sM, slopes, intercepts, "sequential") +#' # (for IRTree mode must be set accordingly) +#' test <- make_test(sM, slopes, intercepts, "irtree") #' #' ################################################################################ #' # responses to 20 items using 5-point Likert scale -#' # with respect to the Plieninger's "simultaneous" (partialy-compensatory) model +#' # with respect to the Plieninger's GPCM (partialy-compensatory) model #' # 1) make scoring matrix -#' sM <- make_scoring_matrix_aem(5, "simultaneous") +#' sM <- make_scoring_matrix_aem(5, "gpcm") #' # 2) generate items' slopes: #' # slopes on the 'middle", "extreme" and "acquiescence" latent traits #' # set to 1 for all items and slopes on the "intensity" latent trait generated @@ -93,10 +93,10 @@ #' argsd = list(mean = 0, sd = 1.5), #' argst = list(min = -1, max = 1)) #' # 4) call `make_test()` -#' test <- make_test(sM, slopes, intercepts, "simultaneous") +#' test <- make_test(sM, slopes, intercepts, "gpcm") #' @export make_test <- function(scoringMatrix, slopes, intercepts, - mode = c('sequential', 'simultaneous'), + mode = c('irtree', 'gpcm'), scoringOnPreviousResponses = NULL, editResponse = NULL, names = paste0("i", 1:nrow(slopes))) { # make_item() performs detailed assertions, so here there are only the basic ones diff --git a/R/resp_styles_to_mplus.R b/R/resp_styles_to_mplus.R new file mode 100644 index 0000000..71741e7 --- /dev/null +++ b/R/resp_styles_to_mplus.R @@ -0,0 +1,685 @@ +#' @title Preparing Mplus code to estimate RS models +#' @description Prepares components of Mplus model description syntax for +#' IRTree model. +#' @param data a data frame +#' @inheritParams make_mplus_gpcm_vmmc_syntax +#' @param savedata optionally a string with a name of the file in which factor +#' scores should be saved +#' @param analysis a list with elements \code{ESTIMATOR}, \code{ALGORITHM}, +#' \code{INTEGRATION} and \code{PROCESSORS} containing Mplus \emph{ANALYSIS} +#' options (provided as strings) +#' @param title string with a title for the analysis +#' @details For the description of model specification see \emph{Details} +#' section in \code{\link{make_mplus_irtree_vmmc_syntax}} +#' @section Limitations: +#' At the moment there is no possibility to prepare models with many +#' no-RS latent traits loading different sets of items. +#' @return A list with elements named \code{TITLE}, \code{VARIABLE}, +#' \code{ANALYSIS}, \code{MODEL}, \code{MODELCONSTRAINT}, \code{SAVEDATA}, +#' \code{rdata}, \code{usevariables} that can be used as arguments to the +#' \code{mplusObject} function from the package \emph{MplusAutomation} using +#' \code{\link{do.call}} +#' @importFrom stats na.omit +#' @export +make_mplus_irtree_model_syntax <- function(data, items, scoringMatrix, + observedExogenous = vector(mode = "character", length = 0L), + observedDependent = vector(mode = "character", length = 0L), + fixSlopes = vector(mode = "character", length = 0L), + reverseCoded = vector(mode = "list", length = 0L), + orthogonal = vector(mode = "character", length = 0L), + weight = NA_character_, + savedata = NA_character_, + analysis = list(ESTIMATOR = "MLR", + ALGORITHM = "INTEGRATION", + INTEGRATION = "STANDARD", + PROCESSORS = "4"), + title = "Some GPCM model with custom scoring matrix") { + stopifnot(is.data.frame(data), + is.character(items), !anyNA(items), length(items) > 0L, + all(!duplicated(items)), all(items %in% names(data)), + is.matrix(scoringMatrix), is.numeric(scoringMatrix), + all(sapply(data[, items, drop = FALSE], + function(x, values) {all(x %in% values)}, + values = c(rownames(scoringMatrix), NA_character_))), + is.character(savedata), length(savedata) == 1L, + is.vector(analysis), + all(c("ESTIMATOR", "ALGORITHM", + "INTEGRATION", "PROCESSORS") %in% names(analysis)), + is.character(title), length(title) == 1L, nchar(title) < 78L) + if (is.character(observedExogenous)) { + stopifnot(all(observedExogenous %in% names(data))) + } else { + stopifnot(is.matrix(observedExogenous), + all(rownames(observedExogenous) %in% names(data))) + } + if (is.character(observedDependent)) { + stopifnot(all(observedDependent %in% names(data))) + } else { + stopifnot(is.matrix(observedDependent), + all(rownames(observedDependent) %in% names(data))) + } + dataIRTree <- expand_responses(responses = data[, items], + scoringMatrix = scoringMatrix) + onlyNAs <- apply(is.na(dataIRTree), 2L, all) + if (any(onlyNAs)) warning("There are pseudoitems with only NAs in the data: '", + paste(colnames(dataIRTree)[onlyNAs], collapse = "', '"), + "'. They will be excluded from the modeling.") + onlyOneValue <- apply(dataIRTree, 2L, + function(x) {return(length(unique(na.omit(x))))}) < 2L + if (any(onlyOneValue)) warning("There are pseudoitems that have only one value in the data: '", + paste(colnames(dataIRTree)[onlyNAs], collapse = "', '"), + "'. They will be excluded from the modeling.") + dataIRTree <- dataIRTree[, !onlyNAs & !onlyOneValue, drop = FALSE] + items <- colnames(dataIRTree) + if (length(reverseCoded) > 0L) { + reverseCoded <- mapply(function(x, nm) {paste0(rep(nm, length(x)), "_", x)}, + reverseCoded, names(reverseCoded), + SIMPLIFY = FALSE) + } + data <- data.frame(dataIRTree, data[, na.omit(c(observedExogenous, + observedDependent, weight)), + drop = FALSE]) + syntax <- make_mplus_irtree_vmmc_syntax(items = items, + scoringMatrix = scoringMatrix, + observedExogenous = observedExogenous, + observedDependent = observedDependent, + fixSlopes = fixSlopes, + reverseCoded = reverseCoded, + orthogonal = orthogonal, + weight = weight) + analysis <- paste(mapply( + function(nm, val) { + return(paste0(nm, " IS ", val, ";")) + }, + names(analysis), analysis, SIMPLIFY = TRUE)) + if (savedata %in% c(NA_character_, "")) { + savedata <- NULL + } else { + savedata <- paste0("FILE IS ", savedata, ";\n", + "SAVE IS FSCORES;") + } + return(as.MplusSyntaxElements(list(TITLE = title, + VARIABLE = syntax$VARIABLE, + ANALYSIS = analysis, + MODEL = syntax$MODEL, + MODELCONSTRAINT = syntax$MODELCONSTRAINT, + SAVEDATA = savedata, + rdata = as.data.frame(data), + usevariables = colnames(data)))) +} +#' @title Preparing Mplus code to estimate RS models +#' @description Prepares components of Mplus model description syntax for +#' IRTree model. +#' @param items a character vector of item names +#' @param scoringMatrix a matrix describing how responses (described in rownames +#' of the matrix) map on \emph{scores} of latent traits (described in columns of +#' the matrix) +#' @inheritParams make_mplus_structural_model +#' @param fixSlopes optionally a character vector of latent trait names +#' for which item slopes parameters should be fixed across items +#' (these names need to occur in column names of \code{scoringMatrix}) +#' @param reverseCoded optionally a named list of character vectors with names +#' of list elements specifying latent trait name and elements giving names of +#' items that are \emph{reverse coded} with respect to this latent trait; +#' please note, that these don't need to be given if slopes of the no-RS +#' trait(s) are not fixed across items +#' @param orthogonal optionally a character vector of latent trait names +#' indicating which latent traits should be specified as orthogonal to each +#' other (all the mentioned latent traits will be specified as orthogonal to each +#' other and all the other latent traits) +#' @param weight optionally a string with a name of the variable storing weights +#' @details Models are identified by fixing variances of the the latent +#' variables \strong{that are not mentioned in \code{fixSlopes}} to 1 and +#' by fixing \emph{slope} parameters to 1 (or -1 in a case of reverse coded +#' items) and freeing latent trait variances that are mentioned in +#' \code{fixSlopes}. +#' @return A list of strings with elements named \code{VARIABLE}, \code{MODEL} +#' and \code{MODELCONSTRAINT} (the last one can be \code{NULL}) +make_mplus_irtree_vmmc_syntax <- function(items, scoringMatrix, + observedExogenous = vector(mode = "character", length = 0L), + observedDependent = vector(mode = "character", length = 0L), + fixSlopes = vector(mode = "character", length = 0L), + reverseCoded = vector(mode = "list", length = 0L), + orthogonal = vector(mode = "character", length = 0L), + weight = NA_character_) { + stopifnot(is.character(items), !anyNA(items), length(items) > 0L, + all(!duplicated(items)), + is.matrix(scoringMatrix), is.numeric(scoringMatrix), + is.character(fixSlopes), all(fixSlopes %in% colnames(scoringMatrix)), + is.list(reverseCoded), all(names(reverseCoded) %in% colnames(scoringMatrix)), + all(!duplicated(names(reverseCoded))), all(unlist(reverseCoded) %in% items), + is.character(orthogonal), all(orthogonal %in% colnames(scoringMatrix)), + is.character(weight), length(weight) == 1L) + if (length(reverseCoded) > 0L & length(fixSlopes) == 0L) { + message("With no slopes being fixed argument 'reverseCoded' won't affect the model specification.") + } + modelStructural <- make_mplus_structural_model(latent = colnames(scoringMatrix), + observedExogenous = observedExogenous, + observedDependent = observedDependent) + observedExogenous <- attributes(modelStructural)$observedExogenous + observedDependent <- attributes(modelStructural)$observedDependent + model <- constraints <- vector(mode = "character", length = 0L) + + for (i in 1L:ncol(scoringMatrix)) { + traitItems <- grep(paste0("^", colnames(scoringMatrix)[i], "_"), items, + value = TRUE) + if (colnames(scoringMatrix)[i] %in% names(reverseCoded)) { + traitItemsReversed <- reverseCoded[[colnames(scoringMatrix)[i]]] + } else { + traitItemsReversed <- vector(mode = "character", length = 0L) + } + + if (colnames(scoringMatrix)[i] %in% fixSlopes) { + traitItems <- setdiff(traitItems, traitItemsReversed) + if (length(traitItems) > 0L) { + model <- c(model, + paste0(colnames(scoringMatrix)[i], " BY ", traitItems[1L], "* ", + paste(traitItems[-1L], collapse = " "), + paste0(" (a_", colnames(scoringMatrix)[i], ")"), ";")) + } + if (length(traitItemsReversed) > 0L) { + model <- c(model, + paste0(colnames(scoringMatrix)[i], " BY ", traitItemsReversed[1L], "* ", + paste(traitItemsReversed[-1L], collapse = " "), + paste0(" (a_", colnames(scoringMatrix)[i], "n)"), ";")) + } + if (length(traitItems) > 0L & length(traitItemsReversed) > 0L) { + constraints <- c(constraints, + paste0("a_", colnames(scoringMatrix)[i], " = -1*", + "a_", colnames(scoringMatrix)[i], "n;")) + } + } else { + model <- c(model, + paste0(colnames(scoringMatrix)[i], " BY ", traitItems[1L], "* ", + paste(traitItems[-1L], collapse = " "), ";")) + } + model <- c(model, + paste0(colnames(scoringMatrix)[i], "@1;")) + } + model <- c(model, + make_mplus_latent_traits_orthogonal_syntax(orthogonal)) + if (!is.na(weight)) { + weight <- paste0("WEIGHT = ", weight, ";") + } else { + weight <- vector(mode = "character", length = 0L) + } + if (length(constraints) == 0L) constraints = NULL + + results <- list(VARIABLE = + paste(c(strwrap(paste0("USEVAR = ", + paste(c(items, observedExogenous, + observedDependent), + collapse = " "), ";"), + width = 80L, exdent = 10L), + strwrap(paste0("CATEGORICAL = ", + paste(items, collapse = " "), ";"), + width = 80L, exdent = 14L), + weight), + collapse = "\n"), + MODEL = paste(c(modelStructural, model), + collapse = "\n"), + MODELCONSTRAINT = paste(constraints, collapse = "\n")) + return(as.MplusSyntaxElements(results)) +} +#' @title Preparing Mplus code to estimate RS models +#' @description Prepares components of Mplus model description syntax for a +#' GPCM (NRM). +#' @inheritParams make_mplus_irtree_model_syntax +#' @details For the description of model specification see \emph{Details} +#' section in \code{\link{make_mplus_gpcm_vmmc_syntax}} +#' @section Limitations: +#' At the moment there is no possibility to prepare models with many +#' no-RS latent traits loading different sets of items. +#' @return A list with elements named \code{TITLE}, \code{VARIABLE}, +#' \code{ANALYSIS}, \code{MODEL}, \code{MODELCONSTRAINT}, \code{SAVEDATA}, +#' \code{rdata}, \code{usevariables} that can be used as arguments to the +#' \code{mplusObject} function from the package \emph{MplusAutomation} using +#' \code{\link{do.call}} +#' @importFrom stats na.omit +#' @export +make_mplus_gpcm_model_syntax <- function(data, items, scoringMatrix, + observedExogenous = vector(mode = "character", length = 0L), + observedDependent = vector(mode = "character", length = 0L), + fixSlopes = vector(mode = "character", length = 0L), + reverseCoded = vector(mode = "list", length = 0L), + orthogonal = vector(mode = "character", length = 0L), + weight = NA_character_, + savedata = NA_character_, + analysis = list(ESTIMATOR = "MLR", + ALGORITHM = "INTEGRATION", + INTEGRATION = "STANDARD", + PROCESSORS = "4"), + title = "Some GPCM model with custom scoring matrix") { + stopifnot(is.data.frame(data), + is.character(items), !anyNA(items), length(items) > 0L, + all(!duplicated(items)), all(items %in% names(data)), + is.matrix(scoringMatrix), is.numeric(scoringMatrix), + all(sapply(data[, items, drop = FALSE], + function(x, values) {all(x %in% values)}, + values = c(rownames(scoringMatrix), NA_character_))), + is.character(savedata), length(savedata) == 1L, + is.vector(analysis), + all(c("ESTIMATOR", "ALGORITHM", + "INTEGRATION", "PROCESSORS") %in% names(analysis)), + is.character(title), length(title) == 1L, nchar(title) < 78L) + if (is.character(observedExogenous)) { + stopifnot(all(observedExogenous %in% names(data))) + } else { + stopifnot(is.matrix(observedExogenous), + all(rownames(observedExogenous) %in% names(data))) + } + if (is.character(observedDependent)) { + stopifnot(all(observedDependent %in% names(data))) + } else { + stopifnot(is.matrix(observedDependent), + all(rownames(observedDependent) %in% names(data))) + } + itemCategories <- lapply(data[, items, drop = FALSE], + function(x) {return(na.omit(unique(x)))}) + syntax <- make_mplus_gpcm_vmmc_syntax(items = items, + scoringMatrix = scoringMatrix, + observedExogenous = observedExogenous, + observedDependent = observedDependent, + fixSlopes = fixSlopes, + reverseCoded = reverseCoded, + orthogonal = orthogonal, + weight = weight, + itemCategories = itemCategories) + if (is.na(weight)) weight = vector(mode = "character", length = 0L) + data <- data[, c(items, observedExogenous, + observedDependent, weight), drop = FALSE] + analysis <- paste(mapply( + function(nm, val) { + return(paste0(nm, " IS ", val, ";")) + }, + names(analysis), analysis, SIMPLIFY = TRUE)) + if (savedata %in% c(NA_character_, "")) { + savedata = NULL + } else { + savedata <- paste0("FILE IS ", savedata, ";\n", + "SAVE IS FSCORES;") + } + return(as.MplusSyntaxElements(list(TITLE = title, + VARIABLE = syntax$VARIABLE, + ANALYSIS = analysis, + MODEL = syntax$MODEL, + MODELCONSTRAINT = syntax$MODELCONSTRAINT, + SAVEDATA = savedata, + rdata = as.data.frame(data), + usevariables = colnames(data)))) +} +#' @title Preparing Mplus code to estimate RS models +#' @description Prepares components of Mplus model description syntax. +#' @inheritParams make_mplus_irtree_vmmc_syntax +#' @param itemCategories a list of values that a given item takes in the data +#' @details Models are identified by fixing variances of the the latent +#' variables \strong{that are not mentioned in \code{fixSlopes}} to 1 and +#' by fixing \emph{slope} parameters to 1 (or -1 in a case of reverse coded +#' items) and freeing latent trait variances that are mentioned in +#' \code{fixSlopes}. +#' +#' Please note that Mplus assumes that the last category is always +#' scored 0, so if \code{scoringMatrix} contains some non-zero elements in its +#' last row function will automatically adjust the coding scheme for latent +#' traits (columns of the \code{scoringMatrix}) where last cell is non-zero by +#' subtracting value in this cell from the whole column. Typically this will +#' introduce negative scores to this column, but this is something Mplus can +#' carry and it doesn't affect estimates of slope parameters. However, +#' \strong{this will make estimated intercept parameters incomparable with the +#' specification using the original scoring scheme}. Also, this will make slope +#' parameters for a given latent trait negative (while preserving the origin - +#' for the purpose of interpretation - of the latent trait itself). +#' @return A list of strings with elements named \code{VARIABLE}, \code{MODEL} +#' and \code{MODELCONSTRAINT} (the last one can be \code{NULL}) +#' @importFrom stats na.omit setNames +make_mplus_gpcm_vmmc_syntax <- function(items, scoringMatrix, + observedExogenous = vector(mode = "character", length = 0L), + observedDependent = vector(mode = "character", length = 0L), + fixSlopes = vector(mode = "character", length = 0L), + reverseCoded = vector(mode = "list", length = 0L), + orthogonal = vector(mode = "character", length = 0L), + weight = NA_character_, + itemCategories = rep(list(rownames(scoringMatrix)), + length(items))) { + stopifnot(is.character(items), !anyNA(items), length(items) > 0L, + all(!duplicated(items)), + is.matrix(scoringMatrix), is.numeric(scoringMatrix), + is.character(fixSlopes), all(fixSlopes %in% colnames(scoringMatrix)), + is.list(reverseCoded), all(names(reverseCoded) %in% colnames(scoringMatrix)), + all(!duplicated(names(reverseCoded))), all(unlist(reverseCoded) %in% items), + is.character(orthogonal), all(orthogonal %in% colnames(scoringMatrix)), + is.character(weight), length(weight) == 1L, + is.list(itemCategories), length(itemCategories) == length(items), + all(sapply(itemCategories, is.vector)), + all(sapply(itemCategories, + function(x, categories) {all(x %in% categories)}, + categories = rownames(scoringMatrix)))) + if (length(reverseCoded) > 0L & length(fixSlopes) == 0L) { + message("With no slopes being fixed argument 'reverseCoded' won't affect the model specification.") + } + itemCategories <- lapply(itemCategories, na.omit) + itemCategories <- + do.call(rbind, + mapply(function(v, nm) data.frame(item = nm, + value = v, + stringsAsFactors = FALSE), + itemCategories, names(itemCategories), + SIMPLIFY = FALSE)) + modelStructural <- make_mplus_structural_model(latent = colnames(scoringMatrix), + observedExogenous = observedExogenous, + observedDependent = observedDependent) + observedExogenous <- attributes(modelStructural)$observedExogenous + observedDependent <- attributes(modelStructural)$observedDependent + model <- constraints <- vector(mode = "character", length = 0L) + + # simple GPCM specification + if (ncol(scoringMatrix) == 1L & + all(sapply(itemCategories, length) == nrow(scoringMatrix))) { + variable <- paste0("CATEGORICAL = ", paste(items, collapse = " "), " (gpcm);") + if (length(reverseCoded) > 0L) { + reverseCoded <- reverseCoded[[1]] + } else { + reverseCoded <- vector(mode = "character", length = 0L) + } + if (colnames(scoringMatrix) %in% fixSlopes) { + straightCoded <- setdiff(items, reverseCoded) + if (length(straightCoded) > 0L) { + model <- c(model, + paste0(colnames(scoringMatrix), " BY ", + paste(straightCoded, collapse = "@1 "), "@1;")) + } + if (length(reverseCoded) > 0L) { + model <- c(model, + paste0(colnames(scoringMatrix), " BY ", + paste(reverseCoded, collapse = "@-1 "), "@-1;")) + } + model <- c(model, + paste0(colnames(scoringMatrix), "*;")) + + } else { + model <- c(model, + paste0(colnames(scoringMatrix), " BY ", items[1L], "* ", + paste(items[-1L], collapse = " "), ";")) + model <- c(model, + paste0(colnames(scoringMatrix), "@1;")) + } + # GPCM with a custom scoring matrix + } else { + variable <- paste0("NOMINAL = ", paste(items, collapse = " "), ";") + reverseCoded <- c(reverseCoded, + setNames(rep(list(vector(mode = "character", length = 0L)), + ncol(scoringMatrix) - length(reverseCoded)), + setdiff(colnames(scoringMatrix), + names(reverseCoded)))) + for (i in 1L:ncol(scoringMatrix)) { + # Mplus assumes that last category is always codded by 0 + # if scoring matrix has another value there, the codding scheme must be changed + if (scoringMatrix[nrow(scoringMatrix), i] != 0) { + scoringMatrix[, i] <- scoringMatrix[, i] - scoringMatrix[nrow(scoringMatrix), i] + } + + syntax <- make_mplus_gpcm_nrm_syntax(scoringColumn = scoringMatrix[, i, drop = FALSE], + items = items, + reverseCoded = reverseCoded[[colnames(scoringMatrix)[i]]], + itemCategories = itemCategories, + fixSlopes = colnames(scoringMatrix)[i] %in% fixSlopes) + model <- c(model, + syntax$loadings, + syntax$latentTrait, + "") + constraints <- c(constraints, + syntax$constraints) + } + model <- c(model, + make_mplus_latent_traits_orthogonal_syntax(orthogonal)) + } + if (!is.na(weight)) { + weight <- paste0("WEIGHT = ", weight, ";") + } else { + weight <- vector(mode = "character", length = 0L) + } + if (length(constraints) == 0L) constraints = NULL + + results <- list(VARIABLE = + paste(c(strwrap(paste0("USEVAR = ", + paste(c(items, observedExogenous, + observedDependent), + collapse = " "), ";"), + width = 80L, exdent = 10L), + strwrap(variable, + width = 80L, exdent = 10L), + weight), + collapse = "\n"), + MODEL = paste(c(modelStructural, model), + collapse = "\n"), + MODELCONSTRAINT = paste(constraints, collapse = "\n")) + return(as.MplusSyntaxElements(results)) +} +#' @title Preparing Mplus code to estimate RS models +#' @description Prepares Mplus model description syntax for the structural part +#' of the model given latent traits, observed exogenous predictors and observed +#' dependent variables. +#' @param latent a character vector of latent variable names +#' @param observedExogenous either: +#' \itemize{ +#' \item{a character vector with names of observed exogenous predictors that +#' should be used to predict latent variables in the model} +#' \item{a matrix with latent traits in columns and observed exogenous +#' predictors in rows specifying which of the exogenous predictors should +#' be used to predict which latent traits (matrix should contain only +#' 0 and 1 or \code{TRUE} and \code{FALSE})} +#' } +#' @param observedDependent either: +#' \itemize{ +#' \item{a character vector with names of observed dependent variables that +#' should be predicted using latent variables in the model} +#' \item{a matrix with latent traits in columns and observed dependent +#' variables in rows specifying which of the dependent variables should +#' be predicted by which latent traits (matrix should contain only +#' 0 and 1 or \code{TRUE} and \code{FALSE})} +#' } +#' @return A character vector +make_mplus_structural_model <- function(latent, observedExogenous, + observedDependent) { + stopifnot(is.character(latent), !anyNA(latent), length(latent) > 0L, all(!duplicated(latent)), + is.character(observedExogenous) | is.numeric(observedExogenous) | is.logical(observedExogenous), + is.character(observedDependent) | is.numeric(observedDependent) | is.logical(observedDependent)) + if (is.character(observedExogenous)) { + stopifnot(is.vector(observedExogenous), + all(!is.na(observedExogenous)), + all(!duplicated(observedExogenous))) + observedExogenous <- matrix(TRUE, ncol = length(latent), + nrow = length(observedExogenous), + dimnames = list(observedExogenous, latent)) + } else { + stopifnot(all(observedExogenous %in% c(0, 1)), + all(colnames(observedExogenous) %in% latent), + !anyNA(rownames(observedExogenous)), + all(!duplicated(rownames(observedExogenous)))) + observedExogenous <- matrix(as.logical(observedExogenous), + nrow = nrow(observedExogenous), + dimnames = dimnames(observedExogenous)) + } + predicting <- apply(observedExogenous, 1L, any) + if (any(!predicting)) { + warning("There are some observed exogenous variables that do not predict any latent variable: '", + paste(rownames(observedExogenous)[!predicting], collapse = "', '"), "'", + "\nThese variables won't be included in the model.") + } + observedExogenous <- observedExogenous[predicting, , drop = FALSE] + if (is.character(observedDependent)) { + stopifnot(is.vector(observedDependent), + all(!is.na(observedDependent)), + all(!duplicated(observedDependent))) + observedDependent <- matrix(TRUE, ncol = length(latent), + nrow = length(observedDependent), + dimnames = list(observedDependent, latent)) + } else { + stopifnot(all(observedDependent %in% c(0, 1)), + all(colnames(observedDependent) %in% latent), + !anyNA(rownames(observedDependent)), + all(!duplicated(rownames(observedDependent)))) + observedDependent <- matrix(as.logical(observedDependent), + nrow = nrow(observedDependent), + dimnames = dimnames(observedDependent)) + } + predicted <- apply(observedDependent, 1L, any) + if (any(!predicted)) { + warning("There are some observed dependent variables that are not predicted by any latent variable: '", + paste(rownames(observedDependent)[!predicted], collapse = "', '"), "'", + "\nThese variables won't be included in the model.") + } + observedDependent <- observedDependent[predicted, , drop = FALSE] + + if (nrow(observedExogenous) > 0L) { + latentPredicted <- apply(observedExogenous, 2L, any) + observedExogenous <- + paste0(latent[latentPredicted], " ON ", + apply(observedExogenous[, latentPredicted, drop = FALSE], 2L, + function(x) {return(paste(names(x)[x], collapse = " "))}), + ";") + observedExogenous <- strwrap(observedExogenous, width = 80L, exdent = 5L) + } else { + observedExogenous <- vector(mode = "character", length = 0L) + } + if (nrow(observedDependent) > 0L) { + observedDependent <- + paste0(rownames(observedDependent), " ON ", + apply(observedDependent, 1L, + function(x) {return(paste(names(x)[x], collapse = " "))}), + ";") + observedDependent <- strwrap(observedDependent, width = 80L, exdent = 5L) + } else { + observedDependent <- vector(mode = "character", length = 0L) + } + if (length(observedExogenous) > 0L & length(observedDependent) > 0L) { + space <- "" + } else { + space <- vector(mode = "character", length = 0L) + } + if (length(observedExogenous) > 0L | length(observedDependent) > 0L) { + results <- c(MODEL = paste(c(observedExogenous, space, observedDependent, ""), + collapse = "\n")) + } else { + results <- vector(mode = "character", length = 0L) + } + attributes(results)$observedExogenous = rownames(observedExogenous) + attributes(results)$observedDependent = rownames(observedDependent) + return(as.MplusSyntaxElements(results)) +} +#' @title Preparing Mplus code to estimate RS models +#' @description Prepares Mplus model syntax describing how items are loaded +#' by latent traits in a GPCM specification of a NRM. +#' @param scoringColumn a one-column matrix (column of a scoring matrix) +#' @param items a character vector with item names +#' @param reverseCoded a character vector with names of reverse-coded items +#' @param itemCategories a data frame with columns named \emph{item} and +#' \emph{value} storing unique (non-NA) values of items that occur in the data +#' @param fixSlopes a logical value indicating whether slopes of the latent +#' trait should be fixed to be the same +#' @return A character vector +make_mplus_gpcm_nrm_syntax <- function(scoringColumn, items, reverseCoded, + itemCategories, fixSlopes) { + stopifnot(is.matrix(scoringColumn), ncol(scoringColumn) == 1L, + is.character(items), length(items) > 0L, !anyNA(items), + is.character(reverseCoded), !anyNA(reverseCoded), + is.data.frame(itemCategories), + "item" %in% names(itemCategories), + "value" %in% names(itemCategories), + is.logical(fixSlopes), length(fixSlopes) == 1L, + fixSlopes %in% c(TRUE, FALSE)) + results <- data.frame(lt = colnames(scoringColumn), + item = rep(items, each = nrow(scoringColumn)), + value = rep(rownames(scoringColumn), length(items)), + wt = rep(scoringColumn[, 1L], length(items)), + absWt = rep(abs(scoringColumn[, 1L]), length(items)), + stringsAsFactors = FALSE) + results$fix = paste0(ifelse(rep(fixSlopes, nrow(results)), + ifelse(results$item %in% reverseCoded, "@-", "@"), + "*"), + results$wt) + results$rev = ifelse(results$item %in% reverseCoded, "n", "") + results <- merge(results, itemCategories, + by = c("item", "value")) + results <- lapply(split(results, results["item"]), + function(x) cbind(x, cat = c(seq(1L, nrow(x) - 1L), 0L))) + results <- do.call(rbind, results) + results <- results[results$cat != 0L & results$wt != 0, ] + constraints <- unique(cbind(results[, c("item", "wt", "absWt", "rev")], + label = paste0(results$lt, results$item, "_", + results$absWt, results$rev))) + results <- paste0(results$lt, " BY ", results$item, "#", results$cat, + results$fix, " (", constraints$label, ");") + results <- sub("([*@])--([[:digit:]])", "\\1\\2", results) + constraints <- unlist(lapply( + split(constraints, constraints$item), + function(x) { + if (nrow(x) == 1L) return(vector(mode = "character", length = 0L)) + c(paste0(x$label[-nrow(x)], " = ", + ifelse(x$rev[-nrow(x)] == "n", "-", ""), x$wt[-nrow(x)], + "/", ifelse(x$rev[-1L] == "n", "-", ""), x$wt[-1L], + "*", x$label[-1L], ";"), + "") + })) + constraints <- gsub("--([[:digit:]]+\\*)", "\\1", constraints) + if (fixSlopes) constraints <- vector(mode = "character", length = 0L) + return(list(loadings = results, + latentTrait = paste0(colnames(scoringColumn), + ifelse(fixSlopes, "*;", "@1;")), + constraints = constraints)) +} +#' @title Preparing Mplus code to estimate RS models +#' @description Prepares Mplus model description syntax that fixes covariances +#' between given latent traits to 0. +#' @param latentTraits a character vector of latent traits names +#' @return A character vector +make_mplus_latent_traits_orthogonal_syntax <- function(latentTraits) { + stopifnot(is.character(latentTraits), !anyNA(latentTraits)) + if (length(latentTraits) == 0L) return(vector(mode = "character", length = 0L)) + + covariancess <- expand.grid(lt1 = latentTraits, lt2 = latentTraits, + stringsAsFactors = FALSE) + covariancess <- + covariancess[as.vector(lower.tri(matrix(1L:nrow(covariancess), + nrow = nrow(covariancess)^0.5))), ] + covariancess <- + sapply(split(covariancess, + droplevels(factor(covariancess$lt1, + unique(covariancess$lt1)))), + function(x) { + return(strwrap(paste0(x$lt1[1L], " WITH ", + paste(x$lt2, collapse = "@0 "), "@0;"), + width = 80L, exdent = nchar(x$lt2[1L]) + 7L))}) + return(covariancess) +} +#' @title Preparing Mplus code to estimate RS models +#' @description Print method for objects containing Mplus syntaxes +#' @param x an object of class \emph{MplusSyntaxElements} +#' @param ... optional arguments to \code{print.data.frame} methods (used only +#' if \code{x} has an element that is a data frame) +#' @param n an integer passed to \code{\link{head}} (used only if \code{x} +#' has an element that is a data frame) +#' @return invisibly \code{x} +#' @importFrom utils head +#' @export +print.MplusSyntaxElements <- function(x, ..., n = 10L) { + for (i in seq_along(x)) { + cat("----------------------------------------\n", names(x)[i], "\n") + if (is.character(x[[i]]) & names(x)[i] == toupper(names(x)[i])) { + cat(paste(x[[i]], collapse = "\n"), "\n\n", sep = "") + } else if (is.data.frame(x[[i]])) { + print(head(x[[i]], ..., n = n)) + } else { + print(x[[i]]) + } + } + invisible(x) +} +#' @title Preparing Mplus code to estimate RS models +#' @description Assigns \emph{MplusSyntaxElements} to an object +#' @param x an object +#' @return \code{x} with \emph{MplusSyntaxElements} class assigned +as.MplusSyntaxElements <- function(x) { + class(x) <- c("MplusSyntaxElements", class(x)) + return(x) +} diff --git a/R/scoring_matrices.R b/R/scoring_matrices.R index a6fad13..e40baff 100644 --- a/R/scoring_matrices.R +++ b/R/scoring_matrices.R @@ -3,10 +3,10 @@ #' trait (represented in columns) affects (or not) chances to choose each #' response category (represented in rows) assuming effects of #' \emph{acquiescence}, \emph{extreme} and \emph{middle} response styles. -#' @param responses vector of available responses (\emph{categories}) - can be +#' @param responses a vector of available responses (\emph{categories}) - can be #' a character vector or positive integer describing number of responses -#' @param sequence text: "simultaneous" or a three-letters sequence describing -#' the order of decisions made by a \emph{respondent}: +#' @param sequence a string: "gpcm" or a three-letters sequence describing +#' the order of nodes in the IRTree: #' \itemize{ #' \item{'m' stands for choosing between middle \emph{category} and some other #' \emph{category}} @@ -16,19 +16,23 @@ #' \item{'e' stands for choosing between \emph{extreme} category and some #' other \emph{category}} #' } -#' @param nMiddle (maximum) number of \emph{middle} \emph{categories} -#' @param nExtreme (half of the) number of \emph{extreme} \emph{categories} -#' @param nAcquiescence number of \emph{acquiescence} \emph{categories} -#' @param reversed logical value - is item a reversed one? (see details) +#' @param nMiddle the (maximum) number of \emph{middle} \emph{categories} +#' @param nExtreme (half of) the number of \emph{extreme} \emph{categories} +#' @param nAcquiescence the number of \emph{acquiescence} \emph{categories} +#' @param reversed a logical value - is item a reversed one? (see details) #' @param aType determines a way in which scoring pattern for acquiescence is #' generated when it appears in different branches of the IRTree (whether to #' create separate columns allowing for different discrimination of the #' acquiescence in different nodes of the tree or to create only a single column #' holding discrimination in different nodes of the tree constant) #' @param iType determines a way in which scoring pattern for additional (see -#' the description of the `aType` parameter above) -#' \emph{intensity} trait will be generated (see details) -#' @details \strong{\code{sequence} other than "simultaneous":} +#' the description of the `aType` parameter above) \emph{intensity} trait will +#' be generated (see details) +#' @details \strong{\code{sequence} other than "gpcm":} +#' +#' For important remarks on the possibilities and limitations of interpretation +#' of IRTree models, that are represented by this type of scoring matrices, +#' see Plieninger (2020). #' #' For number of responses between 5 and 6 function generates scoring #' matrix in a way mimicking Böckenholt's approach (2017) to describe @@ -65,17 +69,17 @@ #' Analogously to \emph{acquiescence} trait these columns can be collapsed into #' one by setting \code{iType = "common"}. #' -#' \strong{\code{sequence} is "simultaneous":} +#' \strong{\code{sequence} is "gpcm":} #' #' In this case a GPCM scoring matrix is generated mimicking approach of #' Plieninger (2016), i.e. assuming that response process is -#' a \emph{simultaneous} and four factors: intensity of the trait that +#' a \emph{gpcm} and four factors: intensity of the trait that #' is \strong{not} a response style (column \emph{i}), tendency to choose middle #' \emph{categories} (column \emph{m}) tendency to choose extreme #' \emph{categories} (column \emph{e}) and tendency to choose acquiescence #' \emph{categories} (column \emph{a}) contribute altogether to propensity #' of choosing each response. -#' @return matrix of integers +#' @return a matrix of integers #' @examples #' # Bockenholt 2017: 73 #' (bockenholtMAE5 <- make_scoring_matrix_aem(5, "mae")) @@ -84,8 +88,8 @@ #' # Bockenholt 2017: 77 #' (bockenholtAEM6 <- make_scoring_matrix_aem(6, "aem")) #' # Plieninger 2016: 39 -#' (plieninger5 <- make_scoring_matrix_aem(5, "simultaneous")) -#' (plieninger5r <- make_scoring_matrix_aem(5, "simultaneous", reversed = TRUE)) +#' (plieninger5 <- make_scoring_matrix_aem(5, "gpcm")) +#' (plieninger5r <- make_scoring_matrix_aem(5, "gpcm", reversed = TRUE)) #' #' # some more complicated cases: #' make_scoring_matrix_aem(10, "ema", nMiddle = 3, nExtreme = 2) @@ -94,12 +98,12 @@ #' make_scoring_matrix_aem(9, "mae", nMiddle = 3, nExtreme = 2, reversed = TRUE) #' @export make_scoring_matrix_aem <- function( - responses, sequence = c("mae", "mea", "aem", "ame", "ema", "eam", - "simultaneous"), + responses, sequence = c("mae", "mea", "aem", "ame", "ema", "eam", "gpcm"), nMiddle = 2L, nExtreme = 1L, nAcquiescence = floor(length(responses) / 2), reversed = FALSE, aType = c("separate", "common"), iType = c("separate", "common")) { + if (sequence == "simultaneous") sequence <- "gpcm" sequence = match.arg(sequence) aType = match.arg(aType) iType = match.arg(iType) @@ -151,7 +155,7 @@ make_scoring_matrix_aem <- function( "Number of responses that is supposed to be acquiescence (`nAcquiescence`) must be no more than a half of number of available responses." = nAcquiescence <= floor(length(responses) / 2)) - if (sequence == "simultaneous") { + if (sequence == "gpcm") { colNames <- c("i", "m", "e", "a") } else { colNames <- c(strsplit(sequence, "")[[1L]], "i") @@ -173,7 +177,7 @@ make_scoring_matrix_aem <- function( rep(1L, nExtreme)), ncol = 1, dimnames = list(responses, "e")) } else if (names(scoringSubMatrices)[i] == "a") { - if (sequence != "simultaneous" && aType == "separate" && i > 1L) { + if (sequence != "gpcm" && aType == "separate" && i > 1L) { scoringMatrix <- cbind(scoringSubMatrices[[1]], scoringSubMatrices[[2]], scoringSubMatrices[[3]], @@ -205,7 +209,7 @@ make_scoring_matrix_aem <- function( rep(1L, nAcquiescence)), ncol = 1, dimnames = list(responses, "a")) } - } else if (sequence == "simultaneous") { + } else if (sequence == "gpcm") { scoringSubMatrices[[i]] <- matrix(sort(0L:(length(responses) - 1), decreasing = reversed), ncol = 1, dimnames = list(responses, "i")) @@ -242,7 +246,7 @@ make_scoring_matrix_aem <- function( scoringSubMatrices[[2]], scoringSubMatrices[[3]], scoringSubMatrices[[4]]) - if (sequence != "simultaneous") { + if (sequence != "gpcm") { # inserting NAs in rows that describe paths that ends up earlier for (i in 1L:(ncol(scoringMatrix) - 1L)) { patterns <- scoringMatrix[!duplicated(scoringMatrix[, 1L:i]), 1L:i, @@ -273,12 +277,13 @@ make_scoring_matrix_aem <- function( #' one wants to use \code{\link{generate_slopes}} and #' \code{\link{generate_intercepts}} functions to generate items' parameters #' with no reference to response styles. -#' @param responses vector of available responses (\emph{categories}) - can be +#' @param responses a vector of available responses (\emph{categories}) - can be #' a character vector or positive integer describing number of responses -#' @param nTraits optionally number of traits affecting the item response; +#' @param nTraits optionally the number of traits affecting the item response; #' disregarded if \code{traitsNames} are provided -#' @param traitsNames optionally character vector containing names of the traits -#' @return matrix of integers +#' @param traitsNames optionally a character vector containing names of the +#' traits +#' @return a matrix of integers #' @examples #' make_scoring_matrix_trivial(5, 2) #' make_scoring_matrix_trivial(5, traitsNames = c("A", "B")) @@ -313,11 +318,11 @@ make_scoring_matrix_trivial <- function(responses, nTraits = 1L, } #' @title Make scoring matrix #' @description Makes response matrix using \emph{random thresholds} approach. -#' @param responses vector of available responses (\emph{categories}) - can be +#' @param responses a vector of available responses (\emph{categories}) - can be #' a character vector or positive integer describing number of responses #' @details Be aware that while using this kind of response matrix latent #' traits must be set orthogonal to assure model identifiability. -#' @return matrix of integers +#' @return a matrix of integers #' @examples #' make_scoring_matrix_rt(5) #' @export @@ -335,9 +340,9 @@ make_scoring_matrix_rt <- function(responses) { } #' @title Make scoring matrix #' @description Makes response matrix using \emph{sum to zero} approach. -#' @param responses vector of available responses (\emph{categories}) - can be +#' @param responses a vector of available responses (\emph{categories}) - can be #' a character vector or positive integer describing number of responses -#' @return matrix of integers +#' @return a matrix of integers #' @examples #' make_scoring_matrix_stz(5) #' @export diff --git a/R/scoring_on_previous_responses.R b/R/scoring_on_previous_responses.R index 958ac18..64a71b8 100644 --- a/R/scoring_on_previous_responses.R +++ b/R/scoring_on_previous_responses.R @@ -19,14 +19,14 @@ #' responses; if there are no more answers \emph{in a given direction}, #' than change direction (\emph{bounce}).} #' } -#' @param previousResponses character vector of previous responses -#' @param scoringMatrix matrix describing how responses (described in rownames +#' @param previousResponses a character vector of previous responses +#' @param scoringMatrix a matrix describing how responses (described in rownames #' of the matrix) map on \emph{scores} of latent traits (described in columns of #' the matrix) -#' @returns one-column matrix of 0 and 1 with the same number of rows +#' @return a one-column matrix of 0 and 1 with the same number of rows #' (and rownames) as \code{scoringMatrix} with column name \emph{ci} #' @examples -#' sM <- make_scoring_matrix_aem(1:5, "simultaneous") +#' sM <- make_scoring_matrix_aem(1:5, "gpcm") #' #' #' answersStraigth <- t(sapply(c(1:5, rep(3, 5)), score_on_last_answer_straight, diff --git a/R/thresholds_and_intercepts.R b/R/thresholds_and_intercepts.R index 447a02d..545548e 100644 --- a/R/thresholds_and_intercepts.R +++ b/R/thresholds_and_intercepts.R @@ -1,14 +1,14 @@ -#' @title Conversion between thresholds and intercepts parametrisations +#' @title Conversion between thresholds and intercepts parametrizations #' @description Utility functions allowing to covert \emph{thresholds} (i.e. -#' parameterisation convienient to think of item steps difficulty) to -#' \emph{intercepts} (i.e. parameterisation used internally by packages +#' parameterization convenient to think of item steps difficulty) to +#' \emph{intercepts} (i.e. parameterization used internally by packages #' \emph{rstyles} and \emph{mirt}) or back forth. -#' @param thresholds vector or matrix (thresholds in cols, items in rows) of +#' @param thresholds a vector or matrix (thresholds in cols, items in rows) of #' item thresholds -#' @param intercepts vector or matrix (intercepts in cols, items in rows) of +#' @param intercepts a vector or matrix (intercepts in cols, items in rows) of #' item intercepts #' @seealso \code{\link{generate_intercepts}} -#' @returns vector or matrix of thresholds or intercepts6 +#' @return a vector or matrix of thresholds or intercepts6 #' @examples #' # 5 items with (general) difficulty evenly spanned between -2 and 2 #' # and for each item thresholds eveny spanned betwenn -1.5 and 1.5 relatively diff --git a/README.md b/README.md index 3105063..7f2836f 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,17 @@ Package *rstyles* provides functions that enables performing simulations involving IRT/CCFA models that assume responses to be affected by so-called *response styles*. It includes functions to generate (randomly or deterministically) items' parameters and to generate responses. +Also (since version 0.7.0) it provides functions that prepare [Mplus](http://statmodel.com/) model syntaxes for IRT/CCFA models involving responses styles. + # Installing the package -The latest version of the package can be installed from GitHub using package `devtools`: +The latest version of the package can be installed from GitHub using package `remotes`: ```{r} -devtools::install.github("tzoltak/rstyles") +remotes::install.github("tzoltak/rstyles") ``` -# How to use the package +# How to use the package - simulating responses ## General description @@ -22,8 +24,8 @@ There are four steps one needs to follow to simulate responses to a test: - Within package *rstyles* model of answering an item is described on the most general level using so-called *scoring matrix*, i.e. a matrix that describes how different latent traits contribute to propensity of choosing a given category (answer). Names of columns of the *scoring matrix* corresponds to the names of latent traits in the model and names of rows to available response categories for each item. The same form of representation is used to describe two quite different types of models: - - noncompensatory IRTree models (Böckenholt, 2012, 2017) in which responding process is assumed to be a sequence of (typically binary, but this is not the only possible approach) decisions and in each of these decisions only one latent trait is involved; this type of models can be estimated by means of defining so-called *pseudo-items* - each of them describing such a single decision made by respondent (compare function `expand_responses()`); in the package *rstyles* this approach is identified as a *sequential mode* of responding; - - partially-compensatory models (Falk & Cai, 2016; Henninger & Meiser, 2020a, 2020b; Plieninger, 2017) in which latent traits are supposed to describe the specific way of perceiving the response scale by the respondent and may be thought as affecting the response process *simultaneously* (contrary to the IRTree approach), typically along with an additional latent trait that describes *what the test (scale) is supposed to measure* (i.e. not a response style); in the package *rstyles* this approach is identified as a *simultaneous mode* of responding. + - noncompensatory IRTree models (Böckenholt, 2012, 2017) in which responding process is assumed to be a sequence of (typically binary, but this is not the only possible approach) decisions and in each of these decisions only one latent trait is involved; this type of models can be estimated by means of defining so-called *pseudo-items* - each of them describing such a single decision made by respondent (compare function `expand_responses()`); in the package *rstyles* this approach is identified as a *irtree mode* of responding; + - partially-compensatory models (Falk & Cai, 2016; Henninger & Meiser, 2020a, 2020b; Plieninger, 2017) in which latent traits are supposed to describe the specific way of perceiving the response scale by the respondent and may be thought as affecting the response process *simultaneously* (contrary to the IRTree approach), typically along with an additional latent trait that describes *what the test (scale) is supposed to measure* (i.e. not a response style); in the package *rstyles* this approach is identified as a *GPCM mode* of responding. - Although *scoring matrix* used to describe this both approaches within the package *rstyles* looks quite the same, it is used in a different way internally in the response generating step - that's why one needs to declare the *mode* of responding while defining items in the second step of the procedure. @@ -78,7 +80,7 @@ sM <- make_scoring_matrix_aem(1:5, "mae") slopes <- generate_slopes(nItems, sM, FUN = rlnorm, meanlog = 0, sdlog = 0.2) intercepts <- generate_intercepts(nItems, sM, FUNd = rnorm, argsd = list(mean = 0, sd = 1.5)) -items <- make_test(sM, slopes, intercepts, "sequential") +items <- make_test(sM, slopes, intercepts, "irtree") # generating "subjects" - uncorrelated traits vcovTraits <- matrix(0, nrow = ncol(sM), ncol = ncol(sM), @@ -101,12 +103,12 @@ mSqt <- mirt(respWide, ### Partially-compensatory random-thresholds GPCM including *middle*, *extreme* and *acquiescence* response styles -- Below the model is defined in which apart of the *trait the test is supposed to measure*, named "i", there are three additional latent traits describing response styles that affect responses *simultaneously*. This traits may be interpreted as describing *middle* ("m"), *extreme* ("e") and *acquiescence* ("a") response styles. +- Below the model is defined in which apart of the *trait the test is supposed to measure*, named "i", there are three additional latent traits describing response styles that affect responses. This traits may be interpreted as describing *middle* ("m"), *extreme* ("e") and *acquiescence* ("a") response styles. - Test consist of 20 items, half of which is *reversed* (i.e. *negatively* associated with the trait called "i"). - Items' *slopes* are generated from a log-normal distribution with expected value and standard deviation on the log scale being 0 and 0.2 respectively. - Items' *intercepts* (*thresholds*) in a two-step procedure in which firstly (*general* or, technically speaking, average) *difficulties* of the items are generated (precisely these are *ease* parameters due to the parameterization that is used by the package) and next *thresholds* are generated relatively to these items' *difficulties*. At the end this both are summed up to get actual *intercepts*. In the code below: - - Items' *difficulties* are sampled from a uniform distribution ranging from -2 to 2. - - *Thresholds* relative to the item's difficulty are generated deterministically as a sequence of four values regularly spanned between 0.9 and -0.9. + - Items' *difficulties* are sampled from a uniform distribution ranging from -2 to 2. + - *Thresholds* relative to the item's difficulty are generated deterministically as a sequence of four values regularly spanned between 0.9 and -0.9. - Latent traits are assumed to be standard normal and independent of each other (this is not a very plausible assumption). - There are 1000 *respondents* (responses that are generated). - Function `mirt()` from package *mirt* is used to estimate 2PL IRT model on the generated data, with a custom *scoring matrix* provided using `gpcm_mats` argument. @@ -120,7 +122,7 @@ require(mirt) set.seed(123456) # generating test nItems <- 20 -sM <- make_scoring_matrix_aem(1:5, "simultaneous") +sM <- make_scoring_matrix_aem(1:5, "gpcm") slopes <- cbind(generate_slopes(nItems, sM[, 1L, drop = FALSE], FUN = rlnorm, meanlog = 0, sdlog = 0.3, nReversed = floor(nItems / 2)), @@ -130,7 +132,7 @@ intercepts <- generate_intercepts(nItems, sM, FUNt = seq, argst = list(from = 0.9, to = -0.9, length.out = 4)) -items <- make_test(sM, slopes, intercepts, "simultaneous") +items <- make_test(sM, slopes, intercepts, "gpcm") # generating "subjects" - uncorrelated traits vcovTraits <- matrix(0, nrow = ncol(sM), ncol = ncol(sM), @@ -165,8 +167,8 @@ Also, it is possible to specify distinct *scoring matrices* for the *reversed* a Log-normal distribution is parameterized on the log scale (i.e. by parameters of the *underlying* normal distribution) but while generating parameters one is always interested in the parameters on the *exponential* scale, i.e. the scale of the sampled values. To deal with this problem package *rstyles* provides a set of functions: -- `lnorm_mean()` and `lnorm_sd()` enables to compute respectively expected value and standard deviation of the log-normal distribution with a given *meanlog* and *sdlog* parameters (compare `?rlnorm`); -- `find_pars_lnorm()` returns values of the *meanlog* and *sdlog* parameters one should use to get expected value and standard deviation of the log-normal distribution specified as arguments to this function. +- `lnorm_mean()` and `lnorm_sd()` enables to compute respectively expected value and standard deviation of the log-normal distribution with a given *meanlog* and *sdlog* parameters (compare `?rlnorm`); +- `find_pars_lnorm()` returns values of the *meanlog* and *sdlog* parameters one should use to get expected value and standard deviation of the log-normal distribution specified as arguments to this function. ### Mixtures @@ -187,13 +189,53 @@ respSimdata <- simdata(slopes, intercepts, N = nrow(theta), itemtype = "gpcm", However, remember that `simdata()` always returns responses as numbers starting from 0 (the first category), irrespective the scoring matrix you provide it. +# How to use the package - generating Mplus syntaxes + +There are two functions enabling to prepare [Mplus](http://statmodel.com/) model syntaxes: + +- `make_mplus_irtree_model_syntax()` for IRTree models, +- `make_mplus_gpcm_model_syntax()` for (G)PCM/NRM models. + +They share the common API and enable for: + +- Using custom scoring matrices. +- Using *reverse coded* items. +- Fixing or freeing slope parameters across items for each latent trait indiviually. +- Setting some latent traits orthogonal to each other. +- Including observed exogenous predictors and observed dependent variables in the model (predicting or being predicted by the latent traits). +- **Using data in which some response categories are *empty*** (no one chose them) for some items. +- Using sampling weights. + +Both functions return a list that one can use along with the `mpluObject()` function from the [*MplusAutomation*](https://cran.r-project.org/package=MplusAutomation) package in the way: + +- `do.call(mplusObject, args = make_mplus_irtree_model_syntax())` + +- or `do.call(mplusObject, args = make_mplus_gpcm_model_syntax()` + +to get a ready-to-be-estimated Mplus model object. See [*MplusAutomation* package vignette](https://cran.r-project.org/web/packages/MplusAutomation/vignettes/vignette.html) for more information on calling Mplus from R. + +Models are identified by either: + +- Freeing item slopes and fixing latent trait variance to 0, if slopes are enabled to vary across items for a given latent trait. +- Fixing item slopes to 1 (times weights in the scoring matrix) and freeing latent trait variance, if slopes are constrained to be the same across items for a given latent variable. + +However, their important limitations are: + +- Only one scoring matrix is used for every item (with the exception of handling *reverse coded* items). +- There is no way to include in the model many no-response-style latent traits loading different sets of items. +- Due to the way Mplus deals with NRM identification scoring matrices including non-zero values in their last row (that is, mostly all typically used) must be reparameterized what affects (see `?make_mplus_gpcm_vmmc_syntax` for a little more information): + - Sign of the estimated slope parameters. + - Values of the intercept parameters. + # To do -- functions to compute non-GPCM (2PLM) models at nodes of *sequentially* responded items; +- functions to compute non-GPCM (2PLM) models at nodes of *IRTree* responded items; - functions to use as `editResponse` argument to `make_item()`; -- implementation of Plieninger & Heck (2018) generalized IRTree approach (large refactorization will be needed because this kind of models can't be described by a simple *scoring matrix*); +# Limitations + +Current implementation can not handle Plieninger & Heck's (2018) generalized IRTree approach because this kind of models can not be described by a simple *scoring matrix*. # Other useful packages and repositories @@ -227,17 +269,17 @@ However, remember that `simdata()` always returns responses as numbers starting # Literature -Böckenholt, U. (2012). Modeling multiple response processes in judgment and choice. Psychological Methods, 17(4), 665–678. +Böckenholt, U. (2012). Modeling multiple response processes in judgment and choice. Psychological Methods, 17(4), 665--678. -Böckenholt, U. (2017). Measuring response styles in Likert items. *Psychological Methods, 22*(1), 69–83. +Böckenholt, U. (2017). Measuring response styles in Likert items. *Psychological Methods, 22*(1), 69--83. -Falk, C. F., & Cai, L. (2016). A flexible full-information approach to the modeling of response styles. *Psychological Methods, 21*(3), 328–347. +Falk, C. F., & Cai, L. (2016). A flexible full-information approach to the modeling of response styles. *Psychological Methods, 21*(3), 328--347. -Henninger, M., & Meiser, T. (2020). Different approaches to modeling response styles in divide-by-total item response theory models (part 1): A model integration. *Psychological Methods, 25*(5), 560–576. +Henninger, M., & Meiser, T. (2020). Different approaches to modeling response styles in divide-by-total item response theory models (part 1): A model integration. *Psychological Methods, 25*(5), 560--576. -Henninger, M., & Meiser, T. (2020). Different approaches to modeling response styles in divide-by-total item response theory models (part 2): Applications and novel extensions. *Psychological Methods, 25*(5), 577–595. +Henninger, M., & Meiser, T. (2020). Different approaches to modeling response styles in divide-by-total item response theory models (part 2): Applications and novel extensions. *Psychological Methods, 25*(5), 577--595. -Plieninger, H. (2017). Mountain or Molehill? A Simulation Study on the Impact of Response Styles. *Educational and Psychological Measurement, 77*(1), 32–53. +Plieninger, H. (2017). Mountain or Molehill? A Simulation Study on the Impact of Response Styles. *Educational and Psychological Measurement, 77*(1), 32--53. Plieninger, H. & Heck, D.W. (2018). A New Model for Acquiescence at the Interface of Psychometrics and Cognitive Psychology. *Multivariate Behavioral Research, 53*(5), 633-654, diff --git a/man/as.MplusSyntaxElements.Rd b/man/as.MplusSyntaxElements.Rd new file mode 100644 index 0000000..e28cf42 --- /dev/null +++ b/man/as.MplusSyntaxElements.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/resp_styles_to_mplus.R +\name{as.MplusSyntaxElements} +\alias{as.MplusSyntaxElements} +\title{Preparing Mplus code to estimate RS models} +\usage{ +as.MplusSyntaxElements(x) +} +\arguments{ +\item{x}{an object} +} +\value{ +\code{x} with \emph{MplusSyntaxElements} class assigned +} +\description{ +Assigns \emph{MplusSyntaxElements} to an object +} diff --git a/man/compute_item_expected_scores.Rd b/man/compute_item_expected_scores.Rd index 183daec..a1ec92e 100644 --- a/man/compute_item_expected_scores.Rd +++ b/man/compute_item_expected_scores.Rd @@ -13,13 +13,13 @@ compute_item_expected_scores(x, vcov) \method{compute_item_expected_scores}{rstylesItem}(x, vcov = diag(ncol(x$scoringMatrix))) } \arguments{ -\item{x}{object of class \emph{rstylesItem} or \emph{rstylesTest}} +\item{x}{an object of class \emph{rstylesItem} or \emph{rstylesTest}} -\item{vcov}{covariance matrix of latent traits (in line with item's scoring +\item{vcov}{a covariance matrix of latent traits (in line with item's scoring matrix); if not provided uncorrelated standard normal is used} } \value{ -table +A table } \description{ Function generates expected distribution of item scores given @@ -28,10 +28,10 @@ items) object constructed using \code{\link{make_test}} and covariance matrix of latent traits, assuming multivariate-normal distribution of latent traits. } \examples{ -itemGPCM <- make_item(scoringMatrix = make_scoring_matrix_aem(1:5, "simultaneous")[, -4], +itemGPCM <- make_item(scoringMatrix = make_scoring_matrix_aem(1:5, "gpcm")[, -4], slopes = c(i = 1, m = 2, e = 3), intercepts = cumsum(c(0, seq(-0.5, 0.5, length.out = 4))), - mode = "simultaneous") + mode = "gpcm") vcov <- matrix(c( 1, 0.5, -0.5, 0.5, 1, -0.25, -0.5, -0.25, 1), @@ -42,13 +42,13 @@ compute_item_expected_scores(itemGPCM, vcov) itemIRTree <- make_item(scoringMatrix = make_scoring_matrix_aem(1:5, "mae"), slopes = c(m = 1, a = 1, e = 1), intercepts = c(m1 = 0, a1 = 0, e1 = 0), - mode = "sequential") + mode = "irtree") vcovIRTree <- vcov colnames(vcovIRTree) <- rownames(vcovIRTree) <- c("a", "m", "e") compute_item_expected_scores(itemIRTree) # orthogonal, standard-normal latent traits compute_item_expected_scores(itemIRTree, vcovIRTree) -sM <- make_scoring_matrix_aem(1:5, "simultaneous")[, -4] +sM <- make_scoring_matrix_aem(1:5, "gpcm")[, -4] test <- make_test(sM, generate_slopes(11, sM, c(1, 2, 3)), generate_intercepts(11, sM, @@ -58,7 +58,7 @@ test <- make_test(sM, FUNt = seq, argst = list(from = -1.5, to = 1.5, length.out = 4)), - "simultaneous") + "gpcm") sapply(compute_item_expected_scores(test, vcov), identity) } \seealso{ diff --git a/man/expand_responses.Rd b/man/expand_responses.Rd index b761195..38c62e5 100644 --- a/man/expand_responses.Rd +++ b/man/expand_responses.Rd @@ -7,13 +7,13 @@ expand_responses(responses, scoringMatrix) } \arguments{ -\item{responses}{matrix of responses with items in columns and observations +\item{responses}{a matrix of responses with items in columns and observations in rows} -\item{scoringMatrix}{matrix describing scoring patterns on each latent trait} +\item{scoringMatrix}{a matrix describing scoring patterns on each latent trait} } \value{ -matrix (of integers) +A matrix (of integers) } \description{ Using scoring matrix provided as its second argument function diff --git a/man/generate_intercepts.Rd b/man/generate_intercepts.Rd index d688703..bbac9b2 100644 --- a/man/generate_intercepts.Rd +++ b/man/generate_intercepts.Rd @@ -14,30 +14,30 @@ generate_intercepts( ) } \arguments{ -\item{nItems}{number of items for which intercepts will be generated} +\item{nItems}{the number of items for which intercepts will be generated} -\item{scoringMatrix}{\emph{scoring matrix} that will be used for the +\item{scoringMatrix}{a \emph{scoring matrix} that will be used for the generated items, specifically generated with \code{\link{make_scoring_matrix_aem}} or \code{\link{make_scoring_matrix_trivial}}} -\item{FUNd}{function that will be used to generate item difficulties,h +\item{FUNd}{a function that will be used to generate item difficulties,h typically \code{\link[stats]{Uniform}}, \code{\link[stats]{Normal}} or \code{\link[truncnorm]{rtruncnorm}}} -\item{argsd}{list of arguments to be passed to \code{FUNd}} +\item{argsd}{a list of arguments to be passed to \code{FUNd}} -\item{FUNt}{optionally function that will be used to generate item +\item{FUNt}{optionally a function that will be used to generate item thresholds (i.e. difficulties of categories relative to difficulty of the -whole item), assuming \emph{simultaneous} item responding process; +whole item), assuming \emph{gpcm} item responding process; typically \code{\link[stats]{Uniform}}, \code{\link[stats]{Normal}} or \code{\link[truncnorm]{rtruncnorm}}; if not set item responding process is -assumed to be a \emph{sequential} one;} +assumed to be a \emph{irtree} one;} -\item{argst}{optionally list of arguments to be passed to \code{FUNt}} +\item{argst}{optionally a list of arguments to be passed to \code{FUNt}} } \value{ -Matrix of \code{nItems} rows and number of columns equal to the +A matrix of \code{nItems} rows and number of columns equal to the number of intercepts. \strong{Be aware that these are \emph{intercepts} and not \emph{thresholds} what are returned} (and intercept for a category \emph{g} is a sum of \emph{thresholds} from the first category up to the @@ -48,9 +48,9 @@ Function generates a matrix of items' intercept (thresholds/difficulties) parameters. } \details{ -\strong{Assuming \emph{sequential} response process:} +\strong{Assuming \emph{irtree} response process:} -Assuming \emph{sequential} response process test item must be characterized +Assuming \emph{irtree} response process test item must be characterized by a set of intercept parameters describing individual thresholds of binary \emph{pseudo-items} modeling consecutive decisions in the assumed sequence of responding process. In such a case: @@ -67,9 +67,9 @@ of responding process. In such a case: \emph{pseudo-items} across all the items;} \item{arguments \code{FUNt} and \code{argst} are not used.} } -\strong{Assuming \emph{simultaneous} response process:} +\strong{Assuming \emph{GPCM} response process:} -Assuming \emph{simultaneous} response process test item must be characterized +Assuming \emph{GPCM} response process test item must be characterized by a set of \emph{intercept} parameters describing relative frequency of specific categories of the response scale. However, it is convenient to define model parameters in another parameterisation, in which item @@ -107,17 +107,17 @@ Returned \emph{intercepts} are computed by summing general item a given item and then computing cumulative sum of this vector. } \examples{ -# 5 items with 5-point response scale assuming "sequential" item response +# 5 items with 5-point response scale assuming IRTree item response # process with "pseudo-items" intercepts sampled from a uniform distribution # with limits +-1.5 sM <- make_scoring_matrix_aem(5, sequence = "mae") generate_intercepts(5, sM, runif, list(min = -1.5, max = 1.5)) -# 10 items with 5-point response scale assuming "sequential" item response +# 10 items with 5-point response scale assuming IRTree item response # process with "pseudo-items" intercepts sampled from a normal distribution # with the mean of 0 and the standard deviation of 1.5 sM <- make_scoring_matrix_aem(5, sequence = "mae") generate_intercepts(5, sM, rnorm, list(mean = 0, sd = 1)) -# 10 items with 5-point response scale assuming "sequential" item response +# 10 items with 5-point response scale assuming IRTree item response # process with "pseudo-items" intercepts sampled from a uniform distribution # with limits set to: # trait 'm' (i.e. the first column in the scoring matrix): from -3 to -1 @@ -128,32 +128,32 @@ generate_intercepts(10, sM, runif, list(min = c(-3, -1, 1), max = c(-1, 1, 3))) -# 10 items with 6-point response scale assuming "simultaneous" item response +# 10 items with 6-point response scale assuming GPCM item response # process with items difficulties sampled from a normal distribution with # the mean of 0 and the standard deviation of 1.5 and thresholds relative # to the items difficulties sampled from a uniform distribution with # the limits of +-2 -sM <- make_scoring_matrix_aem(6, sequence = "simultaneous") +sM <- make_scoring_matrix_aem(6, sequence = "gpcm") generate_intercepts(10, sM, FUNd = rnorm, argsd = list(mean = 0, sd = 1.5), FUNt = runif, argst = list(min = -2, max = 2)) -# 5 items with 6-point response scale assuming "simultaneous" item response +# 5 items with 6-point response scale assuming GPCM item response # process with items difficulties sampled from a uniform distribution with # the limits of +-2 and thresholds relative to the items difficulties sampled # from a normal distribution with the mean of 0 and the standard deviation # defined individually for each item # the limits of +-2 -sM <- make_scoring_matrix_aem(6, sequence = "simultaneous") +sM <- make_scoring_matrix_aem(6, sequence = "gpcm") generate_intercepts(5, sM, FUNd = runif, argsd = list(min = -2, max = 2), FUNt = rnorm, argst = list(mean = 0, sd = c(1, 1.2, 1.4, 1.6, 1.9))) -# 20 items with 5-point response scale assuming "simultaneous" item response +# 20 items with 5-point response scale assuming GPCM item response # process with items difficulties sampled from a uniform distribution with # the limits of +-2 and thresholds relative to the items difficulties # generated deterministically as a sequence of 4 regularly spaced values # from 0.9 to -0.9 -sM <- make_scoring_matrix_aem(5, sequence = "simultaneous") +sM <- make_scoring_matrix_aem(5, sequence = "gpcm") generate_intercepts(20, sM, FUNd = runif, argsd = list(min = -2, max = 2), FUNt = seq, argst = list(from = 0.9, diff --git a/man/generate_item_responeses_gpcm.Rd b/man/generate_item_responeses_gpcm.Rd index 6d43e71..ac6edbf 100644 --- a/man/generate_item_responeses_gpcm.Rd +++ b/man/generate_item_responeses_gpcm.Rd @@ -7,15 +7,15 @@ generate_item_responeses_gpcm(theta, weightsMatrix, intercepts) } \arguments{ -\item{theta}{matrix of latent traits' values} +\item{theta}{a matrix of latent traits' values} -\item{weightsMatrix}{matrix of discrimination parameters (being +\item{weightsMatrix}{a matrix of slope parameters (being multiplication of a design matrix by discriminations of factors)} -\item{intercepts}{intercept parameters} +\item{intercepts}{a vector of intercept parameters} } \value{ -vector of responses on item +a vector of responses on item } \description{ Computes probabilities of each response using (G)PCM and draws diff --git a/man/generate_item_responses_sml.Rd b/man/generate_item_responses_sml.Rd index 23c8154..fb43bcb 100644 --- a/man/generate_item_responses_sml.Rd +++ b/man/generate_item_responses_sml.Rd @@ -2,26 +2,26 @@ % Please edit documentation in R/generate_responses.R \name{generate_item_responses_sml} \alias{generate_item_responses_sml} -\title{Internals: simulating responding to item in a 'simultaneous' way} +\title{Internals: simulating responding to item in the GPCM way} \usage{ generate_item_responses_sml(theta, scoringMatrix, slopes, intercepts) } \arguments{ -\item{theta}{matrix of latent traits' values} +\item{theta}{a matrix of latent traits' values} -\item{scoringMatrix}{matrix describing scoring patterns on each latent trait} +\item{scoringMatrix}{a matrix describing scoring patterns on each latent trait} -\item{slopes}{vector of slope parameters of each trait} +\item{slopes}{a vector of slope parameters of each trait} -\item{intercepts}{intercept parameters} +\item{intercepts}{a vector of intercept parameters} } \value{ -vector of responses on item +a vector of responses on item } \description{ -Function generates responses 'simultaneously` with a whole -scoring matrix used at once. Only (G)PCM approach is suitable in such a case, -because with complicated scoring matrices there is no guarantee that +Function generates responses in the GPCM way with a whole +scoring matrix used at once. Only (G)PCM/NRM approach is suitable in such +a case, because with complicated scoring matrices there is no guarantee that probabilities of responses are increasing along with order of responses (rows) in a scoring matrix. Consequently, no normal ogive models can be used. } diff --git a/man/generate_item_responses_sqn.Rd b/man/generate_item_responses_sqn.Rd index 351c44e..767bb39 100644 --- a/man/generate_item_responses_sqn.Rd +++ b/man/generate_item_responses_sqn.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/generate_responses.R \name{generate_item_responses_sqn} \alias{generate_item_responses_sqn} -\title{Internals: simulating responding to item in a 'sequential' (IRTree) way} +\title{Internals: simulating responding to item in the IRTree way} \usage{ generate_item_responses_sqn( theta, @@ -14,22 +14,22 @@ generate_item_responses_sqn( ) } \arguments{ -\item{theta}{matrix of latent traits' values} +\item{theta}{a matrix of latent traits' values} -\item{scoringMatrix}{matrix describing scoring patterns on each latent trait} +\item{scoringMatrix}{a matrix describing scoring patterns on each latent trait} -\item{slopes}{vector of slope parameters of each trait} +\item{slopes}{a vector of slope parameters for each trait} -\item{intercepts}{intercept parameters} +\item{intercepts}{a vector of intercept parameters} -\item{editResponse}{optional function returning scoring matrix that should be -used instead that provided by \code{scoringMatrix}; this should be function +\item{editResponse}{an optional function returning scoring matrix that should +be used instead that provided by \code{scoringMatrix}; this should be function accepting two arguments: \code{response} - generated response (by the model described with the first column of the \code{scoringMatrix}) that is supposed to be \emph{edited} and \code{scoringMatrix} - current scoring matrix (to be replaced)} -\item{decidingOnPreviousResponse}{logical value indicating whether first +\item{decidingOnPreviousResponse}{a logical value indicating whether first column of provided scoring matrix describes making decision whether to respond on the basis of responses to previous items or not (in this first case \emph{negative} choice shouldn't reduce number of rows in a response @@ -39,8 +39,8 @@ matrix)} vector of responses on item } \description{ -Function generates responses 'sequentially`, i.e. using IRTree -approach. It goes through consecutive columns of a scoring matrix, +Function generates responses using IRTree approach, i.e. +'sequentially`. It goes through consecutive columns of a scoring matrix, calling \code{\link{generate_item_responeses_gpcm}} to get responses at a given node of a tree and the recursively calls itself on subsets of observations with a given response with reduced scoring matrix. diff --git a/man/generate_slopes.Rd b/man/generate_slopes.Rd index 017d003..7fc8a09 100644 --- a/man/generate_slopes.Rd +++ b/man/generate_slopes.Rd @@ -15,9 +15,9 @@ generate_slopes( ) } \arguments{ -\item{nItems}{number of items for which slopes will be generated} +\item{nItems}{the number of items for which slopes will be generated} -\item{scoringMatrix}{\emph{scoring matrix} that will be used for the +\item{scoringMatrix}{a \emph{scoring matrix} that will be used for the generated items, specifically generated with \code{\link{make_scoring_matrix_aem}} or \code{\link{make_scoring_matrix_trivial}}} @@ -26,19 +26,18 @@ generated items, specifically generated with named and in typical applications should be numeric vectors of the length of one or of the number of columns of the \code{scoringMatrix}} -\item{FUN}{function that will be used to generate slopes, typically} +\item{FUN}{a function that will be used to generate slopes, typically} -\item{nReversed}{number of \emph{reversed} (\emph{revers-keyed}) items, i.e. -items that} +\item{nReversed}{the number of \emph{reversed} (\emph{revers-keyed}) items} -\item{reverseTraits}{character vector containing names of traits for which +\item{reverseTraits}{a character vector containing names of traits for which items should be \emph{reversed} (if \code{nReversed > 0}); default value is "i", that is the name assigned to the trait that is assumed to describe \emph{the trait that scale is supposed to measure} (i.e. trait that is not related with response styles) by \code{\link{make_scoring_matrix_aem}} when -called with argument \code{sequence = "simultaneous"}} +called with argument \code{sequence = "gpcm"}} -\item{reverseIndep}{logical value indicating whether sampling items that are +\item{reverseIndep}{a logical value indicating whether sampling items that are \emph{reversed} should be performed independently for each trait (given by \code{reverseTraits}) or for all the traits simultaneously (this argument is only considered if \code{nReversed > 0} and there is more than one trait @@ -48,7 +47,7 @@ given by \code{reverseTraits}) - see details} } \value{ -Matrix of \code{nItems} rows and number of columns equal to the +A matrix of \code{nItems} rows and number of columns equal to the length of vectors provided by \code{...}. } \description{ @@ -113,7 +112,7 @@ generate_slopes(10, sM, FUN = rtruncnorm, # 10 items with slopes generated from a normal distributions with mean of 1 # and standard deviation of 0.2 with half of the items "reverse-keyed" on # the trait "i" -sM <- make_scoring_matrix_aem(5, sequence = "simultaneous") +sM <- make_scoring_matrix_aem(5, sequence = "gpcm") generate_slopes(10, sM, FUN = rnorm, mean = 1, sd = 0.2, nReversed = 5, reverseTraits = "i") } diff --git a/man/generate_test_responses.Rd b/man/generate_test_responses.Rd index 1baa7b4..682c0c6 100644 --- a/man/generate_test_responses.Rd +++ b/man/generate_test_responses.Rd @@ -12,16 +12,16 @@ generate_test_responses( ) } \arguments{ -\item{theta}{matrix (or data frame) of already generated latent traits' +\item{theta}{a matrix (or data frame) of already generated latent traits' values} -\item{items}{list with test items' specification} +\item{items}{a list with test items' specification} -\item{performAssertions}{logical value indicating whether function should +\item{performAssertions}{a logical value indicating whether function should perform assertions of the other arguments (\code{TRUE} by default, may be changed to \code{FALSE} for a little performance gain)} -\item{tryConvertToNumeric}{logical value indicating whether function should +\item{tryConvertToNumeric}{a logical value indicating whether function should try to convert generated responses to numeric values (technically responses are get from rownames of the items' scoring matrices that are character vectors)} diff --git a/man/log-normal.Rd b/man/log-normal.Rd index db16f46..cbccc1a 100644 --- a/man/log-normal.Rd +++ b/man/log-normal.Rd @@ -13,7 +13,7 @@ lnorm_sd(meanlog, sdlog) find_pars_lnorm(m, sd) } \arguments{ -\item{meanlog}{mean (expected value)of the distribution on the log scale} +\item{meanlog}{mean (expected value) of the distribution on the log scale} \item{sdlog}{standard deviation of the distribution on the log scale} @@ -23,7 +23,7 @@ find_pars_lnorm(m, sd) } \value{ \code{lnorm_mean()} and \code{lnorm_sd()} return numeric vectors; -\code{find_pars_lnorm()} returns named numeric vector with elements named +\code{find_pars_lnorm()} returns a named numeric vector with elements named \emph{meanlog} describing means and elements named \emph{sdlog} describing standard deviations. } diff --git a/man/make_item.Rd b/man/make_item.Rd index 66e702e..5b42617 100644 --- a/man/make_item.Rd +++ b/man/make_item.Rd @@ -8,35 +8,35 @@ make_item( scoringMatrix, slopes, intercepts, - mode = c("sequential", "simultaneous"), + mode = c("irtree", "gpcm"), scoringOnPreviousResponses = NULL, editResponse = NULL ) } \arguments{ -\item{scoringMatrix}{matrix describing how responses (described in rownames +\item{scoringMatrix}{a matrix describing how responses (described in rownames of the matrix) map on \emph{scores} of latent traits (described in columns of the matrix)} -\item{slopes}{\strong{named} numeric vector of slope (discrimination) +\item{slopes}{a \strong{named} numeric vector of slope (discrimination) parameters with names describing latent variables matching each slope (must contain at least all the names occurring in column \emph{names} of the \code{scoringMatrix} but may also contain additional slopes matching latent traits scoring patterns that will be returned by functions provided with arguments \code{scoringOnPreviousResponses} or \code{editResponse})} -\item{intercepts}{numeric vector of intercept parameters (must be shorter +\item{intercepts}{a numeric vector of intercept parameters (must be shorter of one than number of rows in the \\code{scoringMatrix} or the same length but with the first element being 0)} -\item{mode}{a way the item should be answered - see -\code{\link{generate_item_responses_sqn}}, -\code{\link{generate_item_responses_sml}}} +\item{mode}{the way the item should be answered - see +\code{\link{generate_item_responses_sqn}} for "irtree" and +\code{\link{generate_item_responses_sml}} for "gpcm"} -\item{scoringOnPreviousResponses}{optional function returning a column vector +\item{scoringOnPreviousResponses}{an optional function returning a column vector that will be put before first column of the \code{scoringMatrix}} -\item{editResponse}{only if \code{mode='sequential'}: optional function +\item{editResponse}{only if \code{mode='irtree'}: an optional function returning scoring matrix that should replace that provided by \code{scoringMatrix} after \emph{response is made} at the first \emph{node}; this should be function accepting two arguments: \code{response} - generated @@ -45,7 +45,7 @@ response (by the model described with the first column of the \code{scoringMatrix} - current scoring matrix (to be replaced)} } \value{ -Object of class \emph{rstylesItem} representing an item. List of +An object of class \emph{rstylesItem} representing an item. List of such objects is passed as a test specification to \code{\link{generate_test_responses}}. } diff --git a/man/make_mplus_gpcm_model_syntax.Rd b/man/make_mplus_gpcm_model_syntax.Rd new file mode 100644 index 0000000..17d2edb --- /dev/null +++ b/man/make_mplus_gpcm_model_syntax.Rd @@ -0,0 +1,98 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/resp_styles_to_mplus.R +\name{make_mplus_gpcm_model_syntax} +\alias{make_mplus_gpcm_model_syntax} +\title{Preparing Mplus code to estimate RS models} +\usage{ +make_mplus_gpcm_model_syntax( + data, + items, + scoringMatrix, + observedExogenous = vector(mode = "character", length = 0L), + observedDependent = vector(mode = "character", length = 0L), + fixSlopes = vector(mode = "character", length = 0L), + reverseCoded = vector(mode = "list", length = 0L), + orthogonal = vector(mode = "character", length = 0L), + weight = NA_character_, + savedata = NA_character_, + analysis = list(ESTIMATOR = "MLR", ALGORITHM = "INTEGRATION", INTEGRATION = "STANDARD", + PROCESSORS = "4"), + title = "Some GPCM model with custom scoring matrix" +) +} +\arguments{ +\item{data}{a data frame} + +\item{items}{a character vector of item names} + +\item{scoringMatrix}{a matrix describing how responses (described in rownames +of the matrix) map on \emph{scores} of latent traits (described in columns of +the matrix)} + +\item{observedExogenous}{either: +\itemize{ + \item{a character vector with names of observed exogenous predictors that + should be used to predict latent variables in the model} + \item{a matrix with latent traits in columns and observed exogenous + predictors in rows specifying which of the exogenous predictors should + be used to predict which latent traits (matrix should contain only + 0 and 1 or \code{TRUE} and \code{FALSE})} +}} + +\item{observedDependent}{either: +\itemize{ + \item{a character vector with names of observed dependent variables that + should be predicted using latent variables in the model} + \item{a matrix with latent traits in columns and observed dependent + variables in rows specifying which of the dependent variables should + be predicted by which latent traits (matrix should contain only + 0 and 1 or \code{TRUE} and \code{FALSE})} +}} + +\item{fixSlopes}{optionally a character vector of latent trait names +for which item slopes parameters should be fixed across items +(these names need to occur in column names of \code{scoringMatrix})} + +\item{reverseCoded}{optionally a named list of character vectors with names +of list elements specifying latent trait name and elements giving names of +items that are \emph{reverse coded} with respect to this latent trait; +please note, that these don't need to be given if slopes of the no-RS +trait(s) are not fixed across items} + +\item{orthogonal}{optionally a character vector of latent trait names +indicating which latent traits should be specified as orthogonal to each +other (all the mentioned latent traits will be specified as orthogonal to each +other and all the other latent traits)} + +\item{weight}{optionally a string with a name of the variable storing weights} + +\item{savedata}{optionally a string with a name of the file in which factor +scores should be saved} + +\item{analysis}{a list with elements \code{ESTIMATOR}, \code{ALGORITHM}, +\code{INTEGRATION} and \code{PROCESSORS} containing Mplus \emph{ANALYSIS} +options (provided as strings)} + +\item{title}{string with a title for the analysis} +} +\value{ +A list with elements named \code{TITLE}, \code{VARIABLE}, +\code{ANALYSIS}, \code{MODEL}, \code{MODELCONSTRAINT}, \code{SAVEDATA}, +\code{rdata}, \code{usevariables} that can be used as arguments to the +\code{mplusObject} function from the package \emph{MplusAutomation} using +\code{\link{do.call}} +} +\description{ +Prepares components of Mplus model description syntax for a +GPCM (NRM). +} +\details{ +For the description of model specification see \emph{Details} +section in \code{\link{make_mplus_gpcm_vmmc_syntax}} +} +\section{Limitations}{ + +At the moment there is no possibility to prepare models with many +no-RS latent traits loading different sets of items. +} + diff --git a/man/make_mplus_gpcm_nrm_syntax.Rd b/man/make_mplus_gpcm_nrm_syntax.Rd new file mode 100644 index 0000000..caf7618 --- /dev/null +++ b/man/make_mplus_gpcm_nrm_syntax.Rd @@ -0,0 +1,34 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/resp_styles_to_mplus.R +\name{make_mplus_gpcm_nrm_syntax} +\alias{make_mplus_gpcm_nrm_syntax} +\title{Preparing Mplus code to estimate RS models} +\usage{ +make_mplus_gpcm_nrm_syntax( + scoringColumn, + items, + reverseCoded, + itemCategories, + fixSlopes +) +} +\arguments{ +\item{scoringColumn}{a one-column matrix (column of a scoring matrix)} + +\item{items}{a character vector with item names} + +\item{reverseCoded}{a character vector with names of reverse-coded items} + +\item{itemCategories}{a data frame with columns named \emph{item} and +\emph{value} storing unique (non-NA) values of items that occur in the data} + +\item{fixSlopes}{a logical value indicating whether slopes of the latent +trait should be fixed to be the same} +} +\value{ +A character vector +} +\description{ +Prepares Mplus model syntax describing how items are loaded +by latent traits in a GPCM specification of a NRM. +} diff --git a/man/make_mplus_gpcm_vmmc_syntax.Rd b/man/make_mplus_gpcm_vmmc_syntax.Rd new file mode 100644 index 0000000..ba4a22f --- /dev/null +++ b/man/make_mplus_gpcm_vmmc_syntax.Rd @@ -0,0 +1,90 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/resp_styles_to_mplus.R +\name{make_mplus_gpcm_vmmc_syntax} +\alias{make_mplus_gpcm_vmmc_syntax} +\title{Preparing Mplus code to estimate RS models} +\usage{ +make_mplus_gpcm_vmmc_syntax( + items, + scoringMatrix, + observedExogenous = vector(mode = "character", length = 0L), + observedDependent = vector(mode = "character", length = 0L), + fixSlopes = vector(mode = "character", length = 0L), + reverseCoded = vector(mode = "list", length = 0L), + orthogonal = vector(mode = "character", length = 0L), + weight = NA_character_, + itemCategories = rep(list(rownames(scoringMatrix)), length(items)) +) +} +\arguments{ +\item{items}{a character vector of item names} + +\item{scoringMatrix}{a matrix describing how responses (described in rownames +of the matrix) map on \emph{scores} of latent traits (described in columns of +the matrix)} + +\item{observedExogenous}{either: +\itemize{ + \item{a character vector with names of observed exogenous predictors that + should be used to predict latent variables in the model} + \item{a matrix with latent traits in columns and observed exogenous + predictors in rows specifying which of the exogenous predictors should + be used to predict which latent traits (matrix should contain only + 0 and 1 or \code{TRUE} and \code{FALSE})} +}} + +\item{observedDependent}{either: +\itemize{ + \item{a character vector with names of observed dependent variables that + should be predicted using latent variables in the model} + \item{a matrix with latent traits in columns and observed dependent + variables in rows specifying which of the dependent variables should + be predicted by which latent traits (matrix should contain only + 0 and 1 or \code{TRUE} and \code{FALSE})} +}} + +\item{fixSlopes}{optionally a character vector of latent trait names +for which item slopes parameters should be fixed across items +(these names need to occur in column names of \code{scoringMatrix})} + +\item{reverseCoded}{optionally a named list of character vectors with names +of list elements specifying latent trait name and elements giving names of +items that are \emph{reverse coded} with respect to this latent trait; +please note, that these don't need to be given if slopes of the no-RS +trait(s) are not fixed across items} + +\item{orthogonal}{optionally a character vector of latent trait names +indicating which latent traits should be specified as orthogonal to each +other (all the mentioned latent traits will be specified as orthogonal to each +other and all the other latent traits)} + +\item{weight}{optionally a string with a name of the variable storing weights} + +\item{itemCategories}{a list of values that a given item takes in the data} +} +\value{ +A list of strings with elements named \code{VARIABLE}, \code{MODEL} +and \code{MODELCONSTRAINT} (the last one can be \code{NULL}) +} +\description{ +Prepares components of Mplus model description syntax. +} +\details{ +Models are identified by fixing variances of the the latent +variables \strong{that are not mentioned in \code{fixSlopes}} to 1 and +by fixing \emph{slope} parameters to 1 (or -1 in a case of reverse coded +items) and freeing latent trait variances that are mentioned in +\code{fixSlopes}. + +Please note that Mplus assumes that the last category is always +scored 0, so if \code{scoringMatrix} contains some non-zero elements in its +last row function will automatically adjust the coding scheme for latent +traits (columns of the \code{scoringMatrix}) where last cell is non-zero by +subtracting value in this cell from the whole column. Typically this will +introduce negative scores to this column, but this is something Mplus can +carry and it doesn't affect estimates of slope parameters. However, +\strong{this will make estimated intercept parameters incomparable with the +specification using the original scoring scheme}. Also, this will make slope +parameters for a given latent trait negative (while preserving the origin - +for the purpose of interpretation - of the latent trait itself). +} diff --git a/man/make_mplus_irtree_model_syntax.Rd b/man/make_mplus_irtree_model_syntax.Rd new file mode 100644 index 0000000..9959fe9 --- /dev/null +++ b/man/make_mplus_irtree_model_syntax.Rd @@ -0,0 +1,98 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/resp_styles_to_mplus.R +\name{make_mplus_irtree_model_syntax} +\alias{make_mplus_irtree_model_syntax} +\title{Preparing Mplus code to estimate RS models} +\usage{ +make_mplus_irtree_model_syntax( + data, + items, + scoringMatrix, + observedExogenous = vector(mode = "character", length = 0L), + observedDependent = vector(mode = "character", length = 0L), + fixSlopes = vector(mode = "character", length = 0L), + reverseCoded = vector(mode = "list", length = 0L), + orthogonal = vector(mode = "character", length = 0L), + weight = NA_character_, + savedata = NA_character_, + analysis = list(ESTIMATOR = "MLR", ALGORITHM = "INTEGRATION", INTEGRATION = "STANDARD", + PROCESSORS = "4"), + title = "Some GPCM model with custom scoring matrix" +) +} +\arguments{ +\item{data}{a data frame} + +\item{items}{a character vector of item names} + +\item{scoringMatrix}{a matrix describing how responses (described in rownames +of the matrix) map on \emph{scores} of latent traits (described in columns of +the matrix)} + +\item{observedExogenous}{either: +\itemize{ + \item{a character vector with names of observed exogenous predictors that + should be used to predict latent variables in the model} + \item{a matrix with latent traits in columns and observed exogenous + predictors in rows specifying which of the exogenous predictors should + be used to predict which latent traits (matrix should contain only + 0 and 1 or \code{TRUE} and \code{FALSE})} +}} + +\item{observedDependent}{either: +\itemize{ + \item{a character vector with names of observed dependent variables that + should be predicted using latent variables in the model} + \item{a matrix with latent traits in columns and observed dependent + variables in rows specifying which of the dependent variables should + be predicted by which latent traits (matrix should contain only + 0 and 1 or \code{TRUE} and \code{FALSE})} +}} + +\item{fixSlopes}{optionally a character vector of latent trait names +for which item slopes parameters should be fixed across items +(these names need to occur in column names of \code{scoringMatrix})} + +\item{reverseCoded}{optionally a named list of character vectors with names +of list elements specifying latent trait name and elements giving names of +items that are \emph{reverse coded} with respect to this latent trait; +please note, that these don't need to be given if slopes of the no-RS +trait(s) are not fixed across items} + +\item{orthogonal}{optionally a character vector of latent trait names +indicating which latent traits should be specified as orthogonal to each +other (all the mentioned latent traits will be specified as orthogonal to each +other and all the other latent traits)} + +\item{weight}{optionally a string with a name of the variable storing weights} + +\item{savedata}{optionally a string with a name of the file in which factor +scores should be saved} + +\item{analysis}{a list with elements \code{ESTIMATOR}, \code{ALGORITHM}, +\code{INTEGRATION} and \code{PROCESSORS} containing Mplus \emph{ANALYSIS} +options (provided as strings)} + +\item{title}{string with a title for the analysis} +} +\value{ +A list with elements named \code{TITLE}, \code{VARIABLE}, +\code{ANALYSIS}, \code{MODEL}, \code{MODELCONSTRAINT}, \code{SAVEDATA}, +\code{rdata}, \code{usevariables} that can be used as arguments to the +\code{mplusObject} function from the package \emph{MplusAutomation} using +\code{\link{do.call}} +} +\description{ +Prepares components of Mplus model description syntax for +IRTree model. +} +\details{ +For the description of model specification see \emph{Details} +section in \code{\link{make_mplus_irtree_vmmc_syntax}} +} +\section{Limitations}{ + +At the moment there is no possibility to prepare models with many +no-RS latent traits loading different sets of items. +} + diff --git a/man/make_mplus_irtree_vmmc_syntax.Rd b/man/make_mplus_irtree_vmmc_syntax.Rd new file mode 100644 index 0000000..3623dd2 --- /dev/null +++ b/man/make_mplus_irtree_vmmc_syntax.Rd @@ -0,0 +1,76 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/resp_styles_to_mplus.R +\name{make_mplus_irtree_vmmc_syntax} +\alias{make_mplus_irtree_vmmc_syntax} +\title{Preparing Mplus code to estimate RS models} +\usage{ +make_mplus_irtree_vmmc_syntax( + items, + scoringMatrix, + observedExogenous = vector(mode = "character", length = 0L), + observedDependent = vector(mode = "character", length = 0L), + fixSlopes = vector(mode = "character", length = 0L), + reverseCoded = vector(mode = "list", length = 0L), + orthogonal = vector(mode = "character", length = 0L), + weight = NA_character_ +) +} +\arguments{ +\item{items}{a character vector of item names} + +\item{scoringMatrix}{a matrix describing how responses (described in rownames +of the matrix) map on \emph{scores} of latent traits (described in columns of +the matrix)} + +\item{observedExogenous}{either: +\itemize{ + \item{a character vector with names of observed exogenous predictors that + should be used to predict latent variables in the model} + \item{a matrix with latent traits in columns and observed exogenous + predictors in rows specifying which of the exogenous predictors should + be used to predict which latent traits (matrix should contain only + 0 and 1 or \code{TRUE} and \code{FALSE})} +}} + +\item{observedDependent}{either: +\itemize{ + \item{a character vector with names of observed dependent variables that + should be predicted using latent variables in the model} + \item{a matrix with latent traits in columns and observed dependent + variables in rows specifying which of the dependent variables should + be predicted by which latent traits (matrix should contain only + 0 and 1 or \code{TRUE} and \code{FALSE})} +}} + +\item{fixSlopes}{optionally a character vector of latent trait names +for which item slopes parameters should be fixed across items +(these names need to occur in column names of \code{scoringMatrix})} + +\item{reverseCoded}{optionally a named list of character vectors with names +of list elements specifying latent trait name and elements giving names of +items that are \emph{reverse coded} with respect to this latent trait; +please note, that these don't need to be given if slopes of the no-RS +trait(s) are not fixed across items} + +\item{orthogonal}{optionally a character vector of latent trait names +indicating which latent traits should be specified as orthogonal to each +other (all the mentioned latent traits will be specified as orthogonal to each +other and all the other latent traits)} + +\item{weight}{optionally a string with a name of the variable storing weights} +} +\value{ +A list of strings with elements named \code{VARIABLE}, \code{MODEL} +and \code{MODELCONSTRAINT} (the last one can be \code{NULL}) +} +\description{ +Prepares components of Mplus model description syntax for +IRTree model. +} +\details{ +Models are identified by fixing variances of the the latent +variables \strong{that are not mentioned in \code{fixSlopes}} to 1 and +by fixing \emph{slope} parameters to 1 (or -1 in a case of reverse coded +items) and freeing latent trait variances that are mentioned in +\code{fixSlopes}. +} diff --git a/man/make_mplus_latent_traits_orthogonal_syntax.Rd b/man/make_mplus_latent_traits_orthogonal_syntax.Rd new file mode 100644 index 0000000..d0eb83c --- /dev/null +++ b/man/make_mplus_latent_traits_orthogonal_syntax.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/resp_styles_to_mplus.R +\name{make_mplus_latent_traits_orthogonal_syntax} +\alias{make_mplus_latent_traits_orthogonal_syntax} +\title{Preparing Mplus code to estimate RS models} +\usage{ +make_mplus_latent_traits_orthogonal_syntax(latentTraits) +} +\arguments{ +\item{latentTraits}{a character vector of latent traits names} +} +\value{ +A character vector +} +\description{ +Prepares Mplus model description syntax that fixes covariances +between given latent traits to 0. +} diff --git a/man/make_mplus_structural_model.Rd b/man/make_mplus_structural_model.Rd new file mode 100644 index 0000000..7942bd3 --- /dev/null +++ b/man/make_mplus_structural_model.Rd @@ -0,0 +1,39 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/resp_styles_to_mplus.R +\name{make_mplus_structural_model} +\alias{make_mplus_structural_model} +\title{Preparing Mplus code to estimate RS models} +\usage{ +make_mplus_structural_model(latent, observedExogenous, observedDependent) +} +\arguments{ +\item{latent}{a character vector of latent variable names} + +\item{observedExogenous}{either: +\itemize{ + \item{a character vector with names of observed exogenous predictors that + should be used to predict latent variables in the model} + \item{a matrix with latent traits in columns and observed exogenous + predictors in rows specifying which of the exogenous predictors should + be used to predict which latent traits (matrix should contain only + 0 and 1 or \code{TRUE} and \code{FALSE})} +}} + +\item{observedDependent}{either: +\itemize{ + \item{a character vector with names of observed dependent variables that + should be predicted using latent variables in the model} + \item{a matrix with latent traits in columns and observed dependent + variables in rows specifying which of the dependent variables should + be predicted by which latent traits (matrix should contain only + 0 and 1 or \code{TRUE} and \code{FALSE})} +}} +} +\value{ +A character vector +} +\description{ +Prepares Mplus model description syntax for the structural part +of the model given latent traits, observed exogenous predictors and observed +dependent variables. +} diff --git a/man/make_scoring_matrix_aem.Rd b/man/make_scoring_matrix_aem.Rd index 92718f6..fe90ea6 100644 --- a/man/make_scoring_matrix_aem.Rd +++ b/man/make_scoring_matrix_aem.Rd @@ -6,7 +6,7 @@ \usage{ make_scoring_matrix_aem( responses, - sequence = c("mae", "mea", "aem", "ame", "ema", "eam", "simultaneous"), + sequence = c("mae", "mea", "aem", "ame", "ema", "eam", "gpcm"), nMiddle = 2L, nExtreme = 1L, nAcquiescence = floor(length(responses)/2), @@ -16,11 +16,11 @@ make_scoring_matrix_aem( ) } \arguments{ -\item{responses}{vector of available responses (\emph{categories}) - can be +\item{responses}{a vector of available responses (\emph{categories}) - can be a character vector or positive integer describing number of responses} -\item{sequence}{text: "simultaneous" or a three-letters sequence describing -the order of decisions made by a \emph{respondent}: +\item{sequence}{a string: "gpcm" or a three-letters sequence describing +the order of nodes in the IRTree: \itemize{ \item{'m' stands for choosing between middle \emph{category} and some other \emph{category}} @@ -31,13 +31,13 @@ the order of decisions made by a \emph{respondent}: other \emph{category}} }} -\item{nMiddle}{(maximum) number of \emph{middle} \emph{categories}} +\item{nMiddle}{the (maximum) number of \emph{middle} \emph{categories}} -\item{nExtreme}{(half of the) number of \emph{extreme} \emph{categories}} +\item{nExtreme}{(half of) the number of \emph{extreme} \emph{categories}} -\item{nAcquiescence}{number of \emph{acquiescence} \emph{categories}} +\item{nAcquiescence}{the number of \emph{acquiescence} \emph{categories}} -\item{reversed}{logical value - is item a reversed one? (see details)} +\item{reversed}{a logical value - is item a reversed one? (see details)} \item{aType}{determines a way in which scoring pattern for acquiescence is generated when it appears in different branches of the IRTree (whether to @@ -46,11 +46,11 @@ acquiescence in different nodes of the tree or to create only a single column holding discrimination in different nodes of the tree constant)} \item{iType}{determines a way in which scoring pattern for additional (see -the description of the `aType` parameter above) -\emph{intensity} trait will be generated (see details)} +the description of the `aType` parameter above) \emph{intensity} trait will +be generated (see details)} } \value{ -matrix of integers +a matrix of integers } \description{ Makes response matrix, i.e. matrix describing how each latent @@ -59,7 +59,11 @@ response category (represented in rows) assuming effects of \emph{acquiescence}, \emph{extreme} and \emph{middle} response styles. } \details{ -\strong{\code{sequence} other than "simultaneous":} +\strong{\code{sequence} other than "gpcm":} + +For important remarks on the possibilities and limitations of interpretation +of IRTree models, that are represented by this type of scoring matrices, +see Plieninger (2020). For number of responses between 5 and 6 function generates scoring matrix in a way mimicking Böckenholt's approach (2017) to describe @@ -96,11 +100,11 @@ column (allowing for specification of different model parameters). Analogously to \emph{acquiescence} trait these columns can be collapsed into one by setting \code{iType = "common"}. -\strong{\code{sequence} is "simultaneous":} +\strong{\code{sequence} is "gpcm":} In this case a GPCM scoring matrix is generated mimicking approach of Plieninger (2016), i.e. assuming that response process is -a \emph{simultaneous} and four factors: intensity of the trait that +a \emph{gpcm} and four factors: intensity of the trait that is \strong{not} a response style (column \emph{i}), tendency to choose middle \emph{categories} (column \emph{m}) tendency to choose extreme \emph{categories} (column \emph{e}) and tendency to choose acquiescence @@ -115,8 +119,8 @@ of choosing each response. # Bockenholt 2017: 77 (bockenholtAEM6 <- make_scoring_matrix_aem(6, "aem")) # Plieninger 2016: 39 -(plieninger5 <- make_scoring_matrix_aem(5, "simultaneous")) -(plieninger5r <- make_scoring_matrix_aem(5, "simultaneous", reversed = TRUE)) +(plieninger5 <- make_scoring_matrix_aem(5, "gpcm")) +(plieninger5r <- make_scoring_matrix_aem(5, "gpcm", reversed = TRUE)) # some more complicated cases: make_scoring_matrix_aem(10, "ema", nMiddle = 3, nExtreme = 2) diff --git a/man/make_scoring_matrix_rt.Rd b/man/make_scoring_matrix_rt.Rd index e051d8f..8f155b0 100644 --- a/man/make_scoring_matrix_rt.Rd +++ b/man/make_scoring_matrix_rt.Rd @@ -7,11 +7,11 @@ make_scoring_matrix_rt(responses) } \arguments{ -\item{responses}{vector of available responses (\emph{categories}) - can be +\item{responses}{a vector of available responses (\emph{categories}) - can be a character vector or positive integer describing number of responses} } \value{ -matrix of integers +a matrix of integers } \description{ Makes response matrix using \emph{random thresholds} approach. diff --git a/man/make_scoring_matrix_stz.Rd b/man/make_scoring_matrix_stz.Rd index 039a8f8..08c173c 100644 --- a/man/make_scoring_matrix_stz.Rd +++ b/man/make_scoring_matrix_stz.Rd @@ -7,11 +7,11 @@ make_scoring_matrix_stz(responses) } \arguments{ -\item{responses}{vector of available responses (\emph{categories}) - can be +\item{responses}{a vector of available responses (\emph{categories}) - can be a character vector or positive integer describing number of responses} } \value{ -matrix of integers +a matrix of integers } \description{ Makes response matrix using \emph{sum to zero} approach. diff --git a/man/make_scoring_matrix_trivial.Rd b/man/make_scoring_matrix_trivial.Rd index 3658672..d838a06 100644 --- a/man/make_scoring_matrix_trivial.Rd +++ b/man/make_scoring_matrix_trivial.Rd @@ -11,16 +11,17 @@ make_scoring_matrix_trivial( ) } \arguments{ -\item{responses}{vector of available responses (\emph{categories}) - can be +\item{responses}{a vector of available responses (\emph{categories}) - can be a character vector or positive integer describing number of responses} -\item{nTraits}{optionally number of traits affecting the item response; +\item{nTraits}{optionally the number of traits affecting the item response; disregarded if \code{traitsNames} are provided} -\item{traitsNames}{optionally character vector containing names of the traits} +\item{traitsNames}{optionally a character vector containing names of the +traits} } \value{ -matrix of integers +a matrix of integers } \description{ Makes trivial response matrix, corresponding to the most simple, diff --git a/man/make_test.Rd b/man/make_test.Rd index bc4cca4..62538d1 100644 --- a/man/make_test.Rd +++ b/man/make_test.Rd @@ -8,31 +8,31 @@ make_test( scoringMatrix, slopes, intercepts, - mode = c("sequential", "simultaneous"), + mode = c("irtree", "gpcm"), scoringOnPreviousResponses = NULL, editResponse = NULL, names = paste0("i", 1:nrow(slopes)) ) } \arguments{ -\item{scoringMatrix}{\emph{scoring matrix} that should be used for items, +\item{scoringMatrix}{a \emph{scoring matrix} that should be used for items, especially one generated with \code{\link{make_scoring_matrix_aem}}} -\item{slopes}{matrix of slope parameters (items in rows, traits in cols), +\item{slopes}{a matrix of slope parameters (items in rows, traits in cols), especially generated with \code{\link{generate_slopes}}} -\item{intercepts}{matrix of intercept parameters (items in rows, +\item{intercepts}{a matrix of intercept parameters (items in rows, intercepts/thresholds in cols), especially generated with \code{\link{generate_intercepts}}} -\item{mode}{a way the item should be answered - see +\item{mode}{the way the item should be answered - see \code{\link{generate_item_responses_sqn}}, \code{\link{generate_item_responses_sml}}} -\item{scoringOnPreviousResponses}{optional function returning a column vector -that will be put before first column of the \code{scoringMatrix}} +\item{scoringOnPreviousResponses}{an optional function returning a column +vector that will be put before first column of the \code{scoringMatrix}} -\item{editResponse}{only if \code{mode='sequential'}: optional function +\item{editResponse}{only if \code{mode='irtree'}: an optional function returning scoring matrix that should replace that provided by \code{scoringMatrix} after \emph{response is made} at the first \emph{node}; this should be function accepting two arguments: \code{response} - generated @@ -40,12 +40,12 @@ response (by the model described with the first column of the \code{scoringMatrix}) that is supposed to be \emph{edited} and \code{scoringMatrix} - current scoring matrix (to be replaced)} -\item{names}{optional character vector providing names of the items (by +\item{names}{an optional character vector providing names of the items (by default names will be created as concatenation of the letter "i" - like "item" - and consecutive integers)} } \value{ -List of objects of the \emph{rstylesItem} class. +A list of objects of the \emph{rstylesItem} class. } \description{ Function makes a list object representing test items given @@ -59,11 +59,11 @@ Function is actually a simple wrapper around \code{\link{make_item}} \strong{Column names of the \code{intercepts} matrix:} \itemize{ - \item{If \code{mode = "simultaneous"} names should be of the form: + \item{If \code{mode = "gpcm"} names should be of the form: \emph{dN} where \emph{d} stands for itself and \emph{N} are consecutive integers from 1 to one less than the number of rows of the the \emph{scoring matrix}.} - \item{If \code{mode = "sequential"} names should be of the form: + \item{If \code{mode = "irtree"} names should be of the form: \emph{traitN} where \emph{trait} are names of traits - the same as those in columns of the \emph{scoring matrix} - and \emph{N} are integers describing consecutive thresholds of a \emph{pseudo-item} @@ -77,7 +77,7 @@ Function is actually a simple wrapper around \code{\link{make_item}} \examples{ ################################################################################ # responses to 10 items using 5-point Likert scale -# with respect to the Bockenholt's IRTree (i.e. "sequential") "MAE" model +# with respect to the Bockenholt's IRTree "MAE" model # 1) make scoring matrix sM <- make_scoring_matrix_aem(5, "mae") # 2) generate items' slopes: @@ -97,14 +97,14 @@ intercepts <- generate_intercepts(10, sM, runif, list(min = -1.5, max = 1.5)) # 4) call `make_test()` -# (for IRTree mode must be set to "sequential") -test <- make_test(sM, slopes, intercepts, "sequential") +# (for IRTree mode must be set accordingly) +test <- make_test(sM, slopes, intercepts, "irtree") ################################################################################ # responses to 20 items using 5-point Likert scale -# with respect to the Plieninger's "simultaneous" (partialy-compensatory) model +# with respect to the Plieninger's GPCM (partialy-compensatory) model # 1) make scoring matrix -sM <- make_scoring_matrix_aem(5, "simultaneous") +sM <- make_scoring_matrix_aem(5, "gpcm") # 2) generate items' slopes: # slopes on the 'middle", "extreme" and "acquiescence" latent traits # set to 1 for all items and slopes on the "intensity" latent trait generated @@ -122,5 +122,5 @@ intercepts <- generate_intercepts(20, sM, FUNd = rnorm, FUNt = runif, argsd = list(mean = 0, sd = 1.5), argst = list(min = -1, max = 1)) # 4) call `make_test()` -test <- make_test(sM, slopes, intercepts, "simultaneous") +test <- make_test(sM, slopes, intercepts, "gpcm") } diff --git a/man/print.MplusSyntaxElements.Rd b/man/print.MplusSyntaxElements.Rd new file mode 100644 index 0000000..43a3dd9 --- /dev/null +++ b/man/print.MplusSyntaxElements.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/resp_styles_to_mplus.R +\name{print.MplusSyntaxElements} +\alias{print.MplusSyntaxElements} +\title{Preparing Mplus code to estimate RS models} +\usage{ +\method{print}{MplusSyntaxElements}(x, ..., n = 10L) +} +\arguments{ +\item{x}{an object of class \emph{MplusSyntaxElements}} + +\item{...}{optional arguments to \code{print.data.frame} methods (used only +if \code{x} has an element that is a data frame)} + +\item{n}{an integer passed to \code{\link{head}} (used only if \code{x} +has an element that is a data frame)} +} +\value{ +invisibly \code{x} +} +\description{ +Print method for objects containing Mplus syntaxes +} diff --git a/man/score_on_previous_answers.Rd b/man/score_on_previous_answers.Rd index 6bc4c2a..ae0b28f 100644 --- a/man/score_on_previous_answers.Rd +++ b/man/score_on_previous_answers.Rd @@ -16,14 +16,14 @@ score_on_last_answer_previous(previousResponses, scoringMatrix) score_on_previous_answers_bounce(previousResponses, scoringMatrix) } \arguments{ -\item{previousResponses}{character vector of previous responses} +\item{previousResponses}{a character vector of previous responses} -\item{scoringMatrix}{matrix describing how responses (described in rownames +\item{scoringMatrix}{a matrix describing how responses (described in rownames of the matrix) map on \emph{scores} of latent traits (described in columns of the matrix)} } \value{ -one-column matrix of 0 and 1 with the same number of rows +a one-column matrix of 0 and 1 with the same number of rows (and rownames) as \code{scoringMatrix} with column name \emph{ci} } \description{ @@ -49,7 +49,7 @@ in a form of creating various \emph{graphical patterns} of responses: } } \examples{ -sM <- make_scoring_matrix_aem(1:5, "simultaneous") +sM <- make_scoring_matrix_aem(1:5, "gpcm") answersStraigth <- t(sapply(c(1:5, rep(3, 5)), score_on_last_answer_straight, diff --git a/man/thresholds_and_intercepts.Rd b/man/thresholds_and_intercepts.Rd index bc60875..f13a8dc 100644 --- a/man/thresholds_and_intercepts.Rd +++ b/man/thresholds_and_intercepts.Rd @@ -10,7 +10,7 @@ \alias{intercepts2thresholds.data.frame} \alias{intercepts2thresholds.matrix} \alias{intercepts2thresholds.default} -\title{Conversion between thresholds and intercepts parametrisations} +\title{Conversion between thresholds and intercepts parametrizations} \usage{ thresholds2intercepts(thresholds) @@ -29,19 +29,19 @@ intercepts2thresholds(intercepts) \method{intercepts2thresholds}{default}(intercepts) } \arguments{ -\item{thresholds}{vector or matrix (thresholds in cols, items in rows) of +\item{thresholds}{a vector or matrix (thresholds in cols, items in rows) of item thresholds} -\item{intercepts}{vector or matrix (intercepts in cols, items in rows) of +\item{intercepts}{a vector or matrix (intercepts in cols, items in rows) of item intercepts} } \value{ -vector or matrix of thresholds or intercepts6 +a vector or matrix of thresholds or intercepts6 } \description{ Utility functions allowing to covert \emph{thresholds} (i.e. -parameterisation convienient to think of item steps difficulty) to -\emph{intercepts} (i.e. parameterisation used internally by packages +parameterization convenient to think of item steps difficulty) to +\emph{intercepts} (i.e. parameterization used internally by packages \emph{rstyles} and \emph{mirt}) or back forth. } \examples{ diff --git a/tests/testthat/test_careless_inattentive.R b/tests/testthat/test_careless_inattentive.R index e178ac2..2c6c43e 100644 --- a/tests/testthat/test_careless_inattentive.R +++ b/tests/testthat/test_careless_inattentive.R @@ -1,9 +1,9 @@ ################################################################################ # setup set.seed(26112020) -# generating items' parameters - "simultaenous" mode +# generating items' parameters - GPCM mode nItems <- 20 -sMSml <- make_scoring_matrix_aem(1:5, "simultaneous") +sMSml <- make_scoring_matrix_aem(1:5, "gpcm") slopesSml <- cbind(ci = rep(10, nItems), generate_slopes(nItems, sMSml[, 1, drop = FALSE], FUN = rlnorm, meanlog = 0, sdlog = 0.3), @@ -43,108 +43,108 @@ expectedTraitsSqt <- setNames(c(2, rep(0, ncol(vcovTraitsSqt) - 1)), colnames(vcovTraitsSqt)) thetaSqt = mnormt::rmnorm(50, mean = expectedTraitsSqt, varcov = vcovTraitsSqt) ################################################################################ -# very intense straightlining with aem - simultaneous -itemsSSml <- make_test(sMSml, slopesSml, interceptsSml, "simultaneous", +# very intense straightlining with aem - gpcm +itemsSSml <- make_test(sMSml, slopesSml, interceptsSml, "gpcm", scoringOnPreviousResponses = score_on_last_answer_straight) set.seed(26112020) respSSml <- generate_test_responses(thetaSml, itemsSSml) -test_that("Straightlining can be generated with simultaneous A, M, E RS", { +test_that("Straightlining can be generated with GPCMs A, M, E RS", { expect_true(all(apply(respSSml, 1, function(x) {return(length(unique(x)))}) == 1)) }) ################################################################################ -# very intense straightlining with mae - sequential -itemsSSqt <- make_test(sMSqt, slopesSqt, interceptsSqt, "sequential", +# very intense straightlining with mae - IRTree +itemsSSqt <- make_test(sMSqt, slopesSqt, interceptsSqt, "irtree", scoringOnPreviousResponses = score_on_last_answer_straight) set.seed(26112020) respSSqt <- generate_test_responses(thetaSqt, itemsSSqt) -test_that("Straightlining can be generated with sequential A, M, E RS", { +test_that("Straightlining can be generated with IRTrees A, M, E RS", { expect_true(all(apply(respSSqt, 1, function(x) {return(length(unique(x)))}) == 1)) }) ################################################################################ -# very "bouncing" C/IRS with aem - simultaneous -itemsBSml <- make_test(sMSml, slopesSml, interceptsSml, "simultaneous", +# very "bouncing" C/IRS with aem - GPCM +itemsBSml <- make_test(sMSml, slopesSml, interceptsSml, "gpcm", scoringOnPreviousResponses = score_on_previous_answers_bounce) set.seed(26112020) respBSml <- generate_test_responses(thetaSml, itemsBSml) -test_that("'Bouncing' pattern can be generated with simultaneous A, M, E RS", { +test_that("'Bouncing' pattern can be generated with GPCMs A, M, E RS", { expect_true(all(apply(respBSml, 1, function(x) { pattern = rep(c(1:5, 4:2), 3) return(all(x == pattern[x[1]:(x[1] + 19)])) }))) }) ################################################################################ -# very "bouncing" C/IRS with mae - sequential -itemsBSqt <- make_test(sMSqt, slopesSqt, interceptsSqt, "sequential", +# very "bouncing" C/IRS with mae - IRTree +itemsBSqt <- make_test(sMSqt, slopesSqt, interceptsSqt, "irtree", scoringOnPreviousResponses = score_on_previous_answers_bounce) set.seed(26112020) respBSqt <- generate_test_responses(thetaSqt, itemsBSqt) -test_that("'Bouncing' pattern can be generated with sequential A, M, E RS", { +test_that("'Bouncing' pattern can be generated with IRTrees A, M, E RS", { expect_true(all(apply(respBSqt, 1, function(x) { pattern = rep(c(1:5, 4:2), 3) return(all(x == pattern[x[1]:(x[1] + 19)])) }))) }) ################################################################################ -# very 'next answer' C/IRS with aem - simultaneous -itemsNSml <- make_test(sMSml, slopesSml, interceptsSml, "simultaneous", +# very 'next answer' C/IRS with aem - GPCM +itemsNSml <- make_test(sMSml, slopesSml, interceptsSml, "gpcm", scoringOnPreviousResponses = score_on_last_answer_next) set.seed(26112020) respNSml <- generate_test_responses(thetaSml, itemsNSml) -test_that("'Next answer' pattern can be generated with simultaneous A, M, E RS", { +test_that("'Next answer' pattern can be generated with GPCMs A, M, E RS", { expect_true(all(apply(respNSml, 1, function(x) { pattern = rep(1:5, 5) return(all(x == pattern[x[1]:(x[1] + 19)])) }))) }) ################################################################################ -# very 'next answer' C/IRS with mae - sequential -itemsNSqt <- make_test(sMSqt, slopesSqt, interceptsSqt, "sequential", +# very 'next answer' C/IRS with mae - IRTree +itemsNSqt <- make_test(sMSqt, slopesSqt, interceptsSqt, "irtree", scoringOnPreviousResponses = score_on_last_answer_next) set.seed(26112020) respNSqt <- generate_test_responses(thetaSqt, itemsNSqt) -test_that("'Next answer' pattern can be generated with sequential A, M, E RS", { +test_that("'Next answer' pattern can be generated with IRTrees A, M, E RS", { expect_true(all(apply(respNSqt, 1, function(x) { pattern = rep(1:5, 5) return(all(x == pattern[x[1]:(x[1] + 19)])) }))) }) ################################################################################ -# very 'previous answer' C/IRS with aem - simultaneous -itemsPSml <- make_test(sMSml, slopesSml, interceptsSml, "simultaneous", +# very 'previous answer' C/IRS with aem - GPCM +itemsPSml <- make_test(sMSml, slopesSml, interceptsSml, "gpcm", scoringOnPreviousResponses = score_on_last_answer_previous) set.seed(26112020) respPSml <- generate_test_responses(thetaSml, itemsPSml) -test_that("'Previous answer' pattern can be generated with simultaneous A, M, E RS", { +test_that("'Previous answer' pattern can be generated with GPCMs A, M, E RS", { expect_true(all(apply(respPSml, 1, function(x) { pattern = rep(5:1, 5) return(all(x == pattern[(6 - x[1]):(6 - x[1] + 19)])) }))) }) ################################################################################ -# very "'previous answer' C/IRS with mae - sequential -itemsPSqt <- make_test(sMSqt, slopesSqt, interceptsSqt, "sequential", +# very "'previous answer' C/IRS with mae - IRTree +itemsPSqt <- make_test(sMSqt, slopesSqt, interceptsSqt, "irtree", scoringOnPreviousResponses = score_on_last_answer_previous) set.seed(26112020) respPSqt <- generate_test_responses(thetaSqt, itemsPSqt) -test_that("'Previous answer' pattern can be generated with sequential A, M, E RS", { +test_that("'Previous answer' pattern can be generated with IRTrees A, M, E RS", { expect_true(all(apply(respPSqt, 1, function(x) { pattern = rep(5:1, 5) return(all(x == pattern[(6 - x[1]):(6 - x[1] + 19)])) diff --git a/tests/testthat/test_scoring_matrices.R b/tests/testthat/test_scoring_matrices.R index 1e57023..7c28eeb 100644 --- a/tests/testthat/test_scoring_matrices.R +++ b/tests/testthat/test_scoring_matrices.R @@ -60,7 +60,7 @@ mae9rm3e2 <- matrix(c(0L, 0L, NA, 1L, 1L, NA, NA, test_that("", { expect_identical(make_scoring_matrix_aem(5, "mae"), bockenholtMAE5) expect_identical(make_scoring_matrix_aem(6, "aem"), bockenholtAEM6) - expect_identical(make_scoring_matrix_aem(5, "simultaneous"), plieninger5) + expect_identical(make_scoring_matrix_aem(5, "gpcm"), plieninger5) expect_identical(make_scoring_matrix_aem(10, "ema", nMiddle = 3, nExtreme = 2), ema10m3e2) expect_identical(make_scoring_matrix_aem(10, "ema", nMiddle = 3, diff --git a/tests/testthat/test_sequential_mae5.R b/tests/testthat/test_sequential_mae5.R index 2997a61..2c14016 100644 --- a/tests/testthat/test_sequential_mae5.R +++ b/tests/testthat/test_sequential_mae5.R @@ -5,7 +5,7 @@ sM <- make_scoring_matrix_aem(1:5, "mae") slopes <- generate_slopes(nItems, sM, FUN = rlnorm, meanlog = 0, sdlog = 0.2) intercepts <- generate_intercepts(nItems, sM, FUNd = rnorm, argsd = list(mean = 0, sd = 1.5)) -items <- make_test(sM, slopes, intercepts, "sequential") +items <- make_test(sM, slopes, intercepts, "irtree") # generating "subjects" - uncorrelated traits vcovTraits <- matrix(0, nrow = ncol(sM), ncol = ncol(sM), @@ -31,7 +31,7 @@ estItemPars <- cbind(m = estItemPars[1:nItems, 1], dm = estItemPars[1:nItems, 4], da = estItemPars[(nItems + 1):(2*nItems), 4], de = estItemPars[(2*nItems + 1):(3*nItems), 4]) -test_that("Item parameters of sequential M, A, E RS (with 5-point scale) recovers in estimation with reasonable MSEs.", { +test_that("Item parameters of IRTrees M, A, E RS (with 5-point scale) recovers in estimation with reasonable MSEs.", { expect_lt(mean((slopes[, 1] - estItemPars[, 1])^2), 0.02) expect_lt(mean((slopes[, 2] - estItemPars[, 2])^2), 0.09) expect_lt(mean((slopes[, 3] - estItemPars[, 3])^2), 0.05) diff --git a/tests/testthat/test_sequential_mae6.R b/tests/testthat/test_sequential_mae6.R index 4a8d2fe..ce1caab 100644 --- a/tests/testthat/test_sequential_mae6.R +++ b/tests/testthat/test_sequential_mae6.R @@ -5,7 +5,7 @@ sM <- make_scoring_matrix_aem(1:6, "mae") slopes <- generate_slopes(nItems, sM, FUN = rlnorm, meanlog = 0, sdlog = 0.2) intercepts <- generate_intercepts(nItems, sM, FUNd = rnorm, argsd = list(mean = 0, sd = 1.5)) -items <- make_test(sM, slopes, intercepts, "sequential") +items <- make_test(sM, slopes, intercepts, "irtree") # generating "subjects" - uncorrelated traits vcovTraits <- matrix(0, nrow = 3, ncol = 3, @@ -34,7 +34,7 @@ estItemPars <- cbind(m = estItemPars[1:nItems, 1], da1 = estItemPars[(nItems + 1):(2*nItems), 5], da2 = estItemPars[(2*nItems + 1):(3*nItems), 5], de = estItemPars[(3*nItems + 1):(4*nItems), 5]) -test_that("Item parameters of sequential M, A, E RS (with 6-point scale) recovers in estimation with reasonable MSEs.", { +test_that("Item parameters of IRTrees M, A, E RS (with 6-point scale) recovers in estimation with reasonable MSEs.", { expect_lt(mean((slopes[, 1] - estItemPars[, 1])^2), 0.02) expect_lt(mean((slopes[, 2] - estItemPars[, 2])^2), 0.05) expect_lt(mean((slopes[, 3] - estItemPars[, 3])^2), 0.05) diff --git a/tests/testthat/test_simultaneous_aem.R b/tests/testthat/test_simultaneous_aem.R index bebecaf..03e929d 100644 --- a/tests/testthat/test_simultaneous_aem.R +++ b/tests/testthat/test_simultaneous_aem.R @@ -1,7 +1,7 @@ set.seed(10122020) # generating test nItems <- 20 -sM <- make_scoring_matrix_aem(1:5, "simultaneous") +sM <- make_scoring_matrix_aem(1:5, "gpcm") slopes <- cbind(generate_slopes(nItems, sM[, 1L, drop = FALSE], FUN = rlnorm, meanlog = 0, sdlog = 0.3, nReversed = floor(nItems / 2)), @@ -11,7 +11,7 @@ intercepts <- generate_intercepts(nItems, sM, FUNt = seq, argst = list(from = 0.9, to = -0.9, length.out = 4)) -items <- make_test(sM, slopes, intercepts, "simultaneous") +items <- make_test(sM, slopes, intercepts, "gpcm") # generating "subjects" - uncorrelated traits vcovTraits <- matrix(0, nrow = ncol(sM), ncol = ncol(sM), @@ -34,7 +34,7 @@ mSml <- suppressMessages(mirt(resp, method = "EM", TOL = 0.1, verbose = FALSE)) estItemPars <- coef(mSml, simplify = TRUE)$items intercepts <- intercepts[, -1] -test_that("Item parameters of simultaneous A, E, M RS (with 5-point scale) recovers in estimation with reasonable MSEs.", { +test_that("Item parameters of GPCMs A, E, M RS (with 5-point scale) recovers in estimation with reasonable MSEs.", { expect_lt(mean((slopes[, 1] - estItemPars[, 1])^2), 0.17) expect_lt(mean((slopes[, 2] - estItemPars[, 2])^2), 0.023) expect_lt(mean((slopes[, 3] - estItemPars[, 3])^2), 0.016)