Skip to content
Open
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
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) 오버헤드를 방지해야 합니다.
## 2025-02-12 - R 언어에서 데이터프레임 부분 업데이트 시 벡터 할당(Vector Assignment) 최적화
**Learning:** R에서 `df[idx, 'col'] <- val` 형태의 2차원 부분집합 할당은 내부적으로 method dispatch, 디멘전 및 팩터 검사, 메모리 깊은 복사를 유발하여 루프와 결합될 경우 매우 큰 오버헤드를 발생시킵니다.
**Action:** 데이터프레임 업데이트 시 `df$col[idx] <- val` 형태의 1차원 직접 벡터 할당을 사용하여 O(1) 수준의 할당 성능을 확보해야 합니다.
34 changes: 16 additions & 18 deletions R/aFIPC.R
Original file line number Diff line number Diff line change
Expand Up @@ -598,15 +598,15 @@ autoFIPC <-
# Preserve mirt's structural estimability flags. Forcing every row TRUE
# frees boundary parameters such as 2PL g/u and makes the Hessian unstable.

NewScaleParms[NewScaleParms$item == 'GROUP', "est"] <- FALSE
OldScaleParms[OldScaleParms$item == 'GROUP', "est"] <- FALSE
NewScaleParms$est[NewScaleParms$item == 'GROUP'] <- FALSE
OldScaleParms$est[OldScaleParms$item == 'GROUP'] <- FALSE

NewScaleParms[NewScaleParms$name == "COV_11", "est"] <- TRUE
OldScaleParms[OldScaleParms$name == "COV_11", "est"] <- TRUE
NewScaleParms$est[NewScaleParms$name == "COV_11"] <- TRUE
OldScaleParms$est[OldScaleParms$name == "COV_11"] <- TRUE

if (itemtype == 'Rasch') {
NewScaleParms[NewScaleParms$name == "a1", "est"] <- FALSE
OldScaleParms[OldScaleParms$name == "a1", "est"] <- FALSE
NewScaleParms$est[NewScaleParms$name == "a1"] <- FALSE
OldScaleParms$est[OldScaleParms$name == "a1"] <- FALSE
}

#IPD
Expand Down Expand Up @@ -813,10 +813,8 @@ autoFIPC <-
newBetaIdx <- NewScaleParms$item == 'BETA'
oldBetaIdx <- OldScaleParms$item == 'BETA'

NewScaleParms[newBetaIdx, "value"] <-
OldScaleParms[oldBetaIdx, "value"]
NewScaleParms[newBetaIdx, "est"] <-
FALSE
NewScaleParms$value[newBetaIdx] <- OldScaleParms$value[oldBetaIdx]
NewScaleParms$est[newBetaIdx] <- FALSE

message('applying BETA parameter as linking')

Expand Down Expand Up @@ -858,13 +856,13 @@ autoFIPC <-
new_mean11_idx <- NewScaleParms$name == "MEAN_11"
old_mean11_idx <- OldScaleParms$name == "MEAN_11"

NewScaleParms[new_cov11_idx, "est"] <- FALSE
OldScaleParms[old_cov11_idx, "est"] <- FALSE
NewScaleParms[new_mean11_idx, "est"] <- FALSE
OldScaleParms[old_mean11_idx, "est"] <- FALSE
NewScaleParms$est[new_cov11_idx] <- FALSE
OldScaleParms$est[old_cov11_idx] <- FALSE
NewScaleParms$est[new_mean11_idx] <- FALSE
OldScaleParms$est[old_mean11_idx] <- FALSE

NewScaleParms[new_cov11_idx, "value"] <- 1
OldScaleParms[old_mean11_idx, "value"] <- 0
NewScaleParms$value[new_cov11_idx] <- 1
OldScaleParms$value[old_mean11_idx] <- 0
Comment on lines +859 to +865

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repo files =="
git ls-files | sed -n '1,120p'

echo "== locate aFIPC.R =="
fd -a 'aFIPC\.R$' . || true

