1 5.5 — Comparison of Survival Curves
두 KM 곡선이 시각적으로 분리되어 보일 때, 그 차이가 통계적으로 유의한지 어떻게 판정하는가. Log-rank 검정 이 표준 도구다 (Woodward, 2014, Ch.5.5).
각 사건 시점 \(t_i\) 에서 두 군 (1, 2) 의 위험 인구 \(n_{1i}, n_{2i}\) 와 사건 수 \(d_{1i}, d_{2i}\) 를 정의. 군 1 의 기대 사건 수 (귀무가설 — 두 군 위험률 동일) :
\[ E_{1i} = d_i \cdot \frac{n_{1i}}{n_i} \quad \text{where } d_i = d_{1i} + d_{2i}, \, n_i = n_{1i} + n_{2i} \]
분산: \[ V_i = \frac{n_{1i} n_{2i} d_i (n_i - d_i)}{n_i^2 (n_i - 1)} \]
검정 통계량: \[ \chi^2 = \frac{\left( \sum_i (d_{1i} - E_{1i}) \right)^2}{\sum_i V_i} \]
자유도 1 의 카이제곱 분포를 따른다 (귀무가설 하).
1.1 수식의 의미 — 매 시점의 “기대 vs 관찰”
- 추상: \(d_{1i} - E_{1i}\) 는 시점 \(t_i\) 에서 군 1 이 무작위 분배(비례)보다 사건을 더 또는 덜 겪었는가의 부호. 귀무 하에서 평균 0.
- 일상어 비유: 농구 경기에서 매 쿼터마다 “기대 점수 vs 실제 점수” 의 차이를 누적. 한 쿼터의 운보다 누적 패턴이 진성 실력 차이를 드러냄.
- 반사실: 두 군의 위험률이 같다면 매 시점에 사건이 비례 분배 → 누적 편차 0 근처. 다르면 한 군에 편향 누적 → 큰 통계량.
1.2 비례 위험 가정 하에서 가장 강한 검정
Log-rank 는 proportional hazards 가정 하에 가장 강한 검정력을 가진다. 즉 \(h_1(t)/h_2(t) = \text{const}\) 가정.
가설: 새 약이 초기에 큰 효과(낮은 위험률) 를 주지만 1 년 후 효과 소멸. 두 군의 hazard 가 1 년 시점에 교차.
3 단계 직관:
- 추상: \(h_1(t)/h_2(t)\) 가 시간에 따라 1 위/아래로 이동. 비례 위험 위반.
- 일상어 비유: 마라톤에서 한 주자가 초반 빠르고 후반 느린 반면 다른 주자가 초반 느리고 후반 빠르면, 누적 거리는 비슷하나 어느 쪽이 빠른지 단순 비교 어려움.
- 반사실: 비례 위험이 깨질 때 log-rank 는 시간 평균 효과만 보아 진성 패턴 (cross-over) 을 가린다. 검정력 ↓ 또는 false negative.
해법: 가중 log-rank (Wilcoxon-Gehan-Breslow, Tarone-Ware), 시간 의존 Cox, restricted mean survival time (RMST).
1.3 Stratified Log-Rank — 교란 통제
연령·성별 같은 교란 변수 \(S\) 의 strata 별로 Log-rank 통계량을 계산하고 합산.
\[ \chi^2_{\text{strat}} = \frac{\left( \sum_s \sum_i (d_{1i,s} - E_{1i,s}) \right)^2}{\sum_s \sum_i V_{i,s}} \]
각 stratum 안에서 두 군이 비교되므로, stratum 간의 baseline 위험률 차이가 통제된다. 이는 Cohort 의 Mantel-Haenszel 일반화.
1.4 시각적 해석 — Pointwise CI 와 차이의 유의성
KM 곡선의 음영 (pointwise CI) 이 두 군에서 겹치지 않는다고 해서 곧 통계적 유의를 의미하지 않는다.
- Pointwise CI: 각 시점에 진성 \(S(t)\) 가 95% 확률로 그 안.
- Log-Rank: 전 시간 축에서 두 곡선의 진성 차이가 0 인가의 검정.
두 곡선이 한 시점에서 시각적으로 떨어져 있어도 log-rank p-값이 0.2 일 수 있음 — 다른 시점에서 비슷하면 누적 편차가 작아짐.
2 5.6 — Competing Risks
KM 의 핵심 가정은 censoring 과 사건이 독립이라는 것이다. 그러나 경쟁 위험(competing risks) 은 이 가정을 직접 깬다.
관심 사건과 다른 사건이 같은 사람에서 발생할 수 있고, 다른 사건의 발생이 관심 사건 발생을 영구히 차단하는 상황 (Woodward, 2014, Ch.5.6).
예시:
- 관심: 폐암 사망 / 경쟁: 심혈관 사망 (전자가 차단)
- 관심: 첫 결혼 / 경쟁: 사망
- 관심: 골절 / 경쟁: 사망 (특히 고령)
2.1 KM 이 깨지는 이유
KM 은 censoring 시점이 사건 시점과 독립이라 가정. 경쟁 위험은 이를 위반.
3 단계 직관:
- 추상: \(T_{\text{관심}} \perp T_{\text{경쟁}}\) — 관심 사건과 경쟁 사건이 독립일 필요. 그러나 같은 사람에서 두 사건이 발생할 수 있고, 둘 다 사람의 건강 상태에 의존하므로 일반적으로 종속.
- 일상어 비유: 등산에서 정상 도달(관심)과 부상으로 하산(경쟁) 이 모두 체력에 의존. 부상자를 단순 censoring 으로 처리하면 “정상 도달했다” 가정에 묻혀 도달률이 부풀려짐.
- 반사실: 경쟁 위험을 무시하고 KM 만 적용하면, \(1 - \hat S(t)\) 가 진성 누적 발생 확률을 과대 추정. 가상의 시나리오 (경쟁 위험 0) 에서의 발생 확률을 추정하는 것이 됨.
2.2 두 가지 추정량 — KM vs CIF
2.2.1 1. Naive 1 - KM (Cause-Specific Marginal)
경쟁 사건을 censoring 처리한 KM 으로부터 \(1 - \hat S_k(t)\).
문제: 이는 “다른 모든 경쟁 사건이 0 이었다면 발생할 누적 확률” 의 추정 — 실제 데이터의 누적 사건율과 다르다.
2.2.2 2. Cumulative Incidence Function (CIF) — Aalen-Johansen
원인 \(k\) 의 누적 발생 확률: \[ \hat F_k(t) = \sum_{t_i \le t} \hat S(t_i^-) \cdot \frac{d_{ki}}{n_i} \]
여기서 \(\hat S(t_i^-) = \prod_{t_j < t_i} (1 - d_j / n_j)\) 는 모든 원인 통합 KM (시점 \(t_i\) 직전).
\(d_{ki}\) 는 시점 \(t_i\) 에서 원인 \(k\) 의 사건 수.
해석: \(\hat F_k(t)\) 는 “시점 \(t\) 까지 원인 \(k\) 의 사건이 발생한 비율” 의 진성 추정. 모든 경쟁 사건 \(\sum_k F_k(t) + S(t) = 1\).
2.3 직관 3 단계 — CIF 의 핵심
- 추상: \(S(t_i^-)\) 가 “시점 \(t_i\) 까지 모든 사건 미발생자” 분율. 거기서 시점 \(t_i\) 에 원인 \(k\) 사건이 새로 발생할 비율을 곱하여 누적.
- 일상어 비유: 100 명이 출발 → 시점 5 까지 70 명이 무사 → 시점 5 에 원인 A 로 5 명, 원인 B 로 3 명 사망. CIF_A 는 5 명이 70 명 출발자에서 발생한 비율로 누적.
- 반사실: KM 이 원인 A 만 보고 원인 B 를 censoring 처리하면, 시점 5 에서 70 명 중 5 명 → 5/70 ≈ 7%. 실제로는 100 명에서 5 명 → 5%. 차이가 7% vs 5% 의 형태로 KM 이 과대 추정.
2.4 Cause-Specific Hazard vs Subdistribution Hazard
경쟁 위험에서 두 가지 hazard 함수.
2.4.1 Cause-Specific Hazard
\[ h_k^{cs}(t) = \lim_{\Delta t \to 0} \frac{P(t \le T < t + \Delta t, \text{cause} = k \mid T \ge t)}{\Delta t} \]
분모: 모든 사건 미발생자. 직관적으로 “지금 무사한 자가 다음 순간 원인 \(k\) 를 겪을 비율”.
2.4.2 Subdistribution Hazard (Fine-Gray)
\[ h_k^{sd}(t) = \lim_{\Delta t \to 0} \frac{P(t \le T < t + \Delta t, \text{cause} = k \mid T \ge t \, \text{or} \, (T < t \, \text{and cause} \neq k))}{\Delta t} \]
분모: 원인 \(k\) 의 사건이 아직 안 일어난 자 (다른 원인으로 사망한 자도 분모에 남김). 직관적으로 “원인 \(k\) 의 누적 발생 확률이 시간에 따라 어떻게 변하는가” 를 직접 모형화.
- 추상: Cause-specific 는 즉각적 위험률, subdistribution 은 누적 발생 확률의 변화율. 전자는 사망 후 분모 제외, 후자는 사망 후도 분모에 남김.
- 일상어 비유: 등산에서 정상 도달 위험률을 (1) 살아있는 등산객 중에서만 vs (2) 부상자도 분모에 포함하여 측정. 두 비율은 다르고, 둘 다 의미 있다.
- 반사실: Cause-specific Cox 모형은 etiology(원인 메커니즘)에 강하고, subdistribution (Fine-Gray) 모형은 prediction(예측)에 강하다. 두 모형의 HR 해석이 달라 혼동 주의.
2.5 Fine-Gray Model
\[ h_k^{sd}(t \mid \mathbf{x}) = h_{0k}^{sd}(t) \exp(\beta^T \mathbf{x}) \]
Cox PH 의 cause-specific 버전. 직접적으로 CIF 의 효과를 모형화.
2.6 어느 모형을 언제 쓰는가
| 목표 | 모형 | 이유 |
|---|---|---|
| 인과 메커니즘 (etiologic) | Cause-specific Cox | 위험률의 즉각 변화 |
| 환자 예측 (prognostic) | Fine-Gray (subdistribution) | 누적 발생 확률 변화 |
| 두 시각 모두 | 양쪽 모형 보고 | 결과의 강건성 |
2.7 사례 — 골절과 사망의 경쟁 위험
가설: 70 세 여성 1,000 명 추적 — 5 년 안에 골반 골절 발생률 측정.
5 년 사이에: - 골절 발생: 80 명 - 골절 없이 사망: 200 명
KM 추정 (사망을 censoring): \(1 - \hat S(5) \approx 80 / 800 = 10\%\) (단순 추정).
CIF 추정: \(\hat F_{\text{골절}}(5) \approx 80 / 1000 = 8\%\).
차이 2%p — 사망이 골절을 차단하므로 진성 발생률이 KM 보다 낮다. 임상 의사가 환자에게 “5 년 안에 골절 위험” 을 말할 때 어느 수치가 적절한가? CIF 가 정직하다.
3 코드 예시 — Log-Rank, CIF, Fine-Gray
import numpy as np
import pandas as pd
from lifelines import KaplanMeierFitter
from lifelines.statistics import logrank_test
from lifelines import CoxPHFitter
# Lifelines 의 경쟁 위험은 다른 패키지 (scikit-survival, R 의 cmprsk) 가 더 강력
# 여기서는 핵심 흐름만
np.random.seed(42)
n = 1000
# 가상 데이터: 두 사건 (관심: 1, 경쟁: 2)
factor = np.random.binomial(1, 0.4, n)
true_T_1 = np.random.exponential(scale=1.0/0.05, size=n)
true_T_2 = np.random.exponential(scale=1.0/0.04, size=n)
true_T_1[factor == 1] /= 1.5
# 더 빨리 발생하는 사건이 관측됨
T = np.minimum(true_T_1, true_T_2)
cause = np.where(true_T_1 < true_T_2, 1, 2)
# Censoring at t=10
C = 10.0
event_indicator = (T <= C).astype(int) # 사건 발생 여부
T_obs = np.minimum(T, C)
cause_obs = np.where(event_indicator == 1, cause, 0) # 0 = censored
df = pd.DataFrame({"T": T_obs, "cause": cause_obs, "factor": factor})
# 1. Log-Rank — cause 1 vs censored
event_cause1 = (df.cause == 1).astype(int)
lr_cause1 = logrank_test(
df.loc[df.factor == 1, "T"], df.loc[df.factor == 0, "T"],
event_observed_A=event_cause1[df.factor == 1].values,
event_observed_B=event_cause1[df.factor == 0].values,
)
print(f"Log-Rank (cause 1, naive): p = {lr_cause1.p_value:.4f}")
# 2. Naive KM 으로 1-S(t) — 과대 추정 가능
kmf_factor = KaplanMeierFitter()
kmf_factor.fit(
df.loc[df.factor == 1, "T"],
event_observed=event_cause1[df.factor == 1].values,
label="Factor",
)
print(f"\nNaive 1 - KM (factor, t=5): {1 - kmf_factor.survival_function_at_times(5.0).values[0]:.3f}")
# 3. CIF — Aalen-Johansen 추정 (lifelines 12+ 의 AalenJohansenFitter 또는 cmprsk)
# from lifelines import AalenJohansenFitter
# aj = AalenJohansenFitter()
# aj.fit(durations=df["T"], event_observed=df["cause"], event_of_interest=1)
# print(aj.cumulative_density_)
# 4. Fine-Gray — sklearn-survival 의 FineGrayModel 또는 R 의 cmprsk
# from sksurv.linear_model import FineGray # (gathered from scikit-survival)주의: lifelines 의 기본 KM 은 경쟁 위험을 직접 다루지 않는다. AalenJohansenFitter (lifelines 0.27+) 또는 scikit-survival, R cmprsk 패키지가 정확한 CIF/Fine-Gray 분석을 제공.
4 결론 — Ch.5.5~5.6 의 메시지
| 도구 | 목적 | 주요 가정 |
|---|---|---|
| Log-Rank | 두 KM 곡선 차이 검정 | Proportional hazards |
| Stratified Log-Rank | 교란 통제 | Stratum 내 PH |
| Naive 1 - KM (cause \(k\)) | 가상 시나리오의 누적 확률 | 경쟁 위험 0 가정 |
| CIF (Aalen-Johansen) | 진성 누적 발생 확률 | 없음 (비모수) |
| Cause-Specific Cox | 메커니즘 모형 | PH per cause |
| Fine-Gray | 예측 모형 | Subdistribution PH |
경쟁 위험이 의심되면 CIF 와 Fine-Gray 가 표준. KM 은 모든 경쟁 사건이 0 인 가상 시나리오를 추정하므로, 환자에게 직접 보여주기에는 부적절.
다음 글(B14) 에서는 가변 추적의 또 다른 분석 도구인 person-year 방법과, 긴 추적의 시간·연령·코호트 분리 분석인 APC 모형 을 다룬다.
5 관련 주제
선행
후속
다른 카테고리