跳到正文
百沐一下百沐一下
本节目录

Cox 回归:单因素与多因素

跟完这篇,您能把一份带随访时间、结局和多个协变量的数据,先用单因素 Cox 把候选变量筛一遍,再放进多因素模型算出校正后的风险比(HR)与置信区间,检验比例风险假设,画一张森林图,并把结果写成一段审稿人挑不出方法硬伤的结论。

Cox 回归在解决什么问题

KM 曲线加 log-rank 能告诉您两组生存有没有差,但它只给一个 p 值,答不了两个关键问题:差多少,以及排除了年龄、分期这些混杂之后还差不差。Cox 比例风险回归就是来补这两块的。

它的输出核心是风险比(hazard ratio, HR)

  • HR > 1:该因素让事件(如死亡)发生得更快,是危险因素。
  • HR < 1:发生得更慢,是保护因素。
  • HR = 1:对风险没有影响。

举例,某基因高表达对比低表达 HR = 2.0,意思是「在任一时刻,高表达组正在发生事件的瞬时风险,是低表达组的 2 倍」。

有三点从一开始就得摆正,免得后面写错:

  1. HR 是「风险速率之比」,不是「多少人会死」,也不是相对风险 RR 或比值比 OR。 它说的是发生的快慢,不直接等于绝对生存概率。
  2. HR 是整个随访期的一个「平均」效应。 这个平均能不能代表全程,取决于下面要讲的比例风险假设。
  3. Cox 半参数、不设定基线生存的具体形状,这正是它比参数模型更常用、更稳的原因。

数据要长什么样

和生存分析一样,最少三类列:随访时间、事件结局、以及您想纳入模型的协变量。

列名含义取值
time从统一起点到事件、或到最后一次随访的时长正数(全表统一单位:月 / 天 / 年)
event随访结束时事件到底发生了没有1 = 发生,0 = 删失
age连续型协变量示例数值
sex / stage分类型协变量示例因子(factor)
head(df)
#>   sample  time  event  age  sex   stage  gene
#> 1   S001   12.4     1   67    M     III  high
#> 2   S002   34.0     0   58    F      II  high     # 随访 34 个月仍存活,删失
#> 3   S003    5.1     1   72    M     III   low

分类变量务必先转成因子,并显式定好参照水平(reference level)——所有 HR 都是相对参照水平来解释的,参照选错,整张表都要重读一遍:

df$stage <- factor(df$stage, levels = c("I", "II", "III"))   # 以 I 期为参照
df$gene  <- factor(df$gene,  levels = c("low", "high"))       # 以低表达为参照

单因素 Cox:先把候选变量筛一遍

多因素模型不宜一股脑塞进所有变量。常规做法是先逐个变量单独跑 Cox,看哪些和结局有关联,再把有苗头的带进多因素模型。

单个变量的写法很短,summary() 里就有 HR、置信区间和 p 值:

library(survival)

fit <- coxph(Surv(time, event) ~ gene, data = df)
summary(fit)
#>            coef  exp(coef)  se(coef)     z   Pr(>|z|)
#> genehigh  0.693     2.000     0.180  3.85    0.00012
#>
#>          exp(coef)  exp(-coef)  lower .95  upper .95
#> genehigh     2.000       0.500      1.406      2.845

读这张输出:exp(coef) 就是 HR(此处 2.0),lower .95 / upper .95 是它的 95% 置信区间(1.41–2.85),Pr(>|z|) 是 p 值。置信区间不跨过 1,与 p<0.05 是一回事,都提示关联显著。

变量多时,套个循环逐个跑,把 HR、CI、p 汇成一张表:

vars <- c("age", "sex", "stage", "gene")

uni_tab <- lapply(vars, function(v) {
  f  <- as.formula(paste("Surv(time, event) ~", v))
  m  <- coxph(f, data = df)
  s  <- summary(m)
  data.frame(
    变量 = rownames(s$coefficients),
    HR   = round(s$coefficients[, "exp(coef)"], 2),
    下限 = round(s$conf.int[, "lower .95"], 2),
    上限 = round(s$conf.int[, "upper .95"], 2),
    p    = signif(s$coefficients[, "Pr(>|z|)"], 2)
  )
})
uni_tab <- do.call(rbind, uni_tab)

筛选阈值要说清一件事:进入多因素的门槛,常按惯例定在单因素 p<0.05,很多研究会放宽到 p<0.1,好让边缘但可能重要的变量有机会被校正。这是常用惯例、按研究和领域调,不是铁律。 另外两点别忘:

  • 临床上公认重要的混杂(如年龄、分期),即便单因素不显著,通常也应保留进多因素模型,因为它们的作用可能被其他变量掩盖。
  • 别用纯自动逐步回归(stepwise)挑变量当唯一依据:它容易过拟合、结果不稳、还难复现。变量取舍最好有临床或前期证据支撑。

多因素 Cox:一起放进模型,得到校正后的 HR

把筛出来的变量用 + 一起写进公式,就是多因素模型。它给出的每个 HR,都是在同时固定其他变量的前提下,该变量单独的效应——这正是「校正混杂」的含义。

multi <- coxph(Surv(time, event) ~ age + stage + gene, data = df)
summary(multi)
#>            coef  exp(coef)  se(coef)     z   Pr(>|z|)
#> age       0.021     1.021     0.009  2.33    0.0198
#> stageII   0.480     1.616     0.240  2.00    0.0455
#> stageIII  0.990     2.691     0.250  3.96   7.5e-05
#> genehigh  0.560     1.751     0.185  3.03    0.0024
#>
#> Concordance = 0.71 (se = 0.02)

