jjzjj

Topic 14. 临床预测模型之校准曲线 (Calibration curve)

桓峰基因 2023-04-11 原文

点击关注,桓峰基因

桓峰基因

生物信息分析,SCI文章撰写及生物信息基础知识学习:R语言学习,perl基础编程,linux系统命令,Python遇见更好的你

57篇原创内容

公众号

前言

Calibration curve,直译过来就是校准曲线或校准图。其实,校准曲线就是实际发生率和预测发生率的散点图。实质上,校准图曲线是Hosmer-Lemeshow拟合优度检验的结果可视化。目前校准曲线常用来评价logistic回归和cox回归模型。

校准曲线的算法

步骤1 对预测概率进行分桶(分桶的策略分为’uniform’, ‘quantile’)

步骤2 求出每个桶里面所有样本预测概率的平均值,作为横坐标

步骤3 求出每个桶里面正例的概率,作为纵坐标。

步骤4 将这些点连起来,就成为了校准曲线。

我们在pubmed 中搜索一篇文章,结直肠癌预测临床转移的概率,这篇文章 IF 26分,利用Lasso 回归筛选变量,并且构建模型。

我们看到其中就是分析了模型的校准曲线,可见,模型评估的一些分析在建模类文章中是必不可少的一部分,所以学会了这类文章不用愁,如下:

实例解析

Cox 回归在 survival 和 rms 这两个包中都可以实现,我们选择rms程序包里面的函数。加载 survival 和 rms 程序包,如下:

if (!require(survival)) {
    install.packages("survival")
}
if (!require("rms")) {
    install.packages("rms")
}
if (!require("PredictABEL")) {
    install.packages("PredictABEL")
}
## Warning: 程辑包'PredictABEL'是用R版本4.1.3 来建造的
library(survival)
library(rms)
library(PredictABEL)

1.数据读取

我们仍然采用软件包自带的肺癌数据库 NCCTG Lung Cancer Data 作为输入数据,如下:

Descrption

Survival in patients with advanced lung cancer from the North Central Cancer Treatment Group. Performance scores rate how well the patient can perform usual daily activities.

data(package = "survival")
# 2. 打包数据
lung$status <- ifelse(lung$status == 1, 0, 1)
dd <- datadist(lung)
options(datadist = "dd")
head(dd)
## $limits
##                 inst    time status age sex ph.ecog ph.karno pat.karno
## Low:effect         3  166.75      0  56   1       0       75        70
## Adjust to         11  255.50      0  63   1       1       80        80
## High:effect       16  396.50      1  69   2       1       90        90
## Low:prediction     1   31.00      0  44   1       0       60        60
## High:prediction   26  740.00      1  76   2       2      100       100
## Low                1    5.00      0  39   1       0       50        30
## High              33 1022.00      1  82   2       3      100       100
##                  meal.cal   wt.loss
## Low:effect       635.0000   0.00000
## Adjust to        975.0000   7.00000
## High:effect     1150.0000  15.75000
## Low:prediction   312.4361  -5.00000
## High:prediction 1500.0000  35.23348
## Low               96.0000 -24.00000
## High            2600.0000  68.00000
## 
## $values
## $values$status
## [1] 0 1
## 
## $values$sex
## [1] 1 2
## 
## $values$ph.ecog
## [1] 0 1 2 3
## 
## $values$ph.karno
## [1]  50  60  70  80  90 100
## 
## $values$pat.karno
## [1]  30  40  50  60  70  80  90 100

2.Cox 回归模型构建

使用rms 程序包中的 cph 函数构造Cox 回归模型,其中的几个变量需要根据之前做Cox回归模型时显著的那几个变量,然后做Cox回归,我们发现 sex 和 ph.ecog 两个变量显著性最高,如下:

cph <- cph(Surv(time, status) ~ age + sex + ph.ecog + ph.karno, data = lung, x = TRUE,
    y = TRUE, surv = TRUE)
