├── .Rbuildignore ├── .Renviron ├── .github └── workflows │ └── R-CMD-check.yaml ├── .gitignore ├── DESCRIPTION ├── ItemResponseTrees.Rproj ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── ItemResponeTrees-package.R ├── ItemResponseTrees-deprecated.R ├── assertions.R ├── data.R ├── extract-mirt-output.R ├── extract-mplus-output.R ├── fit-mirt.R ├── fit-mplus.R ├── fit-tam.R ├── fit.R ├── generate-data.R ├── generate-pcm-data.R ├── helpers.R ├── irtree_paste.R ├── pseudoitems.R ├── s3_tree_model.R ├── s3_tree_model_helpers.R ├── simulation.R └── tidiers.R ├── README.Rmd ├── README.md ├── cran-comments.md ├── data-raw └── jackson.R ├── data └── jackson.rda ├── inst ├── CITATION ├── WORDLIST └── examples │ ├── example-fit.R │ ├── example-generate-data.R │ ├── example-sim.R │ └── example-tidiers.R ├── man ├── ItemResponseTrees-deprecated.Rd ├── ItemResponseTrees-package.Rd ├── augment.irtree_fit.Rd ├── clps.Rd ├── control_mirt.Rd ├── control_mplus.Rd ├── control_tam.Rd ├── extract_mirt_output-deprecated.Rd ├── extract_mplus_output-deprecated.Rd ├── fit.irtree_model.Rd ├── glance.irtree_fit.Rd ├── irtree_create_template.Rd ├── irtree_fit_mirt.Rd ├── irtree_fit_mplus.Rd ├── irtree_fit_tam.Rd ├── irtree_gen_data.Rd ├── irtree_gen_pcm.Rd ├── irtree_gen_tree.Rd ├── irtree_model.Rd ├── irtree_recode.Rd ├── irtree_sim.Rd ├── irtree_sim1.Rd ├── jackson.Rd ├── pseudoitems.Rd ├── reexports.Rd ├── rtruncatednorm.Rd ├── tidy.irtree_fit.Rd ├── write_mirt_input.Rd └── write_mplus_input.Rd ├── tests ├── spelling.R ├── testthat.R └── testthat │ ├── fit-mplus-res1.rds │ ├── fit-mplus-res2.rds │ ├── fit-mplus-res3.rds │ ├── helper.R │ ├── test-constraints.R │ ├── test-fit-mirt.R │ ├── test-fit-mplus.R │ ├── test-fit-tam.R │ ├── test-generate-data.R │ ├── test-jackson.R │ ├── test-misc.R │ ├── test-misspecified-models.R │ └── test-simulation.R ├── tools ├── .gitignore ├── ItemResponseTrees.png ├── ecn-model.pdf ├── ecn-model.png └── ecn-model.tex └── vignettes ├── .gitignore └── ItemResponseTrees-Getting-started-with-IR-trees.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^codecov\.yml$ 2 | ^\.travis\.yml$ 3 | ^README\.Rmd$ 4 | ^.*\.Rproj$ 5 | ^\.Rproj\.user$ 6 | ^LICENSE\.md$ 7 | misc/ 8 | vignettes/.*_cache/ 9 | ^doc$ 10 | ^Meta$ 11 | ^.notes.md$ 12 | ^data-raw$ 13 | ^cran-comments\.md$ 14 | ^CRAN-RELEASE$ 15 | ^\.github$ 16 | -------------------------------------------------------------------------------- /.Renviron: -------------------------------------------------------------------------------- 1 | # _R_CHECK_LENGTH_1_CONDITION_="package:ItemResponseTrees" 2 | # _R_CHECK_LENGTH_1_LOGIC2_="package:ItemResponseTrees" 3 | _R_CHECK_LENGTH_1_CONDITION_=true 4 | _R_CHECK_LENGTH_1_LOGIC2_=true 5 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | - devel 6 | pull_request: 7 | branches: 8 | - master 9 | 10 | name: R-CMD-check 11 | 12 | jobs: 13 | R-CMD-check: 14 | runs-on: ${{ matrix.config.os }} 15 | 16 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | config: 22 | # - {os: macOS-latest, r: 'devel'} 23 | - {os: macOS-latest, r: 'release'} 24 | # - {os: windows-latest, r: 'release'} 25 | # - {os: ubuntu-16.04, r: 'release', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} 26 | - {os: ubuntu-16.04, r: '3.6', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} 27 | 28 | env: 29 | R_REMOTES_NO_ERRORS_FROM_WARNINGS: true 30 | RSPM: ${{ matrix.config.rspm }} 31 | 32 | steps: 33 | - uses: actions/checkout@v2 34 | 35 | - uses: r-lib/actions/setup-r@master 36 | with: 37 | r-version: ${{ matrix.config.r }} 38 | 39 | - uses: r-lib/actions/setup-pandoc@master 40 | 41 | - name: Query dependencies 42 | run: | 43 | install.packages('remotes') 44 | saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) 45 | writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") 46 | shell: Rscript {0} 47 | 48 | - name: Cache R packages 49 | if: runner.os != 'Windows' 50 | uses: actions/cache@v1 51 | with: 52 | path: ${{ env.R_LIBS_USER }} 53 | key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} 54 | restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- 55 | 56 | - name: Install system dependencies 57 | if: runner.os == 'Linux' 58 | env: 59 | RHUB_PLATFORM: linux-x86_64-ubuntu-gcc 60 | run: | 61 | Rscript -e "remotes::install_github('r-hub/sysreqs')" 62 | sysreqs=$(Rscript -e "cat(sysreqs::sysreq_commands('DESCRIPTION'))") 63 | sudo -s eval "$sysreqs" 64 | 65 | - name: Install dependencies 66 | run: | 67 | remotes::install_deps(dependencies = TRUE) 68 | remotes::install_cran("rcmdcheck") 69 | shell: Rscript {0} 70 | 71 | - name: Session info 72 | run: | 73 | options(width = 100) 74 | pkgs <- installed.packages()[, "Package"] 75 | sessioninfo::session_info(pkgs, include_base = TRUE) 76 | shell: Rscript {0} 77 | 78 | - name: Check 79 | env: 80 | _R_CHECK_CRAN_INCOMING_: false 81 | run: rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check") 82 | shell: Rscript {0} 83 | 84 | - name: Show testthat output 85 | if: always() 86 | run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true 87 | shell: bash 88 | 89 | - name: Upload check results 90 | if: failure() 91 | uses: actions/upload-artifact@master 92 | with: 93 | name: ${{ runner.os }}-r${{ matrix.config.r }}-results 94 | path: check 95 | 96 | - name: Test coverage 97 | if: matrix.config.os == 'macOS-latest' && matrix.config.r == 'release' 98 | run: covr::codecov() 99 | shell: Rscript {0} 100 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # History files 2 | .Rhistory 3 | .Rapp.history 4 | 5 | # Session Data files 6 | .RData 7 | 8 | # User-specific files 9 | .Ruserdata 10 | 11 | # Example code in package build process 12 | *-Ex.R 13 | 14 | # Output files from R CMD build 15 | /*.tar.gz 16 | 17 | # Output files from R CMD check 18 | /*.Rcheck/ 19 | 20 | # RStudio files 21 | .Rproj.user/ 22 | 23 | # produced vignettes 24 | vignettes/*.html 25 | vignettes/*.pdf 26 | 27 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 28 | .httr-oauth 29 | 30 | # knitr and R markdown default cache directories 31 | /cache/ 32 | *_cache/ 33 | 34 | # Temporary files created by R markdown 35 | *.utf8.md 36 | *.knit.md 37 | 38 | .Rproj.user 39 | inst/doc 40 | /misc/ 41 | doc 42 | Meta 43 | .notes.md 44 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: ItemResponseTrees 2 | Title: IR-Tree Modeling in 'mirt', 'Mplus', or 'TAM' 3 | Version: 0.2.5 4 | Authors@R: person("Hansjörg", "Plieninger", email = "mail@hansjoerg.me", 5 | role = c("aut", "cre"), comment = c(ORCID = "0000-0002-4416-300X")) 6 | Description: Item response tree (IR-tree) models are a class of item response 7 | theory (IRT) models that assume that the responses to polytomous items can 8 | best be explained by multiple psychological processes; see Böckenholt 9 | (2012) for details. The package 10 | 'ItemResponseTrees' allows to fit such IR-tree models in 'mirt', 'Mplus', or 11 | 'TAM'. The package automates some of the hassle of IR-tree modeling by means 12 | of a consistent syntax. This allows new users to quickly adopt this model 13 | class, and this allows experienced users to fit many complex models 14 | effortlessly. 15 | License: MIT + file LICENSE 16 | URL: https://github.com/hplieninger/ItemResponseTrees 17 | BugReports: https://github.com/hplieninger/ItemResponseTrees/issues 18 | Depends: 19 | R (>= 3.6.0) 20 | Imports: 21 | checkmate (>= 1.9), 22 | dplyr (>= 1.0.0), 23 | generics, 24 | glue, 25 | magrittr, 26 | MASS, 27 | Matrix, 28 | methods, 29 | mirt (>= 1.30), 30 | purrr, 31 | rlang (>= 0.4.0), 32 | sets, 33 | stringr, 34 | tibble, 35 | tidyr (>= 1.0.0), 36 | tidyselect, 37 | vctrs 38 | Suggests: 39 | covr, 40 | future, 41 | knitr, 42 | listenv, 43 | lme4, 44 | modeltests (>= 0.1.0), 45 | MplusAutomation (>= 0.7), 46 | progress, 47 | rmarkdown, 48 | roxygen2, 49 | simsalapar, 50 | sfsmisc, 51 | spelling, 52 | TAM (>= 3.5-19), 53 | testthat (>= 2.1.0) 54 | Encoding: UTF-8 55 | LazyData: true 56 | Roxygen: list(markdown = TRUE) 57 | RoxygenNote: 7.1.0 58 | VignetteBuilder: knitr 59 | Language: en-US 60 | -------------------------------------------------------------------------------- /ItemResponseTrees.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 4 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageCheckArgs: --run-donttest 22 | PackageRoxygenize: rd,collate,namespace,vignette 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2018 2 | COPYRIGHT HOLDER: Hansjörg Plieninger 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2018 Hansjörg Plieninger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(augment,irtree_fit) 4 | S3method(coef,irtree_fit) 5 | S3method(fit,irtree_model) 6 | S3method(glance,irtree_fit) 7 | S3method(print,irtree_fit) 8 | S3method(print,irtree_model) 9 | S3method(summary,irtree_fit) 10 | S3method(tidy,irtree_fit) 11 | export(augment) 12 | export(control_mirt) 13 | export(control_mplus) 14 | export(control_tam) 15 | export(extract_mirt_output) 16 | export(extract_mplus_output) 17 | export(fit) 18 | export(glance) 19 | export(irtree_create_template) 20 | export(irtree_gen_data) 21 | export(irtree_model) 22 | export(irtree_recode) 23 | export(irtree_sim) 24 | export(tidy) 25 | import(stats) 26 | importFrom(generics,augment) 27 | importFrom(generics,fit) 28 | importFrom(generics,glance) 29 | importFrom(generics,tidy) 30 | importFrom(magrittr,"%>%") 31 | importFrom(methods,formalArgs) 32 | importFrom(rlang,.data) 33 | importFrom(tibble,tibble) 34 | importFrom(utils,capture.output) 35 | importFrom(utils,packageVersion) 36 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # ItemResponseTrees 0.2.5 2 | 3 | * Fixed tests and vignette that failed due to changes in package mirt version 1.32.1. 4 | * `augment()` now consistently prefixes new columns with `.fitted.` or `.se.fit.`. 5 | Furthermore, its argument `se.fit` was renamed to `se_fit` (as recommended by package modeltests). 6 | * Uncoupled README from the vignette; README is now much shorter. 7 | * R (>= 3.6.0) is now required in alignment with the dependency mirt. 8 | 9 | # ItemResponseTrees 0.2.4 (2020-04-24) 10 | 11 | * Condition handling was refactored (e.g., `myTryCatch()` was replaced with `simsalapar::tryCatch.W.E()`). 12 | * `sym_diff()` was replaced with `sets::set_symdiff()`, and `sort2()` was removed. 13 | 14 | # ItemResponseTrees 0.2.3 15 | 16 | * Minor, internal changes in the documentation 17 | 18 | # ItemResponseTrees 0.2.2 19 | 20 | * Modified package Description field for CRAN 21 | * Modified vignette, `install.packages` was commented out. 22 | * Credit to Jackson for the `jackson` data and Dontas for `sort2()` is now given via appropriate attribution rather than usage of `\author`. 23 | 24 | # ItemResponseTrees 0.2.1 25 | 26 | * Modified package Title field for CRAN 27 | 28 | # ItemResponseTrees 0.2.0 29 | 30 | ## Breaking changes 31 | 32 | * In the model syntax, the section *Subtree* is deprecated and was merged with section *Constraints*. 33 | * Long variable names are no longer automatically replaced. They are now accepted for mirt and TAM, but no longer for Mplus. 34 | * `logit` is now the default link function everywhere. 35 | * `mirt` is now the default engine in `fit()`. 36 | 37 | ## Improvements 38 | 39 | * More complex models using constraints (that were only available for Mplus) are now implemented for mirt and TAM. 40 | * The new function `irtree_create_template()` helps to create a model string. 41 | * Data set `jackson` (Big Five questionnaire) was added. 42 | * Many internal changes to increase robustness. 43 | * Controlling features of the engine in `fit()` is now done via a single `control` argument (rather than `...`), and helper functions `control_()` make that painless. 44 | * The documentation was improved. 45 | * The output of `tidy()` was enhanced (added columns `parameter` and `component`), and the argument `par_type` is now required for mirt. 46 | 47 | # ItemResponseTrees 0.1.1 48 | 49 | * Add wrapper functions for the TAM package 50 | * Add the multidimensional partial credit model (not an IR-tree model); works only in TAM 51 | 52 | # ItemResponseTrees 0.1 53 | 54 | * First stable version 55 | -------------------------------------------------------------------------------- /R/ItemResponeTrees-package.R: -------------------------------------------------------------------------------- 1 | #' IR-Tree Modeling in mirt, Mplus, or TAM 2 | #' 3 | #' Item response tree (IR-tree) models are a class of item response theory (IRT) 4 | #' models that assume that the responses to polytomous items can best be 5 | #' explained by multiple psychological processes (e.g., Böckenholt, 2012, 6 | #' \url{https://dx.doi.org/10.1037/a0028111}). The package 'ItemResponseTrees' 7 | #' allows to fit such IR-tree models in 8 | #' [mirt](https://cran.r-project.org/package=mirt), 9 | #' [Mplus](https://cran.r-project.org/package=MplusAutomation), or 10 | #' [TAM](https://cran.r-project.org/package=TAM). The package automates some of 11 | #' the hassle of IR-tree modeling by means of a consistent syntax. This allows 12 | #' new users to quickly adopt this model class, and this allows experienced 13 | #' users to fit many complex models effortlessly. 14 | #' 15 | #' @references Böckenholt, U. (2012). Modeling multiple response processes in 16 | #' judgment and choice. *Psychological Methods*, *17*(4), 665–678. 17 | #' https://doi.org/10.1037/a0028111 18 | #' @references Plieninger, H. (2020). Developing and applying IR-tree models: 19 | #' Guidelines, caveats, and an extension to multiple groups. *Organizational 20 | #' Research Methods*. Advance online publication. 21 | #' https://doi.org/10.1177/1094428120911096 22 | #' 23 | #' @docType package 24 | #' @name ItemResponseTrees-package 25 | #' @aliases ItemResponseTrees 26 | #' @author Hansjörg Plieninger 27 | #' 28 | #' @import stats 29 | #' @importFrom utils capture.output packageVersion 30 | #' @importFrom methods formalArgs 31 | #' @importFrom rlang .data 32 | #' @importFrom magrittr %>% 33 | #' @importFrom tibble tibble 34 | #' 35 | #' @encoding UTF-8 36 | #' @keywords internal 37 | "_PACKAGE" 38 | 39 | if (getRversion() >= "2.15.1") utils::globalVariables(c(".")) 40 | -------------------------------------------------------------------------------- /R/ItemResponseTrees-deprecated.R: -------------------------------------------------------------------------------- 1 | #' Deprecated functions in package **ItemResponseTrees** 2 | #' 3 | #' These functions are provided for compatibility with older versions only, 4 | #' and may be defunct as soon as the next release. 5 | #' 6 | #' @details The original help page for these functions is available at 7 | #' help("oldName-deprecated") (note the quotes). 8 | #' 9 | # \itemize{ 10 | # \item `extract_mplus_output()`: This function is deprecated, and will be 11 | # removed in future versions. Use `glance()`, `tidy()`, and `augment()` 12 | # instead. 13 | # \item `extract_mirt_output()`: This function is deprecated, and will be 14 | # removed in future versions. Use `glance()`, `tidy()`, and `augment()` 15 | # instead. 16 | # } 17 | #' 18 | #' @name ItemResponseTrees-deprecated 19 | NULL 20 | -------------------------------------------------------------------------------- /R/assertions.R: -------------------------------------------------------------------------------- 1 | assert_irtree_data <- function(data = NULL, 2 | object = NULL, 3 | engine = NULL, 4 | set_min_to_0 = FALSE) { 5 | 6 | ### Is it a data frame? ### 7 | 8 | checkmate::assert_data_frame(data, all.missing = FALSE, min.rows = 1) 9 | 10 | ### Has it the correct colnames? ### 11 | 12 | if (engine == "mplus") { 13 | checkmate::assert_character(object$covariates, min.chars = 1, 14 | pattern = "^[[:alpha:]][[:alnum:]_]*$", 15 | any.missing = FALSE, unique = TRUE, 16 | null.ok = TRUE, .var.name = "Addendum in object") 17 | 18 | # assert_nchar(object$j_names, 8) 19 | assert_nchar(object$covariates, 8) 20 | checkmate::assert_subset(x = c(object$j_names, object$covariates), 21 | choices = names(data), 22 | empty.ok = FALSE, .var.name = "variable names") 23 | } else { 24 | checkmate::assert_set_equal(names(data), object$j_names) 25 | } 26 | 27 | ### Are all vars integers? ### 28 | 29 | checkmate::assert_data_frame(data[object$j_names], 30 | types = "integerish") 31 | 32 | ### Is the range of the vars in line with 'object'? ### 33 | 34 | # tam: is the minimum == 0? 35 | # test not necessary for tree, because I recode the data 36 | 37 | categ_dat <- na.omit(unique(unlist(data[object$j_names], use.names = FALSE))) 38 | 39 | if (object$class == "tree") { 40 | if (length(sets::set_symdiff(as.numeric(categ_dat), 41 | as.numeric(object$k_names))) > 0) { 42 | stop("'data' has categories ", clps(", ", sort(categ_dat)), 43 | " but 'object' has equations for categories ", clps(", ", object$k_names), "." 44 | , call. = FALSE) 45 | } 46 | } else if (object$class == "pcm" && engine == "tam") { 47 | if (length(sets::set_symdiff(as.numeric(categ_dat), 48 | as.numeric(object$k_names))) > 0) { 49 | if (min(data[object$j_names] != 0, na.rm = TRUE)) { 50 | stop("Minimum of data is not equal to zero. ", 51 | "You should recode your data or set 'set_min_to_0 = TRUE'.", 52 | call. = FALSE) 53 | } 54 | stop("'data' has categories ", clps(", ", sort(categ_dat)), 55 | " but 'object' has weights for categories ", clps(", ", object$k_names), "." 56 | , call. = FALSE) 57 | } 58 | } else if (object$class == "grm") { 59 | # No test possible, because 'object' contains no information about the 60 | # number of categories 61 | } 62 | } 63 | 64 | assert_irtree_proper <- function(object = NULL, improper_okay = FALSE) { 65 | checkmate::qassert(improper_okay, "B1") 66 | if (improper_okay == FALSE & object$proper_model == FALSE) { 67 | stop("The model seems to be an improper model. You might set ", 68 | "'improper_okay' to TRUE, but do this only if you really ", 69 | "know what you are doing.", call. = FALSE) 70 | } 71 | } 72 | 73 | check_nchar <- function(x, max.chars = 8, any.missing = FALSE) { 74 | nchars <- nchar(x) 75 | if (!isFALSE(any(nchars > max.chars, na.rm = any.missing))) { 76 | paste("All elements must have at most", max.chars, "characters.", 77 | "Longest element:", x[which.max(nchars)]) 78 | } else { 79 | TRUE 80 | } 81 | } 82 | assert_nchar <- checkmate::makeAssertionFunction(check_nchar) 83 | 84 | has_namespace <- function(x) { 85 | f1 <- function(x) { 86 | try( 87 | suppressPackageStartupMessages( 88 | requireNamespace(as.character(x), quietly = TRUE)), silent = TRUE) 89 | } 90 | res <- vapply(x, f1, TRUE) 91 | if (all(res)) { 92 | return(invisible(TRUE)) 93 | } else { 94 | stop("Some packages are missing, please run: ", 95 | glue::glue("install.packages(c({paste0('\"', x[!res], '\"', collapse = ', ')}))") 96 | , call. = FALSE) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /R/data.R: -------------------------------------------------------------------------------- 1 | #' IPIP Big Five personality test answers (data set) 2 | #' 3 | #' @description "This is data from an online big five personality test: 4 | #' http://personality-testing.info/tests/BIG5.php. The following items 5 | #' were rated on a likert scale from 1=disagree to 5=agree in relation to how 6 | #' much they applied to the test taker, they were presented to the taker 5 per 7 | #' page" (Jackson, 2012). 8 | #' 9 | #' The following items are reverse keyed and were already recoded: `A1`, `E2`, 10 | #' `C2`, `N2`, `O2`, `A3`, `E4`, `C4`, `N4`, `O4`, `A5`, `E6`, `C6`, `O6`, `A7`, 11 | #' `E8`, `C8`, and `E10`. 12 | #' 13 | #' @details The data set included here is Version 3 from 15.10.2012. It was 14 | #' released by Andrew Jackson under the [CC BY 15 | #' 4.0](https://creativecommons.org/licenses/by/4.0/) license. 16 | #' 17 | #' @format A data frame with 9051 rows and 58 variables: 18 | #' \describe{ 19 | #' \item{E1}{Am the life of the party.} 20 | #' \item{A1}{Feel little concern for others.} 21 | #' \item{C1}{Am always prepared.} 22 | #' \item{N1}{Get stressed out easily.} 23 | #' \item{O1}{Have a rich vocabulary.} 24 | #' \item{E2}{Don't talk a lot.} 25 | #' \item{A2}{Am interested in people.} 26 | #' \item{C2}{Leave my belongings around.} 27 | #' \item{N2}{Am relaxed most of the time.} 28 | #' \item{O2}{Have difficulty understanding abstract ideas.} 29 | #' \item{E3}{Feel comfortable around people.} 30 | #' \item{A3}{Insult people.} 31 | #' \item{C3}{Pay attention to details.} 32 | #' \item{N3}{Worry about things.} 33 | #' \item{O3}{Have a vivid imagination.} 34 | #' \item{E4}{Keep in the background.} 35 | #' \item{A4}{Sympathize with others' feelings.} 36 | #' \item{C4}{Make a mess of things.} 37 | #' \item{N4}{Seldom feel blue.} 38 | #' \item{O4}{Am not interested in abstract ideas.} 39 | #' \item{E5}{Start conversations.} 40 | #' \item{A5}{Am not interested in other people's problems.} 41 | #' \item{C5}{Get chores done right away.} 42 | #' \item{N5}{Am easily disturbed.} 43 | #' \item{O5}{Have excellent ideas.} 44 | #' \item{E6}{Have little to say.} 45 | #' \item{A6}{Have a soft heart.} 46 | #' \item{C6}{Often forget to put things back in their proper place.} 47 | #' \item{N6}{Get upset easily.} 48 | #' \item{O6}{Do not have a good imagination.} 49 | #' \item{E7}{Talk to a lot of different people at parties.} 50 | #' \item{A7}{Am not really interested in others.} 51 | #' \item{C7}{Like order} 52 | #' \item{N7}{Change my mood a lot.} 53 | #' \item{O7}{Am quick to understand things.} 54 | #' \item{E8}{Don't like to draw attention to myself.} 55 | #' \item{A8}{Take time out for others.} 56 | #' \item{C8}{Shirk my duties.} 57 | #' \item{N8}{Have frequent mood swings.} 58 | #' \item{O8}{Use difficult words.} 59 | #' \item{E9}{Don't mind being the center of attention.} 60 | #' \item{A9}{Feel others' emotions.} 61 | #' \item{C9}{Follow a schedule.} 62 | #' \item{N9}{Get irritated easily.} 63 | #' \item{O9}{Spend time reflecting on things.} 64 | #' \item{E10}{Am quiet around strangers.} 65 | #' \item{A10}{Make people feel at ease.} 66 | #' \item{C10}{Am exacting in my work.} 67 | #' \item{N10}{Often feel blue.} 68 | #' \item{O10}{Am full of ideas.} 69 | #' \item{gender}{Gender, either female, male, or other} 70 | #' \item{age}{Age} 71 | #' \item{SecondsElapsed}{Seconds elapsed} 72 | #' \item{E}{Scale mean for extraversion} 73 | #' \item{C}{Scale mean for conscientiousness} 74 | #' \item{N}{Scale mean for neuroticism} 75 | #' \item{O}{Scale mean for openness} 76 | #' \item{A}{Scale mean for agreeableness} 77 | #' } 78 | #' @usage data("jackson") 79 | #' @source Jackson, A. (2012). IPIP Big Five personality test answers. 80 | #' \url{https://doi.org/10.6084/m9.figshare.96542.v3} 81 | "jackson" 82 | -------------------------------------------------------------------------------- /R/extract-mirt-output.R: -------------------------------------------------------------------------------- 1 | #' @title Retrieve estimates from mirt. 2 | #' 3 | #' @description This function takes the output from [irtree_fit_mirt()] and 4 | #' returns the parameter estimates in a convenient way. 5 | #' 6 | #' @param results An object of class \code{\link[mirt]{SingleGroupClass-class}} 7 | #' as returned from [irtree_fit_mirt()]. 8 | #' @param object A description of the user-specified model. See 9 | #' [irtree_model] for more information. 10 | #' @param method Passed to [mirt::fscores()]. 11 | #' @param class String specifying which class of model was fit. 12 | #' @param ... Passed to [mirt::fscores()]. 13 | #' @return A list of parameter estimates and model fit information. 14 | #' 15 | #' @name extract_mirt_output-deprecated 16 | #' @usage extract_mirt_output(results = NULL, object = NULL, method = "MAP", 17 | #' class = NULL, ...) 18 | #' @seealso [ItemResponseTrees-deprecated] 19 | #' @keywords internal 20 | NULL 21 | 22 | #' @rdname ItemResponseTrees-deprecated 23 | #' @usage NULL 24 | #' @section `extract_mirt_output()`: 25 | #' This function is deprecated. Use `glance()`, `tidy()`, and `augment()` 26 | #' instead. 27 | #' 28 | #' @export 29 | #' @keywords internal 30 | extract_mirt_output <- function(results = NULL, # nocov start 31 | object = NULL, 32 | method = "MAP", 33 | class = NULL, 34 | ...) { 35 | 36 | .Deprecated("tidy.irtree_fit") 37 | 38 | checkmate::assert_class(results, "SingleGroupClass") 39 | 40 | checkmate::assert_class(object, "irtree_model", null.ok = TRUE) 41 | 42 | e2 <- new.env() 43 | 44 | if (!is.null(object)) { 45 | e2$class <- object$class 46 | } else { 47 | checkmate::assert_choice(class, choices = c("tree", "grm")) 48 | e2$class <- class 49 | } 50 | 51 | cf <- mirt::coef(results, printSE = FALSE, simplify = TRUE) 52 | 53 | sigma <- cf$cov 54 | sigma[upper.tri(sigma)] <- t(sigma)[upper.tri(sigma)] 55 | 56 | cormat <- cov2cor(sigma) 57 | 58 | personpar_est <- as.data.frame(mirt::fscores(results, method = method, ...)) 59 | 60 | itempar <- list() 61 | 62 | if (e2$class == "tree") { 63 | betapar <- data.frame(param = rownames(cf$items), 64 | est = cf$items[, "d"]) 65 | tmp1 <- tidyr::separate(betapar, "param", c("time", "item"), sep = "_") 66 | itempar$beta <- reshape(tmp1, direction = "wide", idvar = "item") 67 | names(itempar$beta) <- sub("^est[.]", "", names(itempar$beta)) 68 | 69 | alphapar <- data.frame(param = rownames(cf$items), 70 | cf$items[, grepl("a\\d+", colnames(cf$items))]) 71 | tmp1 <- tidyr::separate(alphapar, "param", c("time", "item"), sep = "_") 72 | tmp1$est <- NA 73 | for (ii in seq_along(unique(tmp1$time))) { 74 | tmp1[tmp1$time == unique(tmp1$time)[ii], "est"] <- 75 | tmp1[tmp1$time == unique(tmp1$time)[ii], 2 + ii] 76 | } 77 | itempar$alpha <- reshape(dplyr::select(tmp1, .data$time, .data$item, .data$est), 78 | direction = "wide", idvar = "item") 79 | names(itempar$alpha) <- sub("^est[.]", "", names(itempar$alpha)) 80 | 81 | } else if (e2$class == "grm") { 82 | itempar$beta <- data.frame( 83 | param = rownames(cf$items), 84 | cf$items[, grepl("^d\\d+", colnames(cf$items)), drop = FALSE]) 85 | 86 | itempar$alpha <- data.frame( 87 | param = rownames(cf$items), 88 | cf$items[, grepl("a\\d+", colnames(cf$items)), drop = FALSE]) 89 | itempar <- lapply(itempar, `rownames<-`, NULL) 90 | } 91 | 92 | summaries <- mirt::anova(results) 93 | summaries$converged <- mirt::extract.mirt(results, "converged") 94 | summaries$secondordertest <- mirt::extract.mirt(results, "secondordertest") 95 | 96 | out <- list( 97 | person = list(personpar_est = personpar_est 98 | # , personpar_se = personpar_se 99 | ), 100 | item = itempar, 101 | sigma = sigma, 102 | cormat = cormat, 103 | summaries = summaries 104 | ) 105 | 106 | return(out) 107 | } # nocov end 108 | -------------------------------------------------------------------------------- /R/extract-mplus-output.R: -------------------------------------------------------------------------------- 1 | #' Retrieve estimates From Mplus. 2 | #' 3 | #' This function takes the output from Mplus as returned from 4 | #' [irtree_fit_mplus()] and returns the estimates in a convenient way. 5 | #' 6 | #' @param results A list as returned from [irtree_fit_mplus()]. 7 | #' @param object A description of the user-specified model. See 8 | #' [irtree_model] for more information. 9 | #' @param class String specifying which class of model was fit 10 | #' @param .errors2messages Logical indicating whether errors should be converted 11 | #' to messages 12 | #' @return A list of parameter estimates, model fit information 13 | #' (`summaries`), `warnings`, `errors`. 14 | #' 15 | #' @name extract_mplus_output-deprecated 16 | #' @usage extract_mplus_output(results = NULL, object = NULL, class = NULL, 17 | #' .errors2messages = FALSE) 18 | #' @seealso [ItemResponseTrees-deprecated] 19 | #' @keywords internal 20 | NULL 21 | 22 | #' @rdname ItemResponseTrees-deprecated 23 | #' @usage NULL 24 | #' @section `extract_mplus_output()`: 25 | #' This function is deprecated. Use `glance()`, `tidy()`, and `augment()` 26 | #' instead. 27 | #' 28 | #' @export 29 | #' @keywords internal 30 | extract_mplus_output <- function(results = NULL, # nocov start 31 | object = NULL, 32 | class = NULL, 33 | .errors2messages = FALSE) { 34 | .Deprecated("tidy.irtree_fit") 35 | 36 | checkmate::assert_class(results, "mplus.model") 37 | 38 | checkmate::assert_class(object, "irtree_model", null.ok = TRUE) 39 | 40 | tmp1 <- vapply(results$errors, function(x) { 41 | any(stringr::str_detect(x, 42 | "THE MODEL ESTIMATION DID NOT TERMINATE NORMALLY"))}, 43 | FUN.VALUE = logical(1)) 44 | 45 | if (any(tmp1)) { 46 | if (.errors2messages) { 47 | lapply(results$errors[cumsum(tmp1) > 0], function(x) message("Mplus error: ", clps(" ", x), call. = FALSE)) 48 | } else { 49 | stop("Mplus error: ", clps(" ", unlist(results$errors[cumsum(tmp1) > 0])), call. = FALSE) 50 | } 51 | } 52 | 53 | e2 <- new.env() 54 | e2$lv_names <- 55 | as.character( 56 | na.omit( 57 | stringr::str_extract(results$input$object, "\\w+(?=\\s*BY)"))) 58 | if (!is.null(object)) { 59 | e2$class <- object$class 60 | } else { 61 | checkmate::assert_choice(class, choices = c("tree", "grm")) 62 | e2$class <- class 63 | } 64 | 65 | results$parameters <- lapply(results$parameters, function(x) { 66 | x$se <- ifelse(x$pval == 999, NA, x$se) 67 | x$est_se <- ifelse(x$pval == 999, NA, x$est_se) 68 | x$pval <- ifelse(x$pval == 999, NA, x$pval) 69 | return(x) 70 | }) 71 | 72 | unstd <- results[["parameters"]][["unstandardized"]] 73 | class(unstd) <- "data.frame" 74 | 75 | if (!(is.null(results[["savedata"]]) | length(results[["savedata"]]) == 0)) { 76 | 77 | fscores <- results[["savedata"]] 78 | 79 | fscore_cols1 <- is.element(toupper(names(fscores)), 80 | toupper(e2$lv_names)) 81 | fscore_cols2 <- is.element(toupper(names(fscores)), 82 | toupper(paste0(e2$lv_names, "_SE"))) 83 | personpar_est <- fscores[, fscore_cols1, drop = FALSE] 84 | personpar_se <- fscores[, fscore_cols2, drop = FALSE] 85 | 86 | } else { 87 | personpar_est <- NULL 88 | personpar_se <- NULL 89 | } 90 | 91 | if (!(is.null(results[["tech4"]][["latCovEst"]]))) { 92 | 93 | sigma <- results[["tech4"]][["latCovEst"]] 94 | cormat <- results[["tech4"]][["latCorEst"]] 95 | 96 | if (!is.null(e2$lv_names)) { 97 | sigma <- sigma[toupper(e2$lv_names), toupper(e2$lv_names), drop = FALSE] 98 | cormat <- cormat[toupper(e2$lv_names), toupper(e2$lv_names), drop = FALSE] 99 | } 100 | 101 | } else { 102 | sigma <- NULL 103 | cormat <- NULL 104 | } 105 | 106 | 107 | if (!is.null(e2$lambda$new_name)) { 108 | alphapar <- unstd[tolower(unstd$param) %in% tolower(e2$lambda$new_name), , drop = FALSE] 109 | betapar <- unstd[tolower(unstd$param) %in% paste0(tolower(e2$lambda$new_name), "$1"), , drop = FALSE] 110 | } else { 111 | alphapar <- unstd[grep("[.]BY$", unstd$paramHeader), , drop = FALSE] 112 | betapar <- unstd[unstd$paramHeader == "Thresholds", , drop = FALSE] 113 | } 114 | alphapar$param <- factor(alphapar$param, levels = unique(alphapar$param)) 115 | rownames(alphapar) <- NULL 116 | rownames(betapar) <- NULL 117 | 118 | itempar <- list() 119 | 120 | if (e2$class == "tree") { 121 | 122 | betapar <- tidyr::separate(betapar, "param", c("traititem", "threshold"), 123 | sep = "\\$", extra = "merge") 124 | betapar <- tidyr::separate(betapar, "traititem", c("trait", "item"), 125 | sep = "_", extra = "merge", fill = "right") 126 | betapar$trait <- factor(betapar$trait, levels = unique(betapar$trait)) 127 | betapar$item <- factor(betapar$item, levels = unique(betapar$item)) 128 | 129 | itempar$beta <- reshape( 130 | dplyr::select(betapar, .data$trait, .data$item, .data$est), 131 | direction = "wide", 132 | idvar = "item", 133 | timevar = "trait") 134 | names(itempar$beta) <- sub("^est[.]", "", names(itempar$beta)) 135 | itempar$beta_se <- reshape( 136 | dplyr::select(betapar, .data$trait, .data$item, .data$se), 137 | direction = "wide", 138 | idvar = "item", 139 | timevar = "trait") 140 | names(itempar$beta_se) <- sub("^se[.]", "", names(itempar$beta_se)) 141 | 142 | alphapar <- tidyr::separate(alphapar, "param", c("trait", "item"), 143 | sep = "_", extra = "merge", fill = "right") 144 | alphapar$trait <- factor(alphapar$trait, levels = unique(alphapar$trait)) 145 | alphapar$item <- factor(alphapar$item, levels = unique(alphapar$item)) 146 | 147 | itempar$alpha <- reshape( 148 | dplyr::select(alphapar, .data$trait, .data$item, .data$est), 149 | direction = "wide", 150 | idvar = "item", 151 | timevar = "trait") 152 | names(itempar$alpha) <- sub("^est[.]", "", names(itempar$alpha)) 153 | itempar$alpha_se <- reshape( 154 | dplyr::select(alphapar, .data$trait, .data$item, .data$se), 155 | direction = "wide", 156 | idvar = "item", 157 | timevar = "trait") 158 | names(itempar$alpha_se) <- sub("^se[.]", "", names(itempar$alpha_se)) 159 | 160 | } else if (e2$class == "grm") { 161 | 162 | tmp1 <- tidyr::separate(betapar, "param", c("item", "threshold"), 163 | sep = "\\$") 164 | 165 | itempar$beta <- reshape( 166 | dplyr::select(tmp1, .data$item, .data$threshold, .data$est), 167 | direction = "wide", 168 | idvar = "item", 169 | timevar = "threshold" 170 | ) 171 | names(itempar$beta) <- sub("^est[.]", "b", names(itempar$beta)) 172 | 173 | itempar$beta_se <- reshape( 174 | dplyr::select(tmp1, .data$item, .data$threshold, .data$se), 175 | direction = "wide", 176 | idvar = "item", 177 | timevar = "threshold" 178 | ) 179 | names(itempar$beta_se) <- sub("^se[.]", "b", names(itempar$beta_se)) 180 | 181 | itempar$alpha <- dplyr::select(alphapar, item = .data$param, .data$est) 182 | itempar$alpha_se <- dplyr::select(alphapar, item = .data$param, .data$se) 183 | 184 | itempar <- lapply(itempar, function(x) { 185 | x$item <- factor(x$item, unique(alphapar$param)) 186 | x[order(x$item), ] 187 | }) 188 | } 189 | 190 | out <- list( 191 | person = list(personpar_est = personpar_est, 192 | personpar_se = personpar_se), 193 | item = itempar, 194 | sigma = sigma, 195 | cormat = cormat, 196 | summaries = results$summaries, 197 | warnings = results$warnings, 198 | errors = results$errors, 199 | parameters = results$parameters 200 | ) 201 | 202 | return(out) 203 | } # nocov end 204 | -------------------------------------------------------------------------------- /R/fit-mirt.R: -------------------------------------------------------------------------------- 1 | #' Fit an `irtree_model` using mirt 2 | #' 3 | #' This function takes a `data` frame and a model `object` and runs the model in mirt. 4 | #' 5 | #' @param link String specifying the link function. Only `logit` is 6 | #' implemented in mirt. 7 | #' @inheritParams fit.irtree_model 8 | #' @inheritParams mirt::mirt 9 | #' @keywords internal 10 | irtree_fit_mirt <- function(object = NULL, 11 | data = NULL, 12 | link = "logit", 13 | verbose = interactive(), 14 | control = control_mirt(), 15 | improper_okay = FALSE 16 | ) { 17 | 18 | checkmate::assert_class(object, "irtree_model") 19 | assert_irtree_data(data = data, object = object, engine = "mirt") 20 | data <- tibble::as_tibble(data) 21 | 22 | assert_irtree_proper(object, improper_okay = improper_okay) 23 | 24 | object$j_names <- object$j_names[order(match(object$j_names, names(data)))] 25 | object$lambda$item <- factor(object$lambda$item, levels = object$j_names) 26 | object$lambda <- object$lambda[order(object$lambda$item, object$lambda$irt), ] 27 | 28 | link <- match.arg(link) 29 | checkmate::qassert(verbose, "B1") 30 | checkmate::qassert(control, "l") 31 | tmp1 <- formalArgs(control_mirt) 32 | checkmate::assert_names(names(control), must.include = tmp1[tmp1 != "..."]) 33 | rm(tmp1) 34 | 35 | spec <- c(as.list(environment())) 36 | spec$engine <- "mirt" 37 | 38 | if (object$class == "tree") { 39 | assert_irtree_not_mixture(object) 40 | pseudoitems <- irtree_recode(object = object, data = data[object$j_names]) 41 | } else if (object$class == "grm") { 42 | pseudoitems <- data 43 | } else { 44 | .stop_not_implemented() 45 | } 46 | 47 | mirt_input <- write_mirt_input(object = object, data = pseudoitems) 48 | mirt_string <- mirt_input$mirt_string 49 | 50 | if (TRUE) { 51 | mirt_call <- rlang::call2("mirt", 52 | .ns = "mirt", 53 | data = rlang::expr(pseudoitems), 54 | model = rlang::expr(mirt_string), 55 | itemtype = mirt_input$itemtype, 56 | verbose = verbose, 57 | !!!control) 58 | res <- rlang::eval_tidy(mirt_call) 59 | } 60 | 61 | out <- list(mirt = res, spec = spec) 62 | class(out) <- c("irtree_fit", class(out)) 63 | return(out) 64 | } 65 | 66 | #' Prepare a mirt model 67 | #' 68 | #' This is an internal function used by [irtree_fit_mirt()]. It receives its 69 | #' inputs from the model object and the data set and returns a 70 | #' [mirt::mirt.model] object. 71 | #' 72 | #' @inheritParams irtree_fit_mirt 73 | #' @return A list with three elements. `mirt_string` is the [mirt::mirt.model] 74 | #' object; `itemtype` can be passed to [mirt::mirt()]; 75 | #' `lambda` is the modified lambda matrix from the `object`-argument. 76 | #' @keywords internal 77 | write_mirt_input <- function(object = NULL, 78 | data = NULL) { 79 | 80 | checkmate::assert_class(object, "irtree_model") 81 | 82 | checkmate::assert_data_frame(data, types = "integerish", any.missing = TRUE, 83 | all.missing = FALSE, ncols = nrow(object$lambda)) 84 | 85 | lambda <- object$lambda 86 | 87 | lambda <- lambda[order(lambda$irt, lambda$item), ] 88 | lambda$new_name <- names(data) 89 | 90 | my_values <- lambda[, c("item", "theta", "loading", "new_name")] 91 | 92 | my_values$est <- !grepl("@", my_values$loading) 93 | my_values$value <- as.numeric(sub("@|[*]", "", my_values$loading)) 94 | my_values$name <- factor(my_values$theta, labels = paste0("a", seq_len(object$S))) 95 | 96 | # mirt1 <- split(lambda, lambda$theta) 97 | mirt1 <- split(my_values, my_values$theta) 98 | 99 | # mirt2 <- vapply(seq_along(mirt1), function(x) { 100 | # glue::glue_collapse(c(glue::glue("{names(mirt1[x])} = "), 101 | # # glue::glue(" {mirt1[[x]]$new_name}", ",") 102 | # glue::glue_collapse(glue::glue("{mirt1[[x]]$new_name}"), sep = ", ") 103 | # )) 104 | # }, FUN.VALUE = "") 105 | 106 | mod <- character() 107 | 108 | # REMEMBER: The order in which the latent variables are defined in 109 | # mirt_string (via 'LV = var1, var2, var3') is crucial, because the labels 110 | # of the loadings (e.g., a1, a2, a3) depend on that order 111 | 112 | for (ii in seq_along(mirt1)) { 113 | # mirt_string: Factor = var1, var2, var3 114 | mod <- c(mod, 115 | paste0(names(mirt1[ii]), 116 | " = ", 117 | clps(", ", mirt1[[ii]]$new_name))) 118 | 119 | if (!all(mirt1[[ii]]$est)) { 120 | # mirt_string: COV = Factor1*Factor2 121 | mod <- c(mod, 122 | paste0("COV = ", 123 | clps("*", rep(names(mirt1[ii]), 2)))) 124 | 125 | # mirt_string: Fix loadings 126 | start_vals <- na.omit(mirt1[[ii]]) 127 | start_val_groups <- split(start_vals, start_vals$value) 128 | rm(start_vals) 129 | 130 | for (jj in seq_along(start_val_groups)) { 131 | # MAKE: FIXED = (var1, var2, var3, a1) 132 | mod <- c(mod, 133 | paste0("FIXED = (", 134 | clps(", ", start_val_groups[[jj]]$new_name), 135 | ", ", start_val_groups[[jj]]$name[1], ")")) 136 | 137 | # MAKE: FIXED = (var1, a1, 1.0) 138 | # MAKE: FIXED = (var2, var3, a1, 1.234) 139 | mod <- c( 140 | mod, 141 | paste0( 142 | "START = (", 143 | clps(", ", 144 | c(clps(", ", start_val_groups[[jj]]$new_name), 145 | as.character(start_val_groups[[jj]]$name[1]), 146 | start_val_groups[[jj]]$value[1])), 147 | ")")) 148 | } 149 | } else { 150 | # do nothing 151 | } 152 | } 153 | 154 | # MAKE: bring all COV statements on one line, add covariances 155 | tmp1 <- split(mod, grepl("^COV", mod)) 156 | 157 | string_vars <- sub("COV =\\s+", "", tmp1$`TRUE`) 158 | string_covs <- clps("*", names(mirt1)) 159 | tmp1$`TRUE` <- paste0("COV = ", clps(", ", c(string_vars, string_covs))) 160 | mod <- unname(unlist(tmp1)) 161 | 162 | mirt_string <- clps("\n", mod) 163 | 164 | if (object$class == "tree") { 165 | itemtype <- unname(ifelse(lambda$loading == "@1", "Rasch", "2PL")) 166 | if (length(unique(itemtype)) == 1) { 167 | itemtype <- unique(itemtype) 168 | } 169 | } else if (object$class == "grm") { 170 | itemtype <- "graded" 171 | } else { 172 | stop("Only model classes Tree and GRM are implemented for mirt.") 173 | } 174 | return(out1 = list(mirt_string = mirt_string, 175 | itemtype = itemtype, 176 | lambda = lambda)) 177 | 178 | } 179 | 180 | #' Control aspects of fitting a model in mirt 181 | #' 182 | #' This function should be used to generate the `control` argument of the 183 | #' [`fit()`][fit.irtree_model] function. 184 | #' 185 | #' @param SE,method,quadpts,... These arguments are passed to and documented 186 | #' in [mirt::mirt()]. They can be used to tweak the estimation algorithm. 187 | #' @param control List of arguments passed to argument `control` of 188 | #' [mirt::mirt()]. 189 | #' @param technical List of arguments passed to argument `technical` of 190 | #' [mirt::mirt()]. 191 | #' @return A list with one element for every argument of `control_mirt()`. 192 | #' @examples 193 | #' control_mirt(SE = FALSE, 194 | #' method = "QMCEM", 195 | #' quadpts = 4455, 196 | #' technical = list(NCYCLES = 567), 197 | #' TOL = .001) 198 | #' control_mirt(method = "MHRM", 199 | #' draws = 5544) 200 | #' @export 201 | control_mirt <- function(SE = TRUE, 202 | method = "EM", 203 | quadpts = NULL, 204 | control = list(), 205 | technical = list(), 206 | ...) { 207 | 208 | ctrl <- c(as.list(environment()), list(...)) 209 | 210 | checkmate::qassert(SE, "B1") 211 | checkmate::assert_int(quadpts, null.ok = TRUE, lower = 3) 212 | checkmate::qassert(control, "l") 213 | checkmate::qassert(technical, "l") 214 | 215 | return(ctrl) 216 | } 217 | -------------------------------------------------------------------------------- /R/fit-tam.R: -------------------------------------------------------------------------------- 1 | #' Fit an `irtree_model` using TAM 2 | #' 3 | #' This function takes a `data` frame and a model `object` and runs the model in TAM. 4 | #' 5 | #' @param link String specifying the link function. Only `logit` is 6 | #' implemented in TAM. 7 | #' @inheritParams fit.irtree_model 8 | #' @keywords internal 9 | irtree_fit_tam <- function(object = NULL, 10 | data = NULL, 11 | link = "logit", 12 | verbose = interactive(), 13 | control = control_tam(), 14 | improper_okay = FALSE) { 15 | 16 | checkmate::assert_class(object, "irtree_model") 17 | 18 | if (control$set_min_to_0 && min(data[object$j_names], na.rm = TRUE) != 0) { 19 | data[object$j_names] <- data[object$j_names] - 20 | min(data[object$j_names], na.rm = TRUE) 21 | } 22 | 23 | assert_irtree_data(data = data, object = object, engine = "tam", 24 | set_min_to_0 = control$set_min_to_0) 25 | data <- tibble::as_tibble(data) 26 | 27 | assert_irtree_proper(object, improper_okay = improper_okay) 28 | 29 | if (!isTRUE(all(unlist(object$irt_loadings) == "@1"))) { 30 | tmp1 <- object$irt_items[1] 31 | tmp2 <- paste(names(tmp1), "BY", 32 | paste0(tmp1[[1]][1:3], "@1,", collapse = " "), "... ;") 33 | stop("2PL is not implemented for TAM.\n", 34 | "Please modify section IRT like so:\n", 35 | tmp2, call. = FALSE) 36 | } 37 | 38 | object$j_names <- object$j_names[order(match(object$j_names, names(data)))] 39 | object$lambda$item <- factor(object$lambda$item, levels = object$j_names) 40 | object$lambda <- object$lambda[order(object$lambda$item, object$lambda$irt), ] 41 | 42 | link <- match.arg(link) 43 | checkmate::qassert(verbose, "B1") 44 | checkmate::qassert(control, "l") 45 | tmp1 <- formalArgs(control_tam) 46 | checkmate::assert_names(names(control), must.include = tmp1[tmp1 != "..."]) 47 | 48 | spec <- c(as.list(environment())) 49 | spec$engine <- "tam" 50 | 51 | tam_call <- rlang::call2("tam.mml", 52 | .ns = "TAM", 53 | resp = NULL, 54 | irtmodel = "1PL", 55 | !!!control[names(control) != "set_min_to_0"], 56 | verbose = verbose) 57 | 58 | if (object$class == "tree") { 59 | 60 | assert_irtree_not_mixture(object) 61 | 62 | pseudoitems <- irtree_recode(object = object, data = data[object$j_names]) 63 | 64 | Q <- .make_tam_Q(object = object, pseudoitems = pseudoitems) 65 | 66 | tam_call <- rlang::call_modify(tam_call, Q = rlang::expr(Q), 67 | resp = rlang::expr(pseudoitems)) 68 | 69 | } else if (object$class == "pcm") { 70 | 71 | B <- .make_tam_B(object, array = TRUE) 72 | 73 | tam_call <- rlang::call_modify(tam_call, B = rlang::expr(B), 74 | resp = rlang::expr(data)) 75 | 76 | } else { 77 | stop("Class ", object$class, " is not implemented in TAM.", call. = FALSE) 78 | } 79 | 80 | if (TRUE) { 81 | res <- rlang::eval_tidy(tam_call) 82 | } 83 | 84 | out <- list(tam = res, spec = spec) 85 | class(out) <- c("irtree_fit", class(out)) 86 | return(out) 87 | } 88 | 89 | .make_tam_Q <- function(object = NULL, pseudoitems = NULL) { 90 | 91 | Q <- data.frame(object$lambda, dim = 1) %>% 92 | dplyr::select(.data$theta, .data$new_name, .data$dim) %>% 93 | reshape(direction = "wide", v.names = "dim", 94 | idvar = "new_name", timevar = "theta") %>% 95 | dplyr::mutate(new_name = factor(.data$new_name, levels = names(pseudoitems))) %>% 96 | dplyr::mutate( 97 | dplyr::across(-1, tidyr::replace_na, 0)) 98 | 99 | tmp1 <- paste0("dim.", levels(object$lambda$theta)) 100 | tmp2 <- tmp1[tmp1 %in% names(Q)[-1]] 101 | 102 | Q <- Q[order(Q$new_name), tmp2] 103 | 104 | return(as.matrix(Q, rownames.force = FALSE)) 105 | } 106 | 107 | .make_tam_B <- function(object = NULL, array = TRUE) { 108 | 109 | weights_df <- tibble::enframe(object$weights, "theta", "weights") 110 | weights_df$theta <- factor(weights_df$theta, levels(object$lambda$theta)) 111 | 112 | B1 <- dplyr::full_join(object$lambda, weights_df, by = "theta") 113 | B2 <- tidyr::pivot_wider(B1, id_cols = "item", names_from = "theta", 114 | values_from = "weights", 115 | values_fill = list(weights = list(rep(0, object$K)))) 116 | B3 <- tidyr::unnest(B2, cols = -.data$item) 117 | B4 <- B3[levels(object$lambda$theta)] 118 | B <- Matrix::Matrix(as.matrix(B4)) 119 | 120 | if (array) { 121 | B5 <- array(B, dim = c(object$K, object$J, object$S)) 122 | B <- array(apply(B5, 3, t), 123 | dim = c(object$J, object$K, object$S)) 124 | } 125 | return(B) 126 | 127 | } 128 | 129 | #' Control aspects of fitting a model in TAM 130 | #' 131 | #' This function should be used to generate the `control` argument of the 132 | #' [`fit()`][fit.irtree_model] function. 133 | #' 134 | #' @param set_min_to_0 Logical. [TAM::tam.mml()] expects the data to be scored 0, 135 | #' ..., K. If `set_min_to_0 = TRUE`, the minimum of the data is subtracted from 136 | #' each response, which will likely both satisfy TAM and do no harm to the 137 | #' data. 138 | #' @param control List of arguments passed to argument `control` of 139 | #' [TAM::tam.mml()]. See examples below. 140 | #' @param ... Other arguments passed to [TAM::tam.mml()]. 141 | #' @return A list with one element for every argument of `control_tam()`. 142 | #' @examples 143 | #' control_tam(set_min_to_0 = TRUE, 144 | #' control = list(snodes = 0, 145 | #' maxiter = 1000, 146 | #' increment.factor = 1, 147 | #' fac.oldxsi = 0), 148 | #' constraint = "items") 149 | #' @export 150 | control_tam <- function( 151 | set_min_to_0 = FALSE, 152 | control = list(snodes = 0, 153 | maxiter = 1000, 154 | increment.factor = 1, 155 | fac.oldxsi = 0), 156 | ...) { 157 | 158 | ctrl <- c(as.list(environment()), list(...)) 159 | 160 | checkmate::qassert(set_min_to_0, "B1") 161 | checkmate::qassert(control, "L") 162 | 163 | return(ctrl) 164 | } 165 | -------------------------------------------------------------------------------- /R/fit.R: -------------------------------------------------------------------------------- 1 | #' Fit an ItemResponseTrees model 2 | #' 3 | #' This function takes a `data` frame and an `object` of class [irtree_model] 4 | #' and runs the model in either mirt, Mplus, or TAM. 5 | #' 6 | #' @section Methods: The methods `coef()`, `summary()`, and `print()` are 7 | #' implemented for objects of class `irtree_fit`, and those wrap the 8 | #' respective functions of [mirt][mirt::mirt-package], 9 | #' [MplusAutomation][MplusAutomation::MplusAutomation], or 10 | #' [TAM][TAM::TAM-package]. However, [`glance()`][glance.irtree_fit], 11 | #' [`tidy()`][tidy.irtree_fit], and [`augment()`][augment.irtree_fit] may be 12 | #' more helpful. 13 | #' 14 | #' @param object Object of class `irtree_model`. See [irtree_model] for more 15 | #' information. 16 | #' @param data Data frame containing containing one row per respondent and one 17 | #' column per variable. The variable names must correspond to those used in 18 | #' `object`. 19 | #' @param engine String specifying whether to use mirt, Mplus, or TAM for 20 | #' estimation. 21 | #' @param link String specifying the link function. May be either logit, or (in 22 | #' case of Mplus), probit. 23 | #' @param verbose Logical indicating whether output should be printed to the 24 | #' console. 25 | #' @param control List. The allowed elements of this list depend on the 26 | #' `engine`. Use [control_mirt()], [control_mplus()], or [control_tam()] for 27 | #' convenience. Note that the `fit()` function does not use `...`, but that 28 | #' you can use the `control_*()` functions to pass additional arguments. 29 | #' @param improper_okay Logical indicating whether the model should also be fit 30 | #' if it is not a proper IR-tree model. Set this only to `TRUE` if you really 31 | #' know what you are doing. 32 | #' @return Returns a list of class `irtree_fit`. The first list element is the 33 | #' return value of either [mirt::mirt()], [MplusAutomation::readModels()], or 34 | #' [TAM::tam.mml()]. Further information is provided in the element 35 | #' `spec`. 36 | #' @param ... Not currently used. Use `control` instead. 37 | #' @example inst/examples/example-fit.R 38 | #' @export 39 | fit.irtree_model <- function(object = NULL, 40 | data = NULL, 41 | engine = c("mirt", "mplus", "tam"), 42 | ..., 43 | link = c("logit", "probit"), 44 | verbose = interactive(), 45 | control = NULL, 46 | improper_okay = FALSE) { 47 | 48 | engine <- match.arg(engine) 49 | switch (engine, 50 | mplus = has_namespace("MplusAutomation"), 51 | tam = has_namespace("TAM")) 52 | 53 | if (length(list(...)) > 0) { 54 | stop("The ... are currently not used. Use ", 55 | paste0("control_", engine, "() "), "instead.", call. = FALSE) 56 | } 57 | link <- match.arg(link) 58 | if (is.null(control)) { 59 | control <- switch (engine, 60 | mplus = control_mplus(), 61 | mirt = control_mirt(), 62 | tam = control_tam() 63 | ) 64 | } 65 | 66 | if (engine == "mplus") { 67 | .must_have(object, "weights", FALSE, .engine = engine) 68 | out <- irtree_fit_mplus(object = object, data = data, link = link, 69 | verbose = verbose, control = control, 70 | improper_okay = improper_okay) 71 | } else if (engine == "mirt") { 72 | .must_have(object, "addendum", FALSE, .engine = engine) 73 | .must_have(object, "weights", FALSE, .engine = engine) 74 | out <- irtree_fit_mirt(object = object, data = data, link = link, 75 | verbose = verbose, control = control, 76 | improper_okay = improper_okay) 77 | } else if (engine == "tam") { 78 | if (object$class == "pcm") { 79 | .must_have(object, "constraints", FALSE, .engine = engine) 80 | } 81 | .must_have(object, "addendum", FALSE, .engine = engine) 82 | out <- irtree_fit_tam(object = object, data = data, link = link, 83 | verbose = verbose, control = control, 84 | improper_okay = improper_okay) 85 | } else { 86 | .stop_not_implemented() 87 | } 88 | 89 | return(out) 90 | } 91 | 92 | #' @importFrom generics fit 93 | #' @export 94 | generics::fit 95 | 96 | #' @export 97 | summary.irtree_fit <- function(object, ...) { 98 | # ellipsis::check_dots_used() 99 | if (object$spec$engine == "mplus") { 100 | print(object$mplus$parameters$unstandardized) 101 | } else if (object$spec$engine == "mirt") { 102 | mirt::summary(object$mirt, ...) 103 | } else if (object$spec$engine == "tam") { 104 | summary(object$tam, ...) 105 | } 106 | } 107 | 108 | #' @export 109 | coef.irtree_fit <- function(object, ...) { 110 | # ellipsis::check_dots_used() 111 | if (object$spec$engine == "mplus") { 112 | coef(object$mplus, ...) 113 | } else if (object$spec$engine == "mirt") { 114 | mirt::coef(object$mirt, ...) 115 | } else { 116 | return(NULL) 117 | } 118 | } 119 | 120 | #' @export 121 | print.irtree_fit <- function(x, ...) { 122 | if (x$spec$engine == "mplus") { 123 | print(x$mplus, ...) 124 | } else if (x$spec$engine == "mirt") { 125 | print(x$mirt) 126 | } else if (x$spec$engine == "tam") { 127 | print(x$tam) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /R/generate-pcm-data.R: -------------------------------------------------------------------------------- 1 | #' Generate data from a partial credit model 2 | #' 3 | #' This function generates data from a (multidimensional) PCM. 4 | #' 5 | #' @inheritParams irtree_gen_data 6 | #' @return A list with element `data` containing the data and an 7 | #' element `spec` containing the true parameter values etc. 8 | #' @keywords internal 9 | irtree_gen_pcm <- function(object = NULL, 10 | N = NULL, 11 | sigma = NULL, 12 | theta = NULL, 13 | itempar = NULL, 14 | link = "logit", 15 | na_okay = TRUE, 16 | skip = FALSE 17 | ) { 18 | 19 | spec <- c(as.list(environment())) 20 | spec$J <- object$J 21 | 22 | match.arg(link) 23 | 24 | S <- object$S 25 | J <- object$J 26 | j_names <- object$j_names 27 | K <- object$K 28 | 29 | # INPUT CHECKING ---------------------------------------------------------- 30 | 31 | .must_have(object, "constraints", FALSE, skip = skip) 32 | .must_have(object, "addendum", FALSE, skip = skip) 33 | 34 | if (!isTRUE(all(unlist(object$irt_loadings) == "@1"))) { 35 | warning("2Pl is not implemented for class PCM.") 36 | } 37 | 38 | checkmate::assert_int(N, lower = 1, null.ok = !is.null(theta)) 39 | if (!is.null(theta)) { 40 | spec$theta <- theta <- data.matrix(theta, rownames.force = FALSE) 41 | N <- nrow(theta) 42 | } 43 | checkmate::assert_matrix(theta, mode = "numeric", min.rows = 1, 44 | ncols = object$S, null.ok = !is.null(sigma)) 45 | 46 | if (is.function(sigma)) { 47 | FUN <- match.fun(sigma) 48 | spec$sigma_fun <- FUN 49 | sigma <- FUN() 50 | } 51 | spec$sigma <- sigma 52 | 53 | checkmate::assert_matrix(sigma, mode = "numeric", any.missing = FALSE, 54 | nrows = S, ncols = S, null.ok = !is.null(theta)) 55 | 56 | if (is.function(itempar)) { 57 | FUN <- match.fun(itempar) 58 | spec$itempar_fun <- FUN 59 | itempar <- FUN() 60 | } 61 | checkmate::assert_list(itempar, types = "numeric", any.missing = FALSE, 62 | len = 1, # len = 2, 63 | names = "named") 64 | checkmate::assert_names(names(itempar), subset.of = "beta" 65 | # permutation.of = c("beta", "alpha") 66 | ) 67 | itempar <- lapply(itempar, data.matrix, rownames.force = FALSE) 68 | spec$itempar <- itempar 69 | checkmate::assert_matrix(itempar$beta, nrows = J, ncols = K - 1) 70 | # checkmate::assert_matrix(itempar$alpha, nrows = J, ncols = P) 71 | 72 | # THETA -------------------------------------------------------------------- 73 | # [DIM x N] MATRIX 74 | 75 | if (is.null(theta)) { 76 | theta <- MASS::mvrnorm(ifelse(N == 1, 1.001, N), mu = rep(0, S), Sigma = sigma) 77 | } 78 | colnames(theta) <- object$latent_names$theta 79 | spec$personpar <- theta 80 | spec$theta <- NULL 81 | theta <- Matrix::Matrix(t(theta)) 82 | 83 | # THRESHOLDS --------------------------------------------------------------- 84 | # [ITEMS*(CATEG-1) x N] MATRIX 85 | 86 | thres <- Matrix::Matrix(as.vector(t(itempar$beta)), 87 | nrow = J * (K - 1), ncol = N) 88 | 89 | # B-MATRIX ----------------------------------------------------------------- 90 | # [ITEMS*CATEG x DIM] MATRIX 91 | 92 | B <- .make_tam_B(object, array = FALSE) 93 | 94 | # A-MATRIX ----------------------------------------------------------------- 95 | # [ITEMS*CATEG x ITEMS*(CATEG-1)] MATRIX 96 | 97 | A <- diag(1, K - 1) 98 | A[lower.tri(A)] <- 1 99 | A <- rbind(0, A) 100 | A <- Matrix::bdiag(replicate(J, A, simplify = FALSE)) 101 | 102 | # IRT MODEL -> DATA -------------------------------------------------------- 103 | num <- exp(B %*% theta - A %*% thres) 104 | num <- array(num, dim = c(K, J, N)) 105 | den <- array(rep(colSums(num), each = K), dim = c(K, J, N)) 106 | p <- num / den 107 | 108 | checkmate::qassert(p, "N[0,1]") 109 | checkmate::assert_integerish(apply(p, 2:3, sum), lower = .9, upper = 1.1, 110 | any.missing = FALSE, len = N*J) 111 | 112 | f1 <- function(x) { 113 | storage.mode(x) <- "logical" 114 | apply(x, c(2:3), which) 115 | } 116 | 117 | dat <- 118 | tibble::as_tibble( 119 | setNames( 120 | as.data.frame( 121 | t(f1( 122 | apply(p, 2:3, rmultinom, n = 1, size = 1)))), 123 | j_names)) 124 | 125 | if (!na_okay) { 126 | ii <- 0 127 | while (!.check_all_categ_observed(dat, object$K)) { 128 | dat <- 129 | tibble::as_tibble( 130 | setNames( 131 | as.data.frame( 132 | t(f1( 133 | apply(p, 2:3, rmultinom, n = 1, size = 1)))), 134 | j_names)) 135 | ii <- ii + 1 136 | if (ii >= 25) stop("Could not generate data without missing categories.") 137 | } 138 | } 139 | 140 | p_return <- data.frame( 141 | pers = gl(N, J*K, length = J*K*N), 142 | item = gl(J, K, length = J*K*N, labels = j_names), 143 | categ = gl(K, 1, length = J*K*N), 144 | prob = matrix(p, ncol = 1)[, 1] 145 | ) 146 | 147 | return(list(data = dat, probs = p_return, spec = spec)) 148 | } 149 | -------------------------------------------------------------------------------- /R/helpers.R: -------------------------------------------------------------------------------- 1 | #' Wrapper Around Paste to Collapse a Character Vector 2 | #' 3 | #' @inheritParams base::paste 4 | #' @inheritDotParams base::paste 5 | #' @seealso [base::paste()] 6 | #' @keywords internal 7 | clps <- function(collapse = " ", ..., sep = " ") { 8 | # ellipsis::check_dots_used() 9 | paste(..., sep = sep, collapse = collapse) 10 | } 11 | 12 | .stop_not_implemented <- function() { 13 | stop("The requested behavior is not implemented. ", 14 | "Please modify your function call. For example, ", 15 | "try to use a different 'engine' or modify your model ", 16 | "(especially the model class). Or contact the ", 17 | "package maintainer.", call. = FALSE) 18 | } 19 | 20 | .must_have <- function(model_list = NULL, 21 | element = NA_character_, 22 | must_have = TRUE, 23 | .name = NULL, 24 | .class = NULL, 25 | .engine = NULL, 26 | skip = FALSE) { 27 | if (skip) { 28 | return() 29 | } 30 | if (is.null(model_list[[element]]) == must_have && 31 | (length(model_list[[element]]) == 0) == must_have) { 32 | if (is.null(.name)) { 33 | .name <- stringr::str_to_title(element) 34 | } 35 | 36 | if (!is.null(.class)) { 37 | why <- glue::glue(" if class is '{.class}'") 38 | } else if (!is.null(.engine)) { 39 | why <- glue::glue(" if engine is '{.engine}'") 40 | } else { 41 | why <- "" 42 | } 43 | tmp1 <- glue::glue( 44 | "Problem in 'model': must {ifelse(must_have, '', 'NOT ')}", 45 | "contain a part with heading ", 46 | "'{.name}'{why}." 47 | ) 48 | stop(tmp1, call. = FALSE) 49 | } 50 | } 51 | 52 | #' Sample from a truncated normal distribution 53 | #' 54 | #' @param n Number of observations 55 | #' @param mean Mean 56 | #' @param sd Standard deviation 57 | #' @param ll Lower bound 58 | #' @param ul Upper bound 59 | #' @seealso There is a discussion and code on 60 | #' \url{https://stackoverflow.com/a/14034577}, and there is also the truncnorm 61 | #' package. 62 | #' @keywords internal 63 | rtruncatednorm <- function(n = NULL, mean = 0, sd = 1, ll = -Inf, ul = Inf) { 64 | checkmate::qassert(n, "X1[1,)") 65 | checkmate::qassert(mean, "N1") 66 | checkmate::qassert(sd, "N1[0,)") 67 | checkmate::qassert(ll, "N1") 68 | checkmate::assert_number(ul, lower = ll) 69 | more_n <- ceiling( 70 | (1 - pnorm(ul, mean = mean, sd = sd) + pnorm(ll, mean = mean, sd = sd)) * n * 2) 71 | x <- .rtruncatednorm1(n = n + more_n, mean = mean, sd = sd, ll = ll, ul = ul) 72 | x <- utils::head(x, n) 73 | 74 | while (length(x) < n) { 75 | x <- c(x, .rtruncatednorm1(n = more_n, mean = mean, sd = sd, ll = ll, ul = ul)) 76 | x <- utils::head(x, n) 77 | } 78 | return(x) 79 | } 80 | 81 | .rtruncatednorm1 <- function(n = NULL, mean = 0, sd = 1, ll = -Inf, ul = Inf) { 82 | x <- rnorm(n = n, mean = mean, sd = sd) 83 | x <- x[x > ll & x < ul] 84 | return(x) 85 | } 86 | -------------------------------------------------------------------------------- /R/irtree_paste.R: -------------------------------------------------------------------------------- 1 | #' Create a template of a model string 2 | #' 3 | #' This function prints a template of a model string to the console based on the 4 | #' supplied data frame. This template can be copy-pasted and modified to define 5 | #' an [irtree_model]. 6 | #' 7 | #' @param data Data frame. 8 | #' @param mapping_matrix Matrix of so-called pseudo-items, optional. The 9 | #' observed response categories must appear in the first column. The other 10 | #' columns contain the pseudo-items and each entry may be either `1`, `0`, or 11 | #' `NA`. 12 | #' @param rasch Logical. The string `@1` will be appended to each variable name 13 | #' if `TRUE` with no effect otherwise. 14 | #' @return NULL 15 | #' @examples 16 | #' irtree_create_template(jackson[, c(1, 6, 11)]) 17 | #' #> m1 <- " 18 | #' #> Equations: 19 | #' #> 1 = ... 20 | #' #> 2 = ... 21 | #' #> 3 = ... 22 | #' #> 4 = ... 23 | #' #> 5 = ... 24 | #' #> 25 | #' #> IRT: 26 | #' #> ... BY E1@1, E2@1, E3@1; 27 | #' #> ... BY E1@1, E2@1, E3@1; 28 | #' #> 29 | #' #> Class: 30 | #' #> Tree 31 | #' #> " 32 | #' 33 | #' irtree_create_template(jackson[, c(1, 6, 11)], 34 | #' cbind(1:5, 35 | #' m = c(0, 0, 1, 0, 0), 36 | #' t = c(1, 1, NA, 0, 0), 37 | #' e = c(1, 0, NA, 0, 1))) 38 | #' #> m1 <- " 39 | #' #> Equations: 40 | #' #> 1 = (1-m)*t*e 41 | #' #> 2 = (1-m)*t*(1-e) 42 | #' #> 3 = m 43 | #' #> 4 = (1-m)*(1-t)*(1-e) 44 | #' #> 5 = (1-m)*(1-t)*e 45 | #' #> 46 | #' #> IRT: 47 | #' #> m BY E1@1, E2@1, E3@1; 48 | #' #> t BY E1@1, E2@1, E3@1; 49 | #' #> e BY E1@1, E2@1, E3@1; 50 | #' #> 51 | #' #> Class: 52 | #' #> Tree 53 | #' #> " 54 | #' @export 55 | irtree_create_template <- function(data = NULL, 56 | mapping_matrix = NULL, 57 | rasch = TRUE) { 58 | checkmate::qassert(data, "d") 59 | checkmate::qassert(rasch, "B1") 60 | checkmate::assert_matrix(mapping_matrix, null.ok = TRUE) 61 | 62 | variables <- names(data) 63 | 64 | if (is.null(mapping_matrix)) { 65 | k_names <- 0:1 66 | try(silent = TRUE, expr = { 67 | k_names <- 68 | utils::head( 69 | sort( 70 | unique( 71 | dplyr::pull( 72 | dplyr::select(data, rlang::is_integerish), 1)) 73 | ), 9) 74 | }) 75 | equations <- clps("\n", k_names, "= ...") 76 | 77 | irt <- paste0("... BY ", 78 | paste0(variables, ifelse(rasch, "@1", ""), collapse = ", "), 79 | ";") 80 | irt <- strwrap(irt, exdent = 9) 81 | irt <- paste0("\n", irt, collapse = "") 82 | irt <- replicate(2, irt) 83 | 84 | } else { 85 | 86 | f1 <- function(x) { 87 | x2 <- dplyr::cur_column() 88 | dplyr::case_when( 89 | x == 1 ~ x2, 90 | x == 0 ~ paste0("(1-", x2, ")")) 91 | } 92 | mm2 <- dplyr::mutate(as.data.frame(mapping_matrix), 93 | dplyr::across(-1, f1)) 94 | equations <- paste(mm2[, 1], "=", purrr::pmap_chr(mm2[, -1], ~paste(..., sep = "*"))) 95 | equations <- clps("\n", gsub("[*]NA|NA[*]", "", equations)) 96 | 97 | irt <- paste0(names(mm2)[-1], " BY ", 98 | paste0(variables, ifelse(rasch, "@1", ""), collapse = ", "), 99 | ";") 100 | irt <- strwrap(irt, exdent = 5) 101 | irt <- paste0("\n", irt, collapse = "") 102 | } 103 | 104 | message("\nm1 <- \"\nEquations:\n", 105 | equations, 106 | "\n\nIRT:", 107 | irt, 108 | # 0 = ...\n1 = ..., 109 | "\n\n", 110 | "Class:\nTree\n\"\n") 111 | return(invisible(NULL)) 112 | } 113 | -------------------------------------------------------------------------------- /R/pseudoitems.R: -------------------------------------------------------------------------------- 1 | #' Pseudo-Items 2 | #' 3 | #' @description 4 | #' 5 | #' IR-tree models can be fit on the basis of so-called pseudo-items. To this 6 | #' end, the original, polytomous items are recoded into binary pseudo-items. 7 | #' Whether a pseudo-item is coded as `1`, `0`, or `NA` depends on the model 8 | #' equations (e.g., Böckenholt, 2012; Plieninger, 2020). 9 | #' 10 | #' The ItemResponseTrees package internally works with pseudo-items as well. 11 | #' However, the user has to specify the model equations rather than the 12 | #' pseudo-items in the [irtree_model] syntax. Internally, before fitting the 13 | #' model, the original responses are recoded on the basis of the model supplied 14 | #' by the user by the function [irtree_recode()]. This function may also be used 15 | #' directly if desired. 16 | #' 17 | #' As an alternative to specifying the model equations themselves, users may 18 | #' also use the function [irtree_create_template()] with a mapping matrix (that 19 | #' specifies the structure of the pseudo-items) to generate the model equations 20 | #' automatically. 21 | #' 22 | #' @examples 23 | #' # Mapping matrix for data with three response categories: 24 | #' (mm <- cbind(categ = 0:2, 25 | #' p1 = c(0, 1, 1), 26 | #' p2 = c(NA, 0, 1))) 27 | #' #> categ p1 p2 28 | #' #> [1,] 0 0 NA 29 | #' #> [2,] 1 1 0 30 | #' #> [3,] 2 1 1 31 | #' 32 | #' irtree_create_template(data.frame(x1 = 0:2, x2 = 0:2), 33 | #' mapping_matrix = mm) 34 | #' #> 35 | #' #> m1 <- " 36 | #' #> Equations: 37 | #' #> 0 = (1-p1) 38 | #' #> 1 = p1*(1-p2) 39 | #' #> 2 = p1*p2 40 | #' #> 41 | #' #> IRT: 42 | #' #> p1 BY x1@1, x2@1; 43 | #' #> p2 BY x1@1, x2@1; 44 | #' #> 45 | #' #> Class: 46 | #' #> Tree 47 | #' #> " 48 | #' #> 49 | #' 50 | #' irtree_recode(data = data.frame(x1 = 0:2, x2 = 0:2), 51 | #' mapping_matrix = mm) 52 | #' 53 | #' @references Böckenholt, U. (2012). Modeling multiple response processes in 54 | #' judgment and choice. *Psychological Methods*, *17*(4), 665–678. 55 | #' https://doi.org/10.1037/a0028111 56 | #' @references Plieninger, H. (2020). Developing and applying IR-tree models: 57 | #' Guidelines, caveats, and an extension to multiple groups. *Organizational 58 | #' Research Methods*. Advance online publication. 59 | #' https://doi.org/10.1177/1094428120911096 60 | #' @name pseudoitems 61 | #' @aliases pseudo-items 62 | #' @encoding UTF-8 63 | NULL 64 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "100%" 13 | ) 14 | options(tibble.print_min = 10, tibble.print_max = 20, pillar.min_title_chars = 16) 15 | ``` 16 | 17 | # ItemResponseTrees 18 | 19 | 20 | [![CRAN status](https://www.r-pkg.org/badges/version/ItemResponseTrees)](https://CRAN.R-project.org/package=ItemResponseTrees) 21 | [![R build status](https://github.com/hplieninger/ItemResponseTrees/workflows/R-CMD-check/badge.svg)](https://github.com/hplieninger/ItemResponseTrees/actions) 22 | [![codecov](https://codecov.io/gh/hplieninger/ItemResponseTrees/branch/master/graph/badge.svg)](https://codecov.io/gh/hplieninger/ItemResponseTrees) 23 | [![Say Thanks!](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg)](https://saythanks.io/to/plieninger@uni-mannheim.de) 24 | 25 | 26 | Item response tree (IR-tree) models like the one depicted below are a class of item response theory (IRT) models that assume that the responses to polytomous items can best be explained by multiple psychological processes (e.g., [Böckenholt, 2012](https://dx.doi.org/10.1037/a0028111); [Plieninger, 2020](https://doi.org/10.1177/1094428120911096)). 27 | The package ItemResponseTrees allows to fit such IR-tree models in [mirt](https://cran.r-project.org/package=mirt), [TAM](https://cran.r-project.org/package=TAM), and Mplus (via [MplusAutomation](https://cran.r-project.org/package=MplusAutomation)). 28 | 29 | The package automates some of the hassle of IR-tree modeling by means of a consistent syntax. 30 | This allows new users to quickly adopt this model class, and this allows experienced users to fit many complex models effortlessly. 31 | 32 | ```{r, out.width="80%", echo = FALSE, out.extra='style="border:0px;display: block; margin-left: auto; margin-right: auto;"'} 33 | knitr::include_graphics("tools/ecn-model.png") 34 | ``` 35 | 36 | ## Installation 37 | 38 | You can install the released version of ItemResponseTrees from [CRAN](https://CRAN.R-project.org) with: 39 | 40 | ``` r 41 | install.packages("ItemResponseTrees") 42 | ``` 43 | 44 | And the development version from [GitHub](https://github.com/) with: 45 | 46 | ``` r 47 | # install.packages("remotes") 48 | remotes::install_github("hplieninger/ItemResponseTrees") 49 | ``` 50 | ## Example 51 | 52 | The IR-tree model depicted above can be fit as follows. 53 | For more details, see the [vignette](https://cran.r-project.org/package=ItemResponseTrees/vignettes/ItemResponseTrees-Getting-started-with-IR-trees.html) and `?irtree_model`. 54 | 55 | ```{r example, eval = FALSE} 56 | library("ItemResponseTrees") 57 | 58 | m1 <- " 59 | Equations: 60 | 1 = (1-m)*(1-t)*e 61 | 2 = (1-m)*(1-t)*(1-e) 62 | 3 = m 63 | 4 = (1-m)*t*(1-e) 64 | 5 = (1-m)*t*e 65 | 66 | IRT: 67 | t BY E1, E2, E3, E4, E5, E6, E7, E8, E9; 68 | e BY E1@1, E2@1, E3@1, E4@1, E5@1, E6@1, E7@1, E8@1, E9@1; 69 | m BY E1@1, E2@1, E3@1, E4@1, E5@1, E6@1, E7@1, E8@1, E9@1; 70 | 71 | Class: 72 | Tree 73 | " 74 | 75 | model1 <- irtree_model(m1) 76 | 77 | fit1 <- fit(model1, data = jackson[, paste0("E", 1:9)]) 78 | 79 | glance( fit1) 80 | tidy( fit1, par_type = "difficulty") 81 | augment(fit1) 82 | ``` 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # ItemResponseTrees 5 | 6 | 7 | 8 | [![CRAN 9 | status](https://www.r-pkg.org/badges/version/ItemResponseTrees)](https://CRAN.R-project.org/package=ItemResponseTrees) 10 | [![R build 11 | status](https://github.com/hplieninger/ItemResponseTrees/workflows/R-CMD-check/badge.svg)](https://github.com/hplieninger/ItemResponseTrees/actions) 12 | [![codecov](https://codecov.io/gh/hplieninger/ItemResponseTrees/branch/master/graph/badge.svg)](https://codecov.io/gh/hplieninger/ItemResponseTrees) 13 | [![Say 14 | Thanks\!](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg)](https://saythanks.io/to/plieninger@uni-mannheim.de) 15 | 16 | 17 | Item response tree (IR-tree) models like the one depicted below are a 18 | class of item response theory (IRT) models that assume that the 19 | responses to polytomous items can best be explained by multiple 20 | psychological processes (e.g., 21 | [Böckenholt, 2012](https://dx.doi.org/10.1037/a0028111); 22 | [Plieninger, 2020](https://doi.org/10.1177/1094428120911096)). The 23 | package ItemResponseTrees allows to fit such IR-tree models in 24 | [mirt](https://cran.r-project.org/package=mirt), 25 | [TAM](https://cran.r-project.org/package=TAM), and Mplus (via 26 | [MplusAutomation](https://cran.r-project.org/package=MplusAutomation)). 27 | 28 | The package automates some of the hassle of IR-tree modeling by means of 29 | a consistent syntax. This allows new users to quickly adopt this model 30 | class, and this allows experienced users to fit many complex models 31 | effortlessly. 32 | 33 | 34 | 35 | ## Installation 36 | 37 | You can install the released version of ItemResponseTrees from 38 | [CRAN](https://CRAN.R-project.org) with: 39 | 40 | ``` r 41 | install.packages("ItemResponseTrees") 42 | ``` 43 | 44 | And the development version from [GitHub](https://github.com/) with: 45 | 46 | ``` r 47 | # install.packages("remotes") 48 | remotes::install_github("hplieninger/ItemResponseTrees") 49 | ``` 50 | 51 | ## Example 52 | 53 | The IR-tree model depicted above can be fit as follows. For more 54 | details, see the 55 | [vignette](https://cran.r-project.org/package=ItemResponseTrees/vignettes/ItemResponseTrees-Getting-started-with-IR-trees.html) 56 | and `?irtree_model`. 57 | 58 | ``` r 59 | library("ItemResponseTrees") 60 | 61 | m1 <- " 62 | Equations: 63 | 1 = (1-m)*(1-t)*e 64 | 2 = (1-m)*(1-t)*(1-e) 65 | 3 = m 66 | 4 = (1-m)*t*(1-e) 67 | 5 = (1-m)*t*e 68 | 69 | IRT: 70 | t BY E1, E2, E3, E4, E5, E6, E7, E8, E9; 71 | e BY E1@1, E2@1, E3@1, E4@1, E5@1, E6@1, E7@1, E8@1, E9@1; 72 | m BY E1@1, E2@1, E3@1, E4@1, E5@1, E6@1, E7@1, E8@1, E9@1; 73 | 74 | Class: 75 | Tree 76 | " 77 | 78 | model1 <- irtree_model(m1) 79 | 80 | fit1 <- fit(model1, data = jackson[, paste0("E", 1:9)]) 81 | 82 | glance( fit1) 83 | tidy( fit1, par_type = "difficulty") 84 | augment(fit1) 85 | ``` 86 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## Test environments 2 | * macOS-latest, R release (via GitHub Actions) 3 | * macOS-latest, R devel (via GitHub Actions) 4 | * Windows, R release (local) 5 | * Windows, R devel (via win-builder) 6 | * ubuntu-16.04, R release (via GitHub Actions) 7 | 8 | ## R CMD check results 9 | 10 | 0 errors | 0 warnings | 0 notes 11 | 12 | ## Downstream dependencies 13 | 14 | * This package currently has 0 downstream dependencies. 15 | -------------------------------------------------------------------------------- /data-raw/jackson.R: -------------------------------------------------------------------------------- 1 | library("dplyr") 2 | 3 | df1 <- read.csv(url("https://ndownloader.figshare.com/files/100890"), 4 | stringsAsFactors = FALSE, na.strings = c("NULL", "0")) 5 | 6 | rev_items <- c("q5", "q15", "q25", "q35", "q45", "q1", "q11", "q21", "q31", 7 | "q7", "q17", "q27", "q37", "q8", "q18", "q9", "q19", "q29") 8 | 9 | E <- c("q0", "q5", "q10", "q15", "q20", "q25", "q30", "q35", "q40", "q45") 10 | # N <- c("q1", "q6", "q11", "q16", "q21", "q26", "q31", "q36", "q41", "q46") 11 | A <- c("q1", "q6", "q11", "q16", "q21", "q26", "q31", "q36", "q41", "q46") 12 | # A <- c("q2", "q7", "q12", "q17", "q22", "q27", "q32", "q37", "q42", "q47") 13 | C <- c("q2", "q7", "q12", "q17", "q22", "q27", "q32", "q37", "q42", "q47") 14 | # C <- c("q3", "q8", "q13", "q18", "q23", "q28", "q33", "q38", "q43", "q48") 15 | N <- c("q3", "q8", "q13", "q18", "q23", "q28", "q33", "q38", "q43", "q48") 16 | O <- c("q4", "q9", "q14", "q19", "q24", "q29", "q34", "q39", "q44", "q49") 17 | 18 | df2 <- df1 %>% 19 | mutate_at(vars(starts_with("q")), na_if, y = 0) %>% 20 | mutate_at(rev_items, ~6 - .x) %>% 21 | mutate(gender = factor(gender, levels = 1:3, labels = c("male", "female", "other"), exclude = 0)) %>% 22 | labelled::set_variable_labels( 23 | "q0" = "Am the life of the party.", 24 | "q1" = "Feel little concern for others.", 25 | "q2" = "Am always prepared.", 26 | "q3" = "Get stressed out easily.", 27 | "q4" = "Have a rich vocabulary.", 28 | "q5" = "Don't talk a lot.", 29 | "q6" = "Am interested in people.", 30 | "q7" = "Leave my belongings around.", 31 | "q8" = "Am relaxed most of the time.", 32 | "q9" = "Have difficulty understanding abstract ideas.", 33 | "q10" = "Feel comfortable around people.", 34 | "q11" = "Insult people.", 35 | "q12" = "Pay attention to details.", 36 | "q13" = "Worry about things.", 37 | "q14" = "Have a vivid imagination.", 38 | "q15" = "Keep in the background.", 39 | "q16" = "Sympathize with others' feelings.", 40 | "q17" = "Make a mess of things.", 41 | "q18" = "Seldom feel blue.", 42 | "q19" = "Am not interested in abstract ideas.", 43 | "q20" = "Start conversations.", 44 | "q21" = "Am not interested in other people's problems.", 45 | "q22" = "Get chores done right away.", 46 | "q23" = "Am easily disturbed.", 47 | "q24" = "Have excellent ideas.", 48 | "q25" = "Have little to say.", 49 | "q26" = "Have a soft heart.", 50 | "q27" = "Often forget to put things back in their proper place.", 51 | "q28" = "Get upset easily.", 52 | "q29" = "Do not have a good imagination.", 53 | "q30" = "Talk to a lot of different people at parties.", 54 | "q31" = "Am not really interested in others.", 55 | "q32" = "Like order.", 56 | "q33" = "Change my mood a lot.", 57 | "q34" = "Am quick to understand things.", 58 | "q35" = "Don't like to draw attention to myself.", 59 | "q36" = "Take time out for others.", 60 | "q37" = "Shirk my duties.", 61 | "q38" = "Have frequent mood swings.", 62 | "q39" = "Use difficult words.", 63 | "q40" = "Don't mind being the center of attention.", 64 | "q41" = "Feel others' emotions.", 65 | "q42" = "Follow a schedule.", 66 | "q43" = "Get irritated easily.", 67 | "q44" = "Spend time reflecting on things.", 68 | "q45" = "Am quiet around strangers.", 69 | "q46" = "Make people feel at ease.", 70 | "q47" = "Am exacting in my work.", 71 | "q48" = "Often feel blue.", 72 | "q49" = "Am full of ideas." 73 | ) 74 | 75 | names(df2)[names(df2) %in% E] <- paste0("E", 1:10) 76 | names(df2)[names(df2) %in% N] <- paste0("N", 1:10) 77 | names(df2)[names(df2) %in% A] <- paste0("A", 1:10) 78 | names(df2)[names(df2) %in% C] <- paste0("C", 1:10) 79 | names(df2)[names(df2) %in% O] <- paste0("O", 1:10) 80 | 81 | jackson <- as_tibble(df2) 82 | 83 | usethis::use_data(jackson, overwrite = TRUE, version = 2) 84 | -------------------------------------------------------------------------------- /data/jackson.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hplieninger/ItemResponseTrees/a01af0f708b9e818e166e90a204a432ba695b06a/data/jackson.rda -------------------------------------------------------------------------------- /inst/CITATION: -------------------------------------------------------------------------------- 1 | citHeader("To cite ItemResponseTrees in publications use:") 2 | 3 | citEntry( 4 | entry = "Manual", 5 | title = "{ItemResponseTrees}: {IR}-Tree Modeling in mirt, {Mplus}, or {TAM}", 6 | author = personList(as.person("Hansj\\\"{o}rg Plieninger")), 7 | year = "2020", 8 | url = "https://CRAN.R-project.org/package=ItemResponseTrees", 9 | version = "0.2.5", 10 | note = "R package", 11 | 12 | textVersion = paste( 13 | "Plieninger, H. (2020).", 14 | "ItemResponseTrees: IR-tree modeling in mirt, Mplus, or TAM.", 15 | "Retrieved from https://CRAN.R-project.org/package=ItemResponseTrees") 16 | ) 17 | -------------------------------------------------------------------------------- /inst/WORDLIST: -------------------------------------------------------------------------------- 1 | Böckenholt 2 | Cai 3 | Carstensen 4 | codecov 5 | contraints 6 | doi 7 | Dontas 8 | extraversion 9 | Falk 10 | FSCORES 11 | GRM 12 | http 13 | https 14 | IPIP 15 | irt 16 | irtree 17 | irtrees 18 | likert 19 | loadings 20 | LV 21 | mirt 22 | modeltests 23 | Mplus 24 | MplusAutomation 25 | mplusObject 26 | mpt 27 | MPT 28 | oldName 29 | PCM 30 | php 31 | pseudoitem 32 | pseudoitems 33 | Rasch 34 | RData 35 | README 36 | Recode 37 | recoded 38 | tibble 39 | tidyverse 40 | truncnorm 41 | Tutz 42 | Verhelst 43 | Wetzel 44 | -------------------------------------------------------------------------------- /inst/examples/example-fit.R: -------------------------------------------------------------------------------- 1 | \donttest{ 2 | # Running these examples may take a while 3 | 4 | data("jackson") 5 | df1 <- jackson[1:456, paste0("C", 1:5)] 6 | df2 <- jackson[1:456, c(paste0("C", 1:5), paste0("E", 1:5))] 7 | 8 | irtree_create_template(df1) 9 | 10 | # Graded Response Model --------------------------------------------------- 11 | 12 | m1 <- " 13 | IRT: 14 | t BY C1@1, C2@1, C3@1, C4@1, C5@1; 15 | 16 | Class: 17 | GRM 18 | " 19 | 20 | model1 <- irtree_model(m1) 21 | 22 | fit1 <- fit(model1, data = df1) 23 | 24 | glance(fit1) 25 | tidy(fit1, par_type = "difficulty") 26 | augment(fit1) 27 | 28 | # IR-Tree Models ---------------------------------------------------------- 29 | 30 | ##### IR-tree model for 1 target trait ##### 31 | 32 | m2 <- " 33 | Equations: 34 | 1 = (1-m)*(1-t)*e 35 | 2 = (1-m)*(1-t)*(1-e) 36 | 3 = m 37 | 4 = (1-m)*t*(1-e) 38 | 5 = (1-m)*t*e 39 | 40 | IRT: 41 | t BY C1@1, C2@1, C3@1, C4@1, C5@1; 42 | e BY C1@1, C2@1, C3@1, C4@1, C5@1; 43 | m BY C1@1, C2@1, C3@1, C4@1, C5@1; 44 | 45 | Class: 46 | Tree 47 | " 48 | 49 | model2 <- irtree_model(m2) 50 | 51 | # See ?mirt::mirt for details on method argument 52 | fit2 <- fit(model2, data = df1, control = control_mirt(method = "MHRM")) 53 | 54 | ##### IR-tree model for 2 target traits ##### 55 | 56 | m3 <- " 57 | Equations: 58 | 1 = (1-m)*(1-t)*e 59 | 2 = (1-m)*(1-t)*(1-e) 60 | 3 = m 61 | 4 = (1-m)*t*(1-e) 62 | 5 = (1-m)*t*e 63 | 64 | IRT: 65 | t1 BY C1@1, C2@1, C3@1, C4@1, C5@1; 66 | t2 BY E1@1, E2@1, E3@1, E4@1, E5@1; 67 | e BY C1@1, C2@1, C3@1, C4@1, C5@1, E1@1, E2@1, E3@1, E4@1, E5@1; 68 | m BY C1@1, C2@1, C3@1, C4@1, C5@1, E1@1, E2@1, E3@1, E4@1, E5@1; 69 | 70 | Class: 71 | Tree 72 | 73 | Constraints: 74 | t = t1 | t2 75 | " 76 | 77 | model3 <- irtree_model(m3) 78 | 79 | fit3 <- fit(model3, data = df2, control = control_mirt(method = "MHRM")) 80 | 81 | ##### IR-tree model constrained to Steps Model ##### 82 | 83 | m4 <- " 84 | Equations: 85 | 1 = (1-a1) 86 | 2 = a1*(1-a2) 87 | 3 = a1*a2*(1-a3) 88 | 4 = a1*a2*a3*(1-a4) 89 | 5 = a1*a2*a3*a4 90 | 91 | IRT: 92 | a1 BY C1@1, C2@1, C3@1, C4@1, C5@1; 93 | a2 BY C1@1, C2@1, C3@1, C4@1, C5@1; 94 | a3 BY C1@1, C2@1, C3@1, C4@1, C5@1; 95 | a4 BY C1@1, C2@1, C3@1, C4@1, C5@1; 96 | 97 | Class: 98 | Tree 99 | 100 | Constraints: 101 | a1 = a2 102 | a1 = a3 103 | a1 = a4 104 | " 105 | 106 | model4 <- irtree_model(m4) 107 | 108 | fit4 <- fit(model4, data = df1) 109 | 110 | # Partial Credit Model ---------------------------------------------------- 111 | 112 | ##### Ordinary PCM ##### 113 | 114 | m5 <- " 115 | IRT: 116 | t BY C1@1, C2@1, C3@1, C4@1, C5@1; 117 | 118 | Weights: 119 | t = c(0, 1, 2, 3, 4) 120 | 121 | Class: 122 | PCM 123 | " 124 | 125 | model5 <- irtree_model(m5) 126 | 127 | fit5 <- fit(model5, data = df1 - 1, engine = "tam") 128 | 129 | ##### Multidimensional PCM with constraints ##### 130 | 131 | m6 <- " 132 | IRT: 133 | t1 BY C1@1, C2@1, C3@1, C4@1, C5@1; 134 | t2 BY E1@1, E2@1, E3@1, E4@1, E5@1; 135 | e BY C1@1, C2@1, C3@1, C4@1, C5@1, E1@1, E2@1, E3@1, E4@1, E5@1; 136 | m BY C1@1, C2@1, C3@1, C4@1, C5@1, E1@1, E2@1, E3@1, E4@1, E5@1; 137 | 138 | Weights: 139 | t = c(0, 1, 2, 3, 4) 140 | e = c(1, 0, 0, 0, 1) 141 | m = c(0, 0, 1, 0, 0) 142 | 143 | Class: 144 | PCM 145 | 146 | Constraints: 147 | t = t1 | t2 148 | " 149 | 150 | model6 <- irtree_model(m6) 151 | 152 | fit6 <- fit(model6, data = df2 - 1, engine = "tam", 153 | control = control_tam(control = list(snodes = 1234))) 154 | } 155 | -------------------------------------------------------------------------------- /inst/examples/example-generate-data.R: -------------------------------------------------------------------------------- 1 | # IR-Tree Model ----------------------------------------------------------- 2 | 3 | m1 <- " 4 | Equations: 5 | 1 = (1-m)*(1-t)*e 6 | 2 = (1-m)*(1-t)*(1-e) 7 | 3 = m 8 | 4 = (1-m)*t*(1-e) 9 | 5 = (1-m)*t*e 10 | 11 | IRT: 12 | t BY x1, x2, x3; 13 | e BY x1, x2, x3; 14 | m BY x1, x2, x3; 15 | 16 | Class: 17 | Tree 18 | " 19 | 20 | model1 <- irtree_model(m1) 21 | 22 | dat1 <- irtree_gen_data(model1, N = 5, sigma = diag(3), 23 | itempar = list(beta = matrix(rnorm(9), 3, 3), 24 | alpha = matrix(1, 3, 3))) 25 | dat1$data 26 | 27 | # Partial Credit Model ---------------------------------------------------- 28 | 29 | m2 <- " 30 | IRT: 31 | t BY x1@1, x2@1, x3@1; 32 | e BY x1@1, x2@1, x3@1; 33 | m BY x1@1, x2@1, x3@1; 34 | 35 | Weights: 36 | t = c(0, 1, 2, 3, 4) 37 | e = c(1, 0, 0, 0, 1) 38 | m = c(0, 0, 1, 0, 0) 39 | 40 | Class: 41 | PCM 42 | " 43 | model2 <- irtree_model(m2) 44 | dat2 <- irtree_gen_data(model2, N = 5, sigma = diag(3), 45 | itempar = list(beta = matrix(sort(rnorm(12)), 3, 4))) 46 | dat2$data 47 | 48 | m3 <- " 49 | IRT: 50 | t BY x1@1, x2@1, x3@1; 51 | 52 | Weights: 53 | t = 0:4 54 | 55 | Class: 56 | PCM 57 | " 58 | 59 | model3 <- irtree_model(m3) 60 | 61 | dat3 <- irtree_gen_data(model3, N = 5, sigma = diag(1), 62 | itempar = list(beta = matrix(sort(rnorm(12)), 3, 4))) 63 | dat3$data 64 | -------------------------------------------------------------------------------- /inst/examples/example-sim.R: -------------------------------------------------------------------------------- 1 | \donttest{ 2 | # Running these examples may take a while 3 | 4 | m1 <- " 5 | Equations: 6 | 1 = 1-a 7 | 2 = a*(1-b) 8 | 3 = a*b 9 | 10 | IRT: 11 | a BY x1@1, x2@1, x3@1, x4@1, X5@1, X6@1, X7@1; 12 | b BY x1@1, x2@1, x3@1, x4@1, X5@1, X6@1, X7@1; 13 | 14 | Class: 15 | Tree 16 | " 17 | 18 | m2 <- " 19 | IRT: 20 | a BY x1@1, x2@1, x3@1, x4@1, X5@1, X6@1, X7@1; 21 | 22 | Class: 23 | GRM 24 | " 25 | 26 | model1 <- irtree_model(m1) 27 | model2 <- irtree_model(m2) 28 | 29 | res <- irtree_sim( 30 | ### Data generation ### 31 | gen_model = model1, 32 | link = "logit", 33 | N = 500, 34 | sigma = function(x) diag(2), 35 | itempar = function(x) list( 36 | beta = matrix(sort(runif(model1$J*model1$P, -2, 2)), 37 | model1$J, model1$P), 38 | alpha = matrix(1, model1$J, model1$P)), 39 | na_okay = FALSE, 40 | 41 | ### Estimation ### 42 | fit_model = list(model1, model2), 43 | engine = "mirt", 44 | control = control_mirt(SE = FALSE), 45 | par_type = "difficulty", 46 | 47 | ### Replications ### 48 | R = 2, 49 | save_rdata = FALSE, 50 | 51 | ### Optional parallelization ### 52 | plan = "multiprocess", 53 | plan_args = list(workers = future::availableCores() - 1) 54 | ) 55 | 56 | tab1 <- matrix(NA, 0, 4, dimnames = list(NULL, c("Rep", "Model", "AIC", "BIC"))) 57 | 58 | for (ii in seq_along(res)) { 59 | for (jj in seq_along(res[[ii]]$fits)) { 60 | IC <- res[[ii]]$fits[[jj]]$glanced 61 | tab1 <- rbind(tab1, c(ii, jj, round(IC$AIC, -1), round(IC$BIC, -1))) 62 | } 63 | } 64 | tab1 65 | #> Rep Model AIC BIC 66 | #> [1,] 1 1 6900 6970 67 | #> [2,] 1 2 7000 7060 68 | #> [3,] 2 1 6810 6880 69 | #> [4,] 2 2 6880 6940 70 | } 71 | -------------------------------------------------------------------------------- /inst/examples/example-tidiers.R: -------------------------------------------------------------------------------- 1 | data("jackson") 2 | df1 <- jackson[1:234, paste0("C", 1:5)] 3 | irtree_create_template(df1) 4 | m1 <- " 5 | IRT: 6 | t BY C1@1, C2@1, C3@1, C4@1, C5@1; 7 | Class: 8 | GRM" 9 | fit1 <- fit(irtree_model(m1), data = df1) 10 | 11 | tidy(fit1, par_type = "difficulty") 12 | 13 | glance(fit1) 14 | 15 | augment(fit1) 16 | -------------------------------------------------------------------------------- /man/ItemResponseTrees-deprecated.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ItemResponseTrees-deprecated.R, 3 | % R/extract-mirt-output.R, R/extract-mplus-output.R 4 | \name{ItemResponseTrees-deprecated} 5 | \alias{ItemResponseTrees-deprecated} 6 | \alias{extract_mirt_output} 7 | \alias{extract_mplus_output} 8 | \title{Deprecated functions in package \strong{ItemResponseTrees}} 9 | \description{ 10 | These functions are provided for compatibility with older versions only, 11 | and may be defunct as soon as the next release. 12 | } 13 | \details{ 14 | The original help page for these functions is available at 15 | help("oldName-deprecated") (note the quotes). 16 | } 17 | \section{\code{extract_mirt_output()}}{ 18 | 19 | This function is deprecated. Use \code{glance()}, \code{tidy()}, and \code{augment()} 20 | instead. 21 | } 22 | 23 | \section{\code{extract_mplus_output()}}{ 24 | 25 | This function is deprecated. Use \code{glance()}, \code{tidy()}, and \code{augment()} 26 | instead. 27 | } 28 | 29 | \keyword{internal} 30 | -------------------------------------------------------------------------------- /man/ItemResponseTrees-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ItemResponeTrees-package.R 3 | \docType{package} 4 | \encoding{UTF-8} 5 | \name{ItemResponseTrees-package} 6 | \alias{ItemResponseTrees-package} 7 | \alias{_PACKAGE} 8 | \alias{ItemResponseTrees} 9 | \title{IR-Tree Modeling in mirt, Mplus, or TAM} 10 | \description{ 11 | Item response tree (IR-tree) models are a class of item response theory (IRT) 12 | models that assume that the responses to polytomous items can best be 13 | explained by multiple psychological processes (e.g., Böckenholt, 2012, 14 | \url{https://dx.doi.org/10.1037/a0028111}). The package 'ItemResponseTrees' 15 | allows to fit such IR-tree models in 16 | \href{https://cran.r-project.org/package=mirt}{mirt}, 17 | \href{https://cran.r-project.org/package=MplusAutomation}{Mplus}, or 18 | \href{https://cran.r-project.org/package=TAM}{TAM}. The package automates some of 19 | the hassle of IR-tree modeling by means of a consistent syntax. This allows 20 | new users to quickly adopt this model class, and this allows experienced 21 | users to fit many complex models effortlessly. 22 | } 23 | \references{ 24 | Böckenholt, U. (2012). Modeling multiple response processes in 25 | judgment and choice. \emph{Psychological Methods}, \emph{17}(4), 665–678. 26 | https://doi.org/10.1037/a0028111 27 | 28 | Plieninger, H. (2020). Developing and applying IR-tree models: 29 | Guidelines, caveats, and an extension to multiple groups. \emph{Organizational 30 | Research Methods}. Advance online publication. 31 | https://doi.org/10.1177/1094428120911096 32 | } 33 | \seealso{ 34 | Useful links: 35 | \itemize{ 36 | \item \url{https://github.com/hplieninger/ItemResponseTrees} 37 | \item Report bugs at \url{https://github.com/hplieninger/ItemResponseTrees/issues} 38 | } 39 | 40 | } 41 | \author{ 42 | Hansjörg Plieninger 43 | } 44 | \keyword{internal} 45 | -------------------------------------------------------------------------------- /man/augment.irtree_fit.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tidiers.R 3 | \name{augment.irtree_fit} 4 | \alias{augment.irtree_fit} 5 | \title{Augment data with information from an irtree_fit object} 6 | \usage{ 7 | \method{augment}{irtree_fit}(x = NULL, data = NULL, se_fit = TRUE, method = "EAP", ...) 8 | } 9 | \arguments{ 10 | \item{x}{object of class \code{irtree_fit} as returned from \code{\link[=fit.irtree_model]{fit()}}.} 11 | 12 | \item{data}{Optional data frame that is returned together with the predicted 13 | values. Argument is not needed since the data are contained in the fitted 14 | object.} 15 | 16 | \item{se_fit}{Logical indicating whether standard errors for the fitted 17 | values should be returned as well.} 18 | 19 | \item{method}{This is passed to \code{\link[mirt:fscores]{mirt::fscores()}} or 20 | \code{\link[TAM:IRT.factor.scores.tam]{TAM:::IRT.factor.scores()}} (as argument 21 | \code{type}) if applicable.} 22 | 23 | \item{...}{Additional arguments passed to \code{\link[mirt:fscores]{mirt::fscores()}} or 24 | \code{\link[TAM:IRT.factor.scores.tam]{TAM:::IRT.factor.scores()}} if 25 | applicable.} 26 | } 27 | \value{ 28 | Returns a \link[tibble:tibble-package]{tibble} with one row for each 29 | observation and one (two) additional columns for each latent variable if 30 | \code{se_fit = FALSE} (if \code{se_fit = TRUE}). The names of the new columns start 31 | with \code{.fit} (and \code{.se.fit}). 32 | } 33 | \description{ 34 | Augment accepts a model object and a dataset and adds 35 | information about each observation in the dataset, namely, predicted values 36 | in the .fitted column. New columns always begin with a . prefix to avoid 37 | overwriting columns in the original dataset. 38 | } 39 | \details{ 40 | Note that argument \code{method} is used only for engines mirt and TAM. 41 | } 42 | \examples{ 43 | data("jackson") 44 | df1 <- jackson[1:234, paste0("C", 1:5)] 45 | irtree_create_template(df1) 46 | m1 <- " 47 | IRT: 48 | t BY C1@1, C2@1, C3@1, C4@1, C5@1; 49 | Class: 50 | GRM" 51 | fit1 <- fit(irtree_model(m1), data = df1) 52 | 53 | tidy(fit1, par_type = "difficulty") 54 | 55 | glance(fit1) 56 | 57 | augment(fit1) 58 | } 59 | \seealso{ 60 | \code{\link[generics:augment]{generics::augment()}} 61 | } 62 | -------------------------------------------------------------------------------- /man/clps.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/helpers.R 3 | \name{clps} 4 | \alias{clps} 5 | \title{Wrapper Around Paste to Collapse a Character Vector} 6 | \usage{ 7 | clps(collapse = " ", ..., sep = " ") 8 | } 9 | \arguments{ 10 | \item{collapse}{an optional character string to separate the results. Not 11 | \code{\link[base]{NA_character_}}.} 12 | 13 | \item{...}{ 14 | Arguments passed on to \code{\link[base:paste]{base::paste}} 15 | \describe{ 16 | \item{\code{recycle0}}{\code{\link[base]{logical}} indicating if zero-length 17 | character arguments should lead to the zero-length 18 | \code{\link[base]{character}(0)} after the \code{sep}-phase (which turns into 19 | \code{""} in the \code{collapse}-phase, i.e., when \code{collapse} is 20 | not \code{NULL}).} 21 | }} 22 | 23 | \item{sep}{a character string to separate the terms. Not 24 | \code{\link[base]{NA_character_}}.} 25 | } 26 | \description{ 27 | Wrapper Around Paste to Collapse a Character Vector 28 | } 29 | \seealso{ 30 | \code{\link[base:paste]{base::paste()}} 31 | } 32 | \keyword{internal} 33 | -------------------------------------------------------------------------------- /man/control_mirt.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/fit-mirt.R 3 | \name{control_mirt} 4 | \alias{control_mirt} 5 | \title{Control aspects of fitting a model in mirt} 6 | \usage{ 7 | control_mirt( 8 | SE = TRUE, 9 | method = "EM", 10 | quadpts = NULL, 11 | control = list(), 12 | technical = list(), 13 | ... 14 | ) 15 | } 16 | \arguments{ 17 | \item{SE, method, quadpts, ...}{These arguments are passed to and documented 18 | in \code{\link[mirt:mirt]{mirt::mirt()}}. They can be used to tweak the estimation algorithm.} 19 | 20 | \item{control}{List of arguments passed to argument \code{control} of 21 | \code{\link[mirt:mirt]{mirt::mirt()}}.} 22 | 23 | \item{technical}{List of arguments passed to argument \code{technical} of 24 | \code{\link[mirt:mirt]{mirt::mirt()}}.} 25 | } 26 | \value{ 27 | A list with one element for every argument of \code{control_mirt()}. 28 | } 29 | \description{ 30 | This function should be used to generate the \code{control} argument of the 31 | \code{\link[=fit.irtree_model]{fit()}} function. 32 | } 33 | \examples{ 34 | control_mirt(SE = FALSE, 35 | method = "QMCEM", 36 | quadpts = 4455, 37 | technical = list(NCYCLES = 567), 38 | TOL = .001) 39 | control_mirt(method = "MHRM", 40 | draws = 5544) 41 | } 42 | -------------------------------------------------------------------------------- /man/control_mplus.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/fit-mplus.R 3 | \name{control_mplus} 4 | \alias{control_mplus} 5 | \title{Control aspects of fitting a model in Mplus} 6 | \usage{ 7 | control_mplus( 8 | file = tempfile("irtree_"), 9 | overwrite = FALSE, 10 | cleanup = run, 11 | run = TRUE, 12 | estimator = "MLR", 13 | quadpts = 15, 14 | save_fscores = TRUE, 15 | analysis_list = list(COVERAGE = "0"), 16 | Mplus_command = "Mplus", 17 | warnings2messages = FALSE 18 | ) 19 | } 20 | \arguments{ 21 | \item{file}{String naming the file (or path) of the Mplus files and the data 22 | file. Do not provide file endings, those will be automatically appended.} 23 | 24 | \item{overwrite}{Logical value indicating whether data and input (if present) 25 | files should be overwritten.} 26 | 27 | \item{cleanup}{Logical, whether the Mplus files should be removed on exit.} 28 | 29 | \item{run}{Logical, whether to indeed run Mplus.} 30 | 31 | \item{estimator}{String, passed to argument 'ESTIMATOR' in Mplus.} 32 | 33 | \item{quadpts}{This is passed to argument 'INTEGRATION' of Mplus. Thus, it 34 | may be an integer specifying the number of integration points for the Mplus 35 | default of rectangular numerical integration (e.g., \code{quadpts = 15}). Or it 36 | may be a string, which gives more fine grained control (e.g., \code{quadpts = "MONTECARLO(2000)"}).} 37 | 38 | \item{save_fscores}{Logical, whether to save FSCORES or not.} 39 | 40 | \item{analysis_list}{Named list of strings passed to Mplus' argument 41 | ANALYSIS. See examples below.} 42 | 43 | \item{Mplus_command}{optional. N.B.: No need to pass this parameter for most users (has intelligent 44 | defaults). Allows the user to specify the name/path of the Mplus executable to be used for 45 | running models. This covers situations where Mplus is not in the system's path, 46 | or where one wants to test different versions of the Mplus program.} 47 | 48 | \item{warnings2messages}{Logical, whether Mplus errors and warnings should be 49 | signaled as warnings (the default) or messages.} 50 | } 51 | \value{ 52 | A list with one element for every argument of \code{control_mplus()}. 53 | } 54 | \description{ 55 | This function should be used to generate the \code{control} argument of the 56 | \code{\link[=fit.irtree_model]{fit()}} function. 57 | } 58 | \examples{ 59 | control_mplus(file = tempfile("irtree_", tmpdir = "."), 60 | quadpts = "GAUSS(10)", 61 | analysis_list = list(COVERAGE = "0", 62 | MITERATIONS = "500", 63 | MCONVERGENCE = ".001")) 64 | } 65 | -------------------------------------------------------------------------------- /man/control_tam.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/fit-tam.R 3 | \name{control_tam} 4 | \alias{control_tam} 5 | \title{Control aspects of fitting a model in TAM} 6 | \usage{ 7 | control_tam( 8 | set_min_to_0 = FALSE, 9 | control = list(snodes = 0, maxiter = 1000, increment.factor = 1, fac.oldxsi = 0), 10 | ... 11 | ) 12 | } 13 | \arguments{ 14 | \item{set_min_to_0}{Logical. \code{\link[TAM:tam.mml]{TAM::tam.mml()}} expects the data to be scored 0, 15 | ..., K. If \code{set_min_to_0 = TRUE}, the minimum of the data is subtracted from 16 | each response, which will likely both satisfy TAM and do no harm to the 17 | data.} 18 | 19 | \item{control}{List of arguments passed to argument \code{control} of 20 | \code{\link[TAM:tam.mml]{TAM::tam.mml()}}. See examples below.} 21 | 22 | \item{...}{Other arguments passed to \code{\link[TAM:tam.mml]{TAM::tam.mml()}}.} 23 | } 24 | \value{ 25 | A list with one element for every argument of \code{control_tam()}. 26 | } 27 | \description{ 28 | This function should be used to generate the \code{control} argument of the 29 | \code{\link[=fit.irtree_model]{fit()}} function. 30 | } 31 | \examples{ 32 | control_tam(set_min_to_0 = TRUE, 33 | control = list(snodes = 0, 34 | maxiter = 1000, 35 | increment.factor = 1, 36 | fac.oldxsi = 0), 37 | constraint = "items") 38 | } 39 | -------------------------------------------------------------------------------- /man/extract_mirt_output-deprecated.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/extract-mirt-output.R 3 | \name{extract_mirt_output-deprecated} 4 | \alias{extract_mirt_output-deprecated} 5 | \title{Retrieve estimates from mirt.} 6 | \usage{ 7 | extract_mirt_output(results = NULL, object = NULL, method = "MAP", 8 | class = NULL, ...) 9 | } 10 | \arguments{ 11 | \item{results}{An object of class \code{\link[mirt]{SingleGroupClass-class}} 12 | as returned from \code{\link[=irtree_fit_mirt]{irtree_fit_mirt()}}.} 13 | 14 | \item{object}{A description of the user-specified model. See 15 | \link{irtree_model} for more information.} 16 | 17 | \item{method}{Passed to \code{\link[mirt:fscores]{mirt::fscores()}}.} 18 | 19 | \item{class}{String specifying which class of model was fit.} 20 | 21 | \item{...}{Passed to \code{\link[mirt:fscores]{mirt::fscores()}}.} 22 | } 23 | \value{ 24 | A list of parameter estimates and model fit information. 25 | } 26 | \description{ 27 | This function takes the output from \code{\link[=irtree_fit_mirt]{irtree_fit_mirt()}} and 28 | returns the parameter estimates in a convenient way. 29 | } 30 | \seealso{ 31 | \link{ItemResponseTrees-deprecated} 32 | } 33 | \keyword{internal} 34 | -------------------------------------------------------------------------------- /man/extract_mplus_output-deprecated.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/extract-mplus-output.R 3 | \name{extract_mplus_output-deprecated} 4 | \alias{extract_mplus_output-deprecated} 5 | \title{Retrieve estimates From Mplus.} 6 | \usage{ 7 | extract_mplus_output(results = NULL, object = NULL, class = NULL, 8 | .errors2messages = FALSE) 9 | } 10 | \arguments{ 11 | \item{results}{A list as returned from \code{\link[=irtree_fit_mplus]{irtree_fit_mplus()}}.} 12 | 13 | \item{object}{A description of the user-specified model. See 14 | \link{irtree_model} for more information.} 15 | 16 | \item{class}{String specifying which class of model was fit} 17 | 18 | \item{.errors2messages}{Logical indicating whether errors should be converted 19 | to messages} 20 | } 21 | \value{ 22 | A list of parameter estimates, model fit information 23 | (\code{summaries}), \code{warnings}, \code{errors}. 24 | } 25 | \description{ 26 | This function takes the output from Mplus as returned from 27 | \code{\link[=irtree_fit_mplus]{irtree_fit_mplus()}} and returns the estimates in a convenient way. 28 | } 29 | \seealso{ 30 | \link{ItemResponseTrees-deprecated} 31 | } 32 | \keyword{internal} 33 | -------------------------------------------------------------------------------- /man/fit.irtree_model.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/fit.R 3 | \name{fit.irtree_model} 4 | \alias{fit.irtree_model} 5 | \title{Fit an ItemResponseTrees model} 6 | \usage{ 7 | \method{fit}{irtree_model}( 8 | object = NULL, 9 | data = NULL, 10 | engine = c("mirt", "mplus", "tam"), 11 | ..., 12 | link = c("logit", "probit"), 13 | verbose = interactive(), 14 | control = NULL, 15 | improper_okay = FALSE 16 | ) 17 | } 18 | \arguments{ 19 | \item{object}{Object of class \code{irtree_model}. See \link{irtree_model} for more 20 | information.} 21 | 22 | \item{data}{Data frame containing containing one row per respondent and one 23 | column per variable. The variable names must correspond to those used in 24 | \code{object}.} 25 | 26 | \item{engine}{String specifying whether to use mirt, Mplus, or TAM for 27 | estimation.} 28 | 29 | \item{...}{Not currently used. Use \code{control} instead.} 30 | 31 | \item{link}{String specifying the link function. May be either logit, or (in 32 | case of Mplus), probit.} 33 | 34 | \item{verbose}{Logical indicating whether output should be printed to the 35 | console.} 36 | 37 | \item{control}{List. The allowed elements of this list depend on the 38 | \code{engine}. Use \code{\link[=control_mirt]{control_mirt()}}, \code{\link[=control_mplus]{control_mplus()}}, or \code{\link[=control_tam]{control_tam()}} for 39 | convenience. Note that the \code{fit()} function does not use \code{...}, but that 40 | you can use the \verb{control_*()} functions to pass additional arguments.} 41 | 42 | \item{improper_okay}{Logical indicating whether the model should also be fit 43 | if it is not a proper IR-tree model. Set this only to \code{TRUE} if you really 44 | know what you are doing.} 45 | } 46 | \value{ 47 | Returns a list of class \code{irtree_fit}. The first list element is the 48 | return value of either \code{\link[mirt:mirt]{mirt::mirt()}}, \code{\link[MplusAutomation:readModels]{MplusAutomation::readModels()}}, or 49 | \code{\link[TAM:tam.mml]{TAM::tam.mml()}}. Further information is provided in the element 50 | \code{spec}. 51 | } 52 | \description{ 53 | This function takes a \code{data} frame and an \code{object} of class \link{irtree_model} 54 | and runs the model in either mirt, Mplus, or TAM. 55 | } 56 | \section{Methods}{ 57 | The methods \code{coef()}, \code{summary()}, and \code{print()} are 58 | implemented for objects of class \code{irtree_fit}, and those wrap the 59 | respective functions of \link[mirt:mirt-package]{mirt}, 60 | \link[MplusAutomation:MplusAutomation]{MplusAutomation}, or 61 | \link[TAM:TAM-package]{TAM}. However, \code{\link[=glance.irtree_fit]{glance()}}, 62 | \code{\link[=tidy.irtree_fit]{tidy()}}, and \code{\link[=augment.irtree_fit]{augment()}} may be 63 | more helpful. 64 | } 65 | 66 | \examples{ 67 | \donttest{ 68 | # Running these examples may take a while 69 | 70 | data("jackson") 71 | df1 <- jackson[1:456, paste0("C", 1:5)] 72 | df2 <- jackson[1:456, c(paste0("C", 1:5), paste0("E", 1:5))] 73 | 74 | irtree_create_template(df1) 75 | 76 | # Graded Response Model --------------------------------------------------- 77 | 78 | m1 <- " 79 | IRT: 80 | t BY C1@1, C2@1, C3@1, C4@1, C5@1; 81 | 82 | Class: 83 | GRM 84 | " 85 | 86 | model1 <- irtree_model(m1) 87 | 88 | fit1 <- fit(model1, data = df1) 89 | 90 | glance(fit1) 91 | tidy(fit1, par_type = "difficulty") 92 | augment(fit1) 93 | 94 | # IR-Tree Models ---------------------------------------------------------- 95 | 96 | ##### IR-tree model for 1 target trait ##### 97 | 98 | m2 <- " 99 | Equations: 100 | 1 = (1-m)*(1-t)*e 101 | 2 = (1-m)*(1-t)*(1-e) 102 | 3 = m 103 | 4 = (1-m)*t*(1-e) 104 | 5 = (1-m)*t*e 105 | 106 | IRT: 107 | t BY C1@1, C2@1, C3@1, C4@1, C5@1; 108 | e BY C1@1, C2@1, C3@1, C4@1, C5@1; 109 | m BY C1@1, C2@1, C3@1, C4@1, C5@1; 110 | 111 | Class: 112 | Tree 113 | " 114 | 115 | model2 <- irtree_model(m2) 116 | 117 | # See ?mirt::mirt for details on method argument 118 | fit2 <- fit(model2, data = df1, control = control_mirt(method = "MHRM")) 119 | 120 | ##### IR-tree model for 2 target traits ##### 121 | 122 | m3 <- " 123 | Equations: 124 | 1 = (1-m)*(1-t)*e 125 | 2 = (1-m)*(1-t)*(1-e) 126 | 3 = m 127 | 4 = (1-m)*t*(1-e) 128 | 5 = (1-m)*t*e 129 | 130 | IRT: 131 | t1 BY C1@1, C2@1, C3@1, C4@1, C5@1; 132 | t2 BY E1@1, E2@1, E3@1, E4@1, E5@1; 133 | e BY C1@1, C2@1, C3@1, C4@1, C5@1, E1@1, E2@1, E3@1, E4@1, E5@1; 134 | m BY C1@1, C2@1, C3@1, C4@1, C5@1, E1@1, E2@1, E3@1, E4@1, E5@1; 135 | 136 | Class: 137 | Tree 138 | 139 | Constraints: 140 | t = t1 | t2 141 | " 142 | 143 | model3 <- irtree_model(m3) 144 | 145 | fit3 <- fit(model3, data = df2, control = control_mirt(method = "MHRM")) 146 | 147 | ##### IR-tree model constrained to Steps Model ##### 148 | 149 | m4 <- " 150 | Equations: 151 | 1 = (1-a1) 152 | 2 = a1*(1-a2) 153 | 3 = a1*a2*(1-a3) 154 | 4 = a1*a2*a3*(1-a4) 155 | 5 = a1*a2*a3*a4 156 | 157 | IRT: 158 | a1 BY C1@1, C2@1, C3@1, C4@1, C5@1; 159 | a2 BY C1@1, C2@1, C3@1, C4@1, C5@1; 160 | a3 BY C1@1, C2@1, C3@1, C4@1, C5@1; 161 | a4 BY C1@1, C2@1, C3@1, C4@1, C5@1; 162 | 163 | Class: 164 | Tree 165 | 166 | Constraints: 167 | a1 = a2 168 | a1 = a3 169 | a1 = a4 170 | " 171 | 172 | model4 <- irtree_model(m4) 173 | 174 | fit4 <- fit(model4, data = df1) 175 | 176 | # Partial Credit Model ---------------------------------------------------- 177 | 178 | ##### Ordinary PCM ##### 179 | 180 | m5 <- " 181 | IRT: 182 | t BY C1@1, C2@1, C3@1, C4@1, C5@1; 183 | 184 | Weights: 185 | t = c(0, 1, 2, 3, 4) 186 | 187 | Class: 188 | PCM 189 | " 190 | 191 | model5 <- irtree_model(m5) 192 | 193 | fit5 <- fit(model5, data = df1 - 1, engine = "tam") 194 | 195 | ##### Multidimensional PCM with constraints ##### 196 | 197 | m6 <- " 198 | IRT: 199 | t1 BY C1@1, C2@1, C3@1, C4@1, C5@1; 200 | t2 BY E1@1, E2@1, E3@1, E4@1, E5@1; 201 | e BY C1@1, C2@1, C3@1, C4@1, C5@1, E1@1, E2@1, E3@1, E4@1, E5@1; 202 | m BY C1@1, C2@1, C3@1, C4@1, C5@1, E1@1, E2@1, E3@1, E4@1, E5@1; 203 | 204 | Weights: 205 | t = c(0, 1, 2, 3, 4) 206 | e = c(1, 0, 0, 0, 1) 207 | m = c(0, 0, 1, 0, 0) 208 | 209 | Class: 210 | PCM 211 | 212 | Constraints: 213 | t = t1 | t2 214 | " 215 | 216 | model6 <- irtree_model(m6) 217 | 218 | fit6 <- fit(model6, data = df2 - 1, engine = "tam", 219 | control = control_tam(control = list(snodes = 1234))) 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /man/glance.irtree_fit.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tidiers.R 3 | \name{glance.irtree_fit} 4 | \alias{glance.irtree_fit} 5 | \title{Glance at an irtree_fit object} 6 | \usage{ 7 | \method{glance}{irtree_fit}(x = NULL, ...) 8 | } 9 | \arguments{ 10 | \item{x}{object of class \code{irtree_fit} as returned from \code{\link[=fit.irtree_model]{fit()}}.} 11 | 12 | \item{...}{Additional arguments. Not used.} 13 | } 14 | \value{ 15 | A one-row \link[tibble:tibble-package]{tibble} with columns such as \code{AIC} and \code{BIC}. 16 | \subsection{Converged}{ 17 | 18 | The column \code{converged} indicates whether the model converged or not. For 19 | Mplus, this is \code{TRUE} if the output contained the phrase "The model 20 | estimation terminated normally". For mirt, this is equal to the output of 21 | \code{\link[mirt:extract.mirt]{mirt::extract.mirt(x, "converged")}}. For TAM, this is 22 | \code{NA} if no clear signs of non-convergence were observed. You are encouraged 23 | to check any warnings or errors in any case. 24 | } 25 | 26 | \subsection{Iterations}{ 27 | 28 | \code{iterations} is \code{NA} for Mplus models since respective 29 | information is not easily obtained from the output. 30 | } 31 | } 32 | \description{ 33 | Glance accepts an \code{irtree_fit} object and returns a \link[tibble:tibble-package]{tibble} 34 | with exactly one row of model summaries. 35 | } 36 | \examples{ 37 | data("jackson") 38 | df1 <- jackson[1:234, paste0("C", 1:5)] 39 | irtree_create_template(df1) 40 | m1 <- " 41 | IRT: 42 | t BY C1@1, C2@1, C3@1, C4@1, C5@1; 43 | Class: 44 | GRM" 45 | fit1 <- fit(irtree_model(m1), data = df1) 46 | 47 | tidy(fit1, par_type = "difficulty") 48 | 49 | glance(fit1) 50 | 51 | augment(fit1) 52 | } 53 | \seealso{ 54 | \code{\link[generics:glance]{generics::glance()}}, \code{\link[mirt:extract.mirt]{mirt::extract.mirt(x, "secondordertest")}} 55 | } 56 | -------------------------------------------------------------------------------- /man/irtree_create_template.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/irtree_paste.R 3 | \name{irtree_create_template} 4 | \alias{irtree_create_template} 5 | \title{Create a template of a model string} 6 | \usage{ 7 | irtree_create_template(data = NULL, mapping_matrix = NULL, rasch = TRUE) 8 | } 9 | \arguments{ 10 | \item{data}{Data frame.} 11 | 12 | \item{mapping_matrix}{Matrix of so-called pseudo-items, optional. The 13 | observed response categories must appear in the first column. The other 14 | columns contain the pseudo-items and each entry may be either \code{1}, \code{0}, or 15 | \code{NA}.} 16 | 17 | \item{rasch}{Logical. The string \verb{@1} will be appended to each variable name 18 | if \code{TRUE} with no effect otherwise.} 19 | } 20 | \description{ 21 | This function prints a template of a model string to the console based on the 22 | supplied data frame. This template can be copy-pasted and modified to define 23 | an \link{irtree_model}. 24 | } 25 | \examples{ 26 | irtree_create_template(jackson[, c(1, 6, 11)]) 27 | #> m1 <- " 28 | #> Equations: 29 | #> 1 = ... 30 | #> 2 = ... 31 | #> 3 = ... 32 | #> 4 = ... 33 | #> 5 = ... 34 | #> 35 | #> IRT: 36 | #> ... BY E1@1, E2@1, E3@1; 37 | #> ... BY E1@1, E2@1, E3@1; 38 | #> 39 | #> Class: 40 | #> Tree 41 | #> " 42 | 43 | irtree_create_template(jackson[, c(1, 6, 11)], 44 | cbind(1:5, 45 | m = c(0, 0, 1, 0, 0), 46 | t = c(1, 1, NA, 0, 0), 47 | e = c(1, 0, NA, 0, 1))) 48 | #> m1 <- " 49 | #> Equations: 50 | #> 1 = (1-m)*t*e 51 | #> 2 = (1-m)*t*(1-e) 52 | #> 3 = m 53 | #> 4 = (1-m)*(1-t)*(1-e) 54 | #> 5 = (1-m)*(1-t)*e 55 | #> 56 | #> IRT: 57 | #> m BY E1@1, E2@1, E3@1; 58 | #> t BY E1@1, E2@1, E3@1; 59 | #> e BY E1@1, E2@1, E3@1; 60 | #> 61 | #> Class: 62 | #> Tree 63 | #> " 64 | } 65 | -------------------------------------------------------------------------------- /man/irtree_fit_mirt.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/fit-mirt.R 3 | \name{irtree_fit_mirt} 4 | \alias{irtree_fit_mirt} 5 | \title{Fit an \code{irtree_model} using mirt} 6 | \usage{ 7 | irtree_fit_mirt( 8 | object = NULL, 9 | data = NULL, 10 | link = "logit", 11 | verbose = interactive(), 12 | control = control_mirt(), 13 | improper_okay = FALSE 14 | ) 15 | } 16 | \arguments{ 17 | \item{object}{Object of class \code{irtree_model}. See \link{irtree_model} for more 18 | information.} 19 | 20 | \item{data}{Data frame containing containing one row per respondent and one 21 | column per variable. The variable names must correspond to those used in 22 | \code{object}.} 23 | 24 | \item{link}{String specifying the link function. Only \code{logit} is 25 | implemented in mirt.} 26 | 27 | \item{verbose}{Logical indicating whether output should be printed to the 28 | console.} 29 | 30 | \item{control}{List. The allowed elements of this list depend on the 31 | \code{engine}. Use \code{\link[=control_mirt]{control_mirt()}}, \code{\link[=control_mplus]{control_mplus()}}, or \code{\link[=control_tam]{control_tam()}} for 32 | convenience. Note that the \code{fit()} function does not use \code{...}, but that 33 | you can use the \verb{control_*()} functions to pass additional arguments.} 34 | 35 | \item{improper_okay}{Logical indicating whether the model should also be fit 36 | if it is not a proper IR-tree model. Set this only to \code{TRUE} if you really 37 | know what you are doing.} 38 | } 39 | \description{ 40 | This function takes a \code{data} frame and a model \code{object} and runs the model in mirt. 41 | } 42 | \keyword{internal} 43 | -------------------------------------------------------------------------------- /man/irtree_fit_mplus.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/fit-mplus.R 3 | \name{irtree_fit_mplus} 4 | \alias{irtree_fit_mplus} 5 | \title{Fit an IR-Tree Model using Mplus} 6 | \usage{ 7 | irtree_fit_mplus( 8 | object = NULL, 9 | data = NULL, 10 | link = c("logit", "probit"), 11 | verbose = interactive(), 12 | control = control_mplus(), 13 | improper_okay = FALSE 14 | ) 15 | } 16 | \arguments{ 17 | \item{object}{Object of class \code{irtree_model}. See \link{irtree_model} for more 18 | information.} 19 | 20 | \item{data}{Data frame containing containing one row per respondent and one 21 | column per variable. The variable names must correspond to those used in 22 | \code{object}.} 23 | 24 | \item{link}{String, passed to argument 'LINK' in Mplus. Specifies 25 | the link function.} 26 | 27 | \item{verbose}{Logical indicating whether output should be printed to the 28 | console.} 29 | 30 | \item{control}{List. The allowed elements of this list depend on the 31 | \code{engine}. Use \code{\link[=control_mirt]{control_mirt()}}, \code{\link[=control_mplus]{control_mplus()}}, or \code{\link[=control_tam]{control_tam()}} for 32 | convenience. Note that the \code{fit()} function does not use \code{...}, but that 33 | you can use the \verb{control_*()} functions to pass additional arguments.} 34 | 35 | \item{improper_okay}{Logical indicating whether the model should also be fit 36 | if it is not a proper IR-tree model. Set this only to \code{TRUE} if you really 37 | know what you are doing.} 38 | } 39 | \description{ 40 | This function takes a data frame and a model string and runs the model in Mplus. 41 | } 42 | \examples{ 43 | run <- MplusAutomation::mplusAvailable() == 0 44 | 45 | m1 <- " 46 | IRT: 47 | attitude BY Comfort, Work, Future, Benefit; 48 | 49 | Class: 50 | GRM 51 | " 52 | model1 <- irtree_model(m1) 53 | data(Science, package = "mirt") 54 | 55 | fit1 <- fit(model1, Science, engine = "mplus", 56 | control = control_mplus(run = run)) 57 | } 58 | \keyword{internal} 59 | -------------------------------------------------------------------------------- /man/irtree_fit_tam.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/fit-tam.R 3 | \name{irtree_fit_tam} 4 | \alias{irtree_fit_tam} 5 | \title{Fit an \code{irtree_model} using TAM} 6 | \usage{ 7 | irtree_fit_tam( 8 | object = NULL, 9 | data = NULL, 10 | link = "logit", 11 | verbose = interactive(), 12 | control = control_tam(), 13 | improper_okay = FALSE 14 | ) 15 | } 16 | \arguments{ 17 | \item{object}{Object of class \code{irtree_model}. See \link{irtree_model} for more 18 | information.} 19 | 20 | \item{data}{Data frame containing containing one row per respondent and one 21 | column per variable. The variable names must correspond to those used in 22 | \code{object}.} 23 | 24 | \item{link}{String specifying the link function. Only \code{logit} is 25 | implemented in TAM.} 26 | 27 | \item{verbose}{Logical indicating whether output should be printed to the 28 | console.} 29 | 30 | \item{control}{List. The allowed elements of this list depend on the 31 | \code{engine}. Use \code{\link[=control_mirt]{control_mirt()}}, \code{\link[=control_mplus]{control_mplus()}}, or \code{\link[=control_tam]{control_tam()}} for 32 | convenience. Note that the \code{fit()} function does not use \code{...}, but that 33 | you can use the \verb{control_*()} functions to pass additional arguments.} 34 | 35 | \item{improper_okay}{Logical indicating whether the model should also be fit 36 | if it is not a proper IR-tree model. Set this only to \code{TRUE} if you really 37 | know what you are doing.} 38 | } 39 | \description{ 40 | This function takes a \code{data} frame and a model \code{object} and runs the model in TAM. 41 | } 42 | \keyword{internal} 43 | -------------------------------------------------------------------------------- /man/irtree_gen_data.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/generate-data.R 3 | \name{irtree_gen_data} 4 | \alias{irtree_gen_data} 5 | \title{Generate data} 6 | \usage{ 7 | irtree_gen_data( 8 | object = NULL, 9 | N = NULL, 10 | sigma = NULL, 11 | theta = NULL, 12 | itempar = NULL, 13 | link = c("logit", "probit"), 14 | na_okay = TRUE, 15 | skip = FALSE 16 | ) 17 | } 18 | \arguments{ 19 | \item{object}{Object of class \code{irtree_model}. See \link{irtree_model} for more 20 | information.} 21 | 22 | \item{N}{Integer, the number of persons.} 23 | 24 | \item{sigma}{Either a matrix or a function that returns a matrix. This matrix 25 | is the variance-covariance matrix of the person parameters that is passed 26 | to \code{\link[MASS:mvrnorm]{MASS::mvrnorm()}}. Note that the order of the person 27 | parameters is taken from the section Processes in the model \code{object} (see 28 | \link{irtree_model}).} 29 | 30 | \item{theta}{Optional numeric matrix of person parameters with one row per person and 31 | one column per dimension (i.e., \code{object$S}). If provided, this overrides 32 | \code{N} and \code{sigma}.} 33 | 34 | \item{itempar}{Either a list or a function that returns a list. The list has 35 | an element \code{beta} and an element \code{alpha}. Each of these is a 36 | matrix of item parameters. Note that the order of items (rows) is taken from the 37 | section Items and the order of processes (columns) is taken from the 38 | section Processes in the \code{model} (see \link{irtree_model}).} 39 | 40 | \item{link}{Character. Link function to use.} 41 | 42 | \item{na_okay}{Logical indicating whether variables with unobserved response 43 | categories are permitted. If \code{FALSE}, rejection sampling 44 | is used to ensure that all categories are observed.} 45 | 46 | \item{skip}{Logical. Some features of the \link{irtree_model} syntax, 47 | which are available for model fitting (e.g., \code{Addendum}), are not 48 | implemented for data generation. Those parts of the model are ignored if 49 | \code{skip = TRUE}.} 50 | } 51 | \value{ 52 | A list with element \code{data} containing the data and an 53 | element \code{spec} containing the true parameter values etc. 54 | } 55 | \description{ 56 | This function generates data from an ItemResponseTrees model. 57 | } 58 | \examples{ 59 | # IR-Tree Model ----------------------------------------------------------- 60 | 61 | m1 <- " 62 | Equations: 63 | 1 = (1-m)*(1-t)*e 64 | 2 = (1-m)*(1-t)*(1-e) 65 | 3 = m 66 | 4 = (1-m)*t*(1-e) 67 | 5 = (1-m)*t*e 68 | 69 | IRT: 70 | t BY x1, x2, x3; 71 | e BY x1, x2, x3; 72 | m BY x1, x2, x3; 73 | 74 | Class: 75 | Tree 76 | " 77 | 78 | model1 <- irtree_model(m1) 79 | 80 | dat1 <- irtree_gen_data(model1, N = 5, sigma = diag(3), 81 | itempar = list(beta = matrix(rnorm(9), 3, 3), 82 | alpha = matrix(1, 3, 3))) 83 | dat1$data 84 | 85 | # Partial Credit Model ---------------------------------------------------- 86 | 87 | m2 <- " 88 | IRT: 89 | t BY x1@1, x2@1, x3@1; 90 | e BY x1@1, x2@1, x3@1; 91 | m BY x1@1, x2@1, x3@1; 92 | 93 | Weights: 94 | t = c(0, 1, 2, 3, 4) 95 | e = c(1, 0, 0, 0, 1) 96 | m = c(0, 0, 1, 0, 0) 97 | 98 | Class: 99 | PCM 100 | " 101 | model2 <- irtree_model(m2) 102 | dat2 <- irtree_gen_data(model2, N = 5, sigma = diag(3), 103 | itempar = list(beta = matrix(sort(rnorm(12)), 3, 4))) 104 | dat2$data 105 | 106 | m3 <- " 107 | IRT: 108 | t BY x1@1, x2@1, x3@1; 109 | 110 | Weights: 111 | t = 0:4 112 | 113 | Class: 114 | PCM 115 | " 116 | 117 | model3 <- irtree_model(m3) 118 | 119 | dat3 <- irtree_gen_data(model3, N = 5, sigma = diag(1), 120 | itempar = list(beta = matrix(sort(rnorm(12)), 3, 4))) 121 | dat3$data 122 | } 123 | -------------------------------------------------------------------------------- /man/irtree_gen_pcm.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/generate-pcm-data.R 3 | \name{irtree_gen_pcm} 4 | \alias{irtree_gen_pcm} 5 | \title{Generate data from a partial credit model} 6 | \usage{ 7 | irtree_gen_pcm( 8 | object = NULL, 9 | N = NULL, 10 | sigma = NULL, 11 | theta = NULL, 12 | itempar = NULL, 13 | link = "logit", 14 | na_okay = TRUE, 15 | skip = FALSE 16 | ) 17 | } 18 | \arguments{ 19 | \item{object}{Object of class \code{irtree_model}. See \link{irtree_model} for more 20 | information.} 21 | 22 | \item{N}{Integer, the number of persons.} 23 | 24 | \item{sigma}{Either a matrix or a function that returns a matrix. This matrix 25 | is the variance-covariance matrix of the person parameters that is passed 26 | to \code{\link[MASS:mvrnorm]{MASS::mvrnorm()}}. Note that the order of the person 27 | parameters is taken from the section Processes in the model \code{object} (see 28 | \link{irtree_model}).} 29 | 30 | \item{theta}{Optional numeric matrix of person parameters with one row per person and 31 | one column per dimension (i.e., \code{object$S}). If provided, this overrides 32 | \code{N} and \code{sigma}.} 33 | 34 | \item{itempar}{Either a list or a function that returns a list. The list has 35 | an element \code{beta} and an element \code{alpha}. Each of these is a 36 | matrix of item parameters. Note that the order of items (rows) is taken from the 37 | section Items and the order of processes (columns) is taken from the 38 | section Processes in the \code{model} (see \link{irtree_model}).} 39 | 40 | \item{link}{Character. Link function to use.} 41 | 42 | \item{na_okay}{Logical indicating whether variables with unobserved response 43 | categories are permitted. If \code{FALSE}, rejection sampling 44 | is used to ensure that all categories are observed.} 45 | 46 | \item{skip}{Logical. Some features of the \link{irtree_model} syntax, 47 | which are available for model fitting (e.g., \code{Addendum}), are not 48 | implemented for data generation. Those parts of the model are ignored if 49 | \code{skip = TRUE}.} 50 | } 51 | \value{ 52 | A list with element \code{data} containing the data and an 53 | element \code{spec} containing the true parameter values etc. 54 | } 55 | \description{ 56 | This function generates data from a (multidimensional) PCM. 57 | } 58 | \keyword{internal} 59 | -------------------------------------------------------------------------------- /man/irtree_gen_tree.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/generate-data.R 3 | \name{irtree_gen_tree} 4 | \alias{irtree_gen_tree} 5 | \title{Generate data from an IR-tree model} 6 | \usage{ 7 | irtree_gen_tree( 8 | object = NULL, 9 | N = NULL, 10 | sigma = NULL, 11 | theta = NULL, 12 | itempar = NULL, 13 | link = c("logit", "probit"), 14 | na_okay = TRUE 15 | ) 16 | } 17 | \arguments{ 18 | \item{object}{Object of class \code{irtree_model}. See \link{irtree_model} for more 19 | information.} 20 | 21 | \item{N}{Integer, the number of persons.} 22 | 23 | \item{sigma}{Either a matrix or a function that returns a matrix. This matrix 24 | is the variance-covariance matrix of the person parameters that is passed 25 | to \code{\link[MASS:mvrnorm]{MASS::mvrnorm()}}. Note that the order of the person 26 | parameters is taken from the section Processes in the model \code{object} (see 27 | \link{irtree_model}).} 28 | 29 | \item{theta}{Optional numeric matrix of person parameters with one row per person and 30 | one column per dimension (i.e., \code{object$S}). If provided, this overrides 31 | \code{N} and \code{sigma}.} 32 | 33 | \item{itempar}{Either a list or a function that returns a list. The list has 34 | an element \code{beta} and an element \code{alpha}. Each of these is a 35 | matrix of item parameters. Note that the order of items (rows) is taken from the 36 | section Items and the order of processes (columns) is taken from the 37 | section Processes in the \code{model} (see \link{irtree_model}).} 38 | 39 | \item{link}{Character. Link function to use.} 40 | 41 | \item{na_okay}{Logical indicating whether variables with unobserved response 42 | categories are permitted. If \code{FALSE}, rejection sampling 43 | is used to ensure that all categories are observed.} 44 | } 45 | \value{ 46 | A list with element \code{data} containing the data and an 47 | element \code{spec} containing the true parameter values etc. 48 | } 49 | \description{ 50 | This function generates data from an IR-tree model. 51 | } 52 | \keyword{internal} 53 | -------------------------------------------------------------------------------- /man/irtree_model.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/s3_tree_model.R 3 | \name{irtree_model} 4 | \alias{irtree_model} 5 | \title{ItemResponseTrees model syntax} 6 | \usage{ 7 | irtree_model(model = NULL) 8 | } 9 | \arguments{ 10 | \item{model}{String with a specific structure as described below.} 11 | } 12 | \value{ 13 | List of class \code{irtree_model}. It contains the information extracted 14 | from \code{model}. Side note: The returned list contains an element 15 | \code{mappping_matrix} that contains the pseudoitems. This information is 16 | instructive, and it might be used as an input to the \code{dendrify()} 17 | function of the irtrees package. 18 | } 19 | \description{ 20 | The ItemResponseTrees model syntax describes the statistical model. The 21 | function \code{irtree_model()} turns a user-defined model string into an object of 22 | class \code{irtree_model} that represents the full model as needed by the package. 23 | } 24 | \section{Overview of the Model Syntax}{ 25 | 26 | 27 | \enumerate{ 28 | \item The \code{model} string must contain at least the sections \strong{IRT}, 29 | \strong{Class}, and (if class is tree) \strong{Equations}. 30 | \item Section headings must appear on a separate line ending with a colon (:). 31 | \item The model may contain empty lines and commented lines, which begin 32 | with \verb{#} (do not use inline comments). 33 | \item Line breaks are only allowed in section \strong{IRT}. 34 | } 35 | Details for all the required and optional sections of the \code{model} string are 36 | given in the following. 37 | \subsection{Equations}{ 38 | 39 | The \code{model} must contain a section with heading \strong{Equations} if \strong{Class} is 40 | Tree. 41 | Therein, the model equations are described. 42 | They have a structure similar to \code{Cat = p1*(1-p2)}, where \code{Cat} is any 43 | observed response category in the data set, and where \code{p1} is a freely 44 | chosen name of a parameter. 45 | These names must match the names of the latent variables in the section 46 | \strong{IRT} (combined with \strong{Constraints} if specified). 47 | 48 | If you prefer to work with pseudo-items, \code{\link[=irtree_create_template]{irtree_create_template()}} can 49 | generate the equations for you. 50 | 51 | The equations may contain only products and not sums. 52 | That is, it is not possible to estimate genuine mixture models as, for 53 | example, in the package \href{https://github.com/hplieninger/mpt2irt}{mpt2irt}. 54 | 55 | Each equation must appear on a separate, non-broken line. For example:\preformatted{Equations: 56 | 1 = (1-m)*(1-t)*e 57 | 2 = (1-m)*(1-t)*(1-e) 58 | 3 = m 59 | 4 = (1-m)*t*(1-e) 60 | 5 = (1-m)*t*e 61 | } 62 | } 63 | 64 | \subsection{IRT}{ 65 | 66 | The \code{model} must contain a section with heading \strong{IRT}. Therein, the IRT 67 | structure of the model is described in a way resembling the MODEL part of 68 | an Mplus input file. It has a structure of \code{LV BY item1*, item2@1}, 69 | where \code{LV} is the name of the latent variable/parameter/process, and 70 | \code{item} is the name of the observed variable in the data set, which 71 | is followed by the loading. The loading may either be fixed (e.g., to 1) 72 | using \code{@1} or it may be set free using \code{*} (which is equivalent to 73 | omitting a loading altogether). 74 | 75 | Each measurement model (i.e., the LV and its items) must appear on a 76 | separate line ending with a semicolon. Items must be separated by 77 | commas. Line breaks are allowed. For example:\preformatted{IRT: 78 | t BY x1, x2, x3, x4, x5, x6; 79 | e BY x1@1, x2@1, x3@1, x4@1, x5@1, x6@1; 80 | m BY x1@1, x2@1, x3@1, x4@1, x5@1, x6@1; 81 | } 82 | } 83 | 84 | \subsection{Class}{ 85 | 86 | The \code{model} must contain a section with heading \strong{Class} to specify the 87 | type/class of IRT model to use. 88 | Currently, may be either \code{Tree}, \code{GRM}, or \code{PCM}. For example:\preformatted{Class: 89 | Tree 90 | } 91 | } 92 | 93 | \subsection{Constraints}{ 94 | 95 | The \code{model} may contain a section with heading \strong{Constraints} to specify 96 | equality constraints of latent variables. 97 | Constraints may be useful for multidimensional questionnaires to link 98 | \strong{IRT} and \strong{Equations} in a specific way. 99 | Or, latent variables in \strong{IRT} may be constrained to equality. 100 | \subsection{Constraints in order to link sections IRT and Equations}{ 101 | 102 | A process in the model equations (section \strong{Equations}) may correspond to 103 | multiple latent variables (section \strong{IRT}). 104 | For example, when analyzing a Big Five data set, one may wish to specify only 105 | one extremity process \emph{e} for all items but multiple target traits \emph{t}, namely, 106 | one for each of the five scales. 107 | In such a case, the section \strong{Equations} would list only the parameter \emph{t}, 108 | while the section \strong{IRT} would list the parameters \emph{t1}, ..., \emph{t5}. 109 | 110 | In the framework of MPT, one would think of such a situation in terms of 111 | multiple albeit similar trees with specific parameter contraints across 112 | trees. 113 | For example, one would use one tree for each Big Five scale and fix the 114 | response style parameters to equality across trees but not the target trait 115 | parameters. 116 | 117 | Each line in this section has a structure of \code{Param = LV1 | LV2}, where \code{Param} is the name of the process used only in section 118 | \strong{Equations} and \code{LV1} it the name of the process used only in 119 | section \strong{IRT}. 120 | Use one line for each definition. For example:\preformatted{Constraints: 121 | t = t1 | t2 | t3 | t4 | t5 122 | } 123 | } 124 | 125 | \subsection{Constraints within section IRT}{ 126 | 127 | For example, in a sequential model as proposed by Tutz as well as Verhelst, 128 | one would specify two processes for a 3-point item. The first process would 129 | correspond to a pseudoitem of \code{0-1-1} and the second process to a 130 | pseudoitem of \code{NA-0-1}. 131 | However, the latent variables corresponding to these two processes would 132 | typically be assumed to be equal and need thus be constrained accordingly. 133 | 134 | Each line in this section has a structure of \code{LV1 = LV2}, where 135 | \code{LV1} and \code{LV2} are the names of the latent variables used in 136 | section \strong{IRT}. 137 | Use one line for each definition. For example:\preformatted{Constraints: 138 | LV1 = LV2 139 | LV1 = LV3 140 | } 141 | } 142 | 143 | } 144 | 145 | \subsection{Addendum}{ 146 | 147 | The \code{model} may contain a section with heading \strong{Addendum} if 148 | \code{engine = "mplus"} is used for estimation. 149 | Any code in this section is directly pasted in the \code{MODEL} section of 150 | the Mplus input file. 151 | Use a semicolon at the end of each line; lines must not exceed 90 characters. 152 | Note that the addendum is ignored in \code{\link[=irtree_gen_data]{irtree_gen_data()}}. For example:\preformatted{Addendum: 153 | e WITH t@0; 154 | m WITH t@0; 155 | } 156 | } 157 | 158 | \subsection{Weights}{ 159 | 160 | The \code{model} may contain a section with heading \strong{Weights} if model 161 | \strong{Class} is PCM. 162 | This allows to specify (uni- and) multidimensional partial credit models. 163 | They have been proposed, for example, by Wetzel and Carstensen (2017), as 164 | an alternative to IR-tree models. 165 | Note that fitting these models is only implemented for \code{engine = "tam"}. 166 | 167 | Each line in this section has a structure of \code{LV = weights}, where \code{LV} is 168 | the name of the latent variable used in section \strong{IRT}. 169 | \code{weights} must be valid R code, namely, a vector of weights (see, e.g., 170 | Table 1 in Wetzel & Carstensen, 2017, or Table 2 in Falk & Cai, 2015). 171 | Use one line for each definition. For example:\preformatted{Weights: 172 | t = c(0, 1, 2, 3, 4) 173 | e = c(1, 0, 0, 0, 1) 174 | m = c(0, 0, 1, 0, 0) 175 | } 176 | } 177 | } 178 | 179 | \examples{ 180 | m1 <- " 181 | # Random comment 182 | 183 | Equations: 184 | 1 = (1-m)*(1-t)*e 185 | 2 = (1-m)*(1-t)*(1-e) 186 | 3 = m 187 | 4 = (1-m)*t*(1-e) 188 | 5 = (1-m)*t*e 189 | 190 | IRT: 191 | t1 BY x1@1, x2*, x3*; 192 | t2 BY x4@1, x5*, x6*; 193 | e BY x1@1, x2@1, x3@1, x4@1, x5@1, x6@1; 194 | m BY x1@1, x2@1, x3@1, x4@1, x5@1, x6@1; 195 | 196 | Constraints: 197 | t = t1 | t2 198 | 199 | Class: 200 | Tree 201 | " 202 | 203 | model <- irtree_model(m1) 204 | } 205 | -------------------------------------------------------------------------------- /man/irtree_recode.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/generate-data.R 3 | \name{irtree_recode} 4 | \alias{irtree_recode} 5 | \title{Recode data into pseudoitems} 6 | \usage{ 7 | irtree_recode(object = NULL, data = NULL, keep = FALSE, mapping_matrix = NULL) 8 | } 9 | \arguments{ 10 | \item{object}{Object of class \code{irtree_model}. See \link{irtree_model} for more 11 | information.} 12 | 13 | \item{data}{Data frame containing containing one row per respondent and one 14 | column per variable. The variable names must correspond to those used in 15 | \code{object}.} 16 | 17 | \item{keep}{Logical indicating whether to append the original items to the 18 | data frame of the generated pseudoitems} 19 | 20 | \item{mapping_matrix}{Matrix of so-called pseudo-items, optional and 21 | overwritten by \code{object} if present. The observed response categories must 22 | appear in the first column. The other columns contain the pseudo-items and 23 | each entry may be either \code{1}, \code{0}, or \code{NA}. The first column name must be 24 | \code{categ}, and the other names (related to the pseudo-items) are used to 25 | construct the names of the returned data frame.} 26 | } 27 | \value{ 28 | Data frame 29 | } 30 | \description{ 31 | This function takes a data set with polytomous items and an \code{irtree_model} 32 | and returns the recoded items, the so-called pseudoitems. 33 | } 34 | \examples{ 35 | m1 <- " 36 | IRT: 37 | t BY x1; 38 | e BY x1; 39 | m BY x1; 40 | Equations: 41 | 1 = (1-m)*(1-t)*e 42 | 2 = (1-m)*(1-t)*(1-e) 43 | 3 = m 44 | 4 = (1-m)*t*(1-e) 45 | 5 = (1-m)*t*e 46 | Class: 47 | Tree 48 | " 49 | model1 <- irtree_model(m1) 50 | dat <- data.frame(x1 = 1:5) 51 | irtree_recode(model1, dat, keep = TRUE) 52 | 53 | irtree_recode(data = dat, 54 | mapping_matrix = cbind(categ = 1:5, 55 | m = c(0, 0, 1, 0, 0), 56 | t = c(1, 1, NA, 0, 0), 57 | e = c(1, 0, NA, 0, 1))) 58 | } 59 | -------------------------------------------------------------------------------- /man/irtree_sim.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/simulation.R 3 | \name{irtree_sim} 4 | \alias{irtree_sim} 5 | \title{Run a simulation by generating from and fitting an ItemResponseTrees model} 6 | \usage{ 7 | irtree_sim( 8 | R = 1, 9 | gen_model = NULL, 10 | fit_model = gen_model, 11 | N = NULL, 12 | sigma = NULL, 13 | itempar = NULL, 14 | link = c("logit", "probit"), 15 | na_okay = TRUE, 16 | engine = c("mirt", "mplus", "tam"), 17 | verbose = FALSE, 18 | control = NULL, 19 | improper_okay = FALSE, 20 | par_type = "difficulty", 21 | plan = NULL, 22 | plan_args = list(), 23 | file = NULL, 24 | dir = tempdir(), 25 | save_rdata = TRUE, 26 | in_memory = c("reduced", "everything", "nothing") 27 | ) 28 | } 29 | \arguments{ 30 | \item{R}{Number of replications. Can be either a single number indicating the 31 | number of replications (e.g., \code{R = 100}), or can be a range (e.g., \code{R = 1:100}).} 32 | 33 | \item{gen_model}{Object of class \code{irtree_model} describing the 34 | data-generating model. See \link{irtree_model} for more information.} 35 | 36 | \item{fit_model}{Object of class \code{irtree_model} describing the model that 37 | should be fit to the data. May be a list of multiple objects of class 38 | \code{irtree_model} if different models should be fit to the same data set. See 39 | \link{irtree_model} for more information.} 40 | 41 | \item{N}{Integer, the number of persons.} 42 | 43 | \item{sigma}{Either a matrix or a function that returns a matrix. This matrix 44 | is the variance-covariance matrix of the person parameters that is passed 45 | to \code{\link[MASS:mvrnorm]{MASS::mvrnorm()}}. Note that the order of the person 46 | parameters is taken from the section Processes in the model \code{object} (see 47 | \link{irtree_model}).} 48 | 49 | \item{itempar}{Either a list or a function that returns a list. The list has 50 | an element \code{beta} and an element \code{alpha}. Each of these is a 51 | matrix of item parameters. Note that the order of items (rows) is taken from the 52 | section Items and the order of processes (columns) is taken from the 53 | section Processes in the \code{model} (see \link{irtree_model}).} 54 | 55 | \item{link}{Character. Link function to use.} 56 | 57 | \item{na_okay}{Logical indicating whether variables with unobserved response 58 | categories are permitted. If \code{FALSE}, rejection sampling 59 | is used to ensure that all categories are observed.} 60 | 61 | \item{engine}{String specifying whether to use mirt, Mplus, or TAM for 62 | estimation.} 63 | 64 | \item{verbose}{Logical indicating whether output should be printed to the 65 | console.} 66 | 67 | \item{control}{List. The allowed elements of this list depend on the 68 | \code{engine}. Use \code{\link[=control_mirt]{control_mirt()}}, \code{\link[=control_mplus]{control_mplus()}}, or \code{\link[=control_tam]{control_tam()}} for 69 | convenience. Note that the \code{fit()} function does not use \code{...}, but that 70 | you can use the \verb{control_*()} functions to pass additional arguments.} 71 | 72 | \item{improper_okay}{Logical indicating whether the model should also be fit 73 | if it is not a proper IR-tree model. Set this only to \code{TRUE} if you really 74 | know what you are doing.} 75 | 76 | \item{par_type}{Only used if the fit engine was mirt. Item parameters (or 77 | thresholds) can be either of type \code{easiness} (the mirt default) or 78 | \code{difficulty} (as in Mplus and TAM).} 79 | 80 | \item{plan}{Parameter passed as argument \code{strategy} to \code{\link[future:plan]{future::plan()}}. May 81 | be set to, for example, \code{multiprocess} in order to run the simulations in 82 | parallel.} 83 | 84 | \item{plan_args}{Named list. Parameters passed \code{\link[future:plan]{future::plan()}}.} 85 | 86 | \item{file}{String giving the file path used to save the output if 87 | \code{save_rdata = TRUE}. Note that the file ending is automatically set to 88 | \code{.rda}. This argument is also passed to \code{\link[=irtree_fit_mplus]{irtree_fit_mplus()}} if applicable.} 89 | 90 | \item{dir}{Path name that is used to save the results of every run if 91 | \code{save_rdata = TRUE}.} 92 | 93 | \item{save_rdata}{Logical indicating whether to save the results to an RData 94 | file.} 95 | 96 | \item{in_memory}{Character string indicating what output should be kept in 97 | memory (note the argument \code{save_rdata}, which is not affected by 98 | \code{in_memory}). If \code{"reduced"}, the output of \code{\link[=fit.irtree_model]{fit()}} is 99 | discarded and only summary information is retained. The alternative is to 100 | keep \code{"everything"} in memory, or to keep \code{"nothing"} in memory (which 101 | makes only sense in combination with \code{save_rdata = TRUE}).} 102 | } 103 | \value{ 104 | Returns a list of length \code{R}. For each replication, a list is 105 | returned with two elements. The element \code{spec} contains various 106 | specifications (such as the data). The element \code{fits} is a list with one 107 | element for each \code{fit_model} that contains the output of 108 | \code{\link[=fit.irtree_model]{fit()}} as well as the elements \code{glanced}, \code{tidied}, 109 | and \code{augmented} (see \code{\link[=glance]{glance()}}, \code{\link[=tidy]{tidy()}}, and \code{\link[=augment]{augment()}}). Thus, 110 | \code{res$sim3$fits$m2$glanced} gives model-fit information such as AIC for the 111 | second model in the third replication, and \code{res$sim3$spec$data} contains 112 | the corresponding data set. 113 | 114 | If \code{in_memory = "nothing"}, returns \code{NULL}. 115 | } 116 | \description{ 117 | The function \code{irtree_sim()} generates data from an \link{irtree_model} and fits 118 | one or more models to these data. This process is repeated \code{R} times, and the 119 | argument \code{plan} allows to run the simulation in parallel. 120 | } 121 | \examples{ 122 | \donttest{ 123 | # Running these examples may take a while 124 | 125 | m1 <- " 126 | Equations: 127 | 1 = 1-a 128 | 2 = a*(1-b) 129 | 3 = a*b 130 | 131 | IRT: 132 | a BY x1@1, x2@1, x3@1, x4@1, X5@1, X6@1, X7@1; 133 | b BY x1@1, x2@1, x3@1, x4@1, X5@1, X6@1, X7@1; 134 | 135 | Class: 136 | Tree 137 | " 138 | 139 | m2 <- " 140 | IRT: 141 | a BY x1@1, x2@1, x3@1, x4@1, X5@1, X6@1, X7@1; 142 | 143 | Class: 144 | GRM 145 | " 146 | 147 | model1 <- irtree_model(m1) 148 | model2 <- irtree_model(m2) 149 | 150 | res <- irtree_sim( 151 | ### Data generation ### 152 | gen_model = model1, 153 | link = "logit", 154 | N = 500, 155 | sigma = function(x) diag(2), 156 | itempar = function(x) list( 157 | beta = matrix(sort(runif(model1$J*model1$P, -2, 2)), 158 | model1$J, model1$P), 159 | alpha = matrix(1, model1$J, model1$P)), 160 | na_okay = FALSE, 161 | 162 | ### Estimation ### 163 | fit_model = list(model1, model2), 164 | engine = "mirt", 165 | control = control_mirt(SE = FALSE), 166 | par_type = "difficulty", 167 | 168 | ### Replications ### 169 | R = 2, 170 | save_rdata = FALSE, 171 | 172 | ### Optional parallelization ### 173 | plan = "multiprocess", 174 | plan_args = list(workers = future::availableCores() - 1) 175 | ) 176 | 177 | tab1 <- matrix(NA, 0, 4, dimnames = list(NULL, c("Rep", "Model", "AIC", "BIC"))) 178 | 179 | for (ii in seq_along(res)) { 180 | for (jj in seq_along(res[[ii]]$fits)) { 181 | IC <- res[[ii]]$fits[[jj]]$glanced 182 | tab1 <- rbind(tab1, c(ii, jj, round(IC$AIC, -1), round(IC$BIC, -1))) 183 | } 184 | } 185 | tab1 186 | #> Rep Model AIC BIC 187 | #> [1,] 1 1 6900 6970 188 | #> [2,] 1 2 7000 7060 189 | #> [3,] 2 1 6810 6880 190 | #> [4,] 2 2 6880 6940 191 | } 192 | } 193 | \seealso{ 194 | The data are generated via \code{\link[=irtree_gen_data]{irtree_gen_data()}}, and the models are 195 | fit via \code{\link[=fit.irtree_model]{fit()}}. 196 | } 197 | -------------------------------------------------------------------------------- /man/irtree_sim1.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/simulation.R 3 | \name{irtree_sim1} 4 | \alias{irtree_sim1} 5 | \title{Generate data from and fit an ItemResponseTrees model} 6 | \usage{ 7 | irtree_sim1( 8 | R = 1, 9 | gen_model = NULL, 10 | fit_model = gen_model, 11 | N = NULL, 12 | sigma = NULL, 13 | itempar = NULL, 14 | link = c("logit", "probit"), 15 | na_okay = TRUE, 16 | engine = c("mirt", "mplus", "tam"), 17 | verbose = TRUE, 18 | control = NULL, 19 | improper_okay = FALSE, 20 | par_type = "difficulty", 21 | file = NULL, 22 | dir = tempdir(), 23 | save_rdata = FALSE, 24 | reduce_output = FALSE 25 | ) 26 | } 27 | \arguments{ 28 | \item{R}{Integer used to number the saved output if \code{save_rdata = TRUE}. 29 | Really only useful when used from \code{\link[=irtree_sim]{irtree_sim()}}.} 30 | 31 | \item{gen_model}{Object of class \code{irtree_model} describing the 32 | data-generating model. See \link{irtree_model} for more information.} 33 | 34 | \item{fit_model}{Object of class \code{irtree_model} describing the model that 35 | should be fit to the data. May be a list of multiple objects of class 36 | \code{irtree_model} if different models should be fit to the same data set. See 37 | \link{irtree_model} for more information.} 38 | 39 | \item{N}{Integer, the number of persons.} 40 | 41 | \item{sigma}{Either a matrix or a function that returns a matrix. This matrix 42 | is the variance-covariance matrix of the person parameters that is passed 43 | to \code{\link[MASS:mvrnorm]{MASS::mvrnorm()}}. Note that the order of the person 44 | parameters is taken from the section Processes in the model \code{object} (see 45 | \link{irtree_model}).} 46 | 47 | \item{itempar}{Either a list or a function that returns a list. The list has 48 | an element \code{beta} and an element \code{alpha}. Each of these is a 49 | matrix of item parameters. Note that the order of items (rows) is taken from the 50 | section Items and the order of processes (columns) is taken from the 51 | section Processes in the \code{model} (see \link{irtree_model}).} 52 | 53 | \item{link}{Character. Link function to use.} 54 | 55 | \item{na_okay}{Logical indicating whether variables with unobserved response 56 | categories are permitted. If \code{FALSE}, rejection sampling 57 | is used to ensure that all categories are observed.} 58 | 59 | \item{engine}{String specifying whether to use mirt, Mplus, or TAM for 60 | estimation.} 61 | 62 | \item{verbose}{Logical indicating whether output should be printed to the 63 | console.} 64 | 65 | \item{control}{List. The allowed elements of this list depend on the 66 | \code{engine}. Use \code{\link[=control_mirt]{control_mirt()}}, \code{\link[=control_mplus]{control_mplus()}}, or \code{\link[=control_tam]{control_tam()}} for 67 | convenience. Note that the \code{fit()} function does not use \code{...}, but that 68 | you can use the \verb{control_*()} functions to pass additional arguments.} 69 | 70 | \item{improper_okay}{Logical indicating whether the model should also be fit 71 | if it is not a proper IR-tree model. Set this only to \code{TRUE} if you really 72 | know what you are doing.} 73 | 74 | \item{par_type}{Only used if the fit engine was mirt. Item parameters (or 75 | thresholds) can be either of type \code{easiness} (the mirt default) or 76 | \code{difficulty} (as in Mplus and TAM).} 77 | 78 | \item{file}{String giving the file path used to save the output if 79 | \code{save_rdata = TRUE}. Note that the file ending is automatically set to 80 | \code{.rda}. This argument is also passed to \code{\link[=irtree_fit_mplus]{irtree_fit_mplus()}} if applicable.} 81 | 82 | \item{dir}{Path name that is used to save the results of every run if 83 | \code{save_rdata = TRUE}.} 84 | 85 | \item{save_rdata}{Logical indicating whether to save the results to an RData 86 | file.} 87 | 88 | \item{reduce_output}{Logical indicating whether the returned object should be 89 | reduced (i.e., the output of \code{\link[=fit.irtree_model]{fit()}} is removed and 90 | only summary information is retained).} 91 | } 92 | \value{ 93 | List with two elements. The second element \code{spec} contains various 94 | arguments (such as the data). The first element \code{fits} is a list with one 95 | element for each \code{fit_model} that contains the output of 96 | \code{\link[=fit.irtree_model]{fit()}} as well as the elements \code{glanced}, \code{tidied}, 97 | and \code{augmented} (see \code{\link[=glance]{glance()}}, \code{\link[=tidy]{tidy()}}, and \code{\link[=augment]{augment()}}). 98 | } 99 | \description{ 100 | This function generates data from \code{gen_model}, subsequently fits all the 101 | models in \code{fit_model}, and returns the results and/or saves them to an 102 | external RData file. 103 | } 104 | \seealso{ 105 | \code{\link[=irtree_sim]{irtree_sim()}}, and the wrapped functions 106 | \code{\link[=fit.irtree_model]{fit()}} and \code{\link[=irtree_gen_data]{irtree_gen_data()}}. 107 | } 108 | \keyword{internal} 109 | -------------------------------------------------------------------------------- /man/jackson.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{jackson} 5 | \alias{jackson} 6 | \title{IPIP Big Five personality test answers (data set)} 7 | \format{ 8 | A data frame with 9051 rows and 58 variables: 9 | \describe{ 10 | \item{E1}{Am the life of the party.} 11 | \item{A1}{Feel little concern for others.} 12 | \item{C1}{Am always prepared.} 13 | \item{N1}{Get stressed out easily.} 14 | \item{O1}{Have a rich vocabulary.} 15 | \item{E2}{Don't talk a lot.} 16 | \item{A2}{Am interested in people.} 17 | \item{C2}{Leave my belongings around.} 18 | \item{N2}{Am relaxed most of the time.} 19 | \item{O2}{Have difficulty understanding abstract ideas.} 20 | \item{E3}{Feel comfortable around people.} 21 | \item{A3}{Insult people.} 22 | \item{C3}{Pay attention to details.} 23 | \item{N3}{Worry about things.} 24 | \item{O3}{Have a vivid imagination.} 25 | \item{E4}{Keep in the background.} 26 | \item{A4}{Sympathize with others' feelings.} 27 | \item{C4}{Make a mess of things.} 28 | \item{N4}{Seldom feel blue.} 29 | \item{O4}{Am not interested in abstract ideas.} 30 | \item{E5}{Start conversations.} 31 | \item{A5}{Am not interested in other people's problems.} 32 | \item{C5}{Get chores done right away.} 33 | \item{N5}{Am easily disturbed.} 34 | \item{O5}{Have excellent ideas.} 35 | \item{E6}{Have little to say.} 36 | \item{A6}{Have a soft heart.} 37 | \item{C6}{Often forget to put things back in their proper place.} 38 | \item{N6}{Get upset easily.} 39 | \item{O6}{Do not have a good imagination.} 40 | \item{E7}{Talk to a lot of different people at parties.} 41 | \item{A7}{Am not really interested in others.} 42 | \item{C7}{Like order} 43 | \item{N7}{Change my mood a lot.} 44 | \item{O7}{Am quick to understand things.} 45 | \item{E8}{Don't like to draw attention to myself.} 46 | \item{A8}{Take time out for others.} 47 | \item{C8}{Shirk my duties.} 48 | \item{N8}{Have frequent mood swings.} 49 | \item{O8}{Use difficult words.} 50 | \item{E9}{Don't mind being the center of attention.} 51 | \item{A9}{Feel others' emotions.} 52 | \item{C9}{Follow a schedule.} 53 | \item{N9}{Get irritated easily.} 54 | \item{O9}{Spend time reflecting on things.} 55 | \item{E10}{Am quiet around strangers.} 56 | \item{A10}{Make people feel at ease.} 57 | \item{C10}{Am exacting in my work.} 58 | \item{N10}{Often feel blue.} 59 | \item{O10}{Am full of ideas.} 60 | \item{gender}{Gender, either female, male, or other} 61 | \item{age}{Age} 62 | \item{SecondsElapsed}{Seconds elapsed} 63 | \item{E}{Scale mean for extraversion} 64 | \item{C}{Scale mean for conscientiousness} 65 | \item{N}{Scale mean for neuroticism} 66 | \item{O}{Scale mean for openness} 67 | \item{A}{Scale mean for agreeableness} 68 | } 69 | } 70 | \source{ 71 | Jackson, A. (2012). IPIP Big Five personality test answers. 72 | \url{https://doi.org/10.6084/m9.figshare.96542.v3} 73 | } 74 | \usage{ 75 | data("jackson") 76 | } 77 | \description{ 78 | "This is data from an online big five personality test: 79 | http://personality-testing.info/tests/BIG5.php. The following items 80 | were rated on a likert scale from 1=disagree to 5=agree in relation to how 81 | much they applied to the test taker, they were presented to the taker 5 per 82 | page" (Jackson, 2012). 83 | 84 | The following items are reverse keyed and were already recoded: \code{A1}, \code{E2}, 85 | \code{C2}, \code{N2}, \code{O2}, \code{A3}, \code{E4}, \code{C4}, \code{N4}, \code{O4}, \code{A5}, \code{E6}, \code{C6}, \code{O6}, \code{A7}, 86 | \code{E8}, \code{C8}, and \code{E10}. 87 | } 88 | \details{ 89 | The data set included here is Version 3 from 15.10.2012. It was 90 | released by Andrew Jackson under the \href{https://creativecommons.org/licenses/by/4.0/}{CC BY 4.0} license. 91 | } 92 | \keyword{datasets} 93 | -------------------------------------------------------------------------------- /man/pseudoitems.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pseudoitems.R 3 | \encoding{UTF-8} 4 | \name{pseudoitems} 5 | \alias{pseudoitems} 6 | \alias{pseudo-items} 7 | \title{Pseudo-Items} 8 | \description{ 9 | IR-tree models can be fit on the basis of so-called pseudo-items. To this 10 | end, the original, polytomous items are recoded into binary pseudo-items. 11 | Whether a pseudo-item is coded as \code{1}, \code{0}, or \code{NA} depends on the model 12 | equations (e.g., Böckenholt, 2012; Plieninger, 2020). 13 | 14 | The ItemResponseTrees package internally works with pseudo-items as well. 15 | However, the user has to specify the model equations rather than the 16 | pseudo-items in the \link{irtree_model} syntax. Internally, before fitting the 17 | model, the original responses are recoded on the basis of the model supplied 18 | by the user by the function \code{\link[=irtree_recode]{irtree_recode()}}. This function may also be used 19 | directly if desired. 20 | 21 | As an alternative to specifying the model equations themselves, users may 22 | also use the function \code{\link[=irtree_create_template]{irtree_create_template()}} with a mapping matrix (that 23 | specifies the structure of the pseudo-items) to generate the model equations 24 | automatically. 25 | } 26 | \examples{ 27 | # Mapping matrix for data with three response categories: 28 | (mm <- cbind(categ = 0:2, 29 | p1 = c(0, 1, 1), 30 | p2 = c(NA, 0, 1))) 31 | #> categ p1 p2 32 | #> [1,] 0 0 NA 33 | #> [2,] 1 1 0 34 | #> [3,] 2 1 1 35 | 36 | irtree_create_template(data.frame(x1 = 0:2, x2 = 0:2), 37 | mapping_matrix = mm) 38 | #> 39 | #> m1 <- " 40 | #> Equations: 41 | #> 0 = (1-p1) 42 | #> 1 = p1*(1-p2) 43 | #> 2 = p1*p2 44 | #> 45 | #> IRT: 46 | #> p1 BY x1@1, x2@1; 47 | #> p2 BY x1@1, x2@1; 48 | #> 49 | #> Class: 50 | #> Tree 51 | #> " 52 | #> 53 | 54 | irtree_recode(data = data.frame(x1 = 0:2, x2 = 0:2), 55 | mapping_matrix = mm) 56 | 57 | } 58 | \references{ 59 | Böckenholt, U. (2012). Modeling multiple response processes in 60 | judgment and choice. \emph{Psychological Methods}, \emph{17}(4), 665–678. 61 | https://doi.org/10.1037/a0028111 62 | 63 | Plieninger, H. (2020). Developing and applying IR-tree models: 64 | Guidelines, caveats, and an extension to multiple groups. \emph{Organizational 65 | Research Methods}. Advance online publication. 66 | https://doi.org/10.1177/1094428120911096 67 | } 68 | -------------------------------------------------------------------------------- /man/reexports.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/fit.R, R/tidiers.R 3 | \docType{import} 4 | \name{reexports} 5 | \alias{reexports} 6 | \alias{fit} 7 | \alias{glance} 8 | \alias{tidy} 9 | \alias{augment} 10 | \title{Objects exported from other packages} 11 | \keyword{internal} 12 | \description{ 13 | These objects are imported from other packages. Follow the links 14 | below to see their documentation. 15 | 16 | \describe{ 17 | \item{generics}{\code{\link[generics]{augment}}, \code{\link[generics]{fit}}, \code{\link[generics]{glance}}, \code{\link[generics]{tidy}}} 18 | }} 19 | 20 | -------------------------------------------------------------------------------- /man/rtruncatednorm.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/helpers.R 3 | \name{rtruncatednorm} 4 | \alias{rtruncatednorm} 5 | \title{Sample from a truncated normal distribution} 6 | \usage{ 7 | rtruncatednorm(n = NULL, mean = 0, sd = 1, ll = -Inf, ul = Inf) 8 | } 9 | \arguments{ 10 | \item{n}{Number of observations} 11 | 12 | \item{mean}{Mean} 13 | 14 | \item{sd}{Standard deviation} 15 | 16 | \item{ll}{Lower bound} 17 | 18 | \item{ul}{Upper bound} 19 | } 20 | \description{ 21 | Sample from a truncated normal distribution 22 | } 23 | \seealso{ 24 | There is a discussion and code on 25 | \url{https://stackoverflow.com/a/14034577}, and there is also the truncnorm 26 | package. 27 | } 28 | \keyword{internal} 29 | -------------------------------------------------------------------------------- /man/tidy.irtree_fit.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tidiers.R 3 | \name{tidy.irtree_fit} 4 | \alias{tidy.irtree_fit} 5 | \title{Tidy an irtree_fit object} 6 | \usage{ 7 | \method{tidy}{irtree_fit}(x = NULL, par_type = NULL, ...) 8 | } 9 | \arguments{ 10 | \item{x}{object of class \code{irtree_fit} as returned from \code{\link[=fit.irtree_model]{fit()}}.} 11 | 12 | \item{par_type}{Only used if the fit engine was mirt. Item parameters (or 13 | thresholds) can be either of type \code{easiness} (the mirt default) or 14 | \code{difficulty} (as in Mplus and TAM).} 15 | 16 | \item{...}{Not currently used.} 17 | } 18 | \value{ 19 | A \link[tibble:tibble-package]{tibble} with one row for each model 20 | parameter and the following columns: 21 | \describe{ 22 | \item{\code{term}}{The name of the model parameter.} 23 | \item{\code{estimate}}{The estimated value of the term.} 24 | \item{\code{std.error}}{The standard error of the term.} 25 | \item{\code{statistic}}{The value of the test statistic of the term (Mplus only).} 26 | \item{\code{p.value}}{The p-value associated with the statistic (Mplus only).} 27 | } 28 | } 29 | \description{ 30 | Tidy summarizes information about the parameter estimates of an ItemResponseTrees model. 31 | } 32 | \examples{ 33 | data("jackson") 34 | df1 <- jackson[1:234, paste0("C", 1:5)] 35 | irtree_create_template(df1) 36 | m1 <- " 37 | IRT: 38 | t BY C1@1, C2@1, C3@1, C4@1, C5@1; 39 | Class: 40 | GRM" 41 | fit1 <- fit(irtree_model(m1), data = df1) 42 | 43 | tidy(fit1, par_type = "difficulty") 44 | 45 | glance(fit1) 46 | 47 | augment(fit1) 48 | } 49 | \seealso{ 50 | \code{\link[generics:tidy]{generics::tidy()}} 51 | } 52 | -------------------------------------------------------------------------------- /man/write_mirt_input.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/fit-mirt.R 3 | \name{write_mirt_input} 4 | \alias{write_mirt_input} 5 | \title{Prepare a mirt model} 6 | \usage{ 7 | write_mirt_input(object = NULL, data = NULL) 8 | } 9 | \arguments{ 10 | \item{object}{Object of class \code{irtree_model}. See \link{irtree_model} for more 11 | information.} 12 | 13 | \item{data}{Data frame containing containing one row per respondent and one 14 | column per variable. The variable names must correspond to those used in 15 | \code{object}.} 16 | } 17 | \value{ 18 | A list with three elements. \code{mirt_string} is the \link[mirt:mirt.model]{mirt::mirt.model} 19 | object; \code{itemtype} can be passed to \code{\link[mirt:mirt]{mirt::mirt()}}; 20 | \code{lambda} is the modified lambda matrix from the \code{object}-argument. 21 | } 22 | \description{ 23 | This is an internal function used by \code{\link[=irtree_fit_mirt]{irtree_fit_mirt()}}. It receives its 24 | inputs from the model object and the data set and returns a 25 | \link[mirt:mirt.model]{mirt::mirt.model} object. 26 | } 27 | \keyword{internal} 28 | -------------------------------------------------------------------------------- /man/write_mplus_input.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/fit-mplus.R 3 | \name{write_mplus_input} 4 | \alias{write_mplus_input} 5 | \title{Prepare an Mplus Input File} 6 | \usage{ 7 | write_mplus_input( 8 | object = NULL, 9 | pseudoitems = NULL, 10 | data_file = NULL, 11 | fsco_file = NULL, 12 | save_fscores = FALSE, 13 | quadpts = NULL, 14 | estimator = NULL, 15 | link = NULL, 16 | analysis_list = list() 17 | ) 18 | } 19 | \arguments{ 20 | \item{object}{Object of class \code{irtree_model}. See \link{irtree_model} for more 21 | information.} 22 | 23 | \item{pseudoitems}{Data frame as returned from \code{\link[=irtree_recode]{irtree_recode()}}.} 24 | 25 | \item{data_file}{String, the full file path of the data set.} 26 | 27 | \item{fsco_file}{String, the file name used by Mplus to store the factor scores.} 28 | 29 | \item{save_fscores}{Logical, whether to save FSCORES or not.} 30 | 31 | \item{quadpts}{This is passed to argument 'INTEGRATION' of Mplus. Thus, it 32 | may be an integer specifying the number of integration points for the Mplus 33 | default of rectangular numerical integration (e.g., \code{quadpts = 15}). Or it 34 | may be a string, which gives more fine grained control (e.g., \code{quadpts = "MONTECARLO(2000)"}).} 35 | 36 | \item{estimator}{String, passed to argument 'ESTIMATOR' in Mplus.} 37 | 38 | \item{link}{String, passed to argument 'LINK' in Mplus. Specifies 39 | the link function.} 40 | 41 | \item{analysis_list}{Named list of strings passed to Mplus' argument 42 | ANALYSIS. See examples below.} 43 | } 44 | \value{ 45 | A list of of class \link[MplusAutomation:mplusObject]{MplusAutomation::mplusObject} 46 | } 47 | \description{ 48 | This is an internal function used by \code{\link[=irtree_fit_mplus]{irtree_fit_mplus()}}. It receives its 49 | inputs from the model object and the data set and returns an object of class 50 | \link[MplusAutomation:mplusObject]{MplusAutomation::mplusObject}. 51 | } 52 | \keyword{internal} 53 | -------------------------------------------------------------------------------- /tests/spelling.R: -------------------------------------------------------------------------------- 1 | if (requireNamespace('spelling', quietly = TRUE) & 2 | length(find.package("xml2", verbose = FALSE, quiet = TRUE)) > 0) 3 | spelling::spell_check_test(vignettes = TRUE, error = FALSE, 4 | skip_on_cran = TRUE) 5 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library("testthat") 2 | library("ItemResponseTrees") 3 | 4 | test_check("ItemResponseTrees") 5 | -------------------------------------------------------------------------------- /tests/testthat/fit-mplus-res1.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hplieninger/ItemResponseTrees/a01af0f708b9e818e166e90a204a432ba695b06a/tests/testthat/fit-mplus-res1.rds -------------------------------------------------------------------------------- /tests/testthat/fit-mplus-res2.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hplieninger/ItemResponseTrees/a01af0f708b9e818e166e90a204a432ba695b06a/tests/testthat/fit-mplus-res2.rds -------------------------------------------------------------------------------- /tests/testthat/fit-mplus-res3.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hplieninger/ItemResponseTrees/a01af0f708b9e818e166e90a204a432ba695b06a/tests/testthat/fit-mplus-res3.rds -------------------------------------------------------------------------------- /tests/testthat/helper.R: -------------------------------------------------------------------------------- 1 | # Load packages required for tests ---------------------------------------- 2 | 3 | library("dplyr", mask.ok = "matches") 4 | 5 | # Define functions required for tests ------------------------------------- 6 | 7 | gen_itempar_boeck <- function(J = 10, loading = 1) { 8 | 9 | # mrs 10 | m <- qnorm(.7) 11 | ss <- sqrt(.2) 12 | i1 <- rtruncatednorm(J, mean = m, sd = ss, 13 | ll = m - 2 * ss, ul = m + 2 * ss) 14 | # ers 15 | m <- qnorm(.7) 16 | ss <- sqrt(.2) 17 | i2 <- rtruncatednorm(J, mean = m, sd = ss, 18 | ll = m - 2 * ss, ul = m + 2 * ss) 19 | 20 | # trt 21 | m <- qnorm(.5) 22 | ss <- sqrt(.4) 23 | i3 <- rtruncatednorm(J, mean = m, sd = ss, 24 | ll = m - 2 * ss, ul = m + 2 * ss) 25 | 26 | out <- list() 27 | out$beta <- cbind(i1, i2, i3) 28 | 29 | if (all(loading == 1)) { 30 | out$alpha <- matrix(1, J, 3) 31 | } else { 32 | tmp1 <- runif(J * 3, .5, 1.5) 33 | fixed <- grepl("@", loading) 34 | fix_load <- sub("@", "", loading) 35 | tmp1[fixed] <- as.numeric(fix_load[fixed]) 36 | out$alpha <- matrix(tmp1, J, 3) 37 | } 38 | 39 | return(out) 40 | } 41 | 42 | gen_sigma_boeck <- function(sigma = NULL, df = 50, vars = NULL) { 43 | 44 | if (is.null(sigma)) { 45 | sigma <- matrix(c(1, .4, 0, 46 | .4, 1, 0, 47 | 0, 0, 1), 3, 3, byrow = TRUE) 48 | } 49 | 50 | sig1 <- rWishart(1, df = df, Sigma = sigma)[, , 1] 51 | 52 | if (is.null(vars)) { 53 | vars <- c(rgamma(2, shape = 51, scale = .01), 54 | rgamma(1, shape = 101, scale = .01)) 55 | sds <- sqrt(vars) 56 | } 57 | 58 | sig2 <- diag(sds) %*% cov2cor(sig1) %*% diag(sds) 59 | 60 | return(sig2) 61 | 62 | } 63 | 64 | remove_filenames <- function(object = NULL) { 65 | oclass <- class(object$mplus) 66 | object$mplus <- lapply(object$mplus, `attr<-`, "filename", NULL) 67 | object["mplus"] <- lapply(object["mplus"], `attr<-`, "filename", NULL) 68 | object$mplus$input$title <- NULL 69 | object$mplus$input$savedata <- NULL 70 | object$mplus$input$data$file <- NULL 71 | object$mplus$summaries$Title <- NULL 72 | object$mplus$summaries$Filename <- NULL 73 | object$mplus$parameters <- lapply(object$mplus$parameters, `attr<-`, "filename", NULL) 74 | object$mplus$savedata_info$fileName <- NULL 75 | object$spec$control$file <- NULL 76 | 77 | class(object$mplus) <- oclass 78 | return(object) 79 | } 80 | 81 | expect_integers_in_irtree_model <- function(object) { 82 | act <- quasi_label(rlang::enquo(object), arg = "object") 83 | 84 | act$integers[1] <- checkmate::test_integer(object$k_names, min.len = 2, 85 | null.ok = TRUE) 86 | act$integers[2] <- checkmate::test_integer(object$mapping_matrix, 87 | min.len = 4, null.ok = TRUE) 88 | 89 | if (all(act$integers)) { 90 | succeed() 91 | return(invisible(act$val)) 92 | } 93 | 94 | message <- glue::glue("{act$lab} contains elements that should be integers, ", 95 | "but are not.") 96 | fail(message) 97 | } 98 | -------------------------------------------------------------------------------- /tests/testthat/test-constraints.R: -------------------------------------------------------------------------------- 1 | ##### Data ##### 2 | 3 | data("jackson") 4 | 5 | df1 <- select(jackson, starts_with("E")[1:10], starts_with("C")[1:10]) %>% 6 | sample_n(300) 7 | 8 | ##### Model ##### 9 | 10 | m1 <- " 11 | IRT: 12 | a1 BY E1@1, E2@1, E3@1, E4@1, E5@1, E6@1, E7@1, E8@1, E9@1, E10@1; 13 | a2 BY C1@1, C2@1, C3@1, C4@1, C5@1, C6@1, C7@1, C8@1, C9@1, C10@1; 14 | e BY E1@1, E2@1, E3@1, E4@1, E5@1, E6@1, E7@1, E8@1, E9@1, E10@1, C1@1, C2@1, C3@1, C4@1, 15 | C5@1, C6@1, C7@1, C8@1, C9@1, C10@1; 16 | m BY E1@1, E2@1, E3@1, E4@1, E5@1, E6@1, E7@1, E8@1, E9@1, E10@1, C1@1, C2@1, C3@1, C4@1, 17 | C5@1, C6@1, C7@1, C8@1, C9@1, C10@1; 18 | 19 | Equations: 20 | 1 = (1-m)*(1-t)*e 21 | 2 = (1-m)*(1-t)*(1-e) 22 | 3 = m 23 | 4 = (1-m)*t*(1-e) 24 | 5 = (1-m)*t*e 25 | 26 | Class: 27 | Tree 28 | 29 | Constraints: 30 | t = a1 | a2 31 | e = m 32 | " 33 | 34 | m2 <- " 35 | IRT: 36 | a1 BY E1@1, E2@1, E3@1, E4@1, E5@1, E6@1, E7@1, E8@1, E9@1, E10@1; 37 | a2 BY C1@1, C2@1, C3@1, C4@1, C5@1, C6@1, C7@1, C8@1, C9@1, C10@1; 38 | 39 | Weights: 40 | t = 0:4 41 | 42 | Class: 43 | PCM 44 | 45 | Constraints: 46 | t = a1 | a2 47 | " 48 | 49 | m3 <- " 50 | IRT: 51 | a1 BY E1@1, E2@1, E3@1, E4@1, E5@1, E6@1, E7@1, E8@1, E9@1, E10@1; 52 | a2 BY C1@1, C2@1, C3@1, C4@1, C5@1, C6@1, C7@1, C8@1, C9@1, C10@1; 53 | 54 | Class: 55 | GRM 56 | 57 | Constraints: 58 | t = a1 | a2 59 | " 60 | 61 | model1 <- irtree_model(m1) 62 | model2 <- irtree_model(m2) 63 | model3 <- irtree_model(m3) 64 | 65 | test_that("Model constraints work", { 66 | 67 | skip_on_cran() 68 | 69 | ##### Fit ##### 70 | 71 | verbose <- FALSE 72 | 73 | res11 <- fit(data = df1, 74 | engine = "tam", 75 | object = model1, 76 | control = control_tam( 77 | control = list(snodes = 1000, convD = .01, conv = .001)), 78 | verbose = verbose) 79 | 80 | res12 <- fit(data = df1, 81 | engine = "mirt", 82 | object = model1, 83 | control = control_mirt(SE = FALSE, TOL = .01, quadpts = 10), 84 | verbose = verbose) 85 | 86 | res21 <- fit(data = df1, 87 | engine = "tam", 88 | object = model2, 89 | control = control_tam(set_min_to_0 = TRUE, 90 | control = list(snodes = 1000, convD = .01, conv = .001)), 91 | verbose = verbose) 92 | 93 | res31 <- fit(data = df1, 94 | engine = "mirt", 95 | object = model3, 96 | control = control_mirt(SE = FALSE, TOL = .01, quadpts = 10), 97 | verbose = verbose) 98 | 99 | ##### Tests ##### 100 | 101 | expect_s3_class(model1, "irtree_model") 102 | expect_s3_class(model2, "irtree_model") 103 | expect_s3_class(model3, "irtree_model") 104 | expect_integers_in_irtree_model(model1) 105 | expect_integers_in_irtree_model(model2) 106 | expect_integers_in_irtree_model(model3) 107 | 108 | expect_s3_class(res11, "irtree_fit") 109 | expect_s3_class(res12, "irtree_fit") 110 | expect_s3_class(res21, "irtree_fit") 111 | expect_s3_class(res31, "irtree_fit") 112 | 113 | expect_s3_class(res11$tam, "tam.mml") 114 | expect_s4_class(res12$mirt, "SingleGroupClass") 115 | expect_s3_class(res21$tam, "tam.mml") 116 | expect_s4_class(res31$mirt, "SingleGroupClass") 117 | 118 | ag1 <- augment(res11) 119 | ag2 <- augment(res12) 120 | 121 | expect_gt(min(diag(cor(select(ag1, matches(".fitted.")), 122 | select(ag2, matches(".fitted."))))), .90) 123 | 124 | gl11 <- glance(res11) 125 | gl12 <- glance(res12) 126 | gl21 <- glance(res21) 127 | gl31 <- glance(res31) 128 | 129 | expect_equal(gl11$n.factors, model1$S) 130 | expect_equal(gl12$n.factors, model1$S) 131 | expect_equal(gl21$n.factors, model2$S) 132 | expect_equal(gl31$n.factors, model3$S) 133 | 134 | skip_if_not(MplusAutomation::mplusAvailable() == 0) 135 | 136 | res13 <- fit(data = df1, 137 | engine = "mplus", 138 | object = model1, 139 | verbose = verbose, 140 | link = "logit", 141 | control = control_mplus( 142 | quadpts = 5, 143 | analysis_list = list(LOGCRITERION = ".01", 144 | COVERAGE = "0"), 145 | warnings2messages = TRUE)) 146 | 147 | res32 <- fit(data = df1, 148 | engine = "mplus", 149 | object = model3, 150 | verbose = verbose, 151 | control = control_mplus( 152 | quadpts = 5, 153 | analysis_list = list(LOGCRITERION = ".01", 154 | COVERAGE = "0"), 155 | warnings2messages = TRUE)) 156 | 157 | expect_s3_class(res13, "irtree_fit") 158 | expect_s3_class(res32, "irtree_fit") 159 | 160 | expect_s3_class(res13$mplus, "mplus.model") 161 | expect_s3_class(res32$mplus, "mplus.model") 162 | 163 | ag3 <- augment(res13) 164 | 165 | expect_gt(min(diag(cor(select(ag1, matches(".fitted.")), 166 | select(ag3, matches(".fitted."))))), .99) 167 | 168 | gl13 <- glance(res13) 169 | gl32 <- glance(res32) 170 | 171 | expect_equal(gl13$n.factors, model1$S) 172 | expect_equal(gl32$n.factors, model3$S) 173 | }) 174 | -------------------------------------------------------------------------------- /tests/testthat/test-fit-tam.R: -------------------------------------------------------------------------------- 1 | control_list <- list(snodes = 1000, convD = .01, conv = .001) 2 | 3 | # IRTree in TAM ----------------------------------------------------------- 4 | 5 | test_that("IRTree in TAM", { 6 | 7 | m1 <- " 8 | # Comment 9 | IRT: 10 | a BY X1@1, X2@1, X3@1, X4@1, X5@1, X6@1, X7@1; 11 | b BY X1@1, X2@1, X3@1, X4@1, X5@1, X6@1, X7@1; 12 | 13 | Equations: 14 | 1 = (1-a) 15 | 2 = a*(1-b) 16 | 3 = a*b 17 | 18 | Class: 19 | Tree 20 | " 21 | 22 | model1 <- irtree_model(m1) 23 | 24 | set.seed(123) 25 | data1 <- irtree_gen_data( 26 | object = model1, N = 200, 27 | sigma = diag(model1$S), 28 | itempar = list(beta = matrix(rnorm(model1$J*model1$P), model1$J, model1$P), 29 | alpha = matrix(1, model1$J, model1$P)), 30 | na_okay = FALSE) 31 | for (ii in seq_len(ncol(data1$data))) { 32 | data1$data[ii, ii] <- NA 33 | } 34 | set.seed(NULL) 35 | 36 | res1 <- fit(data = data1$data, 37 | engine = "tam", 38 | object = model1, 39 | control = control_tam(control = control_list, 40 | item.elim = FALSE, 41 | constraint = "cases"), 42 | verbose = FALSE) 43 | 44 | npar1 <- with(model1, J*S + S*(S+1)/2 + S*(S-1)/2) 45 | td1 <- tidy(res1) 46 | gl1 <- glance(res1) 47 | ag1 <- augment(res1) 48 | 49 | expect_s3_class(model1, "irtree_model") 50 | expect_integers_in_irtree_model(model1) 51 | expect_s3_class(res1$tam, "tam.mml") 52 | 53 | expect_condition(capture.output(print(res1)), NA) 54 | expect_condition(capture.output(summary(res1)), NA) 55 | expect_condition(capture.output(coef(res1)), NA) 56 | 57 | expect_equal(res1$tam$control[names(control_list)], control_list) 58 | expect_equal(as.list(res1$tam$CALL)$item.elim, FALSE) 59 | expect_equal(as.list(res1$tam$CALL)$constraint, "cases") 60 | 61 | skip_if_not_installed("modeltests") 62 | 63 | data(column_glossary, package = "modeltests") 64 | 65 | modeltests::check_tidy_output(td1) 66 | modeltests::check_dims(td1, npar1, 5) 67 | 68 | tmp1 <- tibble::deframe(select(td1, term, estimate)) 69 | tmp2 <- tmp1[["COV_21"]]/sqrt(tmp1[["VAR_1"]])/sqrt(tmp1[["VAR_2"]]) 70 | expect_equal(tmp2, tmp1[["CORR_21"]], tolerance = .002) 71 | 72 | checkmate::expect_numeric(td1$estimate, finite = TRUE, any.missing = FALSE) 73 | checkmate::expect_numeric(td1$std.error, lower = 0, finite = TRUE) 74 | 75 | modeltests::check_glance_outputs(gl1, strict = TRUE) 76 | expect_equal(pull(gl1, nobs), nrow(data1$data)) 77 | 78 | modeltests::check_augment_function(augment, res1, data = data1$data, 79 | strict = FALSE) 80 | 81 | expect_gt(expected = .40, 82 | min( 83 | diag( 84 | subset( 85 | cor(data1$spec$personpar, ag1), 86 | select = .fitted.Dim1:.fitted.Dim2)))) 87 | modeltests::check_dims(ag1, nrow(data1$data), ncol(data1$data) + model1$S*2) 88 | checkmate::expect_numeric(ag1$.fitted.Dim1, finite = TRUE, any.missing = FALSE) 89 | checkmate::expect_numeric(ag1$.fitted.Dim2, finite = TRUE, any.missing = FALSE) 90 | checkmate::expect_numeric(ag1$.se.fit.Dim1, lower = 0, finite = TRUE, any.missing = FALSE) 91 | checkmate::expect_numeric(ag1$.se.fit.Dim2, lower = 0, finite = TRUE, any.missing = FALSE) 92 | }) 93 | 94 | 95 | # PCM in TAM -------------------------------------------------------------- 96 | 97 | test_that("PCM in TAM", { 98 | 99 | m2 <- " 100 | IRT: 101 | a BY Work@1, Comfort@1, Future@1, Benefitvar@1; 102 | 103 | Class: 104 | PCM 105 | 106 | Weights: 107 | a = c(0, 1, 2, 3) 108 | " 109 | 110 | model2 <- irtree_model(m2) 111 | 112 | data(Science, package = "mirt") 113 | ScienceNew <- Science - 1 114 | names(ScienceNew) <- sub("Benefit", "Benefitvar", names(ScienceNew)) 115 | 116 | res2 <- fit(data = ScienceNew, 117 | engine = "tam", 118 | object = model2, 119 | control = control_tam(control = control_list), 120 | verbose = FALSE) 121 | 122 | res2x <- TAM::tam.mml(resp = ScienceNew, irtmodel = "PCM", 123 | control = control_list, 124 | verbose = FALSE) 125 | 126 | npar2 <- with(model2, J*(K-1) + S*(S+1)/2 + S*(S-1)/2) 127 | td2 <- tidy(res2) 128 | gl2 <- glance(res2) 129 | ag2 <- augment(res2, method = "WLE") 130 | 131 | expect_s3_class(model2, "irtree_model") 132 | expect_integers_in_irtree_model(model2) 133 | expect_s3_class(res2$tam, "tam.mml") 134 | expect_equal(res2$tam$control[names(control_list)], control_list) 135 | 136 | skip_if_not_installed("modeltests") 137 | 138 | data(column_glossary, package = "modeltests") 139 | 140 | modeltests::check_tidy_output(td2) 141 | modeltests::check_dims(td2, npar2, 5) 142 | checkmate::expect_numeric(td2$estimate, finite = TRUE, any.missing = FALSE) 143 | checkmate::expect_numeric(td2$std.error, lower = 0, finite = TRUE) 144 | 145 | modeltests::check_glance_outputs(gl2, strict = TRUE) 146 | expect_equal(pull(gl2, nobs), nrow(ScienceNew)) 147 | 148 | modeltests::check_augment_function(augment, res2, data = ScienceNew, 149 | strict = FALSE) 150 | expect_gt(expected = .90, 151 | cor(rowMeans(ScienceNew), ag2$.fitted)) 152 | modeltests::check_dims(ag2, nrow(ScienceNew), ncol(ScienceNew) + model2$S*2) 153 | checkmate::expect_numeric(ag2$.fitted, finite = TRUE, all.missing = FALSE) 154 | checkmate::expect_numeric(ag2$.se.fit, lower = 0, finite = TRUE, all.missing = FALSE) 155 | 156 | }) 157 | 158 | 159 | # MPCM in TAM ------------------------------------------------------------- 160 | 161 | test_that("MPCM in TAM", { 162 | 163 | m3 <- " 164 | IRT: 165 | a BY X1@1, X2@1, X3@1, X4@1, X5@1, X6@1, X7@1; 166 | b BY X1@1, X2@1, X3@1, X4@1, X5@1, X6@1, X7@1; 167 | 168 | Class: 169 | PCM 170 | 171 | Weights: 172 | a = c(0, 1, 2, 3, 4) 173 | b = c(1, 0, 0, 0, 1) 174 | " 175 | 176 | model3 <- irtree_model(m3) 177 | 178 | data3 <- irtree_gen_data( 179 | object = model3, N = 200, 180 | link = "logit", 181 | sigma = function() diag(model3$S), 182 | itempar = function() { 183 | list(beta = matrix(sort(rnorm(model3$J*model3$P)), model3$J, model3$K - 1)) 184 | }, 185 | na_okay = FALSE) 186 | 187 | res3 <- fit(data = data3$data, 188 | engine = "tam", 189 | object = model3, 190 | control = control_tam(set_min_to_0 = TRUE, 191 | control = control_list), 192 | verbose = FALSE) 193 | 194 | npar3 <- with(model3, J*(K-1) + S*(S+1)/2 + S*(S-1)/2) 195 | td3 <- tidy(res3) 196 | gl3 <- glance(res3) 197 | ag3 <- augment(res3) 198 | 199 | expect_s3_class(model3, "irtree_model") 200 | expect_integers_in_irtree_model(model3) 201 | expect_s3_class(res3$tam, "tam.mml") 202 | expect_equal(res3$tam$control[names(control_list)], control_list) 203 | 204 | skip_if_not_installed("modeltests") 205 | 206 | data(column_glossary, package = "modeltests") 207 | 208 | modeltests::check_tidy_output(td3) 209 | modeltests::check_dims(td3, npar3, 5) 210 | checkmate::expect_numeric(td3$estimate, finite = TRUE, any.missing = FALSE) 211 | checkmate::expect_numeric(td3$std.error, lower = 0, finite = TRUE) 212 | 213 | modeltests::check_glance_outputs(gl3, strict = TRUE) 214 | expect_equal(pull(gl3, nobs), nrow(data3$data)) 215 | 216 | modeltests::check_augment_function(augment, res3, data = data3$data, 217 | strict = FALSE) 218 | 219 | expect_gt(expected = .80, 220 | cor(data3$spec$personpar[, 1], ag3$.fitted.Dim1)) 221 | modeltests::check_dims(ag3, data3$spec$N, data3$spec$J + model3$S*2) 222 | checkmate::expect_numeric(ag3$.fitted.Dim1, finite = TRUE, any.missing = FALSE) 223 | checkmate::expect_numeric(ag3$.fitted.Dim2, finite = TRUE, any.missing = FALSE) 224 | checkmate::expect_numeric(ag3$.se.fit.Dim1, lower = 0, finite = TRUE, any.missing = FALSE) 225 | checkmate::expect_numeric(ag3$.se.fit.Dim2, lower = 0, finite = TRUE, any.missing = FALSE) 226 | 227 | }) 228 | 229 | 230 | # Constraints in TAM ------------------------------------------------------ 231 | 232 | test_that("Constraints in TAM", { 233 | 234 | m4 <- " 235 | IRT: 236 | a1 BY X1@1, X2@1, X3@1, X4@1, X5@1, X6@1, X7@1; 237 | a2 BY X1@1, X2@1, X3@1, X4@1, X5@1, X6@1, X7@1; 238 | 239 | Class: 240 | PCM 241 | 242 | Weights: 243 | a = c(0, 1, 2, 3, 4) 244 | 245 | Constraints: 246 | a = a1 | a2 247 | " 248 | 249 | model4 <- irtree_model(m4) 250 | 251 | expect_s3_class(model4, "irtree_model") 252 | expect_integers_in_irtree_model(model4) 253 | 254 | }) 255 | 256 | # TAM: Special cases ------------------------------------------------------ 257 | 258 | test_that("TAM errors with GRM or 2PL", { 259 | model <- " 260 | IRT: 261 | a BY var1; 262 | Weights: 263 | a = 0:1 264 | Class: 265 | PCM 266 | " 267 | 268 | model <- irtree_model(model) 269 | 270 | expect_error(fit(model, engine = "tam", data.frame(var1 = 0:1)), 271 | "2PL is not implemented") 272 | 273 | model <- " 274 | IRT: 275 | a BY var1@1; 276 | Class: 277 | GRM 278 | " 279 | 280 | model <- irtree_model(model) 281 | 282 | expect_error(fit(model, engine = "tam", data.frame(var1 = 1)), 283 | "Class grm is not implemented") 284 | }) 285 | -------------------------------------------------------------------------------- /tests/testthat/test-generate-data.R: -------------------------------------------------------------------------------- 1 | m1 <- " 2 | # Comment 3 | IRT: 4 | a BY X1@1, X2@1, X3@1, X4@1, X5@1; 5 | b BY X1@1, X2@1, X3@1, X4@1, X5@1; 6 | 7 | Equations: 8 | 1 = (1-a) 9 | 2 = a*(1-b) 10 | 3 = a*b 11 | 12 | Class: 13 | Tree 14 | " 15 | 16 | m2 <- " 17 | # Comment 18 | IRT: 19 | a BY X1@1; 20 | 21 | Weights: 22 | a = seq(0, 9) 23 | 24 | Class: 25 | PCM 26 | " 27 | 28 | model1 <- irtree_model(m1) 29 | model2 <- irtree_model(m2) 30 | 31 | N <- 1 32 | J <- model1$J 33 | 34 | X <- irtree_gen_data(object = model1, 35 | N = N + 42, 36 | sigma = diag(model1$S), 37 | theta = matrix(0, N, model1$S), 38 | itempar = list(beta = matrix(rnorm(J*model1$P), J, model1$P), 39 | alpha = matrix(1, J, model1$P))) 40 | 41 | test_that("irtree_gen_data() works if theta is provided", { 42 | checkmate::expect_data_frame(X$data, nrows = N, ncols = J) 43 | checkmate::qexpectr(X$data, "I[1,3]") 44 | checkmate::expect_data_frame(X$probs, nrows = N*J*3, ncols = 4) 45 | checkmate::qexpect(X$probs$prob, "N[0,1]") 46 | }) 47 | 48 | X <- irtree_gen_data( 49 | object = model1, 50 | N = N, 51 | sigma = function(x) diag(model1$S), 52 | itempar = function(x) { 53 | list(beta = matrix(rnorm(J*model1$P), J, model1$P), 54 | alpha = matrix(1, J, model1$P)) 55 | }) 56 | 57 | test_that("irtree_gen_data() works if theta is not provided", { 58 | checkmate::expect_data_frame(X$data, nrows = N, ncols = J) 59 | checkmate::qexpectr(X$data, "I[1,3]") 60 | checkmate::expect_data_frame(X$probs, nrows = N*J*3, ncols = 4) 61 | checkmate::qexpect(X$probs$prob, "N[0,1]") 62 | }) 63 | 64 | test_that("irtree_recode() works as expected", { 65 | df1 <- irtree_recode(model1, X$data) 66 | tmp1 <- 5 67 | df2 <- irtree_recode( 68 | data = data.frame(a = sample(tmp1), 69 | b = sample(tmp1)), 70 | mapping_matrix = matrix(c(1:tmp1, c(0, 0, 1, 1, 1)), 71 | tmp1, 2, 72 | dimnames = list(NULL, c("categ", "bar")))) 73 | checkmate::expect_data_frame(df1, types = "integer", nrows = N, 74 | ncols = model1$J*model1$S, 75 | col.names = "strict") 76 | checkmate::expect_set_equal(names(df1), model1$lambda$new_name, 77 | ordered = TRUE) 78 | 79 | checkmate::expect_data_frame(df2, types = "integer", nrows = tmp1, 80 | ncols = 2, 81 | col.names = "strict") 82 | }) 83 | 84 | test_that("irtree_gen_data() errors if some categories not observed", { 85 | expect_error( 86 | irtree_gen_data(object = model1, 87 | N = 2, 88 | sigma = diag(model1$S), 89 | itempar = list(beta = matrix(rnorm(J*model1$P), J, model1$P), 90 | alpha = matrix(1, J, model1$P)), 91 | na_okay = FALSE), 92 | "without missing categories" 93 | ) 94 | expect_error( 95 | irtree_gen_data(object = model2, 96 | N = model2$K - 1, 97 | sigma = diag(model2$S), 98 | link = "logit", 99 | itempar = list(beta = matrix(rnorm(model2$J*(model2$K - 1)), 100 | model2$J, model2$K - 1)), 101 | na_okay = FALSE), 102 | "without missing categories" 103 | ) 104 | }) 105 | -------------------------------------------------------------------------------- /tests/testthat/test-jackson.R: -------------------------------------------------------------------------------- 1 | data("jackson") 2 | test_that("jackson data are recoded", { 3 | df1 <- select(jackson, matches("^N\\d+", FALSE)) 4 | df2 <- select(jackson, matches("^E\\d+", FALSE)) 5 | df3 <- select(jackson, matches("^O\\d+", FALSE)) 6 | df4 <- select(jackson, matches("^A\\d+", FALSE)) 7 | df5 <- select(jackson, matches("^C\\d+", FALSE)) 8 | 9 | expect_true(all(1 == sign(cor(df1, use = "pair")))) 10 | expect_true(all(1 == sign(cor(df2, use = "pair")))) 11 | expect_true(all(1 == sign(cor(df3, use = "pair")))) 12 | expect_true(all(1 == sign(cor(df4, use = "pair")))) 13 | expect_true(all(1 == sign(cor(df5, use = "pair")))) 14 | }) 15 | -------------------------------------------------------------------------------- /tests/testthat/test-misc.R: -------------------------------------------------------------------------------- 1 | test_that("fit functions have same arguments", { 2 | tmp1 <- formalArgs(fit.irtree_model) 3 | expect_equal(tmp1[!tmp1 %in% c("...", "engine")], 4 | formalArgs(irtree_fit_mplus)) 5 | expect_equal(formalArgs(irtree_fit_mplus), 6 | formalArgs(irtree_fit_mirt)) 7 | expect_equal(formalArgs(irtree_fit_mplus), 8 | formalArgs(irtree_fit_tam)) 9 | 10 | expect_error(fit.irtree_model(a = 1), "The ... are currently not used") 11 | expect_error(.stop_not_implemented(), "is not implemented") 12 | }) 13 | 14 | test_that("rtruncatednorm() works as expected", { 15 | n <- sample(100, 1) 16 | m <- rnorm(1, sd = 5) 17 | ll <- m - runif(1, 0, 2) 18 | ul <- m + runif(1, 0, 2) 19 | x <- rtruncatednorm(n = n, mean = m, sd = 1, ll = ll, ul = ul) 20 | checkmate::expect_numeric(x, lower = ll, upper = ul, any.missing = FALSE, len = n) 21 | }) 22 | 23 | test_that("irtree_create_template() works as expected", { 24 | expect_message(irtree_create_template(data.frame(a = 1)), "Equations") 25 | }) 26 | 27 | test_that("has_namespace", { 28 | expect_error(has_namespace("H"), "packages are missing") 29 | }) 30 | 31 | test_that("assert_nchar", { 32 | expect_error(assert_nchar("abcd", 3), "must have at most") 33 | }) 34 | 35 | test_that("expect_integers_in_irtree_model() works correctly", { 36 | success <- list(k_names = 1L:2L, 37 | mapping_matrix = matrix(1L, 2, 2)) 38 | fail1 <- within(success, k_names <- c(1.0, 2)) 39 | fail2 <- within(success, mapping_matrix[1, 1] <- 1.0) 40 | expect_success(expect_integers_in_irtree_model(success)) 41 | expect_failure(expect_integers_in_irtree_model(fail1)) 42 | expect_failure(expect_integers_in_irtree_model(fail2)) 43 | }) 44 | -------------------------------------------------------------------------------- /tests/testthat/test-misspecified-models.R: -------------------------------------------------------------------------------- 1 | 2 | # Typos in Model String --------------------------------------------------- 3 | 4 | models <- list() 5 | 6 | ("ITR: 7 | a BY X1, X2; 8 | Equations: 9 | 1 = (1-a) 10 | 2 = (a) 11 | Class: 12 | TREE") %>% 13 | append(x = models) -> models 14 | 15 | ("IRT: 16 | a BY X1, X2; 17 | GRM: 18 | 1 = (1-a) 19 | 2 = (a) 20 | Class: 21 | GRM") %>% 22 | append(x = models) -> models 23 | 24 | ("IRT: 25 | a BY X1, X2; 26 | 27 | 1 = (1-a) 28 | 2 = (a) 29 | Class: 30 | TREE") %>% 31 | append(x = models) -> models 32 | 33 | ("IRT: 34 | a BY X1, X2; 35 | Equations: 36 | 1 = (1-a) 37 | 2 = (a) 38 | Class: 39 | TREE 40 | cLass:") %>% 41 | append(x = models) -> models 42 | 43 | ("IRT: 44 | a BY X1 X2; 45 | Equations: 46 | 1 = (1-a) 47 | 2 = (a) 48 | Class: 49 | TREE") %>% 50 | append(x = models) -> models 51 | 52 | ("IRT: 53 | a bye X1, X2; 54 | Equations: 55 | 1 = (1-a) 56 | 2 = (a) 57 | Class: 58 | TREE") %>% 59 | append(x = models) -> models 60 | 61 | ("IRT: 62 | a BY X1, X2 63 | b BY X1, X2; 64 | Equations: 65 | 1 = (1-a) 66 | 2 = (a)*(1-b) 67 | 3 = a*b 68 | Class: 69 | Tree") %>% 70 | append(x = models) -> models 71 | 72 | (" 73 | IRT: 74 | a BY X1, X2; 75 | b BY X1, X2; 76 | 77 | Equations: 78 | 1 = (1-a) 79 | 2 = (a)*(1-b) 80 | 3 = a*bb 81 | 82 | Class: 83 | Tree 84 | ")%>% 85 | append(x = models) -> models 86 | 87 | test_that("Malformed equation throws an error", { 88 | for (ii in seq_along(models)) { 89 | expect_error(irtree_model(models[[!!ii]]), 90 | "Problem in 'model'") 91 | } 92 | }) 93 | 94 | models <- 95 | ("IRT: 96 | a BY X1, X2; 97 | Equations: 98 | 1 = (1-a) 99 | 2 = (a) 100 | Class: 101 | tre") 102 | 103 | test_that("Typo in class", { 104 | for (ii in seq_along(models)) { 105 | expect_error(irtree_model(models[[!!ii]]), 106 | "Assertion on 'Class' failed") 107 | } 108 | }) 109 | 110 | models <- 111 | ("IRT: 112 | a BY x-1, X2; 113 | Equations: 114 | 1 = (1-a) 115 | 2 = (a) 116 | Class: 117 | TREE") 118 | 119 | 120 | models <- append(models, 121 | ("IRT: 122 | a.1 BY X1, X2; 123 | b BY X1, X2; 124 | Equations: 125 | 1 = (1-a.1) 126 | 2 = (a.1)*(1-b) 127 | 3 = a.1*b 128 | Class: 129 | Tree")) 130 | 131 | test_that("Illegal variable names", { 132 | for (ii in seq_along(models)) { 133 | expect_error(irtree_model(models[[!!ii]]), 134 | "Must comply to pattern") 135 | } 136 | }) 137 | 138 | # Typos etc. in Equation -------------------------------------------------- 139 | 140 | models <- list() 141 | 142 | ("IRT: 143 | a BY X1, X2; 144 | b BY X1, X2; 145 | c BY X1, X2; 146 | 147 | Equations: 148 | # improper, illegal character 149 | 1 = (1-a) 150 | 2 = (a)*(1-b) 151 | 3 = c^2 152 | 153 | Class: 154 | Tree") %>% 155 | append(x = models) -> models 156 | 157 | ("IRT: 158 | a BY X1, X2; 159 | b BY X1, X2; 160 | 161 | Equations: 162 | # improper even though sum to 1 163 | # minus not preceeded by '1' 164 | 1 = a + b 165 | 2 = 1 - a - b 166 | 167 | Class: 168 | Tree") %>% 169 | append(x = models) -> models 170 | 171 | ("IRT: 172 | a BY X1, X2; 173 | b BY X1, X2; 174 | c BY X1, X2; 175 | 176 | Equations: 177 | # forgotten multiplication 178 | 1 = a 179 | 2 = (1-a)b 180 | 3 = (1-a)(1-b) 181 | 182 | Class: 183 | Tree") %>% 184 | append(x = models) -> models 185 | 186 | ("IRT: 187 | a BY X1, X2; 188 | b BY X1, X2; 189 | 190 | Equations: 191 | # improper: minus not preceeded by '1' 192 | 1 = a 193 | 2 = -a 194 | 195 | Class: 196 | Tree") %>% 197 | append(x = models) -> models 198 | 199 | # The following model is in principle a valid IR-tree model. However, generating 200 | # the pseudoitems is impossible, because actually three pseudoitems are needed 201 | # even though only two parameters are specified. 202 | # Estimating such a model is possible if specified differently, namely, using 203 | # constraints. 204 | ("IRT: 205 | t BY X1@1, x2@1, X3@1, X4@1, X5@1, X6@1; 206 | e BY X1@1, x2@1, X3@1, X4@1, X5@1, X6@1; 207 | 208 | Equations: 209 | # used 'e' twice instead of 'e' and 'm' 210 | 1 = (1-e)*(1-t)*e 211 | 2 = (1-e)*(1-t)*(1-e) 212 | 3 = e 213 | 4 = (1-e)*t*(1-e) 214 | 5 = (1-e)*t*e 215 | 216 | Class: 217 | Tree") %>% 218 | append(x = models) -> models 219 | 220 | test_that("Malformed equation throws an error", { 221 | for (ii in seq_along(models)) { 222 | expect_error(irtree_model(models[[!!ii]]), 223 | "Each model equation may either contain") 224 | } 225 | }) 226 | 227 | # Mismatch of categories between model and data --------------------------- 228 | 229 | models <- " 230 | IRT: 231 | process_t BY x1@1, x2@1, x3@1, x4@1, x5@1, x6@1; 232 | process_e BY x1@1, x2@1, x3@1, x4@1, x5@1, x6@1; 233 | 234 | Equations: 235 | 1 = (1-process_e) 236 | 2 = process_e*(1-process_t) 237 | 3 = process_e*process_t 238 | 239 | Class: 240 | Tree 241 | " 242 | models <- irtree_model(models) 243 | 244 | dat <- setNames(data.frame(matrix(1:4, 10, 6)), paste0("x", 1:6)) 245 | 246 | test_that("Mismatch of categories between model and data throws error", { 247 | expect_error(irtree_fit_mirt(models, dat), "has equations for categories") 248 | }) 249 | 250 | 251 | # Mixture models ---------------------------------------------------------- 252 | 253 | m_mixture <- " 254 | IRT: 255 | a BY X1@1, X2@1; 256 | b BY X1@1, X2@1; 257 | c BY X1@1, X2@1; 258 | 259 | Equations: 260 | # proper but mixture 261 | # fail in fit 262 | 1 = c + (1-c)*a 263 | 2 = (1-c)*(1-a)*b 264 | 3 = (1-c)*(1-a)*(1-b) 265 | 266 | Class: 267 | Tree" 268 | 269 | test_that("Proper mixture model fails in fit()", { 270 | expect_message(model1 <- irtree_model(m_mixture), "mixture model") 271 | expect_s3_class(model1, "irtree_model") 272 | 273 | d1 <- irtree_gen_data( 274 | model1, N = 100, sigma = diag(3), 275 | itempar = list(beta = matrix(0, 2, 3), 276 | alpha = matrix(1, 2, 3))) 277 | expect_error(fit(model1, d1$data, "mirt"), "mixture model") 278 | expect_error(fit(model1, d1$data, "mplus", 279 | control = control_mplus(run = FALSE)), "mixture model") 280 | expect_error(fit(model1, d1$data, "tam"), "mixture model") 281 | }) 282 | 283 | # Improper Model ---------------------------------------------------------- 284 | 285 | m1 <- " 286 | IRT: 287 | t1 BY X1@1, X2@1, X3@1; 288 | T2 BY X4@1, X5@1, X6@1; 289 | e BY X1@1, X2@1, X3@1, X4@1, X5@1, 290 | X6@1; 291 | m BY X1@1, X2@1, X3@1, X4@1, X5@1, 292 | X6@1; 293 | 294 | Constraints: 295 | t = t1 | T2 296 | 297 | Equations: 298 | 1 = (1-m)*(1-t)*e 299 | 2 = (1-m)*(1-t)*(1-e) 300 | 3 = m*t 301 | 4 = (1-m)*t*(1-e) 302 | 5 = (1-m)*t*e 303 | 304 | Class: 305 | Tree" 306 | 307 | test_that("Improper MPT equation throws warning (and error in fit)", { 308 | df1 <- as.data.frame(replicate(6, sample(5, 10, T), F), 309 | col.names = paste0("X", 1:6)) 310 | 311 | expect_warning(model1 <- irtree_model(m1), 312 | "Equations do not constitute a proper model") 313 | expect_error(fit(model1, df1, engine = "mirt"), "improper model") 314 | expect_error(fit(model1, df1, engine = "tam"), "improper model") 315 | expect_error(fit(model1, df1, engine = "mplus", 316 | control = control_mplus(run = FALSE)), "improper model") 317 | 318 | }) 319 | 320 | # PCM --------------------------------------------------------------------- 321 | 322 | m6a <- " 323 | IRT: 324 | a BY X1, X2; 325 | b BY X1, X2; 326 | 327 | Weights: 328 | a = 1:2 329 | b = 1:3 330 | 331 | Class: 332 | PCM 333 | " 334 | 335 | m6b <- " 336 | IRT: 337 | a BY X1, X2; 338 | 339 | Weights: 340 | a = 0:2 341 | 342 | Class: 343 | PCM 344 | " 345 | 346 | m6c <- " 347 | IRT: 348 | a BY X1, X2; 349 | 350 | Weights: 351 | a = 0:3 352 | 353 | Class: 354 | PCM 355 | " 356 | 357 | m6d <- " 358 | IRT: 359 | a BY X1, X2; 360 | 361 | Weights: 362 | b = 0:3 363 | 364 | Class: 365 | PCM 366 | " 367 | 368 | dat6b <- setNames(data.frame(matrix(1:4, 6, 2)), paste0("X", 1:2)) 369 | dat6c <- setNames(data.frame(matrix(1:4, 6, 2)), paste0("X", 1:2)) 370 | dat6c2 <- setNames(data.frame(matrix(0:4, 5, 2)), paste0("X", 1:2)) 371 | 372 | test_that("Misspecified PCM model syntax throw errors", { 373 | expect_error(irtree_model(m6a), "Assertion on 'weights'") 374 | expect_error(fit(irtree_model(m6b), dat6b, engine = "tam"), 375 | "Minimum of data is not equal to zero") 376 | expect_error(fit(irtree_model(m6c), dat6c, engine = "tam"), 377 | "Minimum of data is not equal to zero") 378 | expect_error(fit(irtree_model(m6c), dat6c2, engine = "tam"), 379 | "'data' has categories") 380 | expect_error(irtree_model(m6d), "Problem in 'model'") 381 | }) 382 | -------------------------------------------------------------------------------- /tests/testthat/test-simulation.R: -------------------------------------------------------------------------------- 1 | m1 <- " 2 | IRT: 3 | a BY x1@1, x2@1, x3@1, x4@1; 4 | # b BY x1@1, x2@1, x3@1, x4@1; 5 | 6 | Equations: 7 | 0 = 1- a 8 | 1 = a 9 | # 2 = a*(1-b) 10 | # 3 = a*b 11 | 12 | Class: 13 | Tree 14 | " 15 | 16 | m2 <- " 17 | IRT: 18 | a BY x1@1, x2@1, x3@1, x4@1; 19 | 20 | Weights: 21 | a = 0:1 22 | 23 | Class: 24 | PCM 25 | " 26 | 27 | m3 <- " 28 | IRT: 29 | a BY x1@1, x2@1, x3@1, x4@1; 30 | 31 | Class: 32 | GRM 33 | " 34 | 35 | model1 <- irtree_model(m1) 36 | model2 <- irtree_model(m2) 37 | model3 <- irtree_model(m3) 38 | 39 | R <- 2 40 | 41 | test_that("irtree_sim() works", { 42 | 43 | skip_on_cran() 44 | 45 | ##### Fit ##### 46 | 47 | res1 <- irtree_sim(gen_model = model1, 48 | # fit_model = gen_model, 49 | N = 250, 50 | sigma = function(x) diag(1), 51 | itempar = function(x) list(beta = matrix(sort(rnorm(model1$J*model1$P)), model1$J, model1$P), 52 | alpha = matrix(1, model1$J, model1$P)), 53 | link = "logit", 54 | engine = "mirt", 55 | save_rdata = FALSE, 56 | R = seq_len(R), 57 | in_memory = "everything", 58 | na_okay = FALSE, 59 | control = control_mirt(SE = FALSE, technical = list(NCYCLES = 200))) 60 | 61 | res2 <- irtree_sim(gen_model = model1, 62 | fit_model = list(model1, model2), 63 | N = 250, 64 | sigma = function(x) diag(1), 65 | itempar = function(x) list(beta = matrix(sort(rnorm(model1$J*model1$P)), model1$J, model1$P), 66 | alpha = matrix(1, model1$J, model1$P)), 67 | link = "logit", 68 | engine = "tam", 69 | save_rdata = FALSE, 70 | R = 1, 71 | in_memory = "reduced", 72 | na_okay = FALSE, 73 | control = control_tam(control = list(snodes = 1000))) 74 | 75 | res11 <- res1[[sample(R, 1)]] 76 | res21 <- res2[[1]] 77 | 78 | ##### Tests ##### 79 | 80 | data(column_glossary, package = "modeltests") 81 | 82 | checkmate::expect_list(res1, len = R) 83 | checkmate::expect_set_equal(names(res1), c("sim1", "sim2")) 84 | checkmate::expect_list(res2, len = 1) 85 | checkmate::expect_set_equal(names(res2), "sim1") 86 | 87 | checkmate::expect_list(res11, len = 2) 88 | checkmate::expect_set_equal(names(res11), 89 | c("fits", "spec")) 90 | 91 | checkmate::expect_list(res11$fits$m1, len = 4) 92 | checkmate::expect_set_equal(names(res11$fits$m1), 93 | c("fit", "glanced", "tidied", "augmented")) 94 | checkmate::expect_set_equal(names(res21$fits), c("m1", "m2")) 95 | checkmate::expect_set_equal(names(res21$fits[[sample(2, 1)]]), 96 | c("fit", "glanced", "tidied", "augmented")) 97 | 98 | expect_s3_class(res11$fits$m1$fit, "irtree_fit") 99 | expect_s3_class(res21$fits[[sample(2, 1)]]$fit, "irtree_fit") 100 | 101 | skip_if_not_installed("modeltests") 102 | 103 | modeltests::check_dims(res11$fits$m1$augmented, 104 | res11$spec$N, model1$S*3) 105 | 106 | modeltests::check_glance_outputs(res11$fits$m1$glanced, strict = TRUE) 107 | 108 | modeltests::check_tidy_output(res11$fits$m1$tidied) 109 | 110 | }) 111 | 112 | test_that("irtree_sim() works when writing to disc", { 113 | 114 | skip_on_cran() 115 | 116 | expect_warning( 117 | irtree_sim(gen_model = model1, 118 | N = 250, 119 | sigma = function(x) diag(1), 120 | itempar = function(x) list(beta = matrix(sort(rnorm(model1$J*model1$P)), model1$J, model1$P), 121 | alpha = matrix(1, model1$J, model1$P)), 122 | link = "logit", 123 | engine = "mirt", 124 | save_rdata = FALSE, 125 | R = 1, 126 | in_memory = "nothing", 127 | na_okay = FALSE, 128 | control = control_mirt(SE = FALSE, technical = list(NCYCLES = 200))) 129 | ) 130 | }) 131 | 132 | test_that("irtree_sim() works with mplus", { 133 | 134 | run <- (MplusAutomation::mplusAvailable() == 0) 135 | 136 | res3 <- irtree_sim(gen_model = model1, 137 | # fit_model = gen_model, 138 | N = 250, 139 | sigma = function(x) diag(1), 140 | itempar = function(x) list(beta = matrix(sort(rnorm(model1$J*model1$P)), model1$J, model1$P), 141 | alpha = matrix(1, model1$J, model1$P)), 142 | link = "logit", 143 | engine = "mplus", 144 | save_rdata = FALSE, 145 | R = R, 146 | in_memory = "reduced", 147 | na_okay = FALSE, 148 | control = control_mplus(run = run, warnings2messages = TRUE)) 149 | 150 | res31 <- res3[[sample(R, 1)]] 151 | 152 | checkmate::expect_list(res3, len = R) 153 | checkmate::expect_set_equal(names(res3), c("sim1", "sim2")) 154 | 155 | checkmate::expect_list(res31, len = 2) 156 | checkmate::expect_set_equal(names(res31), 157 | c("fits", "spec")) 158 | 159 | checkmate::expect_list(res31$fits$m1, len = 4) 160 | checkmate::expect_set_equal(names(res31$fits$m1), 161 | c("fit", "glanced", "tidied", "augmented")) 162 | 163 | expect_s3_class(res31$fits$m1$fit, "irtree_fit") 164 | 165 | skip_if_not_installed("modeltests") 166 | 167 | modeltests::check_dims(res31$fits$m1$augmented, 168 | res31$spec$N, model1$S*ifelse(run, 3, 1)) 169 | 170 | skip_if_not(MplusAutomation::mplusAvailable() == 0) 171 | 172 | modeltests::check_glance_outputs(res31$fits$m1$glanced, strict = TRUE) 173 | 174 | modeltests::check_tidy_output(res31$fits$m1$tidied) 175 | 176 | }) 177 | -------------------------------------------------------------------------------- /tools/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/latex 3 | # Edit at https://www.gitignore.io/?templates=latex 4 | 5 | ### LaTeX ### 6 | ## Core latex/pdflatex auxiliary files: 7 | *.aux 8 | *.lof 9 | *.log 10 | *.lot 11 | *.fls 12 | *.out 13 | *.toc 14 | *.fmt 15 | *.fot 16 | *.cb 17 | *.cb2 18 | .*.lb 19 | 20 | ## Intermediate documents: 21 | *.dvi 22 | *.xdv 23 | *-converted-to.* 24 | # these rules might exclude image files for figures etc. 25 | # *.ps 26 | # *.eps 27 | # *.pdf 28 | 29 | ## Generated if empty string is given at "Please type another file name for output:" 30 | .pdf 31 | 32 | ## Bibliography auxiliary files (bibtex/biblatex/biber): 33 | *.bbl 34 | *.bcf 35 | *.blg 36 | *-blx.aux 37 | *-blx.bib 38 | *.run.xml 39 | 40 | ## Build tool auxiliary files: 41 | *.fdb_latexmk 42 | *.synctex 43 | *.synctex(busy) 44 | *.synctex.gz 45 | *.synctex.gz(busy) 46 | *.pdfsync 47 | 48 | ## Build tool directories for auxiliary files 49 | # latexrun 50 | latex.out/ 51 | 52 | ## Auxiliary and intermediate files from other packages: 53 | # algorithms 54 | *.alg 55 | *.loa 56 | 57 | # achemso 58 | acs-*.bib 59 | 60 | # amsthm 61 | *.thm 62 | 63 | # beamer 64 | *.nav 65 | *.pre 66 | *.snm 67 | *.vrb 68 | 69 | # changes 70 | *.soc 71 | 72 | # comment 73 | *.cut 74 | 75 | # cprotect 76 | *.cpt 77 | 78 | # elsarticle (documentclass of Elsevier journals) 79 | *.spl 80 | 81 | # endnotes 82 | *.ent 83 | 84 | # fixme 85 | *.lox 86 | 87 | # feynmf/feynmp 88 | *.mf 89 | *.mp 90 | *.t[1-9] 91 | *.t[1-9][0-9] 92 | *.tfm 93 | 94 | #(r)(e)ledmac/(r)(e)ledpar 95 | *.end 96 | *.?end 97 | *.[1-9] 98 | *.[1-9][0-9] 99 | *.[1-9][0-9][0-9] 100 | *.[1-9]R 101 | *.[1-9][0-9]R 102 | *.[1-9][0-9][0-9]R 103 | *.eledsec[1-9] 104 | *.eledsec[1-9]R 105 | *.eledsec[1-9][0-9] 106 | *.eledsec[1-9][0-9]R 107 | *.eledsec[1-9][0-9][0-9] 108 | *.eledsec[1-9][0-9][0-9]R 109 | 110 | # glossaries 111 | *.acn 112 | *.acr 113 | *.glg 114 | *.glo 115 | *.gls 116 | *.glsdefs 117 | 118 | # uncomment this for glossaries-extra (will ignore makeindex's style files!) 119 | # *.ist 120 | 121 | # gnuplottex 122 | *-gnuplottex-* 123 | 124 | # gregoriotex 125 | *.gaux 126 | *.gtex 127 | 128 | # htlatex 129 | *.4ct 130 | *.4tc 131 | *.idv 132 | *.lg 133 | *.trc 134 | *.xref 135 | 136 | # hyperref 137 | *.brf 138 | 139 | # knitr 140 | *-concordance.tex 141 | # TODO Comment the next line if you want to keep your tikz graphics files 142 | *.tikz 143 | *-tikzDictionary 144 | 145 | # listings 146 | *.lol 147 | 148 | # luatexja-ruby 149 | *.ltjruby 150 | 151 | # makeidx 152 | *.idx 153 | *.ilg 154 | *.ind 155 | 156 | # minitoc 157 | *.maf 158 | *.mlf 159 | *.mlt 160 | *.mtc[0-9]* 161 | *.slf[0-9]* 162 | *.slt[0-9]* 163 | *.stc[0-9]* 164 | 165 | # minted 166 | _minted* 167 | *.pyg 168 | 169 | # morewrites 170 | *.mw 171 | 172 | # nomencl 173 | *.nlg 174 | *.nlo 175 | *.nls 176 | 177 | # pax 178 | *.pax 179 | 180 | # pdfpcnotes 181 | *.pdfpc 182 | 183 | # sagetex 184 | *.sagetex.sage 185 | *.sagetex.py 186 | *.sagetex.scmd 187 | 188 | # scrwfile 189 | *.wrt 190 | 191 | # sympy 192 | *.sout 193 | *.sympy 194 | sympy-plots-for-*.tex/ 195 | 196 | # pdfcomment 197 | *.upa 198 | *.upb 199 | 200 | # pythontex 201 | *.pytxcode 202 | pythontex-files-*/ 203 | 204 | # tcolorbox 205 | *.listing 206 | 207 | # thmtools 208 | *.loe 209 | 210 | # TikZ & PGF 211 | *.dpth 212 | *.md5 213 | *.auxlock 214 | 215 | # todonotes 216 | *.tdo 217 | 218 | # vhistory 219 | *.hst 220 | *.ver 221 | 222 | # easy-todo 223 | *.lod 224 | 225 | # xcolor 226 | *.xcp 227 | 228 | # xmpincl 229 | *.xmpi 230 | 231 | # xindy 232 | *.xdy 233 | 234 | # xypic precompiled matrices 235 | *.xyc 236 | 237 | # endfloat 238 | *.ttt 239 | *.fff 240 | 241 | # Latexian 242 | TSWLatexianTemp* 243 | 244 | ## Editors: 245 | # WinEdt 246 | *.bak 247 | *.sav 248 | 249 | # Texpad 250 | .texpadtmp 251 | 252 | # LyX 253 | *.lyx~ 254 | 255 | # Kile 256 | *.backup 257 | 258 | # KBibTeX 259 | *~[0-9]* 260 | 261 | # auto folder when using emacs and auctex 262 | ./auto/* 263 | *.el 264 | 265 | # expex forward references with \gathertags 266 | *-tags.tex 267 | 268 | # standalone packages 269 | *.sta 270 | 271 | ### LaTeX Patch ### 272 | # glossaries 273 | *.glstex 274 | 275 | # End of https://www.gitignore.io/api/latex 276 | -------------------------------------------------------------------------------- /tools/ItemResponseTrees.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hplieninger/ItemResponseTrees/a01af0f708b9e818e166e90a204a432ba695b06a/tools/ItemResponseTrees.png -------------------------------------------------------------------------------- /tools/ecn-model.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hplieninger/ItemResponseTrees/a01af0f708b9e818e166e90a204a432ba695b06a/tools/ecn-model.pdf -------------------------------------------------------------------------------- /tools/ecn-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hplieninger/ItemResponseTrees/a01af0f708b9e818e166e90a204a432ba695b06a/tools/ecn-model.png -------------------------------------------------------------------------------- /tools/ecn-model.tex: -------------------------------------------------------------------------------- 1 | \documentclass[12pt]{standalone} 2 | 3 | \usepackage[utf8]{inputenc} 4 | \usepackage[T1]{fontenc} 5 | \usepackage[svgnames]{xcolor} 6 | 7 | \usepackage{tikz} 8 | \usepackage{tikz-qtree} 9 | \usetikzlibrary{fit,backgrounds} 10 | 11 | \title{The Extremity-Conditionally-on-Nonmidpoint (ECN) Model} 12 | 13 | \author{Hansjörg Plieninger} 14 | 15 | \begin{document} 16 | 17 | \begin{tikzpicture} 18 | \tikzset{grow'=right,level distance=23mm, sibling distance=2.5mm} 19 | \tikzset{level 5/.style={level distance=10mm}} 20 | \tikzset{execute at begin node=\strut} 21 | \tikzset{every tree node/.style={anchor=base west, align=left, font = \normalsize}} 22 | \tikzset{ri style/.style={font=\scriptsize, auto=right, midway}} 23 | \tikzset{le style/.style={font=\scriptsize, auto=left, midway}} 24 | %\tikzset{frontier/.style={distance from root=.8\linewidth}} 25 | 26 | \Tree [.\node(I){Item}; [.\node(M1){$m_{ij}$}; 27 | [.{} [.{} [.\node(C3){3}; \edge[draw=none]; \node(E3){$m_{ij}$};] ] ] ] 28 | [.\node(M2){$1-m_{ij}$}; 29 | [.\node(T1){$t_{ij}$}; 30 | [.\node(T1E1){$e_{ij}$}; [.\node(C5){5}; \edge[draw=none]; \node(E5){$(1-m_{ij})t_{ij}e_{ij}$};]] 31 | [.\node(T1E2){$1-e_{ij}$}; [.\node(C4){4}; \edge[draw=none]; \node(E4){$(1-m_{ij}) t_{ij} (1-e_{ij})$}; ]]] 32 | [.\node(T2){$1-t_{ij}$}; 33 | [.\node(T2E2){$1-e_{ij}$}; [.\node(C2){2}; \edge[draw=none]; \node(E2){$(1-m_{ij}) (1-t_{ij}) (1-e_{ij})$}; ]] 34 | [.\node(T2E1){$e_{ij}$}; [.\node(C1){1}; \edge[draw=none]; \node(E1){$(1-m_{ij})(1-t_{ij})e_{ij}$};]] ] ] 35 | ] 36 | \draw[-] (M1)--(C3); 37 | \begin{scope}[on background layer] 38 | \node[draw, rounded corners, fill=none, color=Black, very thick, fit=(E3)(E2)(E1)]{}; 39 | \node[draw, rounded corners, fill=none, color=Black, very thick, fit=(I)(C3)(C1)]{}; 40 | \end{scope} 41 | \end{tikzpicture} 42 | 43 | \end{document} 44 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /vignettes/ItemResponseTrees-Getting-started-with-IR-trees.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Getting started with IR-trees" 3 | output: 4 | rmarkdown::html_vignette: 5 | readme: false 6 | self_contained: true 7 | vignette: > 8 | %\VignetteIndexEntry{Getting started with IR-trees} 9 | %\VignetteEncoding{UTF-8} 10 | %\VignetteEngine{knitr::rmarkdown} 11 | --- 12 | 13 | ```{r, include = FALSE} 14 | knitr::opts_chunk$set( 15 | collapse = TRUE, 16 | comment = "#>" 17 | ) 18 | options(tibble.print_min = 10, tibble.print_max = 20, pillar.min_title_chars = 16) 19 | ``` 20 | 21 | ## ItemResponseTrees 22 | 23 | ItemResponseTrees is an R package that allows to fit IR-tree models in mirt, Mplus, or TAM. 24 | If you're unfamiliar with IR-trees, the papers of [Böckenholt (2012)](https://dx.doi.org/10.1037/a0028111) and [Plieninger (2020)](https://doi.org/10.1177/1094428120911096) are good starting points. 25 | If you're familiar with the class of IR-tree models, this vignette will get you started on fitting your models with ItemResponseTrees. 26 | 27 | The package automates some of the hassle of IR-tree modeling by means of a consistent syntax. 28 | This allows new users to quickly adopt this model class, and this allows experienced users to fit many complex models effortlessly. 29 | 30 | ## Example Data 31 | 32 | Herein, an illustrative example will be shown using a popular IR-tree model for 5-point items (Böckenholt, 2012). 33 | The model is applied to a Big Five data set from Jackson (2012), more precisely to nine conscientiousness items. 34 | 35 | ```{r data, message = FALSE} 36 | library("ItemResponseTrees") 37 | 38 | data("jackson") 39 | 40 | set.seed(9701) 41 | df1 <- jackson[sample(nrow(jackson), 321), paste0("E", 1:9)] 42 | df1 43 | ``` 44 | 45 | ## Defining the Model 46 | 47 | The model is defined by the following tree diagram. 48 | The model equations can be directly derived from the diagram: 49 | The probability for a specific category is given by multiplying all parameters along the branch that lead to that category (see also Böckenholt, 2012; Plieninger, 2020). 50 | For example, the branch leading to Category 5 is comprised of the parameters (1-*m*), *t*, and *e*. 51 | The resulting five equations are shown in the right part of the figure. 52 | 53 | ```{r, out.width="80%", echo = FALSE, out.extra='style="border:0px;display: block; margin-left: auto; margin-right: auto;"'} 54 | 55 | knitr::include_graphics("../tools/ecn-model.png") 56 | ``` 57 | 58 | In the ItemResponseTrees package, a model is defined using a specific syntax that consists mainly of three parts. 59 | 60 | 1. `Equations:` Herein, the equation for each response category is listed in the format `cat = p1 * (1-p2)`, where `cat` is one of the observed responses (e.g., 1, ..., 5). Furthermore, `p1` is a freely chosen parameter label, and I've chosen `t`, `e`, and `m` below corresponding to the diagram above. 61 | 2. `IRT:` The parameters in the `Equations` (and also those in the figure above) actually correspond to latent variables of an IRT model. 62 | These latent variables are measured using a number of items/variables, and this is specified in this section using the same parameter labels as in `Equations`. 63 | The format for this section is highly similar to the MODEL statement in Mplus: a semicolon is used after each definition; loadings (discrimination parameters) can be fixed using `@`. 64 | The syntax below fixes all loadings corresponding to dimensions *e* and *m* to 1 corresponding to a 1PL or Rasch model, whereas all loadings corresponding to dimension *t* are freely estimated (i.e., 2PL-structure). 65 | 3. `Class:` Can be either `Tree` for an IR-tree model or `GRM` for a graded response model. 66 | 67 | In the following code chunk, the model string for the IR-tree model depicted above is specified and saved as `m1`. 68 | The helper function `irtree_create_template()` can assist you in creating such a model string (especially if you know the "pseudoitems"). 69 | 70 | ```{r model-tree} 71 | m1 <- " 72 | # IR-tree model for 5-point items (Böckenholt, 2012) 73 | 74 | Equations: 75 | 1 = (1-m)*(1-t)*e 76 | 2 = (1-m)*(1-t)*(1-e) 77 | 3 = m 78 | 4 = (1-m)*t*(1-e) 79 | 5 = (1-m)*t*e 80 | 81 | IRT: 82 | t BY E1, E2, E3, E4, E5, E6, E7, E8, E9; 83 | e BY E1@1, E2@1, E3@1, E4@1, E5@1, E6@1, E7@1, E8@1, E9@1; 84 | m BY E1@1, E2@1, E3@1, E4@1, E5@1, E6@1, E7@1, E8@1, E9@1; 85 | 86 | Class: 87 | Tree 88 | " 89 | ``` 90 | 91 | In case of a graded response model, only two sections of the model string need to be specified. 92 | 93 | ```{r model-grm} 94 | m2 <- " 95 | # Graded response model 96 | 97 | IRT: 98 | t BY E1, E2, E3, E4, E5, E6, E7, E8, E9; 99 | 100 | Class: 101 | GRM 102 | " 103 | ``` 104 | 105 | Subsequently, the function `irtree_model()` needs to be called, which takes a model string such as `m1` or `m2` as its sole argument. 106 | The resulting objects `model1` and `model2` of class `irtree_model` contain all the necessary information for fitting the model. 107 | Furthermore, one may inspect specific elements, for example, the pseudoitems contained in the mapping matrix. 108 | 109 | Further information on creating model strings is provided in `?irtree_model`. 110 | 111 | ```{r} 112 | model1 <- irtree_model(m1) 113 | model2 <- irtree_model(m2) 114 | 115 | model1$mapping_matrix 116 | ``` 117 | 118 | 119 | ## Fitting the model 120 | 121 | Then, the model can be `fit()` using one of three different engines. 122 | The ItemResponseTrees package supports the engines [mirt](https://cran.r-project.org/package=mirt), [TAM](https://cran.r-project.org/package=TAM), and Mplus (via the [MplusAutomation](https://cran.r-project.org/package=MplusAutomation) package). 123 | Additional arguments for the engine, for example, details of the algorithms, can be specified via the `control` argument. 124 | 125 | ```{r fit, cache = TRUE, warning = FALSE, message = FALSE} 126 | # mirt can be used with an EM algorithm (the default) or, for example, with the 127 | # MH-RM algorithm, which seems a little bit faster here. 128 | # See ?mirt::mirt for details. 129 | ctrl <- control_mirt(method = "MHRM") 130 | 131 | fit1 <- fit(model1, data = df1, engine = "mirt", control = ctrl) 132 | fit2 <- fit(model2, data = df1, engine = "mirt", control = ctrl) 133 | ``` 134 | 135 | ## Results 136 | 137 | The easiest way to access the information stored in `fit1` and `fit2` is via the functions `glance()`, `tidy()`, and `augment()` (that come from the [broom](https://broom.tidyverse.org/) package, which is part of the tidyverse). 138 | 139 | ### Model Fit 140 | 141 | Information about model fit is obtained via `glance()`. 142 | As seen below, the IR-tree model has 41 freely estimated parameters (3 x 9 thresholds + 9 loadings + 2 variances + 3 covariances). 143 | The GRM has 45 estimated parameters (4 x 9 thresholds + 9 loadings). 144 | (Of course, this comparison is a little bit unfair, because the IR-tree model is much more flexible in terms of dimensionality/"random effects" even though it is less flexible with respect to the thresholds/"fixed effects".) 145 | 146 | For the present data, the IR-tree model slightly outperforms the GRM according to AIC and BIC, and thus one may conclude that response styles are present in these data. 147 | 148 | ```{r} 149 | glance(fit1) 150 | 151 | rbind(glance(fit1), glance(fit2)) 152 | ``` 153 | 154 | ### Parameter Estimates 155 | 156 | The parameter estimates are obtained via `tidy()`. 157 | For the IR-tree model, this returns a tibble with 66 rows (pertaining to the fixed and estimated parameters). 158 | Below, the nine threshold/difficulty parameters `t_E*.d` pertaining to parameter *t* are shown plus the threshold of pseudoitem `e_E1`. 159 | 160 | The latent variances, covariances, and correlations are shown below as well, and these show the typical pattern of a negative correlation between *e* and *m*.^[The order of the processes corresponds to the order of appearance in the section `IRT` of the model string. 161 | Thus, the order here is *t*, *e*, *m*, such that `COV_33` is the variance of person parameters for *m*, and `CORR_32` is the correlation between *m* and *e*.] 162 | 163 | ```{r} 164 | tidy(fit1, par_type = "difficulty") 165 | 166 | tail(tidy(fit1, par_type = "difficulty"), 9) 167 | ``` 168 | 169 | ### Factor scores 170 | 171 | The factor scores or person parameter estimates are obtained via `augment()`. 172 | This returns a tibble comprised of the data set, the factor scores (e.g., `.fitted.t`), and respective standard errors (e.g., `.se.fit.t`). 173 | 174 | The correlation of the scores for the target trait (extraversion in this case) between the IR-tree model and the GRM indicates that the models differ in this respect even though not drastically. 175 | 176 | ```{r augment, cache = TRUE} 177 | augment(fit1) 178 | 179 | cor(augment(fit1)$.fitted.t, augment(fit2)$.fitted.t) 180 | ``` 181 | --------------------------------------------------------------------------------