ROC曲线分析:评估预测模型的判别能力

生信学长
2025年10月24日
机器学习与统计
ROC曲线分析:评估预测模型的判别能力



点击卡片关注,一起学习生信分析!



什么是ROC曲线?

ROC曲线最初用于雷达信号检测,现在广泛应用于:

  • 「医学诊断」 : 评估诊断试验的准确性
  • 「机器学习」 : 评估分类模型的性能
  • 「预后预测」 : 评估生存预测模型的判别能力

ROC曲线的应用

在生存分析中,ROC曲线可以:

  • 评估风险评分的判别能力
  • 比较不同预测模型的性能
  • 确定最佳截断值(Cut-off)
  • 评估不同时间点的预测准确性

2. ROC曲线原理

2.1 基本概念

「混淆矩阵」 :


实际阳性
实际阴性
「预测阳性」
TP(真阳性)
FP(假阳性)
「预测阴性」
FN(假阴性)
TN(真阴性)

「核心指标」 :

  1. 「灵敏度(Sensitivity)」 = TP/(TP+FN)

    • 正确识别阳性的比例
    • 也称真阳性率(TPR)
  1. 「特异度(Specificity)」 = TN/(TN+FP)

    • 正确识别阴性的比例
  1. 「假阳性率(FPR)」 = 1 - 特异度 = FP/(TN+FP)

2.2 ROC曲线绘制

ROC曲线是以**假阳性率(1-特异度)**为X轴,**真阳性率(灵敏度)**为Y轴的曲线。

绘制方法:

  1. 选择不同的截断值
  2. 计算每个截断值下的灵敏度和特异度
  3. 以(1-特异度, 灵敏度)为坐标绘点
  4. 连接所有点形成ROC曲线

2.3 AUC指标

「AUC」 (Area Under Curve,曲线下面积):

  • 范围: 0.5-1.0
  • 「0.5」 : 随机猜测,无判别能力
  • 「0.5-0.7」 : 较低准确性
  • 「0.7-0.9」 : 一定准确性
  • 「>0.9」 : 较高准确性

「解释」 : AUC表示随机选择一个阳性样本和一个阴性样本,模型对阳性样本打分高于阴性样本的概率。

2.4 时间依赖ROC

在生存分析中,需要考虑 「时间因素」 :

  • 不同时间点的预测准确性可能不同
  • 需要计算1年、3年、5年等时间点的ROC
  • 使用timeROC包实现

3. 环境准备

3.1 R包安装

# 安装必要的R包
install.packages("survival")
install.packages("survminer")
install.packages("timeROC")
install.packages("pROC")

「包的功能」 :

  • pROC : 基本ROC曲线分析
  • timeROC : 时间依赖ROC曲线
  • survival : 生存分析
  • survminer : 生存分析可视化

3.2 数据准备

需要的数据文件:

  • risk.txt : 风险评分数据(来自文章15)
  • clinical.txt : 临床信息数据

4. 代码实现

4.1 读取和准备数据

library(pROC)
library(ggplot2)
library(survival)
library(survminer)
library(timeROC)

# 读取风险数据
risk <- read.table("risk.txt",
                   header = TRUE,
                   sep = "\t",
                   check.names = FALSE,
                   row.names = 1)

# 提取关键列
risk <- risk[, c("time""state""riskScore")]

# 读取临床数据
cli <- read.table("clinical.txt",
                  header = TRUE,
                  sep = "\t",
                  check.names = FALSE,
                  row.names = 1)

# 合并数据
samSample <- intersect(row.names(risk), row.names(cli))
risk1 <- risk[samSample, , drop = FALSE]
cli <- cli[samSample, , drop = FALSE]
rt <- cbind(risk1, cli)

4.2 基本ROC曲线

首先绘制riskScore的基本ROC曲线(二分类):

