参考教程:http://www.sthda.com/english/wiki/survival-analysis-basics
假设有228个被诊断患有肺癌的病人开始长期的随访,如果病人死亡;则随访结束,并记录生存时间(从确诊到死亡的时间)。而生存分析Survival Analysis 主要回答三个方面的问题:
(1)病人的第5年生存率是多少?病人总体的中位生存率的时间可以到多久?
(2)若根据病人信息(例如性别),将病人分为两组,那么这两组的生存率是否有显著差异?
(3)若有很多病人的临床信息或者分子特征,其中哪些属性会显著影响病人的生存?
1、基本概念
1.1 event事件与survival time生存时间
- 在生存分析中,这里的事件一般是指死亡,在广义的生存分析中,事件可以引申至许多其它含义,例如疾病复发等;
如果处理TCGA的数据,会发现有各种各样的event的定义,可以参考笔记https://www.jianshu.com/p/f07588bcf22e。本文笔记全篇采用的是死亡作为事件。
- 而生存时间是指在事件发生的前提下,从病人确诊到死亡的时间。
1.2 Censor 删失值
- 在随访的过程中,可能无法全部病人都可以收集到准确地生存时间。
- 一方面由于随访项目时间限制,直至随访结束,病人仍在世;一方面可能在随访期间,病人失联、或者因其它原因去世等,无法继续随访。
-
这样的数据就称之为Censor,而此时的生存时间即为所能记录到的最长生存时间。病人的status要么是事件发生了,要么就是Censor。
2、Kaplan-Meier与Log-rank test
2.1方法简介
- Kaplan-Meier简单来说是根据病人的status与survival time计算、预估病人在确诊后时间t时刻的生存率(survival probability)如何。
survivor function关注的是event事件不发生的概率;与之相反,之后会提到的hazard function则主要关注event事件发生的概率
- Log-rank test是指将病人分为两组或者多组的情况下,组间的生存率是否有显著差异。
一般上述二者同时分析
2.2 分析R包与示例数据
- R包
# install.packages(c("survival", "survminer"))
library("survival") #生存分析
library("survminer") #结果可视化
- 示例数据
lung {survival}
head(lung)
# inst time status age sex ph.ecog ph.karno pat.karno meal.cal wt.loss
# 1 3 306 2 74 1 1 90 100 1175 NA
# 2 3 455 2 68 1 0 90 90 1225 15
# 3 3 1010 1 56 1 0 90 90 NA 15
# 4 5 210 2 57 1 1 90 60 1150 11
# 5 1 883 2 60 1 0 100 90 NA 0
# 6 12 1022 1 74 1 1 50 80 513 0
# inst: Institution code
# time: Survival time in days(*) 生存时间
# status: censoring status 1=censored, 2=dead(*) 事件是否发生
# age: Age in years
# sex: Male=1 Female=2
# ph.ecog: ECOG performance score (0=good 5=dead)
# ph.karno: Karnofsky performance score (bad=0-good=100) rated by physician
# pat.karno: Karnofsky performance score as rated by patient
# meal.cal: Calories consumed at meals
# wt.loss: Weight loss in last six months
如果挖掘TCGA的数据,通常
0
表示存活,1
表示死亡
2.3 survival
分析
- 开始分析:按性别将病人分为两组
fit <- survfit(Surv(time, status) ~ sex, data = lung)
print(fit)
# Call: survfit(formula = Surv(time, status) ~ sex, data = lung)
#
# n events median 0.95LCL 0.95UCL
# sex=1 138 112 270 212 310
# sex=2 90 53 426 348 550
如上,n
表示每组的病人数;events
表示每组有多少病人死亡;median
表示中位生存率所对应的生存时间;最后两列表示第三列的95%置信区间。
res.sum <- surv_summary(fit)
head(res.sum[res.sum$sex==1,])
# time n.risk n.event n.censor surv std.err upper lower strata sex
# 1 11 138 3 0 0.9782609 0.01268978 1.0000000 0.9542301 sex=1 1
# 2 12 135 1 0 0.9710145 0.01470747 0.9994124 0.9434235 sex=1 1
# 3 13 134 2 0 0.9565217 0.01814885 0.9911586 0.9230952 sex=1 1
# 4 15 132 1 0 0.9492754 0.01967768 0.9866017 0.9133612 sex=1 1
# 5 26 131 1 0 0.9420290 0.02111708 0.9818365 0.9038355 sex=1 1
# 6 30 130 1 0 0.9347826 0.02248469 0.9768989 0.8944820 sex=1 1
head(res.sum[res.sum$sex==2,])
# time n.risk n.event n.censor surv std.err upper lower strata sex
# 120 5 90 1 0 0.9888889 0.01117336 1.0000000 0.9674682 sex=2 2
# 121 60 89 1 0 0.9777778 0.01589104 1.0000000 0.9477934 sex=2 2
# 122 61 88 1 0 0.9666667 0.01957401 1.0000000 0.9302835 sex=2 2
# 123 62 87 1 0 0.9555556 0.02273314 0.9990942 0.9139143 sex=2 2
# 124 79 86 1 0 0.9444444 0.02556550 0.9929738 0.8982868 sex=2 2
# 125 81 85 1 0 0.9333333 0.02817181 0.9863173 0.8831956 sex=2 2
- 如上好像没有看到
log-rank test
的结果,即组间生存率差异的显著性。其实参看下面的可视化结果是有的,如果想提取出来P值的内容,可以如下操作:
surv_diff <- survdiff(Surv(time, status) ~ sex, data = lung)
# Call:
# survdiff(formula = Surv(time, status) ~ sex, data = lung)
#
# N Observed Expected (O-E)^2/E (O-E)^2/V
# sex=1 138 112 91.6 4.55 10.3
# sex=2 90 53 73.4 5.68 10.3
#
# Chisq= 10.3 on 1 degrees of freedom, p= 0.001
#虽然如上结果是有P值的,但是尝试之后发现从对象中只能提取chisq的值,然后再进一步转换
p.val = 1 - pchisq(surv_diff$chisq, length(surv_diff$n) - 1)
p.val
# [1] 0.001311165
2.4 survminer
结果可视化
(1)生存曲线
ggsurvplot(fit,
pval = TRUE, conf.int = TRUE,
risk.table = TRUE)
图释
上图表示:时间对应的生存率的生存曲线图,曲线中的短竖线表示在时间点有缺失值病人。不同颜色表示不同分组,阴影部分表示95%置信区间;左下角p值表示基于log-rank test计算得到的P值,对于本图的结果p=0.0013,即不同性别的生存率有显著差异。
下面的表:不同时间点对应每组尚未发生事件(死亡)的数目。
图形参数(结合具体代码理解)
p1=ggsurvplot(fit,
pval = TRUE, conf.int = TRUE,
risk.table = TRUE, # Add risk table
risk.table.col = "strata", # Change risk table color by groups
linetype = "strata", # Change line type by groups
surv.median.line = "hv", # Specify median survival
ggtheme = theme_bw(), # Change ggplot2 theme
palette = c("#E7B800", "#2E9FDF"))
p2=ggsurvplot(
fit, # survfit object with calculated statistics.
pval = TRUE, # show p-value of log-rank test.
conf.int = TRUE, # show confidence intervals for
# point estimaes of survival curves.
conf.int.style = "step", # customize style of confidence intervals
xlab = "Time in days", # customize X axis label.
break.time.by = 200, # break X axis in time intervals by 200.
ggtheme = theme_light(), # customize plot and risk table with a theme.
risk.table = "abs_pct", # absolute number and percentage at risk.
risk.table.y.text.col = T,# colour risk table text annotations.
risk.table.y.text = FALSE,# show bars instead of names in text annotations
# in legend of risk table.
ncensor.plot = TRUE, # plot the number of censored subjects at time t
surv.median.line = "hv", # add the median survival pointer.
legend.labs =
c("Male", "Female"), # change legend labels.
palette =
c("#E7B800", "#2E9FDF") # custom color palettes.
)
arrange_ggsurvplots(list(p1,p2))
(2)其它类型曲线
- 参数
fun="event"
,表示cumulative event事件累计发生率
p3=ggsurvplot(fit,
conf.int = TRUE,
risk.table.col = "strata", # Change risk table color by groups
ggtheme = theme_bw(), # Change ggplot2 theme
palette = c("#E7B800", "#2E9FDF"),
fun = "event")
- 参数
fun="cumhaz"
,表示cummulative hazard表示累计风险水平(在时刻t,事件发生的可能性)
p4=ggsurvplot(fit,
conf.int = TRUE,
risk.table.col = "strata", # Change risk table color by groups
ggtheme = theme_bw(), # Change ggplot2 theme
palette = c("#E7B800", "#2E9FDF"),
fun = "cumhaz")
arrange_ggsurvplots(list(p3,p4))
3、Cox Proportional-Hazards Model比例风险模型
3.1 简单理解
- 上面了解的log-rank test用于比较两个分组的生存率是否有显著差异,从而反映出这个分组因子的意义。但由于必须要分组,所以必须使用分类变量,或者将连续变量(基因表达)转为分组变量(中位数划分)。
- 而这里的Cox Proportional-Hazards Model可以理解为回归模型。其中回归模型的响应变量Y是Hazard rate(风险因子,可以类比死亡率);而预测变量就是想验证是否会影响病人生存率的临床特征、基因表达等变量(分类变量、连续变量均可)。通过P值反映变量是否有显著影响;通过变量的系数coefficient表征如何影响Hazard rate,更常用的形式为:Hazard ratio(HR)=exp(coef);如果HR<1(coef<0),则表明该变量越大,风险越低,生存率越高;HR>1(coef>0),则表明该变量越大,风险越高,生存率越小。
3.2 单变量Cox回归
res.cox <- coxph(Surv(time, status) ~ sex, data = lung)
res.cox
# Call:
# coxph(formula = Surv(time, status) ~ sex, data = lung)
#
# coef exp(coef) se(coef) z p
# sex -0.5310 0.5880 0.1672 -3.176 0.00149
#
# Likelihood ratio test=10.63 on 1 df, p=0.001111
# n= 228, number of events= 165
exp(res.cox$coefficients)
# sex
# 0.5880028
- 对多个变量分别进行单变量Cox回归
#分别回归
covariates <- c("age", "sex", "ph.karno", "ph.ecog", "wt.loss")
univ_formulas <- sapply(covariates,
function(x) as.formula(paste('Surv(time, status)~', x)))
univ_models <- lapply( univ_formulas, function(x){coxph(x, data = lung)})
#提取各个变量的回归结果
univ_results <- lapply(univ_models,
function(x){
x <- summary(x)
p.value<-signif(x$wald["pvalue"], digits=2)
wald.test<-signif(x$wald["test"], digits=2)
beta<-signif(x$coef[1], digits=2);#coeficient beta
HR <-signif(x$coef[2], digits=2);#exp(beta)
HR.confint.lower <- signif(x$conf.int[,"lower .95"], 2)
HR.confint.upper <- signif(x$conf.int[,"upper .95"],2)
HR <- paste0(HR, " (",
HR.confint.lower, "-", HR.confint.upper, ")")
res<-c(beta, HR, wald.test, p.value)
names(res)<-c("beta", "HR (95% CI for HR)", "wald.test",
"p.value")
return(res)
#return(exp(cbind(coef(x),confint(x))))
})
res <- t(as.data.frame(univ_results, check.names = FALSE))
as.data.frame(res)
# beta HR (95% CI for HR) wald.test p.value
# age 0.019 1 (1-1) 4.1 0.042
# sex -0.53 0.59 (0.42-0.82) 10 0.0015
# ph.karno -0.016 0.98 (0.97-1) 7.9 0.005
# ph.ecog 0.48 1.6 (1.3-2) 18 2.7e-05
# wt.loss 0.0013 1 (0.99-1) 0.05 0.83
3.3 多变量Cox回归(多元回归)
res.cox <- coxph(Surv(time, status) ~ age + sex + ph.ecog, data = lung)
summary(res.cox)
# Call:
# coxph(formula = Surv(time, status) ~ age + sex + ph.ecog, data = lung)
#
# n= 227, number of events= 164
# (因为不存在,1个观察量被删除了)
#
# coef exp(coef) se(coef) z Pr(>|z|)
# age 0.011067 1.011128 0.009267 1.194 0.232416
# sex -0.552612 0.575445 0.167739 -3.294 0.000986 ***
# ph.ecog 0.463728 1.589991 0.113577 4.083 4.45e-05 ***
# ---
# Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
#
# exp(coef) exp(-coef) lower .95 upper .95
# age 1.0111 0.9890 0.9929 1.0297
# sex 0.5754 1.7378 0.4142 0.7994
# ph.ecog 1.5900 0.6289 1.2727 1.9864
#
# Concordance= 0.637 (se = 0.025 )
# Likelihood ratio test= 30.5 on 3 df, p=1e-06
# Wald test = 29.93 on 3 df, p=1e-06
# Score (logrank) test = 30.5 on 3 df, p=1e-06
ggforest(res.cox, data = lung,
main = "Hazard ratio",
cpositions = c(0.10, 0.22, 0.4),
fontsize = 1.0)
3.4 注意点
(1)如果多变量cox回归结果证明多个变量均影响Hazard rate,那么当想要观察其中一个变量所造成分组的生存率差异(KM plot)时,需要其它变量的值拉到统一水平(均值)
(2)Cox回归一个重要的假设,就是变量对于hazard rate的影响不随时间的变化而变化(等比例),可通过统计方法检验变量数据是否适合Cox回归分析。详见 http://www.sthda.com/english/wiki/cox-model-assumptions