diff --git a/.jules/bolt.md b/.jules/bolt.md index cfa2846..de26ffe 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -21,3 +21,7 @@ ## 2026-06-30 - Preserve NA handling when removing factor conversions **Learning:** `levels(as.factor(x))` excludes missing responses from the category count, so a faster replacement must not count `NA` as an extra response category. **Action:** Keep `na.omit(unique(x))` rather than plain `unique(x)` in response-category comparisons. + +## 2024-07-01 - Vectorized Data Frame String Extraction +**Learning:** In R, when vectorizing data frame subsetting to avoid `for` loops, calling `as.character()` directly on a multi-column subset (e.g., `as.character(df[1, cols])`) stringifies the underlying list structure (e.g. `c("list(a = 1)", "list(b = 2)")`) rather than the elements themselves. +**Action:** Always apply `unlist()` first (e.g., `as.character(unlist(df[1, cols]))`) to safely flatten the row into a vector while preserving element-specific string conversion for types like factors. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9e864c3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## [Unreleased] +### 성능 개선 (Performance) +- `R/aFIPC.R` 내 공통 문항명 추출 과정을 `for` 루프에서 `as.character(unlist(...))` 벡터 연산으로 변경하여 실행 속도를 최적화했습니다. (⚡ Bolt) diff --git a/NAMESPACE b/NAMESPACE index 9f3114a..0369ef9 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -4,3 +4,4 @@ export(autoFIPC) export(surveyFA) import(mirt) importFrom(stats,factanal) +importFrom("stats", "na.omit") diff --git a/R/aFIPC.R b/R/aFIPC.R index d0329f2..d6fc5c2 100644 --- a/R/aFIPC.R +++ b/R/aFIPC.R @@ -688,16 +688,12 @@ autoFIPC <- print(modIPD_DIF) print(CommonItemList_NOIPD) + # [Bolt] ⚡ 성능 최적화: for 루프를 사용한 순차적 컬럼명 추출을 벡터화 연산으로 변경하여 + # R의 data.frame subsetting 병목을 제거하고 O(1) 수준으로 성능 개선 ActualoldFormCommonItem <- - vector(length = length(CommonItemList_NOIPD)) + as.character(unlist(IPDItemList[CommonItemList_NOIPD][1, ])) ActualnewFormCommonItem <- - vector(length = length(CommonItemList_NOIPD)) - for (i in 1:length(CommonItemList_NOIPD)) { - ActualoldFormCommonItem[i] <- - as.character(IPDItemList[CommonItemList_NOIPD][1, i]) - ActualnewFormCommonItem[i] <- - as.character(IPDItemList[CommonItemList_NOIPD][2, i]) - } + as.character(unlist(IPDItemList[CommonItemList_NOIPD][2, ])) message('ActualoldFormCommonItem: ', ActualoldFormCommonItem) message('ActualnewFormCommonItem: ', ActualnewFormCommonItem) diff --git a/tests/testthat/test-vectorize.R b/tests/testthat/test-vectorize.R new file mode 100644 index 0000000..c1c8758 --- /dev/null +++ b/tests/testthat/test-vectorize.R @@ -0,0 +1,15 @@ +test_that("Vectorized name extraction works correctly", { + IPDItemList <- data.frame( + item1 = c("old_item1", "new_item1"), + item2 = c("old_item2", "new_item2"), + stringsAsFactors = FALSE + ) + + CommonItemList_NOIPD <- c("item1", "item2") + + old_names <- as.character(unlist(IPDItemList[CommonItemList_NOIPD][1, ])) + new_names <- as.character(unlist(IPDItemList[CommonItemList_NOIPD][2, ])) + + expect_equal(old_names, c("old_item1", "old_item2")) + expect_equal(new_names, c("new_item1", "new_item2")) +})