diff --git a/.Rbuildignore b/.Rbuildignore index 11d3f73..44b7552 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -15,4 +15,4 @@ ^registered_agents\.json$ ^task_agent_mapping\.json$ ^\.gitleaks\.toml$ -^\.jules$ +^\.jules(/.*)?$ diff --git a/.jules/bolt.md b/.jules/bolt.md index 379e5cd..22d58d2 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -1,3 +1,7 @@ +## 2026-06-30 - Pre-calculate Theta Variables to Avoid Redundant MAP estimation +**Learning:** `mirt::fscores(..., method = 'MAP')` is called redundantly multiple times in `R/aFIPC.R`. It's an expensive operation and avoiding duplicate calls by pre-calculating and reusing the variables significantly improves performance. +**Action:** Always pre-calculate and reuse the resulting Theta variables rather than calling it redundantly. + ## 2024-05-18 - R 언어에서 루프 내 정규식 탐색 병목 최적화 **Learning:** R에서 데이터 프레임의 크기가 커질수록 루프 내에서 컬럼명을 추출하고 정규식을 이용해(`grep`) 문자열을 탐색하는 작업이 상당한 성능 오버헤드를 발생시킨다. 특히 O(N) 탐색을 루프 안에서 반복할 경우 O(N^2)의 비효율성을 초래한다. **Action:** 루프 내부에서 자주 호출되는 컬럼명이나 데이터 프레임 구조 탐색을 루프 밖으로 빼서 한 번만 계산하여 벡터로 저장하도록 한다. 정규식보다는 완전 일치 탐색(`%in%`, `match`)이 가능하도록 벡터 연산을 활용해 O(1) 수준으로 성능을 끌어올려야 한다. diff --git a/R/aFIPC.R b/R/aFIPC.R index d14dcaa..d48e55a 100644 --- a/R/aFIPC.R +++ b/R/aFIPC.R @@ -986,28 +986,28 @@ autoFIPC <- # stop('Estimation failed. Please check test quality.') # } + # calculate theta + ThetaOldform <- fscores(oldFormModel, method = 'MAP') + ThetaLinkedform <- fscores(LinkedModel, method = 'MAP') + ThetaNewform <- fscores(newFormModel, method = 'MAP') + # calculate expected score ExpectedScoreOldform <- mirt::expected.test( x = oldFormModel, - Theta = fscores(oldFormModel, method = 'MAP') + Theta = ThetaOldform ) ExpectedScoreLinkedform <- mirt::expected.test( x = LinkedModel, - Theta = fscores(LinkedModel, method = 'MAP') + Theta = ThetaLinkedform ) ExpectedScoreNewform <- mirt::expected.test( x = newFormModel, - Theta = fscores(newFormModel, method = 'MAP') + Theta = ThetaNewform ) - # calculate theta - ThetaOldform <- fscores(oldFormModel, method = 'MAP') - ThetaLinkedform <- fscores(LinkedModel, method = 'MAP') - ThetaNewform <- fscores(newFormModel, method = 'MAP') - # save results as object modelReturn <- new.env() modelReturn$oldFormModel <- oldFormModel diff --git a/tests/testthat/test-package-api.R b/tests/testthat/test-package-api.R index 3366f55..7f0ecf5 100644 --- a/tests/testthat/test-package-api.R +++ b/tests/testthat/test-package-api.R @@ -2,3 +2,41 @@ test_that("autoFIPC is exported", { expect_true("autoFIPC" %in% getNamespaceExports("aFIPC")) expect_true(is.function(aFIPC::autoFIPC)) }) + +test_that("autoFIPC executes without errors in non-interactive environment", { + skip_if_not_installed("mirt") + + set.seed(123) + dat_old <- mirt::simdata( + a = matrix(runif(10, 0.8, 2)), + d = matrix(rnorm(10)), + N = 100, + itemtype = "2PL" + ) + dat_new <- mirt::simdata( + a = matrix(runif(10, 0.8, 2)), + d = matrix(rnorm(10)), + N = 100, + itemtype = "2PL" + ) + + colnames(dat_old) <- paste0("Item", 1:10) + colnames(dat_new) <- c(paste0("Item", 1:5), paste0("NewItem", 6:10)) + + old_mod <- mirt::mirt(dat_old, 1, itemtype = "2PL", SE = FALSE, verbose = FALSE) + new_mod <- mirt::mirt(dat_new, 1, itemtype = "2PL", SE = FALSE, verbose = FALSE) + + res <- aFIPC::autoFIPC( + newformXData = new_mod, + oldformYData = old_mod, + newformCommonItemNames = paste0("Item", 1:5), + oldformCommonItemNames = paste0("Item", 1:5), + itemtype = "2PL", + checkIPD = FALSE, + confirmCommonItems = TRUE + ) + + expect_type(res, "list") + expect_true("ExpectedScoreOldform" %in% names(res)) + expect_true("ThetaOldform" %in% names(res)) +})