cph
## Frequencies of Missing Values Due to Each Variable
## Surv(time, status)                age                sex            ph.ecog 
##                  0                  0                  0                  1 
##           ph.karno 
##                  1 
## 
## Cox Proportional Hazards Model
##  
##  cph(formula = Surv(time, status) ~ age + sex + ph.ecog + ph.karno, 
##      data = lung, x = TRUE, y = TRUE, surv = TRUE)
##  
##  
##                         Model Tests    Discrimination    
##                                               Indexes    
##  Obs       226    LR chi2     31.27    R2       0.129    
##  Events    163    d.f.            4    Dxy      0.263    
##  Center 1.6323    Pr(> chi2) 0.0000    g        0.550    
##                   Score chi2  31.06    gr       1.732    
##                   Pr(> chi2) 0.0000                      
##  
##           Coef    S.E.   Wald Z Pr(>|Z|)
##  age       0.0129 0.0094  1.37  0.1712  
##  sex      -0.5726 0.1692 -3.38  0.0007  
##  ph.ecog   0.6329 0.1760  3.60  0.0003  
##  ph.karno  0.0126 0.0095  1.32  0.1870  
##

3. 校准曲线绘制

本期选用rms 和 PredictABEL 两个包中的函数进行可视化,比较一下差异,主要目的也是方便大家选择。

1. calibrate {rms}

绘制校准曲线时需要注意以下参数,需要根据自己的数据量情况设置参数,如下:

1、绘制校正曲线前需要在模型函数中添加参数x=T, y=T,详细参考帮助

2、u需要与之前模型中定义好的time.inc一致,即365或730;

3、m要根据样本量来确定,由于标准曲线一般将所有样本分为3组(在图中显示3个点),而m代表每组的样本量数,因此m*3应该等于或近似等于样本量;

4、b代表最大再抽样的样本量

cal <- calibrate(cph, cmethod = "KM", method = "boot", u = 365, m = 38, B = 228)
## Using Cox survival estimates at 120 Days
cal
## calibrate.cph(fit = cph, cmethod = "KM", method = "boot", u = 365, 
##     m = 38, B = 228)
## 
## 
## n=226  B=228  u=365 Day
## 
##      index.orig     training         test mean.optimism mean.corrected   n
## [1,] -0.4836310 -0.002478164 -0.003134677  0.0006565131     -0.4842875 228
## [2,] -0.5180526  0.010037703 -0.058465514  0.0685032169     -0.5865558  28
## [3,] -0.4576156  0.075069139  0.053964406  0.0211047329     -0.4787204  11
## [4,] -0.3499039          NaN          NaN           NaN            NaN   0
## [5,] -0.3336164          NaN          NaN           NaN            NaN   0
##      mean.predicted        KM KM.corrected   std.err
## [1,]      0.7422109 0.2585799    0.2579234 0.2736880
## [2,]      0.8120250 0.2939724    0.2254692 0.2582661
## [3,]      0.8530646 0.3954489    0.3743442 0.1908749
## [4,]      0.8882504 0.5383464          NaN 0.1517891
## [5,]      0.9229901 0.5893736          NaN 0.1466856
plot(cal, lwd = 2, lty = 1, errbar.col = c(rgb(0, 118, 192, maxColorValue = 255)),
    xlab = "Nomogram-Predicted Probability of 1-Year OS", ylab = "Actual 1-Year DFS (proportion)",
    col = c(rgb(192, 98, 83, maxColorValue = 255)), subtitles = FALSE, xlim = c(0,
        1), ylim = c(0, 1), main = "Calibrate plot")
lines(cal[, c("mean.predicted", "KM")], type = "l", lwd = 2, col = c(rgb(192, 98,
    83, maxColorValue = 255)), pch = 16)
abline(0, 1, lty = 3, lwd = 2, col = c(rgb(0, 118, 192, maxColorValue = 255)))

Figure 1:calibrate {rms}

