跳转至

微生物组生存分析

一句话概述

将肠道微生物组特征(菌群标志物、多样性指标、功能通路)与患者生存结局关联,利用Cox比例风险回归、Kaplan-Meier曲线和时间依赖ROC评估微生物标志物的预后预测价值。


核心知识点总览

知识点关键内容重要程度
微生物预后标志物菌群丰度/比值/多样性与生存关联⭐⭐⭐⭐⭐
Cox回归模型单因素/多因素风险比估计⭐⭐⭐⭐⭐
Kaplan-Meier分析分组生存曲线与log-rank检验⭐⭐⭐⭐⭐
时间依赖ROCtimeROC评估预后预测精度⭐⭐⭐⭐
微生物评分构建多菌组合风险评分⭐⭐⭐⭐
组成性数据处理CLR/ILR变换用于生存模型⭐⭐⭐
竞争风险模型Fine-Gray处理竞争事件⭐⭐⭐
验证策略内部交叉验证/外部队列验证⭐⭐⭐

各步骤详解

第一步:研究设计与数据准备

白话解释: 微生物组生存分析研究的核心问题是:患者肠道菌群的某些特征是否与疾病预后(如总生存期、无进展生存期)相关?这需要前瞻性或回顾性队列中同时具有微生物组数据和随访生存数据的样本。

技术细节: 数据需求: 1. 微生物组数据:16S rRNA或宏基因组测序的物种丰度表 2. 生存数据:time-to-event(随访时间)+ event(是否发生事件/censoring) 3. 协变量:年龄、性别、分期、治疗方案等

# === 数据准备 ===
library(survival)
library(survminer)
library(compositions)

# 读取微生物丰度数据
otu_table <- read.csv("genus_abundance.csv", row.names = 1)
# 行=菌属, 列=样本

# 读取临床随访数据
clinical <- read.csv("clinical_survival.csv")
# 必须包含: patient_id, OS_time(月), OS_status(1=死亡,0=删失)

# CLR变换处理组成性
otu_clr <- as.data.frame(t(clr(t(otu_table) + 0.5)))

# 合并数据
# 确保微生物数据与临床数据的样本ID一致
common_ids <- intersect(colnames(otu_clr), clinical$patient_id)
otu_merged <- t(otu_clr[, common_ids])
surv_data <- merge(
  data.frame(patient_id = rownames(otu_merged), otu_merged, check.names = FALSE),
  clinical,
  by = "patient_id"
)

# 准备生存对象
surv_obj <- Surv(time = surv_data$OS_time, event = surv_data$OS_status)

第二步:单因素Cox回归筛选候选标志物

白话解释: 首先对每个微生物特征(各菌属丰度、多样性指数等)分别做Cox回归,看哪些与生存显著相关。这一步是"海选"——从数百个微生物特征中筛选出有预后意义的候选标志物。

技术细节:

# === 单因素Cox回归批量筛选 ===

# 对每个菌属做单因素Cox
genus_names <- colnames(otu_merged)
univariate_results <- data.frame()

for (genus in genus_names) {
  formula <- as.formula(paste0("surv_obj ~ `", genus, "`"))
  cox_fit <- coxph(formula, data = surv_data)
  cox_summary <- summary(cox_fit)

  result <- data.frame(
    genus = genus,
    HR = cox_summary$conf.int[1, 1],      # Hazard Ratio
    HR_lower = cox_summary$conf.int[1, 3], # 95% CI lower
    HR_upper = cox_summary$conf.int[1, 4], # 95% CI upper
    p_value = cox_summary$coefficients[1, 5],
    concordance = cox_summary$concordance[1]
  )
  univariate_results <- rbind(univariate_results, result)
}

# 多重检验校正
univariate_results$padj <- p.adjust(univariate_results$p_value, method = "BH")

# 筛选显著标志物
sig_markers <- univariate_results[univariate_results$padj < 0.1, ]
sig_markers <- sig_markers[order(sig_markers$p_value), ]
print(sig_markers)

