Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
^registered_agents\.json$
^task_agent_mapping\.json$
^\.gitleaks\.toml$
^\.jules$
^\.jules(/.*)?$
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -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) 수준으로 성능을 끌어올려야 한다.
16 changes: 8 additions & 8 deletions R/aFIPC.R
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
38 changes: 38 additions & 0 deletions tests/testthat/test-package-api.R
Original file line number Diff line number Diff line change
Expand Up @@ -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))
})
Loading