截断坐标轴,我们发现整体放大,但是偏离对角线很多,如下:

cal <- calibrate(cph, cmethod = "KM", method = "boot", u = 365, m = 38, B = 228)
## Using Cox survival estimates at 120 Days
cal
## calibrate.cph(fit = cph, cmethod = "KM", method = "boot", u = 365, 
##     m = 38, B = 228)
## 
## 
## n=226  B=228  u=365 Day
## 
##      index.orig     training         test mean.optimism mean.corrected   n
## [1,] -0.4836310 -0.002476923 -0.003246274  0.0007693507     -0.4844003 228
## [2,] -0.5180526  0.065659393  0.028730528  0.0369288647     -0.5549814  34
## [3,] -0.4576156  0.143006671  0.081213276  0.0617933958     -0.5194090   9
## [4,] -0.3499039  0.118159126  0.114331558  0.0038275678     -0.3537315   1
## [5,] -0.3336164          NaN          NaN           NaN            NaN   0
##      mean.predicted        KM KM.corrected   std.err
## [1,]      0.7422109 0.2585799    0.2578105 0.2736880
## [2,]      0.8120250 0.2939724    0.2570435 0.2582661
## [3,]      0.8530646 0.3954489    0.3336555 0.1908749
## [4,]      0.8882504 0.5383464    0.5345189 0.1517891
## [5,]      0.9229901 0.5893736          NaN 0.1466856
plot(cal, lwd = 2, lty = 1, errbar.col = c(rgb(0, 118, 192, maxColorValue = 255)),
    xlab = "Nomogram-Predicted Probability of 1-Year OS", ylab = "Actual 1-Year DFS (proportion)",
    col = c(rgb(192, 98, 83, maxColorValue = 255)), subtitles = FALSE, main = "Calibrate plot")

Figure 2:calibrate {rms}

2. plotCalibration {PredictABEL}

这个方式是需要先计算出预测值和真实值,然后用plotCalibration函数绘图,从下图可以看出,数据偏离还是很大的,说明模型的预测能力不够。

newdata <- lung[101:200, ]  #用来校准的数据,这里从源数据中调取了部分
pred.lg <- predict(cph, newdata)  #每位患者的风险评分
newdata$prob <- 1/(1 + exp(-pred.lg))  #将pred.lg做数据转化,数值更直观
prob <- newdata$prob
plotCalibration(data = newdata, cOutcome = 3, predRisk = prob, groups = 10, plottitle = "Calibration plot")  #3为结局指标所在列数

Figure 3:plotCalibration {PredictABEL}

## $Table_HLtest
##               total meanpred meanobs predicted observed
## [0.256,0.315)    10    0.285     0.2      2.85        2
## [0.315,0.394)    10    0.367     0.6      3.67        6
## [0.394,0.415)    10    0.403     0.7      4.03        7
## [0.415,0.453)    10    0.427     0.5      4.27        5
## [0.453,0.519)    10    0.476     0.7      4.76        7
## [0.519,0.560)    10    0.541     0.7      5.41        7
## [0.560,0.576)    10    0.566     0.9      5.66        9
## [0.576,0.597)    10    0.588     0.6      5.88        6
## [0.597,0.656)    10    0.616     0.9      6.16        9
## [0.656,0.715]    10    0.684     0.9      6.84        9
## 
## $Chi_square
## [1] 19.721
## 
## $df
## [1] 8
## 
## $p_value
## [1] 0.0114

结果解读

Figure 1: 横坐标为预测的事件发生率(Predicted risk),纵坐标是观察到的实际事件发生率(Observed risk),范围均为0到1,可以理解为事件发生率(百分比)。对角线的虚线是参考线,即预测值=实际值的情况。红线是曲线拟合线,两边带颜色部分是95%CI。

• 如果预测值=观察值,则红线与参考线完全重合

• 如果预测值>观察值,即高估了风险,则红线在参考线下面

• 如果预测值<观察值,即低估了风险,则黑线在参考线上面