# 森林图展示
library(forestplot)
forest_data <- sig_markers[1:min(20, nrow(sig_markers)), ]
forestplot(
  labeltext = forest_data$genus,
  mean = log(forest_data$HR),
  lower = log(forest_data$HR_lower),
  upper = log(forest_data$HR_upper),
  zero = 0, xlab = "log(Hazard Ratio)"
)

第三步:Kaplan-Meier生存分析

白话解释: 对筛选出的显著标志物,根据其丰度高低将患者分组(通常按中位数或最优截点),然后画Kaplan-Meier生存曲线,直观展示不同菌群特征组的生存差异。

技术细节:

# === Kaplan-Meier 分析 ===

# 方法1:按中位数分组
marker <- "Fusobacterium"
surv_data$marker_group <- ifelse(
  surv_data[[marker]] > median(surv_data[[marker]]),
  "High", "Low"
)

# KM曲线
fit_km <- survfit(surv_obj ~ marker_group, data = surv_data)
ggsurvplot(fit_km, data = surv_data,
           pval = TRUE, pval.method = TRUE,
           risk.table = TRUE,
           palette = c("#E41A1C", "#377EB8"),
           title = paste0(marker, " and Overall Survival"),
           xlab = "Time (months)", ylab = "Survival probability",
           legend.title = marker,
           legend.labs = c("High", "Low"))

# 方法2:最优截点(surv_cutpoint)
cutpoint <- surv_cutpoint(surv_data, time = "OS_time", event = "OS_status",
                           variables = marker)
surv_data$optimal_group <- surv_categorize(cutpoint)[[marker]]

fit_optimal <- survfit(surv_obj ~ optimal_group, data = surv_data)
ggsurvplot(fit_optimal, data = surv_data, pval = TRUE, risk.table = TRUE)

# 方法3:三分位分组
surv_data$tertile_group <- cut(surv_data[[marker]],
                                breaks = quantile(surv_data[[marker]], c(0, 1/3, 2/3, 1)),
                                labels = c("Low", "Medium", "High"),
                                include.lowest = TRUE)
fit_tertile <- survfit(surv_obj ~ tertile_group, data = surv_data)
ggsurvplot(fit_tertile, data = surv_data, pval = TRUE)

第四步:多因素Cox回归与风险评分

白话解释: 单个微生物标志物的预测能力有限。将多个显著标志物组合成一个"微生物风险评分"(microbial risk score),类似于基因组领域的多基因风险评分(PRS),可以显著提高预后预测精度。

技术细节:

# === 多因素Cox模型 ===

# 使用LASSO Cox选择最优标志物组合
library(glmnet)

# 准备X矩阵(候选标志物)和Y(生存数据)
x_matrix <- as.matrix(surv_data[, sig_markers$genus])
y_surv <- Surv(surv_data$OS_time, surv_data$OS_status)

# LASSO Cox回归
set.seed(42)
cv_fit <- cv.glmnet(x_matrix, y_surv, family = "cox",
                     alpha = 1, nfolds = 10)
plot(cv_fit)

# 最优lambda下的系数
best_lambda <- cv_fit$lambda.min
coef_lasso <- coef(cv_fit, s = best_lambda)
selected_features <- rownames(coef_lasso)[coef_lasso[, 1] != 0]
cat("Selected markers:", selected_features, "\n")

# 构建微生物风险评分
risk_score <- as.numeric(predict(cv_fit, newx = x_matrix, s = best_lambda, type = "link"))
surv_data$risk_score <- risk_score

# 按风险评分中位数分组
surv_data$risk_group <- ifelse(risk_score > median(risk_score), "High Risk", "Low Risk")

# KM曲线(风险评分分组)
fit_risk <- survfit(Surv(OS_time, OS_status) ~ risk_group, data = surv_data)
ggsurvplot(fit_risk, data = surv_data,
           pval = TRUE, risk.table = TRUE,
           palette = c("red", "blue"),
           title = "Microbial Risk Score and Survival")

