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
86 changes: 86 additions & 0 deletions experiments/crosscancer/covariate_baseline/BIOP02-140_FINDINGS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# BIOP02-140 v1 — 폐 공변량 기준선 · 증분가치 (KRAS/EGFR/histology)

`claim_level: hypothesis_only` · `critic_status: pending` · jamie, 2026-08-20
방법론 = `COVARIATE_BASELINE_PREREGISTRATION.md`(결과 전 고정, 실행 중 발견한 정정 1건 포함).
스크립트: `fetch_covariates_lung.py`(공변량 조회) → `audit_covariate_incremental_value.py`(분석).
전량 CPU, 재학습 없음(기존 임베딩·`mil_cost_results.json` H&E 예측값 재사용).

## v1 범위

티켓 본문의 완화 옵션대로 **폐(LUNG_NSCLC) 한 암종, KRAS·EGFR·histology_lusc 세 엔드포인트**만.
대장·위·두경부는 후속 카드(BIOP02-139가 HPV/grade_high/ERBB2_amp를 스킵한 것과 같은 판단).

## 공변량

purity(ABSOLUTE, Aran 2015 — 매칭 961/1050) · stage(cBioPortal AJCC, I/II/III/IV, 매칭
993/1050) · 해부학적 부위(ICD_O_3_SITE lobe, 매칭 996/1050) · cohort(LUAD/LUSC, 기존 라벨,
histology_lusc 엔드포인트에는 순환논리 방지로 제외). **grade는 실측 커버리지 0/1050으로 확인 후
제외**(TCGA 폐 임상데이터셋 자체에 없음 — 체리피킹 아님, `fetch_covariates_lung.py` 실행 로그가
근거). 결측은 전부 own category(대체·보간 없음).

## 방법 정정 1건 (실행 중 발견, 결과 계산 전)

원안은 공변량 모델을 `split.csv` train으로 학습→holdout 평가였는데, H&E 예측값
(`mil_cost_results.json`의 `patient_proba`)이 **holdout에만 존재**해 결합모델을 이 방식으로
학습할 수 없음을 `KeyError`로 발견했다(이 시점까지 AUROC 미계산). **holdout 271명 안에서
5-fold StratifiedKFold(seed=42) out-of-fold**로 방법을 바꿔 재등록 후 실행 — 전체 n·n_pos는
원안과 동일해 검정력 손실 없음. 상세 = 사전등록 문서 "정정" 절.

## 결과

| 엔드포인트 | n_pos | H&E-only(인용) | 공변량-only | 결합(공변량+H&E) | ΔAUROC (95% CI) | 판정 |
|---|---|---|---|---|---|---|
| histology_lusc (양성대조) | 153 | 0.939 | **0.586** (0.516–0.654) | 0.931 (0.895–0.962) | **+0.345** (0.278–0.415) | 🟢 유지 |
| egfr_activating | 15 | 0.852 | **0.816** (0.700–0.906) | 0.881 (0.766–0.952) | +0.065 (0.006–0.127) | **판정 불가**(n_pos<25) |
| kras_g12c | 14 | 0.681 | **0.802** (0.738–0.854) | 0.801 (0.739–0.855) | **−0.0004** (−0.009–0.007) | **판정 불가**(n_pos<25) **+ 🔴 공변량-only가 H&E 이상** |

**cohort(LUAD/LUSC) 단일공변량 검증** (티켓 본문 인용 "조직형만으로 예측하면 0.793" 확인):
kras_g12c 단독 cohort 모델 AUROC **0.7795**(5-fold OOF) — 티켓이 인용한 0.793과 방향·크기가
일치(정확히 같은 방법으로 낸 숫자가 아니라 소수점은 다르지만, **결론은 재현됨**). egfr_activating
cohort 단독 = 0.7173.

## 읽기