echo "== relevant symbols in R/aFIPC.R =="
if [ -f R/aFIPC.R ]; then
  wc -l R/aFIPC.R
  rg -n "forceNormalZeroOne|MEAN_1|MEAN_11|new_cov11_idx|old_cov11_idx|new_mean11_idx|old_mean11_idx|freeMEAN|mod2values" R/aFIPC.R
  echo "== lines 830-870 =="
  sed -n '830,870p' R/aFIPC.R | nl -ba -v830
  echo "== lines 580-620 =="
  sed -n '580,620p' R/aFIPC.R | nl -ba -v580
fi

echo "== inspect package mirt availability and mod2values docs if installed =="
Rscript - <<'R'
if (requireNamespace("mirt", quietly = TRUE)) {
  cat("mirt loaded\n")
  m <- mirt::mirt(iris[1:100, -5], 1)
  vals <- mirt::mod2values(m)
  print(vals)
  cat("names matching mean:\n")
  print(vals$name[grepl("MEAN", vals$name)])
} else {
  cat("mirt not available in sandbox\n")
}
R

Repository: ContextualWisdomLab/aFIPC

Length of output: 7521


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== R/aFIPC.R relevant lines =="
sed -n '830,880p' R/aFIPC.R

echo "== mirt availability / example without loading package dependencies =="
Rscript - <<'R'
if (requireNamespace("mirt", quietly = TRUE)) {
  cat("mirt loaded\n")
  m <- mirt::mirt(iris[1:100, -5], 1)
  vals <- mirt::mod2values(m)
  print(vals)
  cat("mean parameter names:\n")
  print(vals$name[grepl("MEAN", vals$name)])
} else {
  cat("mirt not available\n")
}
R

echo "== all MEAN_11 and freeMEAN cases =="
rg -n "MEAN_11|new_mean11_idx|old_mean11_idx|freeMEAN ==" R/aFIPC.R

Repository: ContextualWisdomLab/aFIPC

Length of output: 1943


🌐 Web query:

mirt R package mod2values one factor model MEAN_1 COV_11

💡 Result:

In the mirt R package, the mod2values function is used to convert an estimated model object into a data frame of parameter values, which can then be used as starting values for subsequent model estimations [1][2]. While the mod2values function generates a data frame containing item-level parameters (such as item discrimination and difficulty) [1][3], the specific terms MEAN_1 and COV_11 refer to latent trait parameters (latent means and variances/covariances) often associated with the model's structural components rather than the items themselves. In a one-factor model estimated by mirt, the latent distribution is typically defined by a mean (often fixed to 0 for identification) and a variance (often fixed to 1 for identification) [1][4]. When you inspect the results of a fitted mirt model using functions like coef(model, simplify=TRUE), you will see these values explicitly listed under $means and $cov [1][4]. If you are seeing MEAN_1 or COV_11 in the context of parameter specifications or output, they represent: 1. MEAN_1: The estimated mean of the first latent factor (F1). In many standard IRT models, this is fixed to 0 to identify the scale [1]. 2. COV_11: The estimated variance of the first latent factor (F1), or the covariance between F1 and itself. In many standard IRT models, this is fixed to 1 to identify the scale [1]. The mod2values function focuses on extracting parameters into a format compatible with the pars argument in the mirt function (i.e., a design data frame), which primarily handles item parameters, constraints, and priors [1][3]. Latent structural parameters like means and covariances are typically handled automatically by the mirt estimation engine based on the model specification, but they are not the primary output of mod2values [1][3].

Citations:


🌐 Web query:

mirt mod2values source "MEAN_1" "freeMEAN"

💡 Result:

In the R package mirt, mod2values is a function used to convert an estimated model object back into a data frame of parameter values and characteristics [1][2]. This output format is designed to be used as a starting values input (e.g., via the pars argument) for subsequent model estimations [1][3]. The specific reference to MEAN_1 within this data frame refers to the mean parameter of the first latent factor (F1) in the GroupPars section of the model [1][3]. Regarding freeMEAN, this is not a direct parameter name within the mod2values output, but rather a concept related to model specification [4][5]. In mirt, you control whether latent factor means are estimated (freely estimated) or fixed using the mirt.model syntax [4][6]. Specifically: 1. To freely estimate latent factor means, you use the MEAN keyword within the mirt.model function (e.g., MEAN = F1, F2) [4][5]. 2. When you use mod2values on a model, the resulting data frame contains an est column [7]. This column contains logical values (TRUE/FALSE) indicating whether specific parameters are currently set to be freely estimated or fixed [7]. If a mean was set to be freely estimated in the original model, the corresponding row for MEAN_1 in the mod2values data frame will have the est column set to TRUE [1][3]. In summary, MEAN_1 is the parameter label for the mean of the first factor, and its status as a "free" parameter is indicated by the est column (set to TRUE) within the data frame produced by mod2values [1][7][3].

Citations:


forceNormalZeroOne에서 MEAN_11MEAN_1로 수정하세요.

mirt::mod2values()에서 한 개의 잠재요소의 평균 parameter 이름은 MEAN_1입니다. 현재 코드처럼 MEAN_11을 지정하면 estvalue 갱신이 없어 COV_11은 고정되지만 평균은 고정되지 않습니다.

제안 수정
-      new_mean11_idx <- NewScaleParms$name == "MEAN_11"
-      old_mean11_idx <- OldScaleParms$name == "MEAN_11"
+      new_mean1_idx <- NewScaleParms$name == "MEAN_1"
+      old_mean1_idx <- OldScaleParms$name == "MEAN_1"

-      NewScaleParms$est[new_mean11_idx] <- FALSE
-      OldScaleParms$est[old_mean11_idx] <- FALSE
+      NewScaleParms$est[new_mean1_idx] <- FALSE
+      OldScaleParms$est[old_mean1_idx] <- FALSE

-      OldScaleParms$value[old_mean11_idx] <- 0
+      OldScaleParms$value[old_mean1_idx] <- 0
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
NewScaleParms$est[new_cov11_idx] <- FALSE
OldScaleParms$est[old_cov11_idx] <- FALSE
NewScaleParms$est[new_mean11_idx] <- FALSE
OldScaleParms$est[old_mean11_idx] <- FALSE
NewScaleParms[new_cov11_idx, "value"] <- 1
OldScaleParms[old_mean11_idx, "value"] <- 0
NewScaleParms$value[new_cov11_idx] <- 1
OldScaleParms$value[old_mean11_idx] <- 0
NewScaleParms$est[new_cov11_idx] <- FALSE
OldScaleParms$est[old_cov11_idx] <- FALSE
NewScaleParms$est[new_mean1_idx] <- FALSE
OldScaleParms$est[old_mean1_idx] <- FALSE
NewScaleParms$value[new_cov11_idx] <- 1
OldScaleParms$value[old_mean1_idx] <- 0
🤖 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 859 - 865, forceNormalZeroOne에서 단일 잠재요소의 평균 parameter
인덱스를 참조하는 MEAN_11을 MEAN_1로 변경하세요. 특히 OldScaleParms$est 및 OldScaleParms$value 갱신이
MEAN_1을 사용하도록 수정하고, NewScaleParms의 MEAN_1 참조도 일관되게 유지하세요.

Source: MCP tools

}
if (freeMEAN == T) {
LinkedModelSyntax <-
Expand All @@ -875,8 +873,8 @@ autoFIPC <-
'MEAN = F1'
))

NewScaleParms[NewScaleParms$name == "MEAN_1", "est"] <- TRUE
OldScaleParms[OldScaleParms$name == "MEAN_1", "est"] <- TRUE
NewScaleParms$est[NewScaleParms$name == "MEAN_1"] <- TRUE
OldScaleParms$est[OldScaleParms$name == "MEAN_1"] <- TRUE
} else {
LinkedModelSyntax <-
mirt::mirt.model(paste0(
Expand Down
Loading