본문으로 이동
메뉴 여닫기
환경 설정 메뉴 여닫기
개인 메뉴 여닫기
로그인하지 않음
지금 편집한다면 당신의 IP 주소가 공개될 수 있습니다.
Survival Analysis; 생존분석, Time-to-Event Analysis
어떤 사건(사망, 고장, 이탈, 재범 등)이 일어날 때까지 걸리는 시간을 분석하는 통계 기법

관심 있는 사건이 일어나기까지의 시간(생존 시간)을 종속변수로 삼는다. 의학의 환자 생존, 공학의 부품 수명(신뢰성 분석), 경영의 고객 이탈(churn)처럼 "언제 일어나는가"가 중요한 문제에 쓴다. 관찰 기간 안에 사건이 일어나지 않은 대상이 섞여 있다는 점이 일반 회귀 분석과 가장 다르다.

중도절단(censoring)은 관찰이 끝날 때까지 사건이 일어나지 않았거나, 중간에 추적이 끊겨 정확한 생존 시간을 모르는 경우이다.

  • 우측 중도절단(right censoring): "적어도 t까지는 생존했다"는 것만 안다. 연구 종료, 추적 불가, 다른 이유로 인한 탈락이 해당하며 가장 흔하다.
  • 좌측 중도절단, 구간 중도절단도 있으나 기본 기법은 우측 중도절단을 다룬다.
  • 중도절단된 관측값을 버리거나 사건으로 취급하면 생존 시간을 과소 추정한다. 생존 분석 기법은 "그때까지 살아 있었다"는 정보를 그대로 활용한다.
  • 기본 가정은 중도절단이 사건 발생과 무관하다는 것(비정보적 중도절단)이다.

생존함수와 위험함수

편집 원본 편집

생존 시간을 확률변수 T라 할 때, 생존함수는 t 시점까지 사건이 일어나지 않을 확률이다.

S(t)=P(T>t)=1−F(t)

위험함수(hazard function)는 t까지 생존한 대상이 바로 다음 순간 사건을 겪을 순간 발생률이다.

h(t)=limΔt→0P(t≤T<t+Δt∣T≥t)Δt=f(t)S(t)

누적위험함수 H(t)=∫0th(u)du와는 S(t)=e−H(t) 관계가 있다.

Kaplan-Meier 추정은 분포를 가정하지 않고(비모수) 생존함수를 계단 모양으로 추정한다. 사건이 일어난 시점 ti마다 위험 집합 크기 ni(그 직전까지 생존하고 중도절단되지 않은 수)와 사건 수 di로 조건부 생존 확률을 곱해 나간다.

S^(t)=∏ti≤t(1−dini)

로그순위 검정(log-rank test)은 두 개 이상 집단의 생존곡선이 같은지를 검정한다. 각 사건 시점에서 집단별 관측 사건 수 O와, 생존곡선이 같다고 할 때의 기대 사건 수 E를 비교하며 통계량은 근사적으로 카이제곱 분포(자유도 = 집단 수 − 1)를 따른다. 귀무가설은 "집단 간 생존함수가 같다"이다.

Cox 비례위험모형은 공변량 x가 위험에 미치는 영향을 추정하는 준모수 회귀 모형이다. 기저위험 h0(t)의 모양은 정하지 않고 공변량 효과만 추정한다.

h(t∣x)=h0(t)exp⁡(β1x1+⋯+βpxp)

  • exp(βj)는 위험비(hazard ratio)이다. xj가 1 증가할 때 위험이 몇 배가 되는지를 뜻하며, 1보다 작으면 위험 감소, 크면 위험 증가이다.
  • 비례위험 가정: 두 대상의 위험비가 시간에 따라 변하지 않고 일정해야 한다. Schoenfeld 잔차 검정(R cox.zph()), 로그-로그 생존곡선이 평행한지로 확인한다. 위반되면 층화(strata), 시간 의존 공변량 등을 쓴다.
기법 종류 목적 공변량 R(survival) Python(lifelines)
Kaplan-Meier 비모수 생존곡선 추정, 중앙 생존 시간 집단 구분만 survfit(Surv(time, status) ~ g) KaplanMeierFitter
로그순위 검정 비모수 집단 간 생존곡선 비교 집단 구분만 survdiff() logrank_test
Cox 비례위험 준모수 여러 공변량의 위험비 추정 연속·범주 모두 coxph(), cox.zph() CoxPHFitter
모수적 생존 모형 모수 지수·와이블 등 분포를 가정한 추정 가능 survreg() WeibullAFTFitter 등
library(survival)
# df: week(관찰 기간), arrest(1=사건, 0=중도절단), fin, age, prio 등
km <- survfit(Surv(week, arrest) ~ fin, data = df)
summary(km, times = c(10, 26, 52))
plot(km, col = 1:2)