1. **양성대조 통과** — histology_lusc는 공변량-only가 거의 무작위(0.586)인데 H&E를 더하면
즉시 0.931로 뛴다(ΔCI가 0을 크게 벗어남). 파이프라인이 정상 작동하고, 결합모델·부트스트랩
설계 자체가 신뢰할 만하다는 증거.
2. **KRAS는 티켓의 우려보다 더 나쁘다.** 티켓은 "조직형만으로 0.793 vs H&E 0.681"을 반증으로
들었는데, **purity·stage·site·cohort를 전부 합친 공변량-only 모델이 0.802**로 H&E(0.681)를
더 크게 앞선다. 게다가 H&E를 공변량 위에 얹어도(결합모델 0.801) **사실상 아무것도 더하지
않는다**(Δ=-0.0004, CI가 0을 딱 걸치고 거의 대칭). **H&E가 KRAS 상태에 대해 공변량(주로
조직형)이 이미 아는 것 이상을 읽는다는 증거가 없다.**
3. **EGFR은 방향은 비슷하지만 덜 극단적이다.** 공변량-only(0.816)가 H&E-only(0.852)에
근접하고, ΔAUROC CI([0.006, 0.127])는 형식상 0을 배제하지만 **n_pos=15로 프로젝트 자체
기준(`LAW_HELDOUT_SCOREBOARD.md`, n_pos<25→exploratory)에 못 미쳐 사전등록 규칙대로
"판정 불가"로 보고한다.** CI 하한이 0.006으로 0에 거의 붙어 있다는 것 자체가 표본이
작다는 신호이지, 신뢰할 만한 양의 증분이라는 신호가 아니다.
4. **순도 층화(둘 다 층당 n_pos 5~10, 참고용):** egfr는 고순도(0.862)·저순도(0.842) 거의
차이 없음. kras는 고순도(0.756)·저순도(0.595, 거의 무작위)로 갈리는데 — **표본이 너무
작아 확증할 수 없지만**, 만약 실재한다면 "KRAS의 약한 H&E 신호가 있다면 고순도 샘플에
집중돼 있다"는 가설과 방향이 맞는다. 판정에는 쓰지 않음(사전등록 n_pos<5 하한은 안
걸렸지만 여전히 매우 작은 표본).

## 결정지도 제안 (독립 리뷰 대기, jamie 임의 반영 아님)

- **kras_g12c: 🔴로 본문 명시 권장.** "H&E가 KRAS 상태를 예측한다"가 아니라 "공변량(조직형
주도)이 예측하고 H&E는 그 위에 추가 정보가 없다"로 서술을 바꿔야 함 — 데이터가 이걸
지지한다(위 3번).
- **egfr_activating: 판정 보류, 결정지도 변경 안 함.** 방향은 kras와 같지만(공변량이 대부분
설명) 표본이 너무 작아 🟡 강등을 단정할 근거는 아니다. Paper C 본문에 "탐색적, 표본 부족"
각주는 필요.
- **histology_lusc: 변경 없음(원래 양성대조).**

## DoD 체크

- [x] 공변량 기준선 모델 정의·문서화 (결과 보기 전) — `COVARIATE_BASELINE_PREREGISTRATION.md`
- [x] 엔드포인트별 기준선/H&E/결합 AUROC 표 — 위
- [x] ΔAUROC + CI (bootstrap) — 위
- [x] 순도 층화 결과 — 위(표본 작음, 참고용)
- [x] 결정지도에서 강등할 칸 목록 — kras_g12c(🔴 권고), egfr_activating(보류)
- [ ] **독립 리뷰: 지용기(braveji)** — 아직

## 스킵/후속

- 폐 이외 4개 암종(대장·위·두경부 + 유방 anchor)은 v1 범위 밖 — 별도 카드로 분리 제안.
- LUAD-only 부분집합(조직형 혼합효과 배제) 정밀해부는 v1에서 안 함 — egfr/kras 표본이 이미
n_pos 14~15인데 LUAD로만 좁히면 더 줄어 무의미할 가능성 높음. 필요하면 후속 요청.
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# BIOP02-140 공변량 기준선 — 방법론 사전등록 (v1, 폐 한정)

`claim_level: hypothesis_only` · 작성 2026-08-20, jamie · **결과를 보기 전에 고정**(DoD 1번 항목).

## 범위 (v1)

티켓 본문이 명시한 부담 완화 옵션을 사용: **폐(LUNG_NSCLC) 한 개 암종만, KRAS·EGFR·histology 세
엔드포인트.** 이유는 티켓이 이미 준 것과 같다 — 반증(KRAS 0.681 vs 조직형-only 0.793)이 이미 여기
있고, 티켓 본문 4번 항목("폐 KRAS/EGFR 정밀 해부")도 폐부터 시작하라고 명시한다. 대장·위·두경부는
후속 카드로 분리(BIOP02-139가 HPV/grade_high/ERBB2_amp를 스킵한 것과 같은 판단).

## 공변량 정의 (신규 데이터 fetch 필요분만)

