diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml new file mode 100644 index 0000000..4cd4d78 --- /dev/null +++ b/.github/workflows/R-CMD-check.yaml @@ -0,0 +1,41 @@ +name: R-CMD-check + +on: + push: + pull_request: + +jobs: + R-CMD-check: + name: ${{ matrix.os }} / R ${{ matrix.r }} + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + r: [release] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up R + uses: r-lib/actions/setup-r@v2 + with: + r-version: ${{ matrix.r }} + use-public-rspm: true + + - name: Set up pandoc + uses: r-lib/actions/setup-pandoc@v2 + + - name: Install system dependencies + uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: | + any::rcmdcheck + needs: check + + - name: Check package + uses: r-lib/actions/check-r-package@v2 + with: + upload-snapshots: true diff --git a/R/ACO.R b/R/ACO.R index ba3cf88..b224198 100644 --- a/R/ACO.R +++ b/R/ACO.R @@ -69,6 +69,7 @@ setMethod('show', #' #' @importFrom ggrepel geom_text_repel #' @importFrom tidyr gather +#' @importFrom rlang .data #' #' @export setMethod('plot', @@ -91,12 +92,12 @@ setMethod('plot', pheromone_plot <- ggplot2::ggplot( pheromone_long, - ggplot2::aes_string( - x = "run", - y = "Pheromone", - group = "Item", - fill = "Item", - colour = "Item" + ggplot2::aes( + x = .data$run, + y = .data$Pheromone, + group = .data$Item, + fill = .data$Item, + colour = .data$Item ) ) + ggplot2::geom_area( @@ -122,7 +123,7 @@ setMethod('plot', gamma_plot <- ggplot2::ggplot( summary_results, - ggplot2::aes_string(x = "run", y = "mean.gamma") + ggplot2::aes(x = .data$run, y = .data$mean.gamma) ) + ggplot2::geom_line() + ggplot2::ylab(expression("Mean " * gamma)) + @@ -147,7 +148,7 @@ setMethod('plot', beta_plot <- ggplot2::ggplot( summary_results, - ggplot2::aes_string(x = "run", y = "mean.beta") + ggplot2::aes(x = .data$run, y = .data$mean.beta) ) + ggplot2::geom_line() + ggplot2::ylab(expression("Mean " * beta)) + @@ -172,7 +173,7 @@ setMethod('plot', variance_plot <- ggplot2::ggplot( summary_results, - ggplot2::aes_string(x = "run", y = "mean.var.exp") + ggplot2::aes(x = .data$run, y = .data$mean.var.exp) ) + ggplot2::geom_line() + ggplot2::ylab(expression("Mean Variance Explained")) + diff --git a/R/ACO_lavaan.R b/R/ACO_lavaan.R index c8a3a17..725d20c 100644 --- a/R/ACO_lavaan.R +++ b/R/ACO_lavaan.R @@ -208,6 +208,49 @@ #' ) #' #' abilityShortForm # print the results of the final short form +#' +#' # an example using binary (ordered) data +#' # create the simulated full model and model data +#' sim_model <- " +#' f1 =~ x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10 +#' f2 =~ x11 + x12 + x13 + x14 + x15 + x16 + x17 + x18 + x19 + x20 +#' f3 =~ x21 + x22 + x23 + x24 + x25 + x26 + x27 + x28 + x29 + x30" +#' +#' sim_data <- +#' cbind( +#' psych::sim.rasch(nvar = 10)$items, +#' psych::sim.rasch(nvar = 10)$items, +#' psych::sim.rasch(nvar = 10)$items +#' ) +#' +#' colnames(sim_data) = paste0("x", 1:30) +#' # fit with antcolony.lavaan +#' # note that ONLY the estimator and ordered args +#' # of lavaan.model.specs are changed. This retains +#' # the default args, fitting a CFA but with ordered data. +#' example <- +#' antcolony.lavaan( +#' data = sim_data, +#' ants = 5, evaporation = 0.7, +#' antModel = sim_model, +#' lavaan.model.specs = +#' list(estimator = "wlsmv", ordered = T), +#' list.items = list(paste0('x', 1:10), +#' paste0('x', 11:20), +#' paste0('x', 21:30)), +#' full = 30, +#' i.per.f = c(5, 5, 5), +#' factors = c("f1", "f2", "f3"), +#' steps = 20, +#' fit.indices = c("cfi.scaled"), +#' fit.statistics.test = "(cfi.scaled > 0.90)", +#' summaryfile = NULL, +#' feedbackfile = NULL, +#' max.run = 500, +#' parallel = T +#' ) +#' # note that this example will take a bit of time to run +#' # as ordered data factor analysis is computationally expensive. #' } #' @import lavaan utils #' @export @@ -364,7 +407,20 @@ antcolony.lavaan <- function(data = NULL, sample.cov = NULL, sample.nobs = NULL, select.indicator <- is.element(item.vector, selected.vector) # MODIFY LAVAAN SYNTAX - new_ant_model <- paste(antcolony.lavaan.env$input, collapse = "\n") + new_ant_model <- + input + for (factor in 1:length(factors)) { + temp_factor_definition <- + new_ant_model[grepl(factors[factor], new_ant_model)] + temp_factor_definition <- + sub( + "=~[[:alnum:][:space:] +]{1,}", + paste0("=~ ", paste0(selected.items[[factor]], collapse = " + ")), + temp_factor_definition + ) + new_ant_model[grepl(factors[factor], new_ant_model)] <- + temp_factor_definition + } # Run the model check function # checks for and saves error/warning messages within the lavaan output, @@ -491,8 +547,8 @@ antcolony.lavaan <- function(data = NULL, sample.cov = NULL, sample.nobs = NULL, antResults[[bestAnt,'pheromone']] - # adjusts pheromone and best.so.far values only if the current pheromone is best than the previous. - if (best.pheromone > best.so.far.pheromone) { + # adjusts pheromone and best.so.far values only if the current pheromone is as good or better than the previous. + if (best.pheromone >= best.so.far.pheromone) { include.pheromone <- antResults[[bestAnt, 'solution']] * best.pheromone include <- include + include.pheromone diff --git a/R/simulated_annealing_internals.R b/R/simulated_annealing_internals.R index e4887fa..ce321ca 100644 --- a/R/simulated_annealing_internals.R +++ b/R/simulated_annealing_internals.R @@ -49,7 +49,7 @@ randomInitialModel <- ) } newModelSyntax <- - paste0(newModelSyntax, externalRelation, factorRelation, collapse = "\n") + paste(newModelSyntax, externalRelation, factorRelation, sep = "\n") newModelSyntax <- stringr::str_replace_all(newModelSyntax, "\n\n", "\n") diff --git a/README-ACO example-1.png b/README-ACO example-1.png deleted file mode 100644 index a2cf62d..0000000 Binary files a/README-ACO example-1.png and /dev/null differ diff --git a/README-ACO example-2.png b/README-ACO example-2.png deleted file mode 100644 index d44502d..0000000 Binary files a/README-ACO example-2.png and /dev/null differ diff --git a/README-ACO example-3.png b/README-ACO example-3.png deleted file mode 100644 index 4d08ffc..0000000 Binary files a/README-ACO example-3.png and /dev/null differ diff --git a/README-ACO example-4.png b/README-ACO example-4.png deleted file mode 100644 index 5658051..0000000 Binary files a/README-ACO example-4.png and /dev/null differ diff --git a/README-Simulated Annealing example-1.png b/README-Simulated Annealing example-1.png deleted file mode 100644 index bf616f5..0000000 Binary files a/README-Simulated Annealing example-1.png and /dev/null differ diff --git a/README-Tabu example-1.png b/README-Tabu example-1.png deleted file mode 100644 index 6208793..0000000 Binary files a/README-Tabu example-1.png and /dev/null differ diff --git a/README-Tabu short form-1.png b/README-Tabu short form-1.png deleted file mode 100644 index 97c751a..0000000 Binary files a/README-Tabu short form-1.png and /dev/null differ diff --git a/README-quick-example-ACO-1.png b/README-quick-example-ACO-1.png new file mode 100644 index 0000000..a8113b7 Binary files /dev/null and b/README-quick-example-ACO-1.png differ diff --git a/README-quick-example-ACO-2.png b/README-quick-example-ACO-2.png new file mode 100644 index 0000000..8bba300 Binary files /dev/null and b/README-quick-example-ACO-2.png differ diff --git a/README-quick-example-ACO-3.png b/README-quick-example-ACO-3.png new file mode 100644 index 0000000..1f226d4 Binary files /dev/null and b/README-quick-example-ACO-3.png differ diff --git a/README-quick-example-ACO-4.png b/README-quick-example-ACO-4.png new file mode 100644 index 0000000..24eaa4e Binary files /dev/null and b/README-quick-example-ACO-4.png differ diff --git a/README-quick-example-SA-1.png b/README-quick-example-SA-1.png new file mode 100644 index 0000000..0a1d43d Binary files /dev/null and b/README-quick-example-SA-1.png differ diff --git a/README.Rmd b/README.Rmd index 131adac..36623d1 100644 --- a/README.Rmd +++ b/README.Rmd @@ -3,7 +3,8 @@ output: github_document: html_preview: TRUE --- -```{r, echo = FALSE} + +```{r setup, echo = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "## ", @@ -12,323 +13,190 @@ knitr::opts_chunk$set( ``` # ShortForm -[![CRAN_Status_Badge](http://www.r-pkg.org/badges/version-last-release/ShortForm)](http://cran.r-project.org/package=ShortForm) -[![Travis-CI Build Status](http://travis-ci.org/AnthonyRaborn/ShortForm.svg?branch=master)](http://travis-ci.org/AnthonyRaborn/ShortForm) -[![CRAN Downloads Per Month](https://cranlogs.r-pkg.org/badges/ShortForm)](https://cran.r-project.org/package=ShortForm) -[![CRAN Downloads Total](https://cranlogs.r-pkg.org/badges/grand-total/ShortForm?color=orange)](https://cran.r-project.org/package=ShortForm) +[![CRAN_Status_Badge](http://www.r-pkg.org/badges/version-last-release/ShortForm)](http://cran.r-project.org/package=ShortForm) [![R-CMD-check](https://github.com/AnthonyRaborn/ShortForm/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/AnthonyRaborn/ShortForm/actions/workflows/R-CMD-check.yaml) [![CRAN Downloads Per Month](https://cranlogs.r-pkg.org/badges/ShortForm)](https://cran.r-project.org/package=ShortForm) [![CRAN Downloads Total](https://cranlogs.r-pkg.org/badges/grand-total/ShortForm?color=orange)](https://cran.r-project.org/package=ShortForm) + +## Overview + +**ShortForm** is an R package for constructing short-form assessments from larger item banks using reproducible, optimization-based workflows. + +It provides implementations of metaheuristic search algorithms to automate item selection while preserving prespecified psychometric properties. The package is designed for researchers and practitioners who need scalable, transparent, and reproducible methods for reducing assessment length. + +------------------------------------------------------------------------ + +## Key Features + +- Automated short-form construction from full item banks +- Multiple optimization algorithms: + - Ant Colony Optimization (ACO) + - Tabu Search (TS) + - Simulated Annealing (SA) +- Integration with `lavaan` and `Mplus` workflows +- Support for custom objective functions and model specifications +- Reproducible, scriptable workflows for assessment design + +------------------------------------------------------------------------ + +## Use Cases + +ShortForm is useful when you need to: + +- Reduce assessment length while maintaining measurement validity +- Automate item selection workflows for large-scale assessments +- Build reproducilbe pipelines for psychometric modeling and evaluation + +------------------------------------------------------------------------ -Automatic Short Form Creation for scales. Currently, the Ant Colony Optimization (ACO) Algorithm and the Tabu search are implemented. The original R implementation for the ACO algorithm is from [Leite, Huang, & Marcoulides (2008)](), while the Tabu search function was taken from [Marcoulides & Falk (2018)](). There does not yet seem to be an application of Simulated Annealing (SA) within psychometrics, but Drezner & Marcoulides, 1999 (in *Multiple Linear Regression Viewpoints*, Volume 25(2); not available online) used SA for multiple regression model selection; this package appears to be the first to implement SA for psychometric models. +## Engineering Highlights + +This package demonstrates: + +- Development and maintenance of an R package distributed via CRAN +- Implementation of optimization algorithms for applied statistical workflows +- Automated build and testing pipelines using CI/CD +- Integration with other modeling tools +- Reproducible examples using structured simulated data + +------------------------------------------------------------------------ This document was created on `r Sys.Date()`. ## Installation + ```{r gh-installation, eval = FALSE} install.packages("ShortForm") # the CRAN-approved version require("devtools") devtools::install_github("AnthonyRaborn/ShortForm", branch = "devel") # the developmental version ``` -## Usage - -Here are some (slightly modified) examples from the help documentation using -lavaan. Be warned, the algorithms may take some time to converge, particularly -with large forms, multiple dimensions, and different settings. The time for these -examples to converge on a laptop with an Intel Core i7 8th Gen processor is printed at the bottom. See the `sessionInfo()` below. +## Quick Examples + +```{r quick-example-ACO} +set.seed(58310) +library(ShortForm) + +# Example workflow: +# 1. Define item pool and model +# 2. Run optimization algorithm +# 3. Evaluate resulting short form + +result_ACO <- antcolony.lavaan( + data = simulated_test_data, + antModel = exampleAntModel, + list.items = list(paste0("Item", 1:56)), + full = 56, i.per.f = 15, + factors = "Ability", + steps = 100, + fit.indices = c("cfi", "rmsea"), + fit.statistics.test = "(cfi > 0.98)&(rmsea < 0.03)", + summaryfile = NULL, + feedbackfile = NULL, + max.run = 200 +) -```{r session-info} -sessionInfo() +summary(result_ACO) +plot(result_ACO) ``` -### ACO Algorithm - -```{r ACO example} -start.time.ACO <- Sys.time() -library(ShortForm, quietly = T) -# using simulated test data and the default values for lavaan.model.specs -set.seed(1) -# create simulation data from the `psych` package -# four factors, 12 items each, 48 total items -# factor loading matrix - not quite simple structure -fxMatrix <- - matrix(data = c(rep(x = c(.8, .8, .4, .3), times = 3), - rep(0.2, times = 3*4*3), # first factor loadings - - rep(0.2, times = 3*4), - rep(x = c(.8, .8, .4, .3), times = 3), - rep(0.2, times = 3*4*2), # second factor loadings - - rep(0.2, times = 3*4*2), - rep(x = c(.8, .8, .4, .3), times = 3), - rep(0.2, times = 3*4), # third factor loadings - - rep(0.2, times = 3*4*3), - rep(x = c(.8, .8, .4, .3), times = 3) # fourth factor loadings - ), - ncol = 4) -# factor correlation matrix - all factors uncorrelated -PhiMatrix <- - matrix(data = c(1,0,0,0, - 0,1,0,0, - 0,0,1,0, - 0,0,0,1), ncol = 4) -antData <- - psych::sim( - fx = fxMatrix, - Phi = PhiMatrix, - n = 600, - mu = c(-2, -1, 1, 2), - raw = TRUE - )$observed # observed is the simulated observed data - -colnames(antData) = paste0("Item", 1:48) - -antModel <- ' -Trait1 =~ Item1 + Item2 + Item3 + Item4 + Item5 + Item6 + Item7 + Item8 + Item9 + Item10 + Item11 + Item12 -Trait2 =~ Item13 + Item14 + Item15 + Item16 + Item17 + Item18 + Item19 + Item20 + Item21 + Item22 + Item23 + Item24 -Trait3 =~ Item25 + Item26 + Item27 + Item28 + Item29 + Item30 + Item31 + Item32 + Item33 + Item34 + Item35 + Item36 -Trait4 =~ Item37 + Item38 + Item39 + Item40 + Item41 + Item42 + Item43 + Item44 + Item45 + Item46 + Item47 + Item48 -' - -# then, create the list of the items by the factors -list.items <- - list( - paste0("Item", 1:12), - paste0("Item", 13:24), - paste0("Item", 25:36), - paste0("Item", 37:48) - ) -# finally, call the function with some minor changes to the default values. -abilityShortForm = - antcolony.lavaan(data = antData, - ants = 10, evaporation = 0.9, antModel = antModel, - list.items = list.items, full = 48, i.per.f = c(6,6,6,6), - lavaan.model.specs = - list(model.type = "cfa", auto.var = T, estimator = "default", - ordered = NULL, int.ov.free = TRUE, - int.lv.free = FALSE, auto.fix.first = TRUE, - auto.fix.single = TRUE, std.lv = FALSE, auto.cov.lv.x = TRUE, - auto.th = TRUE, auto.delta = TRUE, - auto.cov.y = TRUE), - factors = c("Trait1", "Trait2", "Trait3", "Trait4"), steps = 100, - max.run = 100, - parallel = T) -abilityShortForm # print the results of the final short form -plot(abilityShortForm, type = 'pheromone') # the pheromone plot for class "antcolony" -``` +```{r quick-example-SA} +set.seed(58310) +result_SA <- simulatedAnnealing( + initialModel = paste("Ability =~", paste0("Item", 1:56, collapse = " + ")), + originalData = simulated_test_data, + maxSteps = 20, + maxItems = 40, + items = paste0("Item", 1:56), + setChains = 4 +) -A similar example can be found in the `antcolony.mplus` function, but requires you to have a valid Mplus installation on the computer. It took a total of `r round(as.difftime(Sys.time() - start.time.ACO, units = "mins"),2)` `r attr(round(as.difftime(Sys.time() - start.time.ACO, units = "mins"),2), 'units')` to run this example. - -### Tabu Search Algorithm - -This example demonstrates how to use the Tabu search for model specification searches when the original model may be misspecified in some way. - -```{r Tabu example} -start.time.Tabu <- Sys.time() -library(ShortForm, quietly = T) -set.seed(2) -# create simulation data from the `psych` package -# two factors, 12 items total -# factor loading matrix - not quite simple structure -fxMatrix <- - matrix(data = c( - # first factor loadings - rep(x = c(.8, .8, .6, .6), times = 3), - # second factor loadings - rep(x = c(.2), times = 12) - ), - ncol = 2) -# factor correlation matrix - all factors uncorrelated -PhiMatrix <- - matrix(data = c(1,0, - 0,1 - ), ncol = 2) -tabuData <- - psych::sim( - fx = fxMatrix, - Phi = PhiMatrix, - n = 600, - raw = TRUE - )$observed # observed is the simulated observed data - -colnames(tabuData) = paste0("Item", 1:12) - -tabuModel <- ' -Trait1 =~ Item1 + Item2 + Item3 + Item4 + Item5 + Item6 + 0*Item7 + 0*Item8 + 0*Item9 + 0*Item10 + 0*Item11 + 0*Item12 -Trait2 =~ 0*Item1 + 0*Item2 + 0*Item3 + 0*Item4 + 0*Item5 + 0*Item6 + Item7 + Item8 + Item9 + Item10 + Item11 + Item12 -' -# fit the initial misspecified model for Tabu -init.model <- lavaan::lavaan(model = tabuModel, data = tabuData, - auto.var=TRUE, auto.fix.first=FALSE, std.lv=TRUE, - auto.cov.lv.x=FALSE) - -# use search.prep to prepare for the Tabu search -ptab <- - search.prep(fitted.model = init.model, - loadings=TRUE, - fcov=FALSE, - errors=FALSE) - -Tabu_example <- - suppressWarnings( - tabu.sem(init.model = init.model, - ptab = ptab, - obj = AIC, - niter = 20, - tabu.size = 10) - ) # the suppressWarning wrapping hides the lavaan WARNING output from improper models - -# check the final model -summary(Tabu_example) - -# plot the change in the objective/criterion function over each run -plot(Tabu_example) +summary(result_SA) +plot(result_SA) ``` -It took a total of `r round(as.difftime(Sys.time() - start.time.Tabu, units = "mins"),2)` `r attr(round(as.difftime(Sys.time() - start.time.Tabu, units = "mins"),2), 'units')` to run this example. - -The next Tabu example demonstrates how to use it to find a short form of a prespecified length with different data. - -```{r Tabu short form} -start.time.Tabu <- Sys.time() -library(ShortForm, quietly = T) -# set the seed to reproduce this example -set.seed(3) -# create simulation data from the `psych` package -# four factors, 12 items each, 48 total items -# factor loading matrix - not quite simple structure -fxMatrix <- - matrix(data = c(rep(x = c(.8, .8, .4, .3), times = 3), - rep(0.2, times = 3*4*3), # first factor loadings - - rep(0.2, times = 3*4), - rep(x = c(.8, .8, .4, .3), times = 3), - rep(0.2, times = 3*4*2), # second factor loadings - - rep(0.2, times = 3*4*2), - rep(x = c(.8, .8, .4, .3), times = 3), - rep(0.2, times = 3*4), # third factor loadings - - rep(0.2, times = 3*4*3), - rep(x = c(.8, .8, .4, .3), times = 3) # fourth factor loadings - ), - ncol = 4) -# factor correlation matrix - all factors uncorrelated -PhiMatrix <- - matrix(data = c(1,0,0,0, - 0,1,0,0, - 0,0,1,0, - 0,0,0,1), ncol = 4) -tabuData <- - psych::sim( - fx = fxMatrix, - Phi = PhiMatrix, - n = 600, - mu = c(-2, -1, 1, 2), - raw = TRUE - )$observed # observed is the simulated observed data - -colnames(tabuData) = paste0("Item", 1:48) - -tabuModel <- ' -Trait1 =~ Item1 + Item2 + Item3 + Item4 + Item5 + Item6 + Item7 + Item8 + Item9 + Item10 + Item11 + Item12 -Trait2 =~ Item13 + Item14 + Item15 + Item16 + Item17 + Item18 + Item19 + Item20 + Item21 + Item22 + Item23 + Item24 -Trait3 =~ Item25 + Item26 + Item27 + Item28 + Item29 + Item30 + Item31 + Item32 + Item33 + Item34 + Item35 + Item36 -Trait4 =~ Item37 + Item38 + Item39 + Item40 + Item41 + Item42 + Item43 + Item44 + Item45 + Item46 + Item47 + Item48 -' - -# specify the criterion function that the Tabu Search minimizes -# wrap this in a tryCatch in case a model does not converge! -# specify an appropriate error value: since we're minimizing, error value must be large -tabuCriterion = function(x) { - tryCatch(lavaan::fitmeasures(object = x, fit.measures = 'chisq'), - error = function(e) Inf) -} - -# use the tabuShortForm function -# reduce form to the best 12 items, 3 per factor -tabuShort <- - tabuShortForm(initialModel = tabuModel, originalData = tabuData, - numItems = c(5,5,5,5), criterion = tabuCriterion, - niter = 20, tabu.size = 10, verbose = FALSE - ) - -# check the chosen model -summary(tabuShort) -# plot the changes in the objective function over each iteration -plot(tabuShort) +```{r quick-example-TS} +set.seed(58310) +shortAntModel <- " +Ability =~ Item1 + Item2 + Item3 + Item4 + Item5 + Item6 + Item7 + Item8 +Ability ~ Outcome +" + +result_TS <- tabuShortForm( + initialModel = shortAntModel, + originalData = simulated_test_data, numItems = 7, + niter = 1, tabu.size = 3, parallel = FALSE +) +summary(result_TS) ``` -It took a total of `r round(as.difftime(Sys.time() - start.time.Tabu, units = "mins"),2)` `r attr(round(as.difftime(Sys.time() - start.time.Tabu, units = "mins"),2), 'units')` to run this example. - -### Simulated Annealing - -This example demonstrates the use of simulated annealing for creating short forms. - -```{r Simulated Annealing example} -start.time.SA <- Sys.time() -library(ShortForm, quietly = T) -# create simulation data from the `psych` package -# four factors, 12 items each, 48 total items -# factor loading matrix - not quite simple structure -set.seed(4) -fxMatrix <- - matrix(data = c(rep(x = c(.8, .8, .4, .3), times = 3), - rep(0.2, times = 3*4*3), # first factor loadings - - rep(0.2, times = 3*4), - rep(x = c(.8, .8, .4, .3), times = 3), - rep(0.2, times = 3*4*2), # second factor loadings - - rep(0.2, times = 3*4*2), - rep(x = c(.8, .8, .4, .3), times = 3), - rep(0.2, times = 3*4), # third factor loadings - - rep(0.2, times = 3*4*3), - rep(x = c(.8, .8, .4, .3), times = 3) # fourth factor loadings - ), - ncol = 4) -# factor correlation matrix - all factors uncorrelated -PhiMatrix <- - matrix(data = c(1,0,0,0, - 0,1,0,0, - 0,0,1,0, - 0,0,0,1), ncol = 4) -annealData <- - psych::sim( - fx = fxMatrix, - Phi = PhiMatrix, - n = 600, - mu = c(-2, -1, 1, 2), - raw = TRUE - )$observed # observed is the simulated observed data - -colnames(annealData) = paste0("Item", 1:48) - -annealModel <- ' -Trait1 =~ Item1 + Item2 + Item3 + Item4 + Item5 + Item6 + Item7 + Item8 + Item9 + Item10 + Item11 + Item12 -Trait2 =~ Item13 + Item14 + Item15 + Item16 + Item17 + Item18 + Item19 + Item20 + Item21 + Item22 + Item23 + Item24 -Trait3 =~ Item25 + Item26 + Item27 + Item28 + Item29 + Item30 + Item31 + Item32 + Item33 + Item34 + Item35 + Item36 -Trait4 =~ Item37 + Item38 + Item39 + Item40 + Item41 + Item42 + Item43 + Item44 + Item45 + Item46 + Item47 + Item48 -' - -lavaan.model.specs <- - list(model.type = "cfa", - auto.var = TRUE, estimator = "default", ordered = NULL, - int.ov.free = TRUE, int.lv.free = FALSE, std.lv = TRUE, auto.fix.first = FALSE, - auto.fix.single = TRUE, auto.cov.lv.x = TRUE, auto.th = TRUE, - auto.delta = TRUE, auto.cov.y = TRUE) - -# perform the SA algorithm -set.seed(1) -SA_example <- - simulatedAnnealing(initialModel = annealModel, originalData = annealData, maxSteps = 200, - fitStatistic = 'cfi', maximize = TRUE, - temperature = "logistic", items = paste0("Item", 1:48), - lavaan.model.specs = lavaan.model.specs, - maxChanges = 3, maxItems = c(6,6,6,6), setChains = 4) -summary(SA_example) -plot(SA_example) # plot showing how the fit value changes at each step +## Workflow + +```{mermaid} +flowchart LR + A@{ shape: lean-r, label: "Full Item Bank" } --> B[Specify Model] + B --> C[Run Optimization Algorithm] + C --> D[Evaluate Candidate Forms] + D --> |One or More Forms Fit| E@{ shape: diamond, label: "Select Short Form" } + D --> |No Adequate Forms Found| C + E --> F@{ shape: lean-l, label: "Reporting & Visualization" } + ``` -It took a total of `r round(as.difftime(Sys.time() - start.time.SA, units = "mins"),2)` `r attr(round(as.difftime(Sys.time() - start.time.SA, units = "mins"),2), 'units')` to run the SA example, and a total of `r round(as.difftime(Sys.time() - start.time.ACO, units = "mins"),2)` `r attr(round(as.difftime(Sys.time() - start.time.ACO, units = "mins"),2), 'units')` to run all four together. \ No newline at end of file +## Outputs + +ShortForm produces diagnostic outputs such as: + +- Objective function convergence plots +- Algorithm-specific diagnostics +- Summary statistics for selected item sets + +------------------------------------------------------------------------ + +## Algorithms + +### Ant Colony Optimization (ACO) + +Implementation adapted from [Leite, Huang, & Marcoulides (2008)](https://doi.org/10.1080/00273170802285743). + +### Tabu Search (TS) + +Based on [Marcoulides & Falk (2018)](https://doi.org/10.1080/10705511.2017.1409074), extended for model search and short-form construction. + +### Simulated Annealing (SA) + +Provides one of the first implementations of simulated annealing for psychometric model selection workflows. Implemented following [Kirkpatrick et al. (1983)](https://doi.org/10.1126/science.220.4598.671). + +------------------------------------------------------------------------ + +## Performance Notes + +Computational time depends on: + +- Number and types of items +- Dimensionality/model complexity +- Algorithm convergence settings + +Parallelization is supported for certain workflows. + +------------------------------------------------------------------------ + +## Reproducibility + +ShortForm is designed for reproducible analytical workflows: + +- Fully scriptable +- Compatible with R Markdown and Quarto +- Supports integration into larger modeling pipelines + +------------------------------------------------------------------------ + +## License + +LGPL (\>= 2.0, \< 3) \| Mozilla Public License + +------------------------------------------------------------------------ + +## Citation + +If you use this package, please cite: + +Raborn A, Leite W (2024). *ShortForm: Automatic Short Form Creation*. . ShortForm , R package version 0.5.6, . diff --git a/README.md b/README.md index fd4f959..db9c458 100644 --- a/README.md +++ b/README.md @@ -2,26 +2,62 @@ # ShortForm [![CRAN_Status_Badge](http://www.r-pkg.org/badges/version-last-release/ShortForm)](http://cran.r-project.org/package=ShortForm) -[![Travis-CI Build -Status](http://travis-ci.org/AnthonyRaborn/ShortForm.svg?branch=master)](http://travis-ci.org/AnthonyRaborn/ShortForm) +[![R-CMD-check](https://github.com/AnthonyRaborn/ShortForm/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/AnthonyRaborn/ShortForm/actions/workflows/R-CMD-check.yaml) [![CRAN Downloads Per Month](https://cranlogs.r-pkg.org/badges/ShortForm)](https://cran.r-project.org/package=ShortForm) [![CRAN Downloads Total](https://cranlogs.r-pkg.org/badges/grand-total/ShortForm?color=orange)](https://cran.r-project.org/package=ShortForm) -Automatic Short Form Creation for scales. Currently, the Ant Colony -Optimization (ACO) Algorithm and the Tabu search are implemented. The -original R implementation for the ACO algorithm is from [Leite, Huang, & -Marcoulides (2008)](doi:10.1080/00273170802285743), while the Tabu -search function was taken from [Marcoulides & Falk -(2018)](doi:10.1080/10705511.2017.1409074). There does not yet seem to -be an application of Simulated Annealing (SA) within psychometrics, but -Drezner & Marcoulides, 1999 (in *Multiple Linear Regression Viewpoints*, -Volume 25(2); not available online) used SA for multiple regression -model selection; this package appears to be the first to implement SA -for psychometric models. +## Overview -This document was created on 2024-05-22. +**ShortForm** is an R package for constructing short-form assessments +from larger item banks using reproducible, optimization-based workflows. + +It provides implementations of metaheuristic search algorithms to +automate item selection while preserving prespecified psychometric +properties. The package is designed for researchers and practitioners +who need scalable, transparent, and reproducible methods for reducing +assessment length. + +------------------------------------------------------------------------ + +## Key Features + +- Automated short-form construction from full item banks +- Multiple optimization algorithms: + - Ant Colony Optimization (ACO) + - Tabu Search (TS) + - Simulated Annealing (SA) +- Integration with `lavaan` and `Mplus` workflows +- Support for custom objective functions and model specifications +- Reproducible, scriptable workflows for assessment design + +------------------------------------------------------------------------ + +## Use Cases + +ShortForm is useful when you need to: + +- Reduce assessment length while maintaining measurement validity +- Automate item selection workflows for large-scale assessments +- Build reproducilbe pipelines for psychometric modeling and evaluation + +------------------------------------------------------------------------ + +## Engineering Highlights + +This package demonstrates: + +- Development and maintenance of an R package distributed via CRAN +- Implementation of optimization algorithms for applied statistical + workflows +- Automated build and testing pipelines using CI/CD +- Integration with other modeling tools +- Reproducible examples using structured simulated data + +------------------------------------------------------------------------ + +This document was created on 2026-04-29. ## Installation @@ -31,451 +67,252 @@ require("devtools") devtools::install_github("AnthonyRaborn/ShortForm", branch = "devel") # the developmental version ``` -## Usage - -Here are some (slightly modified) examples from the help documentation -using lavaan. Be warned, the algorithms may take some time to converge, -particularly with large forms, multiple dimensions, and different -settings. The time for these examples to converge on a laptop with an -Intel Core i7 8th Gen processor is printed at the bottom. See the -`sessionInfo()` below. +## Quick Examples ``` r -sessionInfo() -## R version 4.3.3 (2024-02-29 ucrt) -## Platform: x86_64-w64-mingw32/x64 (64-bit) -## Running under: Windows 11 x64 (build 22621) +set.seed(58310) +library(ShortForm) +## Package 'ShortForm' version 0.5.6 + +# Example workflow: +# 1. Define item pool and model +# 2. Run optimization algorithm +# 3. Evaluate resulting short form + +result_ACO <- antcolony.lavaan( + data = simulated_test_data, + antModel = exampleAntModel, + list.items = list(paste0("Item", 1:56)), + full = 56, i.per.f = 15, + factors = "Ability", + steps = 100, + fit.indices = c("cfi", "rmsea"), + fit.statistics.test = "(cfi > 0.98)&(rmsea < 0.03)", + summaryfile = NULL, + feedbackfile = NULL, + max.run = 200 +) +## Run number 1 and ant number 1. Run number 1 and ant number 2. Run number 1 and ant number 3. Run number 1 and ant number 4. Run number 1 and ant number 5. Run number 1 and ant number 6. Run number 1 and ant number 7. Run number 1 and ant number 8. Run number 1 and ant number 9. Run number 1 and ant number 10. Run number 1 and ant number 11. Run number 1 and ant number 12. Run number 1 and ant number 13. Run number 1 and ant number 14. Run number 1 and ant number 15. Run number 1 and ant number 16. Run number 1 and ant number 17. Run number 1 and ant number 18. Run number 1 and ant number 19. Run number 1 and ant number 20. Run number 2 and ant number 1. Run number 2 and ant number 2. Run number 2 and ant number 3. Run number 2 and ant number 4. Run number 2 and ant number 5. Run number 2 and ant number 6. Run number 2 and ant number 7. Run number 2 and ant number 8. Run number 2 and ant number 9. Run number 2 and ant number 10. Run number 2 and ant number 11. Run number 2 and ant number 12. Run number 2 and ant number 13. Run number 2 and ant number 14. Run number 2 and ant number 15. Run number 2 and ant number 16. Run number 2 and ant number 17. Run number 2 and ant number 18. Run number 2 and ant number 19. Run number 2 and ant number 20. Run number 3 and ant number 1. Run number 3 and ant number 2. Run number 3 and ant number 3. Run number 3 and ant number 4. Run number 3 and ant number 5. Run number 3 and ant number 6. Run number 3 and ant number 7. Run number 3 and ant number 8. Run number 3 and ant number 9. Run number 3 and ant number 10. Run number 3 and ant number 11. Run number 3 and ant number 12. Run number 3 and ant number 13. Run number 3 and ant number 14. Run number 3 and ant number 15. Run number 3 and ant number 16. Run number 3 and ant number 17. Run number 3 and ant number 18. Run number 3 and ant number 19. Run number 3 and ant number 20. Run number 4 and ant number 1. Run number 4 and ant number 2. Run number 4 and ant number 3. Run number 4 and ant number 4. Run number 4 and ant number 5. Run number 4 and ant number 6. Run number 4 and ant number 7. Run number 4 and ant number 8. Run number 4 and ant number 9. Run number 4 and ant number 10. Run number 4 and ant number 11. Run number 4 and ant number 12. Run number 4 and ant number 13. Run number 4 and ant number 14. Run number 4 and ant number 15. Run number 4 and ant number 16. Run number 4 and ant number 17. Run number 4 and ant number 18. Run number 4 and ant number 19. Run number 4 and ant number 20. Run number 5 and ant number 1. Run number 5 and ant number 2. Run number 5 and ant number 3. Run number 5 and ant number 4. Run number 5 and ant number 5. Run number 5 and ant number 6. Run number 5 and ant number 7. Run number 5 and ant number 8. Run number 5 and ant number 9. Run number 5 and ant number 10. Run number 5 and ant number 11. Run number 5 and ant number 12. Run number 5 and ant number 13. Run number 5 and ant number 14. Run number 5 and ant number 15. Run number 5 and ant number 16. Run number 5 and ant number 17. Run number 5 and ant number 18. Run number 5 and ant number 19. Run number 5 and ant number 20. Run number 6 and ant number 1. Run number 6 and ant number 2. Run number 6 and ant number 3. Run number 6 and ant number 4. Run number 6 and ant number 5. Run number 6 and ant number 6. Run number 6 and ant number 7. Run number 6 and ant number 8. Run number 6 and ant number 9. Run number 6 and ant number 10. Run number 6 and ant number 11. Run number 6 and ant number 12. Run number 6 and ant number 13. Run number 6 and ant number 14. Run number 6 and ant number 15. Run number 6 and ant number 16. Run number 6 and ant number 17. Run number 6 and ant number 18. Run number 6 and ant number 19. Run number 6 and ant number 20. Run number 7 and ant number 1. Run number 7 and ant number 2. Run number 7 and ant number 3. Run number 7 and ant number 4. Run number 7 and ant number 5. Run number 7 and ant number 6. Run number 7 and ant number 7. Run number 7 and ant number 8. Run number 7 and ant number 9. Run number 7 and ant number 10. Run number 7 and ant number 11. Run number 7 and ant number 12. Run number 7 and ant number 13. Run number 7 and ant number 14. Run number 7 and ant number 15. Run number 7 and ant number 16. Run number 7 and ant number 17. Run number 7 and ant number 18. Run number 7 and ant number 19. Run number 7 and ant number 20. Run number 8 and ant number 1. Run number 8 and ant number 2. Run number 8 and ant number 3. Run number 8 and ant number 4. Run number 8 and ant number 5. Run number 8 and ant number 6. Run number 8 and ant number 7. Run number 8 and ant number 8. Run number 8 and ant number 9. Run number 8 and ant number 10. Run number 8 and ant number 11. Run number 8 and ant number 12. Run number 8 and ant number 13. Run number 8 and ant number 14. Run number 8 and ant number 15. Run number 8 and ant number 16. Run number 8 and ant number 17. Run number 8 and ant number 18. Run number 8 and ant number 19. Run number 8 and ant number 20. Run number 9 and ant number 1. Run number 9 and ant number 2. Run number 9 and ant number 3. Run number 9 and ant number 4. Run number 9 and ant number 5. Run number 9 and ant number 6. Run number 9 and ant number 7. Run number 9 and ant number 8. Run number 9 and ant number 9. Run number 9 and ant number 10. Run number 9 and ant number 11. Run number 9 and ant number 12. Run number 9 and ant number 13. Run number 9 and ant number 14. Run number 9 and ant number 15. Run number 9 and ant number 16. Run number 9 and ant number 17. Run number 9 and ant number 18. Run number 9 and ant number 19. Run number 9 and ant number 20. Run number 10 and ant number 1. Run number 10 and ant number 2. Run number 10 and ant number 3. Run number 10 and ant number 4. Run number 10 and ant number 5. Run number 10 and ant number 6. Run number 10 and ant number 7. Run number 10 and ant number 8. Run number 10 and ant number 9. Run number 10 and ant number 10. Run number 10 and ant number 11. Run number 10 and ant number 12. Run number 10 and ant number 13. Run number 10 and ant number 14. Run number 10 and ant number 15. Run number 10 and ant number 16. Run number 10 and ant number 17. Run number 10 and ant number 18. Run number 10 and ant number 19. Run number 10 and ant number 20. Run number 11 and ant number 1. Run number 11 and ant number 2. Run number 11 and ant number 3. Run number 11 and ant number 4. Run number 11 and ant number 5. Run number 11 and ant number 6. Run number 11 and ant number 7. Run number 11 and ant number 8. Run number 11 and ant number 9. Run number 11 and ant number 10. Run number 11 and ant number 11. Run number 11 and ant number 12. Run number 11 and ant number 13. Run number 11 and ant number 14. Run number 11 and ant number 15. Run number 11 and ant number 16. Run number 11 and ant number 17. Run number 11 and ant number 18. Run number 11 and ant number 19. Run number 11 and ant number 20. Run number 12 and ant number 1. Run number 12 and ant number 2. Run number 12 and ant number 3. Run number 12 and ant number 4. Run number 12 and ant number 5. Run number 12 and ant number 6. Run number 12 and ant number 7. Run number 12 and ant number 8. Run number 12 and ant number 9. Run number 12 and ant number 10. Run number 12 and ant number 11. Run number 12 and ant number 12. Run number 12 and ant number 13. Run number 12 and ant number 14. Run number 12 and ant number 15. Run number 12 and ant number 16. Run number 12 and ant number 17. Run number 12 and ant number 18. Run number 12 and ant number 19. Run number 12 and ant number 20. Run number 13 and ant number 1. Run number 13 and ant number 2. Run number 13 and ant number 3. Run number 13 and ant number 4. Run number 13 and ant number 5. Run number 13 and ant number 6. Run number 13 and ant number 7. Run number 13 and ant number 8. Run number 13 and ant number 9. Run number 13 and ant number 10. Run number 13 and ant number 11. Run number 13 and ant number 12. Run number 13 and ant number 13. Run number 13 and ant number 14. Run number 13 and ant number 15. Run number 13 and ant number 16. Run number 13 and ant number 17. Run number 13 and ant number 18. Run number 13 and ant number 19. Run number 13 and ant number 20. Run number 14 and ant number 1. Run number 14 and ant number 2. Run number 14 and ant number 3. Run number 14 and ant number 4. Run number 14 and ant number 5. Run number 14 and ant number 6. Run number 14 and ant number 7. Run number 14 and ant number 8. Run number 14 and ant number 9. Run number 14 and ant number 10. Run number 14 and ant number 11. Run number 14 and ant number 12. Run number 14 and ant number 13. Run number 14 and ant number 14. Run number 14 and ant number 15. Run number 14 and ant number 16. Run number 14 and ant number 17. Run number 14 and ant number 18. Run number 14 and ant number 19. Run number 14 and ant number 20. Run number 15 and ant number 1. Run number 15 and ant number 2. Run number 15 and ant number 3. Run number 15 and ant number 4. Run number 15 and ant number 5. Run number 15 and ant number 6. Run number 15 and ant number 7. Run number 15 and ant number 8. Run number 15 and ant number 9. Run number 15 and ant number 10. Run number 15 and ant number 11. Run number 15 and ant number 12. Run number 15 and ant number 13. Run number 15 and ant number 14. Run number 15 and ant number 15. Run number 15 and ant number 16. Run number 15 and ant number 17. Run number 15 and ant number 18. Run number 15 and ant number 19. Run number 15 and ant number 20. Run number 16 and ant number 1. Run number 16 and ant number 2. Run number 16 and ant number 3. Run number 16 and ant number 4. Run number 16 and ant number 5. Run number 16 and ant number 6. Run number 16 and ant number 7. Run number 16 and ant number 8. Run number 16 and ant number 9. Run number 16 and ant number 10. Run number 16 and ant number 11. Run number 16 and ant number 12. Run number 16 and ant number 13. Run number 16 and ant number 14. Run number 16 and ant number 15. Run number 16 and ant number 16. Run number 16 and ant number 17. Run number 16 and ant number 18. Run number 16 and ant number 19. Run number 16 and ant number 20. Run number 17 and ant number 1. Run number 17 and ant number 2. Run number 17 and ant number 3. Run number 17 and ant number 4. Run number 17 and ant number 5. Run number 17 and ant number 6. Run number 17 and ant number 7. Run number 17 and ant number 8. Run number 17 and ant number 9. Run number 17 and ant number 10. Run number 17 and ant number 11. Run number 17 and ant number 12. Run number 17 and ant number 13. Run number 17 and ant number 14. Run number 17 and ant number 15. Run number 17 and ant number 16. Run number 17 and ant number 17. Run number 17 and ant number 18. Run number 17 and ant number 19. Run number 17 and ant number 20. Run number 18 and ant number 1. Run number 18 and ant number 2. Run number 18 and ant number 3. Run number 18 and ant number 4. Run number 18 and ant number 5. Run number 18 and ant number 6. Run number 18 and ant number 7. Run number 18 and ant number 8. Run number 18 and ant number 9. Run number 18 and ant number 10. Run number 18 and ant number 11. Run number 18 and ant number 12. Run number 18 and ant number 13. Run number 18 and ant number 14. Run number 18 and ant number 15. Run number 18 and ant number 16. Run number 18 and ant number 17. Run number 18 and ant number 18. Run number 18 and ant number 19. Run number 18 and ant number 20. Run number 19 and ant number 1. Run number 19 and ant number 2. Run number 19 and ant number 3. Run number 19 and ant number 4. Run number 19 and ant number 5. Run number 19 and ant number 6. Run number 19 and ant number 7. Run number 19 and ant number 8. Run number 19 and ant number 9. Run number 19 and ant number 10. Run number 19 and ant number 11. Run number 19 and ant number 12. Run number 19 and ant number 13. Run number 19 and ant number 14. Run number 19 and ant number 15. Run number 19 and ant number 16. Run number 19 and ant number 17. Run number 19 and ant number 18. Run number 19 and ant number 19. Run number 19 and ant number 20. Run number 20 and ant number 1. Run number 20 and ant number 2. Run number 20 and ant number 3. Run number 20 and ant number 4. Run number 20 and ant number 5. Run number 20 and ant number 6. Run number 20 and ant number 7. Run number 20 and ant number 8. Run number 20 and ant number 9. Run number 20 and ant number 10. Run number 20 and ant number 11. Run number 20 and ant number 12. Run number 20 and ant number 13. Run number 20 and ant number 14. Run number 20 and ant number 15. Run number 20 and ant number 16. Run number 20 and ant number 17. Run number 20 and ant number 18. Run number 20 and ant number 19. Run number 20 and ant number 20. Run number 21 and ant number 1. Run number 21 and ant number 2. Run number 21 and ant number 3. Run number 21 and ant number 4. Run number 21 and ant number 5. Run number 21 and ant number 6. Run number 21 and ant number 7. Run number 21 and ant number 8. Run number 21 and ant number 9. Run number 21 and ant number 10. Run number 21 and ant number 11. Run number 21 and ant number 12. Run number 21 and ant number 13. Run number 21 and ant number 14. Run number 21 and ant number 15. Run number 21 and ant number 16. Run number 21 and ant number 17. Run number 21 and ant number 18. Run number 21 and ant number 19. Run number 21 and ant number 20. Run number 22 and ant number 1. Run number 22 and ant number 2. Run number 22 and ant number 3. Run number 22 and ant number 4. Run number 22 and ant number 5. Run number 22 and ant number 6. Run number 22 and ant number 7. Run number 22 and ant number 8. Run number 22 and ant number 9. Run number 22 and ant number 10. Run number 22 and ant number 11. Run number 22 and ant number 12. Run number 22 and ant number 13. Run number 22 and ant number 14. Run number 22 and ant number 15. Run number 22 and ant number 16. Run number 22 and ant number 17. Run number 22 and ant number 18. Run number 22 and ant number 19. Run number 22 and ant number 20. Run number 23 and ant number 1. Run number 23 and ant number 2. Run number 23 and ant number 3. Run number 23 and ant number 4. Run number 23 and ant number 5. Run number 23 and ant number 6. Run number 23 and ant number 7. Run number 23 and ant number 8. Run number 23 and ant number 9. Run number 23 and ant number 10. Run number 23 and ant number 11. Run number 23 and ant number 12. Run number 23 and ant number 13. Run number 23 and ant number 14. Run number 23 and ant number 15. Run number 23 and ant number 16. Run number 23 and ant number 17. Run number 23 and ant number 18. Run number 23 and ant number 19. Run number 23 and ant number 20. Run number 24 and ant number 1. Run number 24 and ant number 2. Run number 24 and ant number 3. Run number 24 and ant number 4. Run number 24 and ant number 5. Run number 24 and ant number 6. Run number 24 and ant number 7. Run number 24 and ant number 8. Run number 24 and ant number 9. Run number 24 and ant number 10. Run number 24 and ant number 11. Run number 24 and ant number 12. Run number 24 and ant number 13. Run number 24 and ant number 14. Run number 24 and ant number 15. Run number 24 and ant number 16. Run number 24 and ant number 17. Run number 24 and ant number 18. Run number 24 and ant number 19. Run number 24 and ant number 20. Run number 25 and ant number 1. Run number 25 and ant number 2. Run number 25 and ant number 3. Run number 25 and ant number 4. Run number 25 and ant number 5. Run number 25 and ant number 6. Run number 25 and ant number 7. Run number 25 and ant number 8. Run number 25 and ant number 9. Run number 25 and ant number 10. Run number 25 and ant number 11. Run number 25 and ant number 12. Run number 25 and ant number 13. Run number 25 and ant number 14. Run number 25 and ant number 15. Run number 25 and ant number 16. Run number 25 and ant number 17. Run number 25 and ant number 18. Run number 25 and ant number 19. Run number 25 and ant number 20. [1] "Compiling results." + +summary(result_ACO) +## Algorithm: Ant Colony Optimization +## Total Run Time: 42.668 secs ## -## Matrix products: default +## lavaan 0.6-20 ended normally after 51 iterations ## +## Estimator ML +## Optimization method NLMINB +## Number of model parameters 32 ## -## locale: -## [1] LC_COLLATE=English_United States.utf8 -## [2] LC_CTYPE=English_United States.utf8 -## [3] LC_MONETARY=English_United States.utf8 -## [4] LC_NUMERIC=C -## [5] LC_TIME=English_United States.utf8 +## Number of observations 1000 ## -## time zone: America/Phoenix -## tzcode source: internal +## Model Test User Model: +## +## Test statistic 112.459 +## Degrees of freedom 104 +## P-value (Chi-square) 0.269 ## -## attached base packages: -## [1] stats graphics grDevices utils datasets methods base ## -## loaded via a namespace (and not attached): -## [1] compiler_4.3.3 fastmap_1.1.1 cli_3.6.2 tools_4.3.3 -## [5] htmltools_0.5.7 rstudioapi_0.15.0 yaml_2.3.8 rmarkdown_2.26 -## [9] knitr_1.45 xfun_0.42 digest_0.6.35 rlang_1.1.3 -## [13] evaluate_0.23 +## Final Model Syntax: +## Ability =~ Item12 + Item15 + Item19 + Item2 + Item27 + Item28 + Item31 + Item37 +## + Item42 + Item44 + Item5 + Item50 + Item54 + Item55 + Item9 +## Outcome ~ Ability +plot(result_ACO) +## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0. +## ℹ Please use `linewidth` instead. +## ℹ The deprecated feature was likely used in the ShortForm package. +## Please report the issue at +## . +## This warning is displayed once every 8 hours. +## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was +## generated. +## $Pheromone ``` -### ACO Algorithm +![](README-quick-example-ACO-1.png) -``` r -start.time.ACO <- Sys.time() -library(ShortForm, quietly = T) -## Package 'ShortForm' version 0.5.4 -# using simulated test data and the default values for lavaan.model.specs -set.seed(1) -# create simulation data from the `psych` package -# four factors, 12 items each, 48 total items -# factor loading matrix - not quite simple structure -fxMatrix <- - matrix(data = c(rep(x = c(.8, .8, .4, .3), times = 3), - rep(0.2, times = 3*4*3), # first factor loadings - - rep(0.2, times = 3*4), - rep(x = c(.8, .8, .4, .3), times = 3), - rep(0.2, times = 3*4*2), # second factor loadings - - rep(0.2, times = 3*4*2), - rep(x = c(.8, .8, .4, .3), times = 3), - rep(0.2, times = 3*4), # third factor loadings - - rep(0.2, times = 3*4*3), - rep(x = c(.8, .8, .4, .3), times = 3) # fourth factor loadings - ), - ncol = 4) -# factor correlation matrix - all factors uncorrelated -PhiMatrix <- - matrix(data = c(1,0,0,0, - 0,1,0,0, - 0,0,1,0, - 0,0,0,1), ncol = 4) -antData <- - psych::sim( - fx = fxMatrix, - Phi = PhiMatrix, - n = 600, - mu = c(-2, -1, 1, 2), - raw = TRUE - )$observed # observed is the simulated observed data - -colnames(antData) = paste0("Item", 1:48) - -antModel <- ' -Trait1 =~ Item1 + Item2 + Item3 + Item4 + Item5 + Item6 + Item7 + Item8 + Item9 + Item10 + Item11 + Item12 -Trait2 =~ Item13 + Item14 + Item15 + Item16 + Item17 + Item18 + Item19 + Item20 + Item21 + Item22 + Item23 + Item24 -Trait3 =~ Item25 + Item26 + Item27 + Item28 + Item29 + Item30 + Item31 + Item32 + Item33 + Item34 + Item35 + Item36 -Trait4 =~ Item37 + Item38 + Item39 + Item40 + Item41 + Item42 + Item43 + Item44 + Item45 + Item46 + Item47 + Item48 -' - -# then, create the list of the items by the factors -list.items <- - list( - paste0("Item", 1:12), - paste0("Item", 13:24), - paste0("Item", 25:36), - paste0("Item", 37:48) - ) -# finally, call the function with some minor changes to the default values. -abilityShortForm = - antcolony.lavaan(data = antData, - ants = 10, evaporation = 0.9, antModel = antModel, - list.items = list.items, full = 48, i.per.f = c(6,6,6,6), - lavaan.model.specs = - list(model.type = "cfa", auto.var = T, estimator = "default", - ordered = NULL, int.ov.free = TRUE, - int.lv.free = FALSE, auto.fix.first = TRUE, - auto.fix.single = TRUE, std.lv = FALSE, auto.cov.lv.x = TRUE, - auto.th = TRUE, auto.delta = TRUE, - auto.cov.y = TRUE), - factors = c("Trait1", "Trait2", "Trait3", "Trait4"), steps = 100, - max.run = 100, - parallel = T) -## Run number 1 and ant number 1. Run number 1 and ant number 2. Run number 1 and ant number 3. Run number 1 and ant number 4. Run number 1 and ant number 5. Run number 1 and ant number 6. Run number 1 and ant number 7. Run number 1 and ant number 8. Run number 1 and ant number 9. Run number 1 and ant number 10. Run number 2 and ant number 1. Run number 2 and ant number 2. Run number 2 and ant number 3. Run number 2 and ant number 4. Run number 2 and ant number 5. Run number 2 and ant number 6. Run number 2 and ant number 7. Run number 2 and ant number 8. Run number 2 and ant number 9. Run number 2 and ant number 10. Run number 3 and ant number 1. Run number 3 and ant number 2. Run number 3 and ant number 3. Run number 3 and ant number 4. Run number 3 and ant number 5. Run number 3 and ant number 6. Run number 3 and ant number 7. Run number 3 and ant number 8. Run number 3 and ant number 9. Run number 3 and ant number 10. Run number 4 and ant number 1. Run number 4 and ant number 2. Run number 4 and ant number 3. Run number 4 and ant number 4. Run number 4 and ant number 5. Run number 4 and ant number 6. Run number 4 and ant number 7. Run number 4 and ant number 8. Run number 4 and ant number 9. Run number 4 and ant number 10. Run number 5 and ant number 1. Run number 5 and ant number 2. Run number 5 and ant number 3. Run number 5 and ant number 4. Run number 5 and ant number 5. Run number 5 and ant number 6. Run number 5 and ant number 7. Run number 5 and ant number 8. Run number 5 and ant number 9. Run number 5 and ant number 10. Run number 6 and ant number 1. Run number 6 and ant number 2. Run number 6 and ant number 3. Run number 6 and ant number 4. Run number 6 and ant number 5. Run number 6 and ant number 6. Run number 6 and ant number 7. Run number 6 and ant number 8. Run number 6 and ant number 9. Run number 6 and ant number 10. Run number 7 and ant number 1. Run number 7 and ant number 2. Run number 7 and ant number 3. Run number 7 and ant number 4. Run number 7 and ant number 5. Run number 7 and ant number 6. Run number 7 and ant number 7. Run number 7 and ant number 8. Run number 7 and ant number 9. Run number 7 and ant number 10. Run number 8 and ant number 1. Run number 8 and ant number 2. Run number 8 and ant number 3. Run number 8 and ant number 4. Run number 8 and ant number 5. Run number 8 and ant number 6. Run number 8 and ant number 7. Run number 8 and ant number 8. Run number 8 and ant number 9. Run number 8 and ant number 10. Run number 9 and ant number 1. Run number 9 and ant number 2. Run number 9 and ant number 3. Run number 9 and ant number 4. Run number 9 and ant number 5. Run number 9 and ant number 6. Run number 9 and ant number 7. Run number 9 and ant number 8. Run number 9 and ant number 9. Run number 9 and ant number 10. Run number 10 and ant number 1. Run number 10 and ant number 2. Run number 10 and ant number 3. Run number 10 and ant number 4. Run number 10 and ant number 5. Run number 10 and ant number 6. Run number 10 and ant number 7. Run number 10 and ant number 8. Run number 10 and ant number 9. Run number 10 and ant number 10. Run number 11 and ant number 1. Run number 11 and ant number 2. Run number 11 and ant number 3. Run number 11 and ant number 4. Run number 11 and ant number 5. Run number 11 and ant number 6. Run number 11 and ant number 7. Run number 11 and ant number 8. Run number 11 and ant number 9. Run number 11 and ant number 10. Run number 12 and ant number 1. Run number 12 and ant number 2. Run number 12 and ant number 3. Run number 12 and ant number 4. Run number 12 and ant number 5. Run number 12 and ant number 6. Run number 12 and ant number 7. Run number 12 and ant number 8. Run number 12 and ant number 9. Run number 12 and ant number 10. Run number 13 and ant number 1. Run number 13 and ant number 2. Run number 13 and ant number 3. Run number 13 and ant number 4. Run number 13 and ant number 5. Run number 13 and ant number 6. Run number 13 and ant number 7. Run number 13 and ant number 8. Run number 13 and ant number 9. Run number 13 and ant number 10. Run number 14 and ant number 1. Run number 14 and ant number 2. Run number 14 and ant number 3. Run number 14 and ant number 4. Run number 14 and ant number 5. Run number 14 and ant number 6. Run number 14 and ant number 7. Run number 14 and ant number 8. Run number 14 and ant number 9. Run number 14 and ant number 10. Run number 15 and ant number 1. Run number 15 and ant number 2. Run number 15 and ant number 3. Run number 15 and ant number 4. Run number 15 and ant number 5. Run number 15 and ant number 6. Run number 15 and ant number 7. Run number 15 and ant number 8. Run number 15 and ant number 9. Run number 15 and ant number 10. Run number 16 and ant number 1. Run number 16 and ant number 2. Run number 16 and ant number 3. Run number 16 and ant number 4. Run number 16 and ant number 5. Run number 16 and ant number 6. Run number 16 and ant number 7. Run number 16 and ant number 8. Run number 16 and ant number 9. Run number 16 and ant number 10. Run number 17 and ant number 1. Run number 17 and ant number 2. Run number 17 and ant number 3. Run number 17 and ant number 4. Run number 17 and ant number 5. Run number 17 and ant number 6. Run number 17 and ant number 7. Run number 17 and ant number 8. Run number 17 and ant number 9. Run number 17 and ant number 10. Run number 18 and ant number 1. Run number 18 and ant number 2. Run number 18 and ant number 3. Run number 18 and ant number 4. Run number 18 and ant number 5. Run number 18 and ant number 6. Run number 18 and ant number 7. Run number 18 and ant number 8. Run number 18 and ant number 9. Run number 18 and ant number 10. Run number 19 and ant number 1. Run number 19 and ant number 2. Run number 19 and ant number 3. Run number 19 and ant number 4. Run number 19 and ant number 5. Run number 19 and ant number 6. Run number 19 and ant number 7. Run number 19 and ant number 8. Run number 19 and ant number 9. Run number 19 and ant number 10. Run number 20 and ant number 1. Run number 20 and ant number 2. Run number 20 and ant number 3. Run number 20 and ant number 4. Run number 20 and ant number 5. Run number 20 and ant number 6. Run number 20 and ant number 7. Run number 20 and ant number 8. Run number 20 and ant number 9. Run number 20 and ant number 10. Run number 21 and ant number 1. Run number 21 and ant number 2. Run number 21 and ant number 3. Run number 21 and ant number 4. Run number 21 and ant number 5. Run number 21 and ant number 6. Run number 21 and ant number 7. Run number 21 and ant number 8. Run number 21 and ant number 9. Run number 21 and ant number 10. Run number 22 and ant number 1. Run number 22 and ant number 2. Run number 22 and ant number 3. Run number 22 and ant number 4. Run number 22 and ant number 5. Run number 22 and ant number 6. Run number 22 and ant number 7. Run number 22 and ant number 8. Run number 22 and ant number 9. Run number 22 and ant number 10. Run number 23 and ant number 1. Run number 23 and ant number 2. Run number 23 and ant number 3. Run number 23 and ant number 4. Run number 23 and ant number 5. Run number 23 and ant number 6. Run number 23 and ant number 7. Run number 23 and ant number 8. Run number 23 and ant number 9. Run number 23 and ant number 10. Run number 24 and ant number 1. Run number 24 and ant number 2. Run number 24 and ant number 3. Run number 24 and ant number 4. Run number 24 and ant number 5. Run number 24 and ant number 6. Run number 24 and ant number 7. Run number 24 and ant number 8. Run number 24 and ant number 9. Run number 24 and ant number 10. Run number 25 and ant number 1. Run number 25 and ant number 2. Run number 25 and ant number 3. Run number 25 and ant number 4. Run number 25 and ant number 5. Run number 25 and ant number 6. Run number 25 and ant number 7. Run number 25 and ant number 8. Run number 25 and ant number 9. Run number 25 and ant number 10. Run number 26 and ant number 1. Run number 26 and ant number 2. Run number 26 and ant number 3. Run number 26 and ant number 4. Run number 26 and ant number 5. Run number 26 and ant number 6. Run number 26 and ant number 7. Run number 26 and ant number 8. Run number 26 and ant number 9. Run number 26 and ant number 10. Run number 27 and ant number 1. Run number 27 and ant number 2. Run number 27 and ant number 3. Run number 27 and ant number 4. Run number 27 and ant number 5. Run number 27 and ant number 6. Run number 27 and ant number 7. Run number 27 and ant number 8. Run number 27 and ant number 9. Run number 27 and ant number 10. Run number 28 and ant number 1. Run number 28 and ant number 2. Run number 28 and ant number 3. Run number 28 and ant number 4. Run number 28 and ant number 5. Run number 28 and ant number 6. Run number 28 and ant number 7. Run number 28 and ant number 8. Run number 28 and ant number 9. Run number 28 and ant number 10. Run number 29 and ant number 1. Run number 29 and ant number 2. Run number 29 and ant number 3. Run number 29 and ant number 4. Run number 29 and ant number 5. Run number 29 and ant number 6. Run number 29 and ant number 7. Run number 29 and ant number 8. Run number 29 and ant number 9. Run number 29 and ant number 10. Run number 30 and ant number 1. Run number 30 and ant number 2. Run number 30 and ant number 3. Run number 30 and ant number 4. Run number 30 and ant number 5. Run number 30 and ant number 6. Run number 30 and ant number 7. Run number 30 and ant number 8. Run number 30 and ant number 9. Run number 30 and ant number 10. Run number 31 and ant number 1. Run number 31 and ant number 2. Run number 31 and ant number 3. Run number 31 and ant number 4. Run number 31 and ant number 5. Run number 31 and ant number 6. Run number 31 and ant number 7. Run number 31 and ant number 8. Run number 31 and ant number 9. Run number 31 and ant number 10. Run number 32 and ant number 1. Run number 32 and ant number 2. Run number 32 and ant number 3. Run number 32 and ant number 4. Run number 32 and ant number 5. Run number 32 and ant number 6. Run number 32 and ant number 7. Run number 32 and ant number 8. Run number 32 and ant number 9. Run number 32 and ant number 10. Run number 33 and ant number 1. Run number 33 and ant number 2. Run number 33 and ant number 3. Run number 33 and ant number 4. Run number 33 and ant number 5. Run number 33 and ant number 6. Run number 33 and ant number 7. Run number 33 and ant number 8. Run number 33 and ant number 9. Run number 33 and ant number 10. Run number 34 and ant number 1. Run number 34 and ant number 2. Run number 34 and ant number 3. Run number 34 and ant number 4. Run number 34 and ant number 5. Run number 34 and ant number 6. Run number 34 and ant number 7. Run number 34 and ant number 8. Run number 34 and ant number 9. Run number 34 and ant number 10. Run number 35 and ant number 1. Run number 35 and ant number 2. Run number 35 and ant number 3. Run number 35 and ant number 4. Run number 35 and ant number 5. Run number 35 and ant number 6. Run number 35 and ant number 7. Run number 35 and ant number 8. Run number 35 and ant number 9. Run number 35 and ant number 10. Run number 36 and ant number 1. Run number 36 and ant number 2. Run number 36 and ant number 3. Run number 36 and ant number 4. Run number 36 and ant number 5. Run number 36 and ant number 6. Run number 36 and ant number 7. Run number 36 and ant number 8. Run number 36 and ant number 9. Run number 36 and ant number 10. Run number 37 and ant number 1. Run number 37 and ant number 2. Run number 37 and ant number 3. Run number 37 and ant number 4. Run number 37 and ant number 5. Run number 37 and ant number 6. Run number 37 and ant number 7. Run number 37 and ant number 8. Run number 37 and ant number 9. Run number 37 and ant number 10. Run number 38 and ant number 1. Run number 38 and ant number 2. Run number 38 and ant number 3. Run number 38 and ant number 4. Run number 38 and ant number 5. Run number 38 and ant number 6. Run number 38 and ant number 7. Run number 38 and ant number 8. Run number 38 and ant number 9. Run number 38 and ant number 10. Run number 39 and ant number 1. Run number 39 and ant number 2. Run number 39 and ant number 3. Run number 39 and ant number 4. Run number 39 and ant number 5. Run number 39 and ant number 6. Run number 39 and ant number 7. Run number 39 and ant number 8. Run number 39 and ant number 9. Run number 39 and ant number 10. Run number 40 and ant number 1. Run number 40 and ant number 2. Run number 40 and ant number 3. Run number 40 and ant number 4. Run number 40 and ant number 5. Run number 40 and ant number 6. Run number 40 and ant number 7. Run number 40 and ant number 8. Run number 40 and ant number 9. Run number 40 and ant number 10. Run number 41 and ant number 1. Run number 41 and ant number 2. Run number 41 and ant number 3. Run number 41 and ant number 4. Run number 41 and ant number 5. Run number 41 and ant number 6. Run number 41 and ant number 7. Run number 41 and ant number 8. Run number 41 and ant number 9. Run number 41 and ant number 10. Run number 42 and ant number 1. Run number 42 and ant number 2. Run number 42 and ant number 3. Run number 42 and ant number 4. Run number 42 and ant number 5. Run number 42 and ant number 6. Run number 42 and ant number 7. Run number 42 and ant number 8. Run number 42 and ant number 9. Run number 42 and ant number 10. Run number 43 and ant number 1. Run number 43 and ant number 2. Run number 43 and ant number 3. Run number 43 and ant number 4. Run number 43 and ant number 5. Run number 43 and ant number 6. Run number 43 and ant number 7. Run number 43 and ant number 8. Run number 43 and ant number 9. Run number 43 and ant number 10. Run number 44 and ant number 1. Run number 44 and ant number 2. Run number 44 and ant number 3. Run number 44 and ant number 4. Run number 44 and ant number 5. Run number 44 and ant number 6. Run number 44 and ant number 7. Run number 44 and ant number 8. Run number 44 and ant number 9. Run number 44 and ant number 10. Run number 45 and ant number 1. Run number 45 and ant number 2. Run number 45 and ant number 3. Run number 45 and ant number 4. Run number 45 and ant number 5. Run number 45 and ant number 6. Run number 45 and ant number 7. Run number 45 and ant number 8. Run number 45 and ant number 9. Run number 45 and ant number 10. Run number 46 and ant number 1. Run number 46 and ant number 2. Run number 46 and ant number 3. Run number 46 and ant number 4. Run number 46 and ant number 5. Run number 46 and ant number 6. Run number 46 and ant number 7. Run number 46 and ant number 8. Run number 46 and ant number 9. Run number 46 and ant number 10. Run number 47 and ant number 1. Run number 47 and ant number 2. Run number 47 and ant number 3. Run number 47 and ant number 4. Run number 47 and ant number 5. Run number 47 and ant number 6. Run number 47 and ant number 7. Run number 47 and ant number 8. Run number 47 and ant number 9. Run number 47 and ant number 10. Run number 48 and ant number 1. Run number 48 and ant number 2. Run number 48 and ant number 3. Run number 48 and ant number 4. Run number 48 and ant number 5. Run number 48 and ant number 6. Run number 48 and ant number 7. Run number 48 and ant number 8. Run number 48 and ant number 9. Run number 48 and ant number 10. [1] "Compiling results." -abilityShortForm # print the results of the final short form -## Algorithm: Ant Colony Optimization -## Total Run Time: 1.015 mins -## -## Function call: -## antcolony.lavaan(data = antData, ants = 10, evaporation = 0.9, antModel = -## antModel, list.items = list.items, full = 48, i.per.f = c(6, 6, 6, 6), factors -## = c("Trait1", "Trait2", "Trait3", "Trait4"), steps = 100, lavaan.model.specs -## = list(model.type = "cfa", auto.var = T, estimator = "default", ordered -## = NULL, int.ov.free = TRUE, int.lv.free = FALSE, auto.fix.first = TRUE, -## auto.fix.single = TRUE, std.lv = FALSE, auto.cov.lv.x = TRUE, auto.th = TRUE, -## auto.delta = TRUE, auto.cov.y = TRUE), max.run = 100, parallel = T) -## -## Final Model Syntax: -## -## Trait1 =~ Item9 + Item2 + Item10 + Item5 + Item11 + Item6 -## Trait2 =~ Item21 + Item14 + Item13 + Item22 + Item18 + Item17 -## Trait3 =~ Item29 + Item34 + Item25 + Item30 + Item33 + Item26 -## Trait4 =~ Item45 + Item46 + Item41 + Item38 + Item37 + Item42 -plot(abilityShortForm, type = 'pheromone') # the pheromone plot for class "antcolony" -``` + ## + ## $Gamma + +![](README-quick-example-ACO-2.png) -![](README-ACO%20example-1.png) + ## + ## $Beta -A similar example can be found in the `antcolony.mplus` function, but -requires you to have a valid Mplus installation on the computer. It took -a total of 1.06 mins to run this example. +![](README-quick-example-ACO-3.png) -### Tabu Search Algorithm + ## + ## $Variance -This example demonstrates how to use the Tabu search for model -specification searches when the original model may be misspecified in -some way. +![](README-quick-example-ACO-4.png) ``` r -start.time.Tabu <- Sys.time() -library(ShortForm, quietly = T) -set.seed(2) -# create simulation data from the `psych` package -# two factors, 12 items total -# factor loading matrix - not quite simple structure -fxMatrix <- - matrix(data = c( - # first factor loadings - rep(x = c(.8, .8, .6, .6), times = 3), - # second factor loadings - rep(x = c(.2), times = 12) - ), - ncol = 2) -# factor correlation matrix - all factors uncorrelated -PhiMatrix <- - matrix(data = c(1,0, - 0,1 - ), ncol = 2) -tabuData <- - psych::sim( - fx = fxMatrix, - Phi = PhiMatrix, - n = 600, - raw = TRUE - )$observed # observed is the simulated observed data - -colnames(tabuData) = paste0("Item", 1:12) - -tabuModel <- ' -Trait1 =~ Item1 + Item2 + Item3 + Item4 + Item5 + Item6 + 0*Item7 + 0*Item8 + 0*Item9 + 0*Item10 + 0*Item11 + 0*Item12 -Trait2 =~ 0*Item1 + 0*Item2 + 0*Item3 + 0*Item4 + 0*Item5 + 0*Item6 + Item7 + Item8 + Item9 + Item10 + Item11 + Item12 -' -# fit the initial misspecified model for Tabu -init.model <- lavaan::lavaan(model = tabuModel, data = tabuData, - auto.var=TRUE, auto.fix.first=FALSE, std.lv=TRUE, - auto.cov.lv.x=FALSE) - -# use search.prep to prepare for the Tabu search -ptab <- - search.prep(fitted.model = init.model, - loadings=TRUE, - fcov=FALSE, - errors=FALSE) - -Tabu_example <- - suppressWarnings( - tabu.sem(init.model = init.model, - ptab = ptab, - obj = AIC, - niter = 20, - tabu.size = 10) - ) # the suppressWarning wrapping hides the lavaan WARNING output from improper models -## Running iteration 1 of 20. Running iteration 2 of 20. Running iteration 3 of 20. Running iteration 4 of 20. Running iteration 5 of 20. Running iteration 6 of 20. Running iteration 7 of 20. Running iteration 8 of 20. Running iteration 9 of 20. Running iteration 10 of 20. Running iteration 11 of 20. Running iteration 12 of 20. Running iteration 13 of 20. Running iteration 14 of 20. Running iteration 15 of 20. Running iteration 16 of 20. Running iteration 17 of 20. Running iteration 18 of 20. Running iteration 19 of 20. Running iteration 20 of 20. - -# check the final model -summary(Tabu_example) -## Algorithm: Tabu Search -## Total Run Time: 1.908 mins +set.seed(58310) +result_SA <- simulatedAnnealing( + initialModel = paste("Ability =~", paste0("Item", 1:56, collapse = " + ")), + originalData = simulated_test_data, + maxSteps = 20, + maxItems = 40, + items = paste0("Item", 1:56), + setChains = 4 +) +## Initializing short form creation. +## The initial short form is: +## Ability =~ Item10 + Item43 + Item35 + Item31 + Item42 + Item49 + Item48 + Item20 + Item32 + Item18 + Item36 + Item34 + Item45 + Item50 + Item3 + Item54 + Item2 + Item44 + Item24 + Item7 + Item22 + Item46 + Item53 + Item13 + Item39 + Item38 + Item51 + Item21 + Item15 + Item56 + Item8 + Item29 + Item40 + Item26 + Item23 + Item47 + Item1 + Item52 + Item27 + Item9 +## +## Using the short form randomNeighbor function. +## Finished initializing short form options. +## Current Progress: +## Chain number 1 complete. +## Chain number 2 complete. +## Chain number 3 complete. +## Chain number 4 complete. + +summary(result_SA) +## Algorithm: Simulated Annealing +## Total Run Time: 20.71 secs ## -## lavaan 0.6.17 ended normally after 32 iterations +## lavaan 0.6-20 ended normally after 41 iterations ## ## Estimator ML ## Optimization method NLMINB -## Number of model parameters 29 +## Number of model parameters 80 ## -## Number of observations 600 +## Number of observations 1000 ## ## Model Test User Model: ## -## Test statistic 42.131 -## Degrees of freedom 49 -## P-value (Chi-square) 0.746 +## Test statistic 767.372 +## Degrees of freedom 740 +## P-value (Chi-square) 0.236 ## ## ## Final Model Syntax: -## Trait1 =~ Item1 + Item2 + Item3 + Item4 + Item5 + Item6 + Item7 + Item8 + Item9 -## + Item10 + Item11 + Item12 -## Trait2 =~ Item1 + Item3 + Item5 + Item10 + Item11 - -# plot the change in the objective/criterion function over each run -plot(Tabu_example) +## Ability =~ Item6 + Item8 + Item56 + Item31 + Item42 + Item49 + Item48 + Item33 + +## Item32 + Item25 + Item19 + Item55 + Item20 + Item41 + Item3 + Item54 + Item2 + +## Item44 + Item24 + Item7 + Item23 + Item46 + Item30 + Item10 + Item39 + Item38 +## + Item28 + Item21 + Item5 + Item36 + Item4 + Item29 + Item40 + Item35 + Item34 +## + Item13 + Item11 + Item16 + Item27 + Item15 +plot(result_SA) ``` -![](README-Tabu%20example-1.png) - -It took a total of 1.92 mins to run this example. - -The next Tabu example demonstrates how to use it to find a short form of -a prespecified length with different data. +![](README-quick-example-SA-1.png) ``` r -start.time.Tabu <- Sys.time() -library(ShortForm, quietly = T) -# set the seed to reproduce this example -set.seed(3) -# create simulation data from the `psych` package -# four factors, 12 items each, 48 total items -# factor loading matrix - not quite simple structure -fxMatrix <- - matrix(data = c(rep(x = c(.8, .8, .4, .3), times = 3), - rep(0.2, times = 3*4*3), # first factor loadings - - rep(0.2, times = 3*4), - rep(x = c(.8, .8, .4, .3), times = 3), - rep(0.2, times = 3*4*2), # second factor loadings - - rep(0.2, times = 3*4*2), - rep(x = c(.8, .8, .4, .3), times = 3), - rep(0.2, times = 3*4), # third factor loadings - - rep(0.2, times = 3*4*3), - rep(x = c(.8, .8, .4, .3), times = 3) # fourth factor loadings - ), - ncol = 4) -# factor correlation matrix - all factors uncorrelated -PhiMatrix <- - matrix(data = c(1,0,0,0, - 0,1,0,0, - 0,0,1,0, - 0,0,0,1), ncol = 4) -tabuData <- - psych::sim( - fx = fxMatrix, - Phi = PhiMatrix, - n = 600, - mu = c(-2, -1, 1, 2), - raw = TRUE - )$observed # observed is the simulated observed data - -colnames(tabuData) = paste0("Item", 1:48) - -tabuModel <- ' -Trait1 =~ Item1 + Item2 + Item3 + Item4 + Item5 + Item6 + Item7 + Item8 + Item9 + Item10 + Item11 + Item12 -Trait2 =~ Item13 + Item14 + Item15 + Item16 + Item17 + Item18 + Item19 + Item20 + Item21 + Item22 + Item23 + Item24 -Trait3 =~ Item25 + Item26 + Item27 + Item28 + Item29 + Item30 + Item31 + Item32 + Item33 + Item34 + Item35 + Item36 -Trait4 =~ Item37 + Item38 + Item39 + Item40 + Item41 + Item42 + Item43 + Item44 + Item45 + Item46 + Item47 + Item48 -' - -# specify the criterion function that the Tabu Search minimizes -# wrap this in a tryCatch in case a model does not converge! -# specify an appropriate error value: since we're minimizing, error value must be large -tabuCriterion = function(x) { - tryCatch(lavaan::fitmeasures(object = x, fit.measures = 'chisq'), - error = function(e) Inf) -} - -# use the tabuShortForm function -# reduce form to the best 12 items, 3 per factor -tabuShort <- - tabuShortForm(initialModel = tabuModel, originalData = tabuData, - numItems = c(5,5,5,5), criterion = tabuCriterion, - niter = 20, tabu.size = 10, verbose = FALSE - ) -## Running iteration 1 of 20. Running iteration 2 of 20. Running iteration 3 of 20. Running iteration 4 of 20. Running iteration 5 of 20. Running iteration 6 of 20. Running iteration 7 of 20. Running iteration 8 of 20. Running iteration 9 of 20. Running iteration 10 of 20. Running iteration 11 of 20. Running iteration 12 of 20. Running iteration 13 of 20. Running iteration 14 of 20. Running iteration 15 of 20. Running iteration 16 of 20. Running iteration 17 of 20. Running iteration 18 of 20. Running iteration 19 of 20. Running iteration 20 of 20. - -# check the chosen model -summary(tabuShort) +set.seed(58310) +shortAntModel <- " +Ability =~ Item1 + Item2 + Item3 + Item4 + Item5 + Item6 + Item7 + Item8 +Ability ~ Outcome +" + +result_TS <- tabuShortForm( + initialModel = shortAntModel, + originalData = simulated_test_data, numItems = 7, + niter = 1, tabu.size = 3, parallel = FALSE +) +## Running iteration 1 of 1. +summary(result_TS) ## Algorithm: Tabu Search -## Total Run Time: 2.137 mins +## Total Run Time: 2.272 secs ## -## lavaan 0.6.17 ended normally after 32 iterations +## lavaan 0.6-20 ended normally after 29 iterations ## ## Estimator ML ## Optimization method NLMINB -## Number of model parameters 46 +## Number of model parameters 15 ## -## Number of observations 600 +## Number of observations 1000 ## ## Model Test User Model: ## -## Test statistic 129.734 -## Degrees of freedom 164 -## P-value (Chi-square) 0.978 +## Test statistic 18.858 +## Degrees of freedom 20 +## P-value (Chi-square) 0.531 ## ## ## Final Model Syntax: -## Trait1 =~ Item1 + Item2 + Item5 + Item6 + Item9 -## Trait2 =~ Item13 + Item14 + Item17 + Item18 + Item22 -## Trait3 =~ Item26 + Item29 + Item30 + Item33 + Item34 -## Trait4 =~ Item39 + Item43 + Item44 + Item47 + Item40 -# plot the changes in the objective function over each iteration -plot(tabuShort) +## Ability =~ Item1 + Item6 + Item3 + Item4 + Item5 + Item7 + Item8 +## Ability ~ Outcome ``` -![](README-Tabu%20short%20form-1.png) +## Workflow + +``` mermaid +flowchart LR + A@{ shape: lean-r, label: "Full Item Bank" } --> B[Specify Model] + B --> C[Run Optimization Algorithm] + C --> D[Evaluate Candidate Forms] + D --> |One or More Forms Fit| E@{ shape: diamond, label: "Select Short Form" } + D --> |No Adequate Forms Found| C + E --> F@{ shape: lean-l, label: "Reporting & Visualization" } + +``` -It took a total of 2.14 mins to run this example. +## Outputs -### Simulated Annealing +ShortForm produces diagnostic outputs such as: -This example demonstrates the use of simulated annealing for creating -short forms. +- Objective function convergence plots +- Algorithm-specific diagnostics +- Summary statistics for selected item sets -``` r -start.time.SA <- Sys.time() -library(ShortForm, quietly = T) -# create simulation data from the `psych` package -# four factors, 12 items each, 48 total items -# factor loading matrix - not quite simple structure -set.seed(4) -fxMatrix <- - matrix(data = c(rep(x = c(.8, .8, .4, .3), times = 3), - rep(0.2, times = 3*4*3), # first factor loadings - - rep(0.2, times = 3*4), - rep(x = c(.8, .8, .4, .3), times = 3), - rep(0.2, times = 3*4*2), # second factor loadings - - rep(0.2, times = 3*4*2), - rep(x = c(.8, .8, .4, .3), times = 3), - rep(0.2, times = 3*4), # third factor loadings - - rep(0.2, times = 3*4*3), - rep(x = c(.8, .8, .4, .3), times = 3) # fourth factor loadings - ), - ncol = 4) -# factor correlation matrix - all factors uncorrelated -PhiMatrix <- - matrix(data = c(1,0,0,0, - 0,1,0,0, - 0,0,1,0, - 0,0,0,1), ncol = 4) -annealData <- - psych::sim( - fx = fxMatrix, - Phi = PhiMatrix, - n = 600, - mu = c(-2, -1, 1, 2), - raw = TRUE - )$observed # observed is the simulated observed data - -colnames(annealData) = paste0("Item", 1:48) - -annealModel <- ' -Trait1 =~ Item1 + Item2 + Item3 + Item4 + Item5 + Item6 + Item7 + Item8 + Item9 + Item10 + Item11 + Item12 -Trait2 =~ Item13 + Item14 + Item15 + Item16 + Item17 + Item18 + Item19 + Item20 + Item21 + Item22 + Item23 + Item24 -Trait3 =~ Item25 + Item26 + Item27 + Item28 + Item29 + Item30 + Item31 + Item32 + Item33 + Item34 + Item35 + Item36 -Trait4 =~ Item37 + Item38 + Item39 + Item40 + Item41 + Item42 + Item43 + Item44 + Item45 + Item46 + Item47 + Item48 -' - -lavaan.model.specs <- - list(model.type = "cfa", - auto.var = TRUE, estimator = "default", ordered = NULL, - int.ov.free = TRUE, int.lv.free = FALSE, std.lv = TRUE, auto.fix.first = FALSE, - auto.fix.single = TRUE, auto.cov.lv.x = TRUE, auto.th = TRUE, - auto.delta = TRUE, auto.cov.y = TRUE) - -# perform the SA algorithm -set.seed(1) -SA_example <- - simulatedAnnealing(initialModel = annealModel, originalData = annealData, maxSteps = 200, - fitStatistic = 'cfi', maximize = TRUE, - temperature = "logistic", items = paste0("Item", 1:48), - lavaan.model.specs = lavaan.model.specs, - maxChanges = 3, maxItems = c(6,6,6,6), setChains = 4) -## Initializing short form creation. -## The initial short form is: -## Trait1 =~ Item9 + Item4 + Item7 + Item1 + Item2 + Item5 -## Trait2 =~ Item19 + Item23 + Item14 + Item15 + Item13 + Item17 -## Trait3 =~ Item29 + Item34 + Item30 + Item31 + Item25 + Item33 -## Trait4 =~ Item41 + Item48 + Item45 + Item46 + Item42 + Item47 -## Using the short form randomNeighbor function. -## Finished initializing short form options. -## Current Progress: -## Chain number 1 complete. -## Chain number 2 complete. -## Chain number 3 complete. -## Chain number 4 complete. -summary(SA_example) -## Algorithm: Simulated Annealing -## Total Run Time: 41.456 secs -## -## lavaan 0.6.17 ended normally after 33 iterations -## -## Estimator ML -## Optimization method NLMINB -## Number of model parameters 54 -## -## Number of observations 600 -## -## Model Test User Model: -## -## Test statistic 367.147 -## Degrees of freedom 246 -## P-value (Chi-square) 0.000 -## -## -## Final Model Syntax: -## Trait1 =~ Item11 + Item2 + Item3 + Item6 + Item4 + Item9 -## Trait2 =~ Item17 + Item21 + Item13 + Item24 + Item16 + Item23 -## Trait3 =~ Item26 + Item35 + Item33 + Item34 + Item30 + Item25 -## Trait4 =~ Item43 + Item37 + Item42 + Item40 + Item38 + Item46 -plot(SA_example) # plot showing how the fit value changes at each step -``` +------------------------------------------------------------------------ + +## Algorithms + +### Ant Colony Optimization (ACO) + +Implementation adapted from [Leite, Huang, & Marcoulides +(2008)](https://doi.org/10.1080/00273170802285743). + +### Tabu Search (TS) + +Based on [Marcoulides & Falk +(2018)](https://doi.org/10.1080/10705511.2017.1409074), extended for +model search and short-form construction. + +### Simulated Annealing (SA) + +Provides one of the first implementations of simulated annealing for +psychometric model selection workflows. Implemented following +[Kirkpatrick et +al. (1983)](https://doi.org/10.1126/science.220.4598.671). + +------------------------------------------------------------------------ + +## Performance Notes + +Computational time depends on: + +- Number and types of items +- Dimensionality/model complexity +- Algorithm convergence settings + +Parallelization is supported for certain workflows. + +------------------------------------------------------------------------ + +## Reproducibility + +ShortForm is designed for reproducible analytical workflows: + +- Fully scriptable +- Compatible with R Markdown and Quarto +- Supports integration into larger modeling pipelines + +------------------------------------------------------------------------ + +## License + +LGPL (\>= 2.0, \< 3) \| Mozilla Public License + +------------------------------------------------------------------------ + +## Citation -![](README-Simulated%20Annealing%20example-1.png) +If you use this package, please cite: -It took a total of 42.09 secs to run the SA example, and a total of 5.82 -mins to run all four together. +Raborn A, Leite W (2024). *ShortForm: Automatic Short Form Creation*. +. ShortForm +, R package version +0.5.6, . diff --git a/man/antcolony.lavaan.Rd b/man/antcolony.lavaan.Rd index cb1cc83..f2d2fbb 100644 --- a/man/antcolony.lavaan.Rd +++ b/man/antcolony.lavaan.Rd @@ -261,6 +261,49 @@ abilityShortForm <- antcolony.lavaan( ) abilityShortForm # print the results of the final short form + +# an example using binary (ordered) data +# create the simulated full model and model data +sim_model <- " +f1 =~ x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10 +f2 =~ x11 + x12 + x13 + x14 + x15 + x16 + x17 + x18 + x19 + x20 +f3 =~ x21 + x22 + x23 + x24 + x25 + x26 + x27 + x28 + x29 + x30" + +sim_data <- + cbind( + psych::sim.rasch(nvar = 10)$items, + psych::sim.rasch(nvar = 10)$items, + psych::sim.rasch(nvar = 10)$items + ) + +colnames(sim_data) = paste0("x", 1:30) +# fit with antcolony.lavaan +# note that ONLY the estimator and ordered args +# of lavaan.model.specs are changed. This retains +# the default args, fitting a CFA but with ordered data. +example <- +antcolony.lavaan( + data = sim_data, + ants = 5, evaporation = 0.7, + antModel = sim_model, + lavaan.model.specs = + list(estimator = "wlsmv", ordered = T), + list.items = list(paste0('x', 1:10), + paste0('x', 11:20), + paste0('x', 21:30)), + full = 30, + i.per.f = c(5, 5, 5), + factors = c("f1", "f2", "f3"), + steps = 20, + fit.indices = c("cfi.scaled"), + fit.statistics.test = "(cfi.scaled > 0.90)", + summaryfile = NULL, + feedbackfile = NULL, + max.run = 500, + parallel = T +) +# note that this example will take a bit of time to run +# as ordered data factor analysis is computationally expensive. } } \seealso{