survdiff(Surv(week, arrest) ~ fin, data = df)        # 로그순위 검정

cox <- coxph(Surv(week, arrest) ~ fin + age + prio, data = df)
summary(cox)                                         # exp(coef) = 위험비
cox.zph(cox)                                         # 비례위험 가정 검정

lifelines에 들어 있는 Rossi 재범 자료(출소자 432명을 52주 동안 추적, 재정 지원 fin 여부 등)를 쓴다.

import numpy as np
from lifelines import KaplanMeierFitter, CoxPHFitter
from lifelines.datasets import load_rossi
from lifelines.statistics import logrank_test, proportional_hazard_test

df = load_rossi()   # week: 관찰 기간(주), arrest: 1=재체포(사건), 0=중도절단
print(df.shape, "중도절단 비율:", round(1 - df["arrest"].mean(), 3))

kmf = KaplanMeierFitter()
kmf.fit(df["week"], event_observed=df["arrest"], label="all")
print(kmf.survival_function_at_times([10, 26, 52]).round(4))

g1, g0 = df[df["fin"] == 1], df[df["fin"] == 0]   # 재정 지원 받음 / 안 받음
res = logrank_test(g1["week"], g0["week"], g1["arrest"], g0["arrest"])
print(f"로그순위 검정: chi2={res.test_statistic:.3f}, p={res.p_value:.4f}")

cph = CoxPHFitter()
cph.fit(df, duration_col="week", event_col="arrest")
print(cph.summary[["coef", "exp(coef)", "p"]].round(4))
print(f"Concordance={cph.concordance_index_:.4f}")

ph = proportional_hazard_test(cph, df, time_transform="rank")
print(ph.summary[["test_statistic", "p"]].round(4))

실행 결과:

(432, 9) 중도절단 비율: 0.736
10    0.9653
26    0.8750
52    0.7361
Name: all, dtype: float64
로그순위 검정: chi2=3.838, p=0.0501
             coef  exp(coef)       p
covariate
fin       -0.3794     0.6843  0.0474
age       -0.0574     0.9442  0.0090
race       0.3139     1.3688  0.3081
wexp      -0.1498     0.8609  0.4803
mar       -0.4337     0.6481  0.2561
paro      -0.0849     0.9186  0.6646
prio       0.0915     1.0958  0.0014
Concordance=0.6403
      test_statistic       p
age          11.4535  0.0007
fin           0.0151  0.9023
mar           0.7095  0.3996
paro          0.1343  0.7140
prio          0.0188  0.8908
race          1.4265  0.2323
wexp          7.3149  0.0068
  • 432명 중 73.6%가 52주 동안 재체포되지 않아 중도절단되었다. 이들을 버리면 재범 위험을 크게 과대평가하게 된다.
  • Kaplan-Meier 추정으로 52주 시점의 생존율(재체포되지 않을 확률)은 약 0.736이다. 이 자료는 52주 시점의 중도절단이 모두 연구 종료 때문이어서 중도절단 비율과 같아진다.
  • 로그순위 검정의 p-값이 0.0501로 유의수준 0.05 경계에 있어, 재정 지원 여부만으로는 생존곡선 차이가 유의하다고 단정하기 어렵다.
  • Cox 모형에서 다른 변수를 통제하면 fin의 위험비는 0.684(p = 0.047)로, 재정 지원을 받으면 재체포 위험이 약 32% 낮다. 전과 횟수 prio가 1 늘 때마다 위험이 약 1.096배, 나이 age가 1살 늘 때마다 약 0.944배가 된다.
  • 비례위험 검정에서 age와 wexp의 p-값이 0.05보다 작아 비례위험 가정 위반이 의심된다. 이 변수들은 층화하거나 시간과의 상호작용을 넣는 방식으로 보완한다.
  • 중도절단의 뜻과, 중도절단 자료를 버리면 안 되는 이유를 설명할 수 있어야 한다.
  • Kaplan-Meier 추정식 Π(1 − di/ni)과 로그순위 검정의 귀무가설(집단 간 생존함수가 같다)을 기억한다.
  • Cox 모형에서 exp(β)는 위험비이며, 비례위험 가정(위험비가 시간에 따라 일정)을 cox.zph 같은 검정으로 확인한다.
  • 실기에서는 생존곡선 그리기 → 로그순위 검정 → Cox 회귀 → 위험비 해석 순서로 서술하는 흐름이 쓰일 수 있다.