# === 多因素Cox(调整协变量)===
# 在风险评分基础上调整临床因素
multi_cox <- coxph(Surv(OS_time, OS_status) ~ risk_score + age + sex + stage,
                    data = surv_data)
summary(multi_cox)
# 如果risk_score在调整后仍显著,说明独立于已知预后因素

第五步:时间依赖ROC分析

白话解释: ROC曲线评估分类器的区分能力。但生存分析的特殊性在于有时间维度——同一个标志物在不同时间点的预测能力可能不同(如6个月生存和5年生存)。时间依赖ROC (timeROC) 可以评估标志物在每个时间点的AUC值。

技术细节:

# === 时间依赖ROC ===
library(timeROC)
library(survivalROC)

# 方法1:timeROC包
roc_result <- timeROC(
  T = surv_data$OS_time,
  delta = surv_data$OS_status,
  marker = surv_data$risk_score,
  cause = 1,
  times = c(12, 24, 36, 60),  # 1年, 2年, 3年, 5年
  iid = TRUE
)

# 查看各时间点AUC
print(roc_result$AUC)
# AUC > 0.7 通常认为有较好的预测价值

# 绘制时间依赖ROC曲线
plot(roc_result, time = 12, col = "red", title = "Time-dependent ROC")
plot(roc_result, time = 36, col = "blue", add = TRUE)
legend("bottomright", c("1-year", "3-year"), col = c("red", "blue"), lty = 1)

# AUC随时间变化图
plot(c(12, 24, 36, 60), roc_result$AUC,
     type = "b", xlab = "Time (months)", ylab = "AUC",
     ylim = c(0.5, 1), main = "AUC over time")
abline(h = 0.7, lty = 2, col = "grey")

# 方法2:与临床因素比较
roc_clinical <- timeROC(
  T = surv_data$OS_time,
  delta = surv_data$OS_status,
  marker = as.numeric(surv_data$stage),
  cause = 1,
  times = c(12, 24, 36, 60)
)

# 比较AUC差异是否显著
compare(roc_result, roc_clinical)

# 方法3:C-index(一致性指数)
library(Hmisc)
c_index <- concordance(Surv(OS_time, OS_status) ~ risk_score, data = surv_data)
print(c_index$concordance)  # C-index > 0.6 可接受, > 0.7 较好

第六步:模型验证与稳健性检查

白话解释: 任何预测模型都需要验证其泛化能力——在新数据上是否同样有效。常用方法包括:Bootstrap内部验证、留一法交叉验证、独立外部队列验证。

技术细节:

# === 模型验证 ===

# 方法1:Bootstrap内部验证
library(rms)

# 将Cox模型用rms包重新拟合
dd <- datadist(surv_data)
options(datadist = "dd")
cox_rms <- cph(Surv(OS_time, OS_status) ~ risk_score, data = surv_data,
               x = TRUE, y = TRUE, surv = TRUE)

# Bootstrap验证(200次)
val <- validate(cox_rms, B = 200, dxy = TRUE)
# Optimism-corrected C-index
c_index_corrected <- 0.5 + val["Dxy", "index.corrected"] / 2
cat("Optimism-corrected C-index:", c_index_corrected, "\n")

# Calibration plot
cal <- calibrate(cox_rms, u = 36, B = 200)  # 36个月校准
plot(cal)

# 方法2:K-fold交叉验证
library(caret)
set.seed(42)
folds <- createFolds(surv_data$OS_status, k = 5, list = TRUE)
cv_cindex <- numeric(5)

for (i in 1:5) {
  train_idx <- unlist(folds[-i])
  test_idx <- folds[[i]]

  # 训练
  cv_fit <- coxph(Surv(OS_time, OS_status) ~ risk_score,
                   data = surv_data[train_idx, ])
  # 预测
  pred <- predict(cv_fit, newdata = surv_data[test_idx, ], type = "risk")
  # C-index
  cv_cindex[i] <- concordance(Surv(OS_time, OS_status) ~ pred,
                               data = surv_data[test_idx, ])$concordance
}
cat("CV C-index:", mean(cv_cindex), "±", sd(cv_cindex), "\n")