# 定义颜色
bioCol <- c("DarkOrchid""Orange2""MediumSeaGreen""NavyBlue",
            "#8B668B""#FF4500""#135612""#561214")

# ROC分析(生存 vs 死亡)
roc1 <- roc(rt$state ~ rt$riskScore)

# 查看AUC
cat("AUC:", auc(roc1), "\n")
cat("95%CI:", ci.auc(roc1), "\n")

# 创建picture目录
if (!dir.exists("picture")) {
  dir.create("picture")
}

# 绘制ROC曲线
png(file = "picture/ROC.riskscore.png",
    width = 5 * 300,
    height = 5 * 300,
    res = 300)

plot(roc1,
     print.auc = TRUE,
     col = bioCol[1],
     legacy.axes = TRUE,
     main = "ROC Curve for Risk Score",
     lwd = 3)

dev.off()
基本ROC曲线
基本ROC曲线

「图形解读」 :

  • 「X轴」 : 1 - 特异度(假阳性率)
  • 「Y轴」 : 灵敏度(真阳性率)
  • 「紫色曲线」 : riskScore的ROC曲线
  • 「对角虚线」 : 随机猜测线(AUC=0.5)
  • 「AUC = 0.706」 : 表示模型有一定的判别能力

「结果解释」 :

  • AUC = 0.706,说明riskScore对患者预后有较好的判别能力
  • 曲线越靠近左上角,判别能力越强
  • 95%CI: 0.659-0.754,置信区间不包含0.5,统计学显著

4.3 时间依赖ROC曲线

生存分析中,需要评估不同时间点的预测准确性:

# 计算1年、3年、5年的ROC
ROC_rt <- timeROC(
T = risk$time,
  delta = risk$state,
  marker = risk$riskScore,
  cause = 1,
  weighting = 'aalen',
  times = c(135),
  ROC = TRUE
)

# 查看各时间点的AUC
cat("1年AUC:", ROC_rt$AUC[1], "\n")
cat("3年AUC:", ROC_rt$AUC[2], "\n")
cat("5年AUC:", ROC_rt$AUC[3], "\n")

# 绘制时间依赖ROC曲线
png(file = "picture/ROC.all.png",
    width = 5 * 300,
    height = 5 * 300,
    res = 300)

plot(ROC_rt,
     time = 1,
     col = bioCol[1],
     title = FALSE,
     lwd = 4)

plot(ROC_rt,
     time = 3,
     col = bioCol[2],
     add = TRUE,
     title = FALSE,
     lwd = 4)

plot(ROC_rt,
     time = 5,
     col = bioCol[3],
     add = TRUE,
     title = FALSE,
     lwd = 4)

legend('bottomright',
       c(paste0('AUC at 1 years: ', sprintf("%.03f", ROC_rt$AUC[1])),
         paste0('AUC at 3 years: ', sprintf("%.03f", ROC_rt$AUC[2])),
         paste0('AUC at 5 years: ', sprintf("%.03f", ROC_rt$AUC[3]))),
       col = bioCol[1:3],
       lwd = 4,
       bty = 'n',
       title = "Time-dependent ROC")

dev.off()
时间依赖ROC曲线
时间依赖ROC曲线

「图形解读」 :

  • 「紫色曲线」 : 1年生存的ROC (AUC=0.740)
  • 「橙色曲线」 : 3年生存的ROC (AUC=0.743)
  • 「绿色曲线」 : 5年生存的ROC (AUC=0.764)

「关键发现」 :

  1. 「AUC随时间增加」 : 0.740 → 0.743 → 0.764
  2. 「5年预测最准确」 : AUC=0.764,判别能力较好
  3. 「模型稳定性好」 : 不同时间点AUC都>0.7

4.4 多变量ROC比较

比较riskScore与临床变量的预测性能:

# 定义预测时间点
predictTime <- 5# 5年

aucText <- c()

png(file = "picture/cliROC.all.png",
    width = 5.5 * 300,
    height = 5.5 * 300,
    res = 300)

