点击关注,桓峰基因
桓峰基因
生物信息分析,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)
我们仍然采用软件包自带的肺癌数据库 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
使用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
##
本期选用rms 和 PredictABEL 两个包中的函数进行可视化,比较一下差异,主要目的也是方便大家选择。
绘制校准曲线时需要注意以下参数,需要根据自己的数据量情况设置参数,如下:
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}
这个方式是需要先计算出预测值和真实值,然后用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结果表明模型合格),最后对模型进行可视化展示(如森林图、列线图,生存点图等)。
Harrell FE Jr: Regression Modeling StrategiesWith Applications to Linear Models, Logistic Regres-sion, and Survival Analysis. New York, NY, SpringerVerlag, 2001.
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.
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.
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah
我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss
我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢
我有一些非常大的模型,我必须将它们迁移到最新版本的Rails。这些模型有相当多的验证(User有大约50个验证)。是否可以将所有这些验证移动到另一个文件中?说app/models/validations/user_validations.rb。如果可以,有人可以提供示例吗? 最佳答案 您可以为此使用关注点:#app/models/validations/user_validations.rbrequire'active_support/concern'moduleUserValidationsextendActiveSupport:
对于Rails模型,是否可以/建议让一个类的成员不持久保存到数据库中?我想将用户最后选择的类型存储在session变量中。由于我无法从我的模型中设置session变量,我想将值存储在一个“虚拟”类成员中,该成员只是将值传递回Controller。你能有这样的类(class)成员吗? 最佳答案 将非持久属性添加到Rails模型就像任何其他Ruby类一样:classUser扩展解释:在Ruby中,所有实例变量都是私有(private)的,不需要在赋值前定义。attr_accessor创建一个setter和getter方法:classUs
我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案
ruby如何管理内存。例如:如果我们在执行过程中采用C程序,则以下是内存模型。类似于这个ruby如何处理内存。C:__________________|||stack|||------------------||||------------------|||||Heap|||||__________________|||data|__________________|text|__________________Ruby:? 最佳答案 Ruby中没有“内存”这样的东西。Class#allocate分配一个对象并返回该对象。这就是程序
我正在使用Rails3.1并在一个论坛上工作。我有一个名为Topic的模型,每个模型都有许多Post。当用户创建新主题时,他们也应该创建第一个Post。但是,我不确定如何以相同的形式执行此操作。这是我的代码:classTopic:destroyaccepts_nested_attributes_for:postsvalidates_presence_of:titleendclassPost...但这似乎不起作用。有什么想法吗?谢谢! 最佳答案 @Pablo的回答似乎有你需要的一切。但更具体地说...首先改变你View中的这一行对此#