Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
3 changes: 3 additions & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@
^\.jules(/.*)?$
^\.trivyignore\.yaml$
^trivy\.yaml$
^\.semgrepignore$
^test_dummy\.R$
^test_validation\.R$
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@
## 2025-02-12 - R 언어에서 반복적인 mirt 모델 생성 시 불필요한 데이터프레임 부분집합 추출 최적화
**Learning:** R에서 데이터프레임의 특정 열을 추출하는 작업(`df[cols]`)은 O(N)의 메모리 복사를 수반합니다. `autoFIPC`에서 `mirt` 모델의 파라미터를 설정하거나 호출하는 과정 중에 `newformXDataK[colnames(newFormModel@Data$data)]` 코드가 반복해서 사용되었고, 심지어 `ncol()`을 위해 단순히 개수를 구할 때도 사용되어 불필요한 메모리 할당과 오버헤드를 초래했습니다.
**Action:** 조건문이나 반복문 내부에서 불필요하게 데이터프레임 부분집합 연산이 반복되지 않도록 외부에서 한 번만 `linkedFormData <- newformXDataK[colnames(newFormModel@Data$data)]`로 캐싱(caching)한 뒤, `ncol(linkedFormData)`와 `data = linkedFormData` 형태로 재사용하여 메모리 복사와 O(N) 오버헤드를 방지해야 합니다.
## 2024-05-24 - [데이터 프레임 서브셋팅을 intersect로 대체하여 컬럼 이름 추출 최적화]
**Learning:** 데이터 프레임을 서브셋팅(예: `colnames(df[cols])`)하여 컬럼 이름을 추출하면 데이터의 O(N) 메모리 복사가 발생합니다.
**Action:** 데이터를 복사하지 않고 일치하는 컬럼을 효율적으로 검색하려면 `intersect(cols, colnames(df))`를 사용해야 합니다. 참고로 `intersect(A, B)`는 `A` 요소의 순서를 유지하므로, 의도한 컬럼 순서를 보존하려면 `cols`가 첫 번째 인수가 되어야 합니다.
10 changes: 6 additions & 4 deletions R/aFIPC.R
Original file line number Diff line number Diff line change
Expand Up @@ -620,8 +620,9 @@ autoFIPC <-
IPDItemCount <- 0

# IPD target item checking
newFormColNames <- colnames(newformXDataK[colnames(newFormModel@Data$data)])
oldFormColNames <- colnames(oldformYDataK[colnames(oldFormModel@Data$data)])
# ⚡ Bolt: Extract column names using intersect() to avoid O(N) dataframe memory copy
newFormColNames <- intersect(colnames(newFormModel@Data$data), colnames(newformXDataK))
oldFormColNames <- intersect(colnames(oldFormModel@Data$data), colnames(oldformYDataK))
Comment on lines +623 to +625

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

존재하지 않는 공통 문항을 조용히 제외하지 말고 입력 오류로 처리하세요.

intersect()는 양쪽 데이터에 모두 존재하는 이름만 반환합니다. 요청된 문항이 한쪽 모델에 없으면 valid_idx가 해당 쌍을 제거하고, 아래 linking 루프의 !is.na() 조건도 같은 쌍을 건너뜁니다. 그 결과 checkIPD = FALSE인 경로에서도 일부 문항만 연결된 모델이 오류 없이 반환될 수 있습니다.

if (checkIPD) 전에 두 교집합을 한 번 계산하고, 모든 요청 이름이 존재하는지 검증한 뒤 두 경로에서 재사용하세요. 누락된 이름이 있으면 stop()으로 보고해야 합니다. 누락 이름을 검증하는 회귀 테스트도 추가하세요.

권장 수정 예시
+newFormColNames <- intersect(
+  colnames(newFormModel@Data$data),
+  colnames(newformXDataK)
+)
+oldFormColNames <- intersect(
+  colnames(oldFormModel@Data$data),
+  colnames(oldformYDataK)
+)
+
+missing_new <- setdiff(as.character(newformCommonItemNames), newFormColNames)
+missing_old <- setdiff(as.character(oldformCommonItemNames), oldFormColNames)
+if (length(missing_new) > 0L || length(missing_old) > 0L) {
+  stop("Common item names are missing from the corresponding model data")
+}

Also applies to: 753-755

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@R/aFIPC.R` around lines 623 - 625, Update the column-name handling around
newFormColNames and oldFormColNames to compute both intersections before the
checkIPD branch, validate that every requested name exists in its corresponding
model, and stop with an input-error message listing missing names. Reuse the
validated intersections in both linking paths so missing question pairs cannot
be silently filtered; add a regression test covering missing requested names.


# ⚡ Bolt: Vectorized match() to avoid dynamic array growth overhead inside a for loop
idxNew <- match(newformCommonItemNames, newFormColNames)
Expand Down Expand Up @@ -749,8 +750,9 @@ autoFIPC <-
}
}

newFormColNames <- colnames(newformXDataK[colnames(newFormModel@Data$data)])
oldFormColNames <- colnames(oldformYDataK[colnames(oldFormModel@Data$data)])
# ⚡ Bolt: Extract column names using intersect() to avoid O(N) dataframe memory copy
newFormColNames <- intersect(colnames(newFormModel@Data$data), colnames(newformXDataK))
oldFormColNames <- intersect(colnames(oldFormModel@Data$data), colnames(oldformYDataK))

# ⚡ Bolt: Cache parameter indices to avoid O(N) linear search inside loop
newScaleParmsItemIdxCache <- split(seq_len(nrow(NewScaleParms)), NewScaleParms$item)
Expand Down
Loading