Figure 3: 横坐标为预测的事件发生率(Predicted Probablity),纵坐标是观察到的实际事件发生率(Actual Rate),范围均为0到1,可以理解为事件发生率(百分比)。对角线的虚线是参考线,即预测值=实际值的情况。

最理想的情况下, 校准曲线是一条对角线(预测概率等于经验概率),校准曲线不一定会单调递增, 比如, 当分桶的数量比较多时或者分类器比较弱时,通常情况下, Logistic Regression的校准曲线非常贴近于对角线,缺乏自信的模型的校准曲线是sigmoid形的。

校准曲线是一种评价模型的方法,在实际项目中应该是构建好模型,然后评价模型,改善模型,确定最终模型(C-index/ROC/DCA结果表明模型合格),最后对模型进行可视化展示(如森林图、列线图,生存点图等)。

References:

  1. Harrell FE Jr: Regression Modeling StrategiesWith Applications to Linear Models, Logistic Regres-sion, and Survival Analysis. New York, NY, SpringerVerlag, 2001.

  2. Hosmer DW, Hosmer T, Le Cessie S, Lemeshow S. A comparison of goodness-of-fit tests for the logistic regression model. Stat Med 1997; 16:965-980.

  3. Huang YQ, Liang CH, He L, et al. Development and Validation of a Radiomics Nomogram for Preoperative Prediction of Lymph Node Metastasis in Colorectal Cancer. J Clin Oncol. 2016 Jul 10;34(20):2436.

有关Topic 14. 临床预测模型之校准曲线 (Calibration curve)的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  3. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  4. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  5. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  6. ruby-on-rails - 如何将验证与模型分开 - 2

    我有一些非常大的模型,我必须将它们迁移到最新版本的Rails。这些模型有相当多的验证(User有大约50个验证)。是否可以将所有这些验证移动到另一个文件中?说app/models/validations/user_validations.rb。如果可以,有人可以提供示例吗? 最佳答案 您可以为此使用关注点:#app/models/validations/user_validations.rbrequire'active_support/concern'moduleUserValidationsextendActiveSupport:

  7. ruby-on-rails - Rails 模型——非持久类成员或属性? - 2

    对于Rails模型,是否可以/建议让一个类的成员不持久保存到数据库中?我想将用户最后选择的类型存储在session变量中。由于我无法从我的模型中设置session变量,我想将值存储在一个“虚拟”类成员中,该成员只是将值传递回Controller。你能有这样的类(class)成员吗? 最佳答案 将非持久属性添加到Rails模型就像任何其他Ruby类一样:classUser扩展解释:在Ruby中,所有实例变量都是私有(private)的,不需要在赋值前定义。attr_accessor创建一个setter和getter方法:classUs

  8. ruby-on-rails - Rails - 从另一个模型中创建一个模型的实例 - 2

    我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案

  9. ruby-on-rails - Ruby 中的内存模型 - 2

    ruby如何管理内存。例如:如果我们在执行过程中采用C程序,则以下是内存模型。类似于这个ruby如何处理内存。C:__________________|||stack|||------------------||||------------------|||||Heap|||||__________________|||data|__________________|text|__________________Ruby:? 最佳答案 Ruby中没有“内存”这样的东西。Class#allocate分配一个对象并返回该对象。这就是程序

  10. ruby-on-rails - Rails 3.1 中具有相同形式的多个模型? - 2

    我正在使用Rails3.1并在一个论坛上工作。我有一个名为Topic的模型,每个模型都有许多Post。当用户创建新主题时,他们也应该创建第一个Post。但是,我不确定如何以相同的形式执行此操作。这是我的代码:classTopic:destroyaccepts_nested_attributes_for:postsvalidates_presence_of:titleendclassPost...但这似乎不起作用。有什么想法吗?谢谢! 最佳答案 @Pablo的回答似乎有你需要的一切。但更具体地说...首先改变你View中的这一行对此#

随机推荐