# 绘制riskScore的ROC
i <- 3
ROC_rt <- timeROC(
T = risk$time,
  delta = risk$state,
  marker = risk$riskScore,
  cause = 1,
  weighting = 'aalen',
  times = c(predictTime),
  ROC = TRUE
)

plot(ROC_rt,
     time = predictTime,
     col = bioCol[i - 2],
     title = FALSE,
     lwd = 4)

aucText <- c(paste0("Risk"", AUC=", sprintf("%.3f", ROC_rt$AUC[2])))

# 添加对角参考线
abline(01, lty = 2, col = "gray")

# 循环计算各临床变量的ROC
for (i in4:ncol(rt)) {
# 将因子变量转换为数值
  marker_data <- as.numeric(as.factor(rt[, i]))

  ROC_rt <- timeROC(
    T = rt$time,
    delta = rt$state,
    marker = marker_data,
    cause = 1,
    weighting = 'aalen',
    times = c(predictTime),
    ROC = TRUE
  )

  plot(ROC_rt,
       time = predictTime,
       col = bioCol[i - 2],
       title = FALSE,
       lwd = 4,
       add = TRUE)

  aucText <- c(aucText, paste0(colnames(rt)[i], ", AUC=",
                                sprintf("%.3f", ROC_rt$AUC[2])))
}

# 添加图例
legend("bottomright",
       aucText,
       lwd = 4,
       bty = "n",
       col = bioCol[1:length(aucText)],
       title = "5-year ROC",
       cex = 0.8)

dev.off()
多变量ROC比较
多变量ROC比较

「图形解读」 :

各变量的5年AUC排名:

  1. 「Risk」 : 0.764 (最优) - 紫色
  2. 「T」 (肿瘤大小): 0.587 - 深蓝色
  3. 「Stage」 (临床分期): 0.585 - 深绿色
  4. 「N」 (淋巴结): 0.538 - 粉色
  5. 「subdivision」 : 0.516 - 棕色
  6. 「M」 (远处转移): 0.515 - 橙色
  7. 「Age」 : 0.509 - 黄色
  8. 「Gender」 : 0.466 - 浅绿色

「关键发现」 :

  • 「Risk评分表现最佳」 (AUC=0.764),远超单个临床变量
  • 「T和Stage」 是最好的临床预后因素(AUC≈0.59)
  • 「Gender」 预测能力最差(AUC=0.466,接近随机)
  • 「整合多个变量」 的Risk模型优于任何单一临床指标

4.5 计算最佳截断值

使用Youden指数确定最佳截断值:

# 计算最佳截断值
best_coords <- coords(roc1, "best", ret = c("threshold""specificity""sensitivity"))

cat("最佳截断值:", best_coords$threshold, "\n")
cat("对应灵敏度:", best_coords$sensitivity, "\n")
cat("对应特异度:", best_coords$specificity, "\n")
cat("Youden指数:", best_coords$sensitivity + best_coords$specificity - 1"\n")

「结果」 :

  • 最佳截断值: 0.840
  • 灵敏度: 0.662 (66.2%)
  • 特异度: 0.662 (66.2%)
  • Youden指数: 0.323

「应用」 :

  • riskScore > 0.840 → 高风险组
  • riskScore ≤ 0.840 → 低风险组
  • 该截断值使灵敏度和特异度达到最佳平衡

5. 结果解读

5.1 模型性能评估

从我们的ROC分析中得到:

评估指标
数值
评价
基本AUC
0.706
一定准确性
1年AUC
0.740
较好判别能力
3年AUC
0.743
较好判别能力
5年AUC
0.764
较好判别能力
最佳截断值
0.840
风险分层

「总体评价」 :

  • 模型具有 「较好的判别能力」 (AUC=0.7-0.8)
  • 「长期预测优于短期」 (5年AUC最高)
  • 「优于所有单一临床变量」