# 方法3:外部验证(如果有独立队列)
# external_data <- read.csv("external_cohort.csv")
# external_pred <- predict(final_model, newdata = external_data)
# 计算外部队列的C-index和KM分组效果

第七步:高级分析:竞争风险与联合模型

白话解释: 在某些临床场景中,患者可能因非目标事件死亡(如车祸),这构成"竞争风险"。此外,如果有纵向微生物组数据(多次采样),联合模型可以将微生物动态变化与生存结局关联起来。

技术细节:

# === 竞争风险模型 ===
library(cmprsk)
library(tidycmprsk)

# 假设有两种事件:1=癌症死亡, 2=其他原因死亡, 0=删失
# Fine-Gray模型
fg_fit <- crr(
  ftime = surv_data$OS_time,
  fstatus = surv_data$competing_status,  # 0/1/2
  cov1 = surv_data[, c("risk_score", "age", "stage")],
  failcode = 1  # 关注癌症死亡
)
summary(fg_fit)

# === Alpha多样性与生存 ===
# Shannon多样性指数作为连续变量
cox_diversity <- coxph(Surv(OS_time, OS_status) ~ Shannon + age + sex + stage,
                        data = surv_data)
summary(cox_diversity)

# 非线性关系检验(受限立方样条)
library(rms)
cox_rcs <- cph(Surv(OS_time, OS_status) ~ rcs(Shannon, 4) + age + sex + stage,
                data = surv_data, x = TRUE, y = TRUE)
anova(cox_rcs)  # 非线性项p值
# 如果非线性项显著,说明多样性与生存的关系不是简单线性

实战命令速查

# 快速生存分析流程
library(survival); library(survminer); library(timeROC)
surv_obj <- Surv(time, event)
# 单因素筛选
coxph(surv_obj ~ marker, data=df)
# KM曲线
ggsurvplot(survfit(surv_obj ~ group, data=df), pval=TRUE, risk.table=TRUE)
# 时间依赖ROC
timeROC(T=df$time, delta=df$event, marker=df$score, times=c(12,36,60))

面试常问点

Q1: 微生物组数据用于生存分析时,为什么需要CLR变换?

A: 微生物相对丰度数据是组成性的(和为1),violating Cox回归对协变量独立性的假设。CLR变换将数据从单纯形空间映射到实数空间,消除虚假负相关,使传统统计方法(如Cox回归)的假设成立。不变换直接用相对丰度会产生偏倚的风险比估计。

Q2: 如何处理微生物组高维数据中的多重检验问题?

A: (1) FDR校正(BH方法)控制假发现率;(2) 使用正则化方法(LASSO/Elastic Net Cox)同时做特征选择和系数估计,隐式处理多重比较;(3) 预先降维(如PCoA坐标、模块化score)减少检验数量;(4) 分析策略上先筛选后验证(discovery + validation cohort)。

Q3: C-index和时间依赖AUC有什么区别?

A: C-index是全时间范围的一致性指标——衡量模型是否能正确排序所有病人对的事件时间先后。时间依赖AUC是特定时间点的区分能力——衡量在某一时刻模型区分已事件/未事件个体的能力。C-index是全局指标,timeAUC可以看模型在不同时间点的表现。两者互补使用。

Q4: 微生物风险评分的生物学解释性如何?

A: LASSO Cox选出的菌属组合虽然统计上最优,但生物学解释需要额外分析:(1) 查文献确认这些菌与疾病的已知关联;(2) 分析菌群功能通路是否与预后机制一致;(3) 在动物模型中验证因果关系;(4) 与免疫/代谢指标做关联分析解释机制。

Q5: 小样本量(<100例)做微生物生存分析的注意事项?