怎么读、怎么落到解释:

  • genehigh 的 HR 从单因素的 2.0 降到多因素的 1.75,说明原来那份差异里,有一部分其实是年龄、分期带来的;校正之后基因本身仍有独立作用。这种「校正前后 HR 变化」本身就是要报告、要讨论的信息。
  • 连续变量看「每增加一个单位」的效应。 age 的 HR = 1.021,是每长 1 岁风险增加约 2%。若想按每 10 岁报告,可先把变量除以 10 再建模,解释更直观——换算尺度只影响表述,不改变模型本身
  • 分类变量逐水平对参照解释。 stageIII 的 HR = 2.69,指 III 期对比 I 期(参照)的风险;II 期对比 I 期为 1.62。
  • Concordance(C-index) 衡量模型区分预后好坏的能力,0.5 等于随机、越接近 1 越好,可作为整体表现的一个参考指标。

一条常被审稿人追问的经验法则:每纳入一个变量,至少要有约 10 个事件(events per variable, EPV ≥ 10)。事件数相对变量太少,模型会不稳、HR 偏离、置信区间虚宽。这是广为引用的经验惯例、非硬性规定,但纳入变量前值得先数一数 event == 1 有多少个。

比例风险假设是什么,怎么检验

Cox 之所以能用一个 HR 概括全程,靠的是一条前提——比例风险(proportional hazards, PH)假设:两组的风险之比在整个随访期里大致恒定,不随时间变。 一旦这个假设不成立,那个单一 HR 就是把「早期差很多、晚期不差」之类的时变效应硬压成了一个平均值,会误导读者。

标准检验用 Schoenfeld 残差,函数是 cox.zph()

zph <- cox.zph(multi)
zph
#>          chisq  df      p
#> age       0.72   1  0.40
#> stage     5.90   1  0.015    # p 小,提示该变量可能违反 PH
#> gene      0.31   1  0.58
#> GLOBAL    6.80   3  0.079

plot(zph)      # 看每个变量的 Schoenfeld 残差随时间是否大致水平(水平即满足)

判读:某个变量或 GLOBAL 的 p<0.05,提示该项可能违反 PH 假设p<0.05 仍是常用惯例、按数据判断,不是铁律);plot() 里若残差曲线随时间明显上翘或下滑、而非大致水平,也是同样的信号。除此之外,KM 曲线交叉,往往也预示 PH 不成立。

假设被违反时,几条标准的处理路子(按情形选,不必全上):

做法适用写法要点
分层(stratify)该变量是混杂、但您不关心它的 HR+ strata(stage),让各层有各自的基线风险
加时间交互项您关心该变量、且其效应确随时间变在模型中引入变量与时间的交互(需按 cox.zph 结果与时间函数设定)
分时段报告 HR效应有明显阶段性分早期 / 晚期各报一个 HR,并说明为何分段

最要紧的是:如实报告您检验了 PH 假设、结果如何、违反时怎么处理。藏着不报,比违反本身更容易被挑。

画森林图

森林图是把多因素结果一图看尽的标准图:每个变量一行,点是 HR,横线是 95% 置信区间,竖直参考线在 HR = 1——线越靠右、且整条不压到 1,危险作用越确凿;压过 1 则不显著。

最省事的是 survminer::ggforest,直接吃 coxph 对象:

library(survminer)

ggforest(
  multi, data = df,
  main = "多因素 Cox 回归",
  fontsize = 0.9
)

想要更强的排版控制(自定义分组、合并单因素与多因素两列 HR),可用 forestplot 包手动搭。读图和写作时记住:看的是「线有没有压到竖线 1」,不是单看点估计离 1 多远——置信区间很宽、只是碰巧点估计偏大的变量,结论要留余地。

结果怎么写进论文

图和表都齐了,落到笔头。一段规范的 Cox 结果,通常把这几件事说全:

  1. 两张 HR 一起给。 论文里常见「Table 2」把单因素、多因素的 HR、95% CI、p 并排列出,让读者看清校正前后怎么变。

  2. 交代参照水平和变量尺度。 分类变量以哪一水平为参照、连续变量按每几个单位报告,都要写明,否则 HR 无法解释。

  3. 写清 PH 假设检验过。 一句「采用 Schoenfeld 残差检验比例风险假设」再加结果,是方法完整性的标配。

  4. 一句话范式(可直接改用):

    多因素 Cox 回归显示,在校正年龄与临床分期后,该基因高表达组的死亡风险仍显著高于低表达组(HR = 1.75,95% CI 1.22–2.52,p = 0.002)。

    (句中数字仅为示例格式,请换成您自己的真实结果。)

同时把方法边界一并交代,别等审稿人来问:

  • HR 是时间平均效应,依赖 PH 假设成立。 若假设被违反且未处理,单个 HR 会误导。
  • 只校正了模型里放进去的混杂,没测量到的混杂仍可能有偏;变量间高度共线时,各自 HR 也会不稳。
  • 有竞争风险时(关心的结局会被其他事件「抢走」,如研究本病死亡却有人先死于他因),标准 Cox 会有偏,需竞争风险模型(如 Fine-Gray),本篇不覆盖。
  • 单中心、小样本、EPV 偏低都会让结果偏乐观,如实写进局限。

在平台上跑一遍

以上从单因素筛变量、多因素建模、PH 假设检验到森林图,这整条链在百沐一下都不用写代码:上传带 timeevent 和各协变量的数据表,用一句话说清目的——「对这份数据做 Cox 回归,先单因素筛一遍,把 p<0.1 的变量放进多因素模型,以低表达和 I 期为参照,检验比例风险假设,并画森林图」——平台会自动选方法、算 HR 与置信区间、检验 PH、出图,并给出可复现的 R 代码。想改参照水平、筛选阈值或变量尺度,直接在对话里说明即可。上手见 智能统计使用指南,或直接开始:https://app.baimuyixia.com

相关