5.2 与临床变量对比

Risk评分 vs 临床变量(5年AUC):

Risk:  0.764 ████████████████ T:     0.587 ████████████ Stage: 0.585 ████████████ N:     0.538 ███████████ Age:   0.509 ██████████ Gender:0.466 █████████ 

「提升幅度」 :

  • vs T分期: 提升30% ((0.764-0.587)/0.587)
  • vs Stage: 提升31%
  • vs Gender: 提升64%

5.3 时间趋势分析

AUC随时间变化:

  • 1年: 0.740
  • 3年: 0.743 (+0.003)
  • 5年: 0.764 (+0.021)

「趋势解读」 :

  • AUC呈上升趋势
  • 模型对 「长期预后预测更准确」
  • 说明筛选的基因与长期生存密切相关

6. ROC曲线的优势与局限


6.2 局限性

  1. 「不考虑校准度」 :

    • 只评估排序能力
    • 需结合校准曲线评估
  1. 「类别不平衡」 :

    • 正负样本比例极不平衡时可能失真
    • 可考虑使用PR曲线
  1. 「缺乏临床意义」 :

    • AUC不直接反映临床获益
    • 需结合DCA评估净获益
  1. 「生存数据特殊性」 :


    • 删失数据处理复杂
    • 需使用时间依赖ROC

7. 进阶分析

7.1 DeLong检验

比较两个ROC曲线的AUC是否有显著差异:

library(pROC)

# 构建另一个预测模型(如仅用Age)
roc2 <- roc(rt$state ~ rt$Age)

# DeLong检验
test_result <- roc.test(roc1, roc2, method = "delong")

cat("DeLong检验 p值:", test_result$p.value, "\n")

7.2 Bootstrap置信区间

估计AUC的稳健性:

# Bootstrap重采样(2000次)
ci_result <- ci.auc(roc1, method = "bootstrap", boot.n = 2000)

cat("AUC:", auc(roc1), "\n")
cat("95% CI:", ci_result[1], "-", ci_result[3], "\n")

7.3 决策曲线分析(DCA)

评估模型的临床净获益:

library(rmda)

# 构建决策曲线
dca_result <- decision_curve(
  rt$state ~ rt$riskScore,
  data = rt,
  thresholds = seq(010.01),
  confidence.intervals = 0.95
)

# 绘制DCA曲线
plot_decision_curve(dca_result,
                    curve.names = "Risk Score",
                    col = "red",
                    lwd = 2)

7.4 部分AUC(pAUC)

关注特定灵敏度或特异度范围:

# 计算特异度在80%-100%范围内的部分AUC
pauc <- auc(roc1, partial.auc = c(10.8), partial.auc.focus = "sp")

cat("pAUC (sp 0.8-1.0):", pauc, "\n")

8. 注意事项

8.1 数据准备

「删失数据处理」 :

  • 基本ROC: 将删失样本视为"阴性"
  • 时间依赖ROC: 正确处理删失(推荐)

「样本量要求」 :

  • 至少需要100个样本
  • 每组至少10个事件
  • 样本越多,AUC估计越稳定

8.2 模型选择

「weighting参数」 (timeROC):

  • "aalen" : Aalen加性模型(推荐)
  • "marginal" : Kaplan-Meier方法
  • "cox" : Cox比例风险模型

「选择建议」 :

  • 一般情况使用 "aalen"
  • 小样本使用 "marginal"
  • 比例风险假设成立用 "cox"

8.3 结果报告

「标准报告格式」 :

Risk Score showed good discrimination ability with AUC of 0.706 (95% CI: 0.659-0.754) for overall survival status. Time-dependent ROC analysis demonstrated AUC of 0.740, 0.743, and 0.764 for 1-year, 3-year, and 5-year OS, respectively. 





送你一份科研小礼物,开启科研生涯!

点击即可领取:

》〉戳我,领取一份科研小礼物