A: (1) 限制模型中协变量数量(每个事件至少需要10-15个观察值/变量);(2) 使用正则化避免过拟合;(3) 只做验证性分析(验证已报道的标志物),避免发现性研究;(4) 报告内部验证的optimism-corrected metrics;(5) 明确说明统计效力限制。


易错点

1. 使用最优截点而不报告数据窥探

surv_cutpoint虽然方便,但它在同一数据上搜索最优切点等于数据窥探(data dredging),会高估真实效果。必须在独立验证集中确认该截点的有效性,或使用预定义截点(如中位数)。

2. 忽略零膨胀导致的分组偏差

许多菌属在大比例样本中为零。如果按中位数分组,而中位数恰好为零,则分组结果为"0 vs 非零",本质上变成了存在/缺失分析而非丰度高低分析。应明确报告。

3. Cox模型比例风险假设违反

Cox回归假设HR不随时间变化。如果某微生物的保护效应随时间减弱(如手术后早期菌群重要,后期不重要),则违反PH假设。需用Schoenfeld残差检验,违反时使用时间分层或加时间交互项。

4. 过拟合——多标志物模型在小样本中

在30-50例样本上构建10个菌属的风险评分几乎必然过拟合。需严格的交叉验证和正则化,并报告optimism-corrected性能。

5. 忽略治疗作为时间依赖协变量

如果微生物组采样在基线,但患者后续接受不同治疗,治疗可能同时影响微生物和生存。应在模型中调整治疗方案,或按治疗分层分析。


补充知识

微生物组预后研究的代表性文献

  • Fusobacterium nucleatum与结直肠癌预后(Mima et al., Gut, 2016)
  • 基线肠道菌群多样性预测免疫检查点治疗响应(Gopalakrishnan et al., Science, 2018)
  • Akkermansia muciniphila与PD-1治疗效果(Routy et al., Science, 2018)

相关R包

  • survival/survminer:标准生存分析
  • timeROC:时间依赖ROC
  • glmnet:LASSO/Elastic Net Cox
  • rms:模型验证和校准
  • cmprsk:竞争风险
  • MiRKAT(含MiRKAT-S):基于核函数的微生物群落与生存时间关联检验

MiRKAT-S方法简介

MiRKAT-S(Microbiome Regression-based Kernel Association Test for Survival)是将微生物组整体群落结构(而非单个菌属)与生存结局关联的社区级(community-level)检验方法。它通过生态距离矩阵(如UniFrac、Bray-Curtis)构建核矩阵,利用方差成分得分检验(variance-component score test)评估微生物群落整体组成与删失生存时间的关联。相比对每个菌逐一做Cox回归(上述流程),MiRKAT-S是一个omnibus检验——直接回答"微生物组整体是否与预后有关"。

# === MiRKAT-S 社区级生存分析 ===
library(MiRKAT)

# 准备UniFrac距离矩阵
library(phyloseq)
ps <- readRDS("phyloseq_object.rds")
unifrac_dist <- UniFrac(ps, weighted = TRUE)

# 生存数据
obstime <- surv_data$OS_time
delta <- surv_data$OS_status  # 1=事件, 0=删失

# 协变量(可选)
covar <- surv_data[, c("age", "sex", "stage")]

# 运行MiRKAT-S
mirkat_s_result <- MiRKAT_S(
  obstime = obstime,
  delta = delta,
  X = as.matrix(covar),      # 协变量矩阵
  D = as.matrix(unifrac_dist) # 距离矩阵
)
print(mirkat_s_result)
# 输出p值:微生物群落整体是否与生存显著相关

参考文献:Plantinga et al., Microbiome, 2017

引用推荐

  • Cox回归: Cox, Journal of the Royal Statistical Society, 1972
  • timeROC: Blanche et al., Statistics in Medicine, 2013
  • 微生物组预后综述: Helmink et al., Nature Medicine, 2019
  • MiRKAT-S: Plantinga et al., Microbiome, 2017