| 공변량 | 출처 | 커버리지(폐, n=1051 중) | 처리 |
|---|---|---|---|
| 종양순도 | ABSOLUTE purity (Aran et al. 2015, *Nat Commun* 6:8971) — `TCGA_mastercalls.abs_tables_JHU_UCSC.txt`, GDC file id `4f277128-f793-4354-a13d-30cc7fe9f6b5` | 확인 예정(fetch 스크립트가 join 후 보고) | 연속값, StandardScaler. 환자당 첫 매칭 tumor(`-01`) 샘플. 결측 환자는 "missing" 이진 플래그 + 값은 0으로 대체(모델이 결측을 별도로 학습하도록 — 값 추정/보간 안 함) |
| 조직형(cohort, LUAD/LUSC) | 기존 `patient_labels.csv`의 `cohort` 컬럼(이미 있음, 신규 fetch 아님) | 100% | **egfr_activating·kras_g12c에는 포함.** **histology_lusc에는 제외**(그 자체가 라벨이라 포함하면 순환논리) |
| stage | cBioPortal `AJCC_PATHOLOGIC_TUMOR_STAGE` (luad/lusc_tcga_pan_can_atlas_2018, PATIENT-level) | 실측: LUAD 512/1026, LUSC 484/969 (약 절반) | `STAGE IA/IB→I`, `IIA/IIB→II`, `IIIA/IIIB→III`, `IV→IV`(하위구분 접미사 제거, 표준 관행). 결측은 own category `"missing"`(대체 없음) — one-hot, `missing` 포함 |
| 해부학적 부위 | cBioPortal `ICD_O_3_SITE` | 실측: LUAD 514/1026, LUSC 485/969 | ICD-O-3 lobe 코드 그대로 category화(C34.0 주기관지/C34.1 상엽/C34.2 중엽/C34.3 하엽/C34.8 중복/C34.9 NOS). 결측은 own category `"missing"` |
| grade | cBioPortal `GRADE` | **실측 0/1026, 0/969 — 폐 코호트에 전혀 없음**(TCGA 폐 임상 데이터셋 자체가 등급을 안 매김, 유방과 다른 관행) | **v1에서 제외.** 체리피킹 아님 — 사전 실측으로 커버리지 0% 확인 후 배제(코드로 확인, 근거 = fetch 스크립트 출력 로그). 남는 4종(폐 이외 암종 포함) 카드 후속에서 필요 시 재확인 |

## 기준선 모델

- `sklearn.linear_model.LogisticRegression(max_iter=2000, C=1.0)`, 범주형은 one-hot(결측 포함),
연속값(purity)은 `StandardScaler`.
- **학습**: 기존 `split.csv`의 `train` 환자만. **평가**: 기존 `mil_cost_results.json`의
`patient_proba` 키 집합(= site-disjoint holdout, val+test pooled, n=271) — H&E 모델과 정확히
같은 평가 대상이라야 비교가 성립한다.
- 결측으로 인한 환자 제외 없음(missing category가 흡수) — 안 그래도 n_pos 14~15인 표본을 더
줄이지 않기 위해서다.

## 증분가치(핵심 지표)

- **결합모델** = 공변량 + H&E 예측확률(`mil_cost_results.json`의 `patient_proba`, 단일 실수
피처로 추가) → 같은 LogisticRegression, 같은 train/holdout.
- **ΔAUROC = AUROC(결합모델, holdout) − AUROC(공변량-only, holdout)**.
- **CI**: paired bootstrap, n=1000·seed=42(`run_mil_cost.py`의 `bootstrap_auc` 컨벤션과 동일
n/seed) — 매 반복에서 **같은 재표집 인덱스**로 두 AUROC를 계산해 차이를 내는 방식(두 모델의
공유 표본변동을 상쇄하기 위해 독립 bootstrap이 아니라 paired로 함). `기존 run_mil_cost.py`의
`bootstrap_auc()`를 단일 AUROC 계산에 그대로 재사용.

## 순도 층화

- 층화 기준은 **holdout 집합(n=271) 안에서** 계산한 purity 중앙값(**train이 아니라 holdout** —
층화의 목적이 holdout 안에서 성능이 갈리는지를 보는 것이므로). 동점은 저순도 쪽으로.
- 층별로 H&E-only bootstrap AUROC(`run_mil_cost.py` 컨벤션 재사용) 계산.
- **사전에 박아두는 검정력 하한**: 층별 `n_pos < 5`면 AUROC 점추정을 계산하되 **"검정력 부족,
판정 불가"로만 보고**하고 🟢/🟡/🔴 판정에 쓰지 않는다. egfr_activating(n_pos=15)·kras_g12c
(n_pos=14)는 전체가 이미 프로젝트 자체 기준(`LAW_HELDOUT_SCOREBOARD.md`, n_pos<25→exploratory)
에 못 미치므로, 층화하면 층당 7명 안팎 — **결과가 어떻게 나오든 이 두 엔드포인트의 층화는
"판정 불가"로 보고될 가능성이 높다는 걸 미리 밝혀둔다**(사후 정당화 방지, 결과를 보고 이
하한을 바꾸지 않는다).

## 판정 규칙 (티켓 본문 그대로, 결과를 보기 전에 재확인)

| 결과 | 결정지도 영향 |
|---|---|
| ΔAUROC의 95% CI가 0을 배제 | 🟢 유지 |
| ΔAUROC의 95% CI가 0을 포함 | 🟡 강등 — "H&E가 예측"이 아니라 "공변량이 예측" |
| 공변량-only AUROC 점추정이 H&E-only(mil_cost의 `real.auc`) 점추정보다 높음 | 🔴 본문 명시 |

**검정력 부족(층 n_pos<5, 또는 전체 n_pos<25)이면 위 표를 적용하지 않고 "판정 불가"로만 보고**
— 이건 결과를 보기 전에 정하는 규칙이지, 약한 신호를 가리려는 사후 조치가 아니다.

## 히스토리 검증 대상 (사전등록)

티켓 본문이 인용한 "조직형만으로 예측하면 0.793"은 **아직 코드로 확인된 수치가 아니다**
(CLAUDE.md 금지사항 — 발표/티켓 텍스트의 숫자를 그대로 근거로 쓰지 않는다). 이 사전등록이 끝난
직후 첫 번째로 계산할 값 = "cohort(LUAD/LUSC) 단일 공변량 → kras_g12c 예측 AUROC, 위와 동일한
train/holdout". 이 값이 0.793과 다르게 나와도(더 높든 낮든) **있는 그대로 보고**하고, 티켓 본문
수정은 별도로 제안한다.

## ⚠️ 정정 (2026-08-20, 실행 중 발견 — 결과를 보기 전) — train/holdout 분리 방법 변경

원안(공변량 모델을 `split.csv`의 train으로 학습, holdout으로 평가)을 실행하다 `mil_cost_results.json`
의 `patient_proba`가 **holdout(val+test)에 대해서만 저장돼 있고 train 환자에 대한 H&E 예측값은
존재하지 않는다**(재학습 없이는 만들 수 없음, GPU 필요 — v1 CPU-only 스코프 밖)는 걸
`KeyError`로 확인했다. **이 시점까지 AUROC·ΔAUROC 어떤 수치도 계산되지 않은 상태**였다 — 약한
결과를 보고 방법을 바꾼 게 아니라, 방법 자체가 데이터 가용성 때문에 애초에 불가능했다는 걸
첫 실행에서 잡은 것이다.

**수정된 방법**: 공변량-only 모델과 (공변량+H&E) 결합모델 둘 다 **holdout 271명 안에서
5-fold StratifiedKFold(seed=42)** 교차검증으로 학습·평가한다(폴드 안에서 공변량 모델을 4/5로
학습 → 나머지 1/5 예측, 5개 폴드 풀링해 out-of-fold AUROC). H&E 예측값은 이미 고정된 값(재학습
없이 피처로만 사용)이라 이 설계는 "고정된 블랙박스 점수 위에 공변량이 얼마나 더하는가"를 측정하는
표준적 방법이고, 전체 n(271)·n_pos(14~15)는 원안과 동일해 검정력 손실이 없다. `train_ids`(원래
`split.csv`의 train)는 이 비교에서 더 이상 쓰지 않는다. 판정 규칙·순도 층화·검정력 하한은
변경 없음.

## 재사용

- `run_mil_cost.py`의 `bootstrap_auc()` — 그대로 import.
- `mil_cost_results.json`의 `patient_proba` — H&E 예측확률 원천, 재계산 안 함.
- `patient_labels.csv`/`split.csv` — 기존 라벨·split, 변경 없음.
- 신규: purity/stage/site fetch만 (grade는 위에서 실측 후 배제).
Loading
Loading