搞预测模型的同行,这两年应该没少被审稿人问一句话:“你的模型AUC很高,校准图也很好,然后呢?它到底能不能改变临床决策?”这个问题,一般就是用一条DCA决策曲线来回应的。
DCA全称Decision Curve Analysis,中文通常叫决策曲线分析,是Vickers和Elkin在2006年提出的办法,目的只有一个:把模型预测出来的“概率”放到真实的临床决策场景里,回答“在什么阈值下,按这个模型采取干预,净收益是正的”。本文我会从数学原理、临床逻辑、曲线解读讲到R和Python的完整代码实现,同时穿插我实际跑数据时踩过的坑。适合已经建好模型、准备写论文或者被审稿人要求补图的人,也能帮刚开始学临床预测模型的人理解DCA到底在算什么。
1. 先搞明白DCA到底在解决什么问题
1.1 准确率很高,不代表模型一定有用
很多初学者拿到一个预测模型,第一反应就是看AUC。AUC到了0.85,感觉论文已经成功了一半。但AUC这个指标本质上是区分度的考核,它只管一件事:随机抽一个生病的人和一个不生病的人,模型能不能把两者的风险概率排对顺序。至于“排序排对了”之后怎么转化为临床行动,AUC一个字都没提。
举个例子,一个恶性疾病在普通门诊人群里的患病率是5%。你建了一个模型,AUC 0.88,看着很漂亮。但如果这个模型的预测结果集中在很窄的概率区间里,医生拿到的预测概率基本上都在0.2到0.4之间,那医生该怎么办?是把所有概率超过0.3的人都收治,还是只处理概率最高的一小撮人?收治的人里面可能有很多假阳性,而不收治的人里面又有漏诊。AUC回答不了这个问题,灵敏度特异度也回答不了,因为这两个指标本质上还是在描述“诊断性能”,并不是“这个决定值不值得做”。
这里就要引入一个没有出现在传统指标里的东西:临床决策的代价。任何诊断或干预行为都有两方面的后果——给真阳性患者带来的获益,给假阳性患者造成的伤害。伤害可能来自不必要的穿刺、过度治疗、昂贵检查、患者焦虑等等。如果同样的准确率下,一种决策策略造成的假阳性伤害特别大,那即便AUC很高,这个模型在临床上可能还是不如“什么都不做”或者“所有人统统干预”。
1.2 阈值概率:把概率翻译成行动
要迈出从统计到决策的这一步,DCA引入了一个最关键的概念:阈值概率,记为pt。这个pt指的是,当模型预测概率达到多少时,你会认为“这个患者值得采取干预”。比如你把pt定在0.2,意思就是预测概率低于0.2的患者暂不处理,高于或等于0.2的患者给予干预。
这个阈值不是模型给的,也不是统计软件自动算的,它应该来自临床背景和决策者的价值判断。比如某种治疗非常安全、副作用很小,那么即使预测概率只有5%,你可能也愿意干预,pt会定得很低。反过来,如果干预手段本身有较大创伤,比如手术切除或者长期服用免疫抑制药,你就只愿意对风险很高的患者动手,pt自然要定得高一些。
关键在于,pt一旦定下来,就等价于确定了“真阳性获益”和“假阳性代价”之间的换算比例。对于一个阈值pt,如果一个患者的预测概率恰好等于pt,医生会觉得“治和不治差不多”,因为真阳性被救下的获益,刚好被假阳性付出的代价抵消。用公式写就是:假阳性代价 = 真阳性获益 × pt / (1 - pt)。这个pt / (1 - pt)在DCA里被称为优势比加权项,它是整条曲线的灵魂。
1.3 净收益公式并不神秘
DCA的输出指标叫净收益,Net Benefit,常用NB表示。最简单的二分类结局情况下,公式长这样:
NB = TP / N - FP / N × pt / (1 - pt)
其中N是总样本量,TP是被正确识别的真阳性人数,FP是被误判为阳性的假阳性人数。这里所有数字都不需要加权,因为把N一除,净收益就直接理解成“平均到每个患者身上的净获益”。这个公式在告诉你:真阳性是收益,假阳性是损失,而损失的权重由阈值pt决定。
我拿一个1000人的小队列手算一下,方便你彻底搞懂。假设真实患病人数是50人,患病率5%。取阈值pt = 0.2,此时模型把80个人判为高风险,其中30人确实是患者,50人是误伤。那么TP = 30,FP = 50,w = 0.2 / 0.8 = 0.25,所以净收益等于30/1000 - 50/1000 × 0.25 = 0.03 - 0.0125 = 0.0175。
这个0.0175可以理解成:在一千个患者里,采用“预测概率超过20%就干预”的策略,相当于在扣除了假阳性代价之后,净挽回了17.5个真阳性患者的获益。如果把策略换成“所有人都干预”,净收益是0.05 - 0.95 × 0.25 = -0.1875,比模型差得多。这就是DCA的核心思想:不做价值判断,只把两个决策方案放到同一个天平上比一比。
2. 决策曲线解读:别只看曲线高低
2.1 图上那三条线分别代表什么
拿到一张标准的决策曲线图,横轴是阈值概率pt,一般从0到1,纵轴是净收益NB,通常会有三条主要曲线:一条“不干预”参考线,是所有患者都不接受干预,净收益永远是0,也就是横轴本身;一条“全体干预”参考线,是所有人都接受干预,这条线是一条斜向下的直线,从事件率开始,随着阈值升高而下降;还有一条或者几条倾斜的模型曲线,是你要重点解读的对象。
“全体干预”线为什么是斜线?因为在阈值很低的时候,比如pt = 0.01,假阳性代价项w约等于0.0101,这时代价很小,所以对几乎所有患者采取干预也许是收益最大的方案。可随着pt提高,w变大,假阳性代价越来越重,“谁都治”的策略就开始吃亏了。这条线在阈值等于事件率附近通常会穿过零线,之后一路往下走。
模型曲线描述的是:在每个阈值pt下,用“预测概率超过pt就干预”这个规则,净收益是多少。判断一个模型有没有价值,不要求它全程压着所有参考线,只要它在某个有实际意义的阈值范围内高于“全体干预”和“不干预”两条参考线,就能说明它在这个场景下是值得用的。
2.2 净收益和曲线面积之间没有固定关系
有一个最常见的误读:把决策曲线简单理解成“两条线谁在上面谁就更好”。如果只看单个阈值点,这种说法基本成立,但曲线是一个连续变化的过程,不同阈值下结论甚至可能反转。比如模型A在低阈值时净收益高,模型B在高阈值时净收益高,这时候说谁更好就没有意义,必须回到真实临床场景里看“决策者更关心哪个阈值范围”。
还有一个容易忽略的点:决策曲线不需要去算曲线下面积。我见过有人把DCA曲线做成ROC那样跑一个AUC值,然后拿去比较模型优劣,这是概念混淆。DCA的价值在阈值区间,不在曲线下面积。曲线的形状对解读来说很重要,横轴过宽或者过窄都会误导读者。标准做法是只展示对临床有意义的阈值范围,比如0到0.5或0到0.6,超出这个范围基本没有决策者会把阈值定得那么高。
2.3 一个完整的实际案例解读
我之前帮朋友看一个脓毒症风险预测的项目,队列里事件率约为8%,模型加入两个生物标志物后AUC从0.74升到0.79。画DCA时,两条模型曲线在阈值8%到45%之间都高于“全体干预”线和“不干预”线,而且在20%附近净收益达到峰值0.052。
这个结果该怎么解读?首先,阈值8%开始,模型曲线越过“全体干预”线,意味着在这个患病率水平下,盲目把所有患者都收治ICU是不划算的,但模型能把真正的高危人群筛出来。其次,在20%这个点,净收益最高,每处理1000个患者,相当于在扣掉假阳性代价后净保住了52个真阳性患者的获益。当阈值超过45%后,由于预测概率很少超过这个值,模型开始趋近于“不干预”,净收益逼近0。这说明这个模型的临床适用范围是阈值10%到45%,在这个窗口里它能帮医生做决策,出了这个窗口价值就有限了。
这么解读下来,审稿人最关心的问题其实都覆盖了:事件率是多少、模型在哪个阈值范围内有效、相对“全部干预”和“全部不干预”的增量是多少。
3. R语言代码实现:dcurves包绘图效率最高
3.1 数据和模型准备阶段,先做三件事
不管用哪个R包,画DCA前都要先把数据和模型准备到位。第一步确认结局变量是0/1编码,而且1必须代表发生了目标事件,0代表未发生事件。如果编码反了,净收益计算会完全乱掉。第二步确认你手里有每个样本的预测概率,这个概率必须来自已经拟合好的模型,而且模型最好做过校准。第三步,看清楚时间属性——如果结局是二分类,直接处理;如果是生存数据,就必须指定一个评估时间点,因为生存事件在不同时间点的状态不一样,计算方法也不一样。
实际项目中很多人会在这里踩坑。比如把预测概率列存成字符型,或者概率里有缺失值,dcurves包计算时直接报错;又比如事件变量明明是因子类型,逻辑回归内部把因子的第一水平当成了参考类别,导致预测概率是“不发生事件”的概率,画出来的DCA曲线低到离谱。解决办法很简单:建模前用factor显式设置水平,预测后用str()确认输出结构。
3.2 二分类结局的DCA代码
R里目前最省事的包是dcurves,它是Vickers团队维护的现代版本,直接用ggplot2出图。安装和基础用法如下:
# 安装和载入 install.packages("dcurves") library(dcurves) # 假设你的数据包含 outcome 和预测概率 pred_prob # 直接用概率列画 dca(outcome ~ pred_prob, data = df, thresholds = seq(0, 0.5, by = 0.01)) |> ggplot()如果你是直接用原始变量而不是预测概率列,dcurves也支持传入原始自变量,它会自动拟合模型后计算:
dca(cancer ~ cancerpredmarker + age, data = df_binary, thresholds = seq(0, 0.5, by = 0.01)) |> ggplot()这个语法会为每一个变量单独生成一条决策曲线,而不是把两个变量合在一起。如果你希望比较的是多个完整模型,最稳妥的做法是先分别拟合模型,得到每个模型的预测概率,然后用dca(outcome ~ prob_model1 + prob_model2)的形式进行比较。这一步经常有人弄混,写论文的时候一定要说清楚曲线代表的是哪个模型的预测概率。
thresholds参数控制横轴的范围和密度。默认情况下dcurves会自动选一组阈值,但实际应用中我更建议手动指定,比如seq(0, 0.5, by = 0.01),既覆盖常见的临床决策区间,画出来又平滑。如果样本量很小,步长可以适当放大到0.02,否则曲线会左右抖动,看起来很难看。
3.3 生存数据DCA怎么处理
生存结局是临床预测模型的重灾区,因为直接在生存数据上用二分类DCA会损失时间信息。正确的做法是在dca函数里使用Surv()格式的响应变量,并指定time参数:
library(survival) dca(Surv(ttcancer, cancer) ~ cancerpredmarker, data = df_surv, time = 5) |> ggplot()这里time = 5表示评估5年时的决策收益。dcurves会基于Kaplan-Meier估计或者累计发生率函数,计算在5年这个时间点上各个阈值对应的净收益。如果随访时间不够长,超过time的样本不能简单当作事件或删失处理,需要谨慎解释。
生存DCA里还有一个细节:如果你的结局存在竞争风险,比如患者可能先死于其他疾病,再用传统的Kaplan-Meier去估计事件概率会高估结局发生率。这种情况下要优先考虑使用累计发生率函数,或者换用支持竞争风险模型的专用方法,否则决策曲线会偏向于“积极干预”。
3.4 给曲线加上置信区间和辅助线
论文里面光秃秃一条线不够看,审稿人经常会要求提供置信区间。dcurves提供两种方式:一种是基于bootstrap的自助法,另一种是解析法。推荐在数据量允许的情况下使用bootstrap,代码上加一个参数就行:
dca(outcome ~ pred_prob, data = df, thresholds = seq(0, 0.5, by = 0.01)) |> dcurves::standardized_net_benefit() |> ggplot()有些版本是直接在dca对象上调用as_tibble()后再用ggplot2手工画。如果你只是想要研究内部验证的置信区间,bootstrap次数设置在500次以上比较稳定。不过诚实地说,bootstrap置信区间在某些情况下会因为数据稀疏变得特别宽,尤其是高阈值区域,这本身就是一个信号:高阈值范围的结论不可靠,报告时要谨慎。
dcurves还有一个harm参数,可以指定干预本身的潜在伤害率。默认harm = 0,也就是假设干预没有额外伤害,这对某些场景来说是合理的,但对创伤性治疗来说,把harm设为0会被审稿人质疑。加了这个参数后,净收益曲线整体会往下走,具体下降幅度取决于伤害率设置。
4. Python代码实现:不用专用包也能完整复现
4.1 为什么需要手动实现DCA
R里有很多现成DCA包,但Python生态里没有像dcurves那么统一的专用包。虽然有sklearn和lifelines可以完成模型拟合,但DCA本身不在它们的标准接口里。更麻烦的是,DCA的计算逻辑并不复杂,可一旦依赖某个第三方包,换环境、更新版本都可能遇到坑。我自己的习惯是直接在Python里手写一个计算净收益的函数,代码量不大,逻辑清晰,还能方便地嵌入到私人复现脚本里。
4.2 核心计算函数与绘图代码
给你一套我封装好的Python代码,直接复制就能用。它的核心逻辑是按阈值网格循环,在每个阈值点计算TP、FP,再代入净收益公式。
import numpy as np import pandas as pd import matplotlib.pyplot as plt def cal_dca(y_true, y_pred, thresholds=None): """ 计算二分类结局下的决策曲线数据 y_true: 0/1数组,1表示事件发生 y_pred: 预测概率,取值0~1 返回DataFrame:threshold, NB_model, NB_all, NB_none """ y_true = np.asarray(y_true) y_pred = np.asarray(y_pred) n = len(y_true) prevalence = y_true.mean() if thresholds is None: # 自动生成阈值网格,按0.01步长 thresholds = np.arange(0, 1, 0.01) results = [] for pt in thresholds: if pt <= 0 or pt >= 1: # 端点无效,直接跳过,避免除零 continue pred_label = (y_pred >= pt).astype(int) tp = np.sum((pred_label == 1) & (y_true == 1)) fp = np.sum((pred_label == 1) & (y_true == 0)) w = pt / (1 - pt) nb_model = tp / n - (fp / n) * w # 参考线:全部不干预 NB = 0 nb_none = 0.0 # 参考线:全部干预 p_all = pred_label.sum() # 这个不用于全体干预线 nb_all = prevalence - (1 - prevalence) * w results.append((pt, nb_model, nb_all, nb_none)) df_dca = pd.DataFrame( results, columns=["threshold", "NB_model", "NB_all", "NB_none"] ) return df_dca def plot_dca(df_dca): plt.figure(figsize=(7, 5)) plt.plot(df_dca["threshold"], df_dca["NB_model"], label="Model", lw=2) plt.plot(df_dca["threshold"], df_dca["NB_all"], label="Treat All", lw=1.5, linestyle="--") plt.axhline(0, color="grey", linestyle="-", lw=1) plt.xlabel("Threshold Probability") plt.ylabel("Net Benefit") plt.legend() plt.ylim(-0.05, 0.15) # 根据实际数据调整 plt.grid(alpha=0.3) plt.show()这段代码的边界处理比较容易出错。pt等于0时,会把所有人都判成阳性,且权重为0,此时的净收益等于患病率,这个点其实有数学意义,但因为分母1 - pt没有问题,而阈值0在临床场景里约等于“零代价干预”,通常不会有人这么用,所以直接跳过端点对绘图更友好。pt等于1时更危险,权重趋近无穷大,直接算会得到负无穷,必须跳过。
4.3 接入模型预测概率并绘制
拿到任意一个分类器的预测概率,就可以直接套用上面的函数。我用逻辑回归举例:
from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42 ) model = LogisticRegression(max_iter=1000) model.fit(X_train, y_train) y_prob = model.predict_proba(X_test)[:, 1] df_dca = cal_dca(y_test, y_prob, thresholds=np.arange(0.01, 0.5, 0.01)) plot_dca(df_dca)如果你同时要比较两个模型,比如逻辑回归和随机森林,只要分别拿到预测概率,再生成两条模型线即可。多模型对比的光影要处理好:统一阈值范围、统一纵轴范围、统一参考线,否则不同图之间没有可比性。
4.4 Python实现时常见的数值坑
手动实现DCA,最大的坑不是公式,是数据层面。第一,y_true必须是0和1的标准整数,如果传入了字符串或1/2编码,所有判断条件都会出问题。第二,y_pred必须是模型输出的概率值,不能是评分或者Logit值,否则阈值范围完全对不上。第三,如果预测概率有缺失值,numpy的sum会把缺失值当成1参与计算,导致结果错得莫名其妙。最佳实践是在计算前加一句np.isfinite检查,把所有非有限值剔除或者定位到行号处理掉。
我还习惯在全部模型计算完成后,手动跟R包结果对一下数,比如随机抽三个阈值点,把TP和FP数量对比一遍。两个语言算出来的NB通常在小数点后十几位才开始分叉,出现显著差异基本就是哪边的数据处理有问题。
5. 实战调参与避坑:阈值范围、样本量、偏倚问题
5.1 阈值范围到底应该取多宽
这是我在实际项目中经常被问到的一个问题。理论上横轴能从0取到1,但绝大多数临床场景下,决策阈值不会超过0.5。如果一个治疗手段的假阳性代价很高,医生希望预测概率达到60%才干预,那确实存在阈值设高的情况,但这种情况在决策曲线里往往表现为模型线和“不干预”线贴在一起,因为几乎没多少人预测概率会超过0.6,模型不能提供任何决策增量,画出来也没有信息量。
有一个更科学的思路,从“代价比”反推阈值范围。DCA中的w = pt / (1 - pt)本质是“为了保住一个真阳性,愿意付出多少个假阳性干预”的代价比。比如你设定阈值0.1,那权重大约是0.11,也就是说你愿意为了每1个真阳性获益接受0.11个假阳性损伤。倒过来,如果你认为一次不必要的干预带来的伤害,相当于治好一个真阳性获益的十分之一,那么阈值就应该设在0.1附近。很多成熟研究中会把这层对应关系写进报告,让读者理解“曲线展示的这段阈值范围,实际对应的是哪一类临床价值判断”。
5.2 样本量小和病例对照设计的影响
DCA的计算虽然只是简单的加减乘除,但小样本下曲线会很不稳定。我见过两千样本的队列,阈值步长取0.005时,整条曲线像心电图一样上下跳,这不是模型坏,而是每个阈值点上刚好只有几个人改变决策,TP和FP的变化被急剧放大。遇到这种情况,要么调大步长,比如0.02或0.05,要么用平滑方法处理曲线。
更隐蔽的问题是研究设计。如果你用的是病例对照数据,也就是有意选取确定发病和确定不发病的人,然后各占一半,那得到的患病率不是自然人群的患病率。而“全体干预”参考线里的prevalence必须用目标人群的真实事件率,否则参考线画得毫无意义。处理办法有两个方向:一是重新加权让样本人群的事件率接近目标人群,二是在报告里说清楚这个决策曲线是对哪个特定人群而言的,不能直接外推到患病率不同的其他队列。
5.3 模型校准度对DCA影响极大
DCA有一个隐藏前提:y_pred是比较可靠的预测概率,不是瞎排出来的分数。校准度差的模型,比如预测概率整体偏高,那低阈值区域的净收益会被明显高估,因为大量假阳性被误判成了真阳性概率高的人。所以实际操作中,我建议画DCA之前先画校准图,校准图都不像样的话,DCA基本不能用来下临床结论。
有几种校准处理方式。最简单的做法是对预测概率做一个Logistic回归校准,也就是把原始预测概率作为自变量,真实结局作为因变量重新拟合,再用得到的新概率去做DCA。复杂一点的用Isotonic回归做非参数校准。但要注意,校准步骤本身会引入额外的过拟合风险,尤其是外部验证时校准效果会打折,这一点要在方法部分写明白。
5.4 论文里报告DCA时应该写哪些要素
一个能通过审稿的DCA结果,至少要包含下面这些信息:队列来源和样本量、事件数以及计算得到的事件率、结局定义、如果是生存数据还要注明评估时间点、模型预测概率的来源(内部验证还是外部验证)、阈值概率范围的选择依据、主要参考线的解释、关键阈值点的净收益数值、置信区间是否报告以及用什么方法计算。实际写作中,很多人只贴一张图,不写横轴阈值范围和事件率,这让读者完全无法判断图的可靠性。我个人习惯是图下注明“Net benefit curves at threshold probabilities from 0 to 0.5, with 8% event rate, estimated at 5 years”,比空泛地写一句“DCA curve showed that the model was beneficial”要有说服力得多。
很多期刊还会要求给出决策曲线的数值表格,也就是把不同阈值下的TP、FP、净收益列出来,方便读者自己算。我建议有精力的话把0.05、0.10、0.15、0.20、0.25这几个常见阈值点的净收益整理成小表格放进补充材料,审稿人问到的时候会省很多事。
6. 常见问题与排查技巧实录
6.1 曲线在低阈值区域“起飞”,可信吗
模型曲线在非常低的阈值处往往有一个快速上升段,然后回落。这个现象本身不假,但低阈值区域的净收益对模型校准非常敏感。阈值0.05对应的权重只有0.05/0.95约等于0.0526,这意味着假阳性的代价极小,只要你把一小部分高危人群筛出来,净收益就很容易为正。这种情况下曲线高不高并不能说明模型多强,只能说明低阈值下“稍微筛一下”总比什么都不干强。如果你的目标场景里干预措施有不可忽视的伤害,建议从0.1或者更高的阈值开始画图,避免给读者造成“模型无所不能”的印象。
6.2 净收益出现负值,模型就废了吗
不是。净收益为负只说明在那个阈值点,按模型决策不如“不干预”。这不能说明模型在其他阈值没用。临床上真正关心的是决策曲线落在两条参考线上方的区间有多大,这个区间越宽、越接近临床常用的决策阈值,模型价值就越大。如果一条曲线只有一截很短的小区间在参考线上方,那说明模型能帮上忙的场景很窄,要如实写;如果整条曲线都在参考线下方,那大概率是校准出了问题或者事件率极低,得回头检查建模流程。
6.3 常见问题速查表
| 现象 | 可能原因 | 解决建议 |
|---|---|---|
| 曲线在低阈值处急剧下降 | 结局编码反了或预测概率方向反了 | 检查1代表事件、预测概率方向,做交叉表 |
| 模型曲线长期低于全体干预线 | 事件率太高,或者模型并没有真的把高危人群区分出来 | 检查患病率分布,确认模型校准度 |
| 曲线抖动严重 | 样本量小或阈值步长太密 | 调大步长,用bootstrap置信区间辅助判断 |
| 全体干预参考线位置和预想不符 | prevalence计算错误,或使用了非目标人群事件率 | 用真实目标人群的事件率,带权数据先还原频数 |
| 两个模型曲线几乎重叠 | 预测概率排序高度相似,或两个模型实际区分度都很差 | 检查AUC差异和校准差异,报告置信区间 |
| 高阈值区域曲线异常穿越 | 高阈值下决策人数极少,TP/FP估计不稳 | 限制展示的阈值范围,或使用平滑曲线 |
网上有些人会问“决策曲线××指标源码”一类的问题,本质上都是把某个阈值下的TP、FP、净收益数值提取出来做成表格或点图。理解了净收益的计算过程,你自己就算出任意阈值点上的指标数值,不需要依赖任何人的所谓“源码包”。
6.4 我踩过的几个坑
第一,结局编码方向。这个错误几乎每个新手都会犯,尤其是用因子型变量的时候,逻辑回归默认按字母序或因子水平序处理,导致“1”可能对应未发生事件。我在一个项目中直接把预测概率和结局放在同一个DataFrame里跑DCA,曲线看起来完美地压过了参考线,结果发现是方向反了,全线净收益完全错位。从此以后,我在建模之前都会强行把结局变量as.integer并检查table()。
第二,生存数据的结局定义。用生存数据做DCA,时间点选得不同,结果差异很大。比如5年和1年的净收益曲线可能差出一大截,因为早期事件和晚期事件的发生率不一样。写论文时一定要明确标注“评估时间点”,不能只写“生存DCA”。
第三,不要盲目相信默认阈值范围。R里的dcurves包和很多教程默认阈值从0到1,但实际画图时最好根据医学背景裁剪横轴。如果你们的干预手段是有创的,把图截到0到0.4就够了,拉太宽既难看又容易误导。
第四,正式的预测模型文章中,DCA不应该孤立出现。它最好是和AUC、校准曲线、临床决策阈值一起打包呈现,形成“区分度-校准度-临床效用”的完整证据链。只有一条孤零零的DCA曲线,说服力会打折扣。
我个人现在做项目的习惯是:模型拟合完成后先跑校准图,校准没问题,再计算AUC,最后画DCA。DCA画完不会立刻下结论,而是把关键阈值点的净收益和“不干预”“全体干预”两条参考线做差,算出实际增量,然后写进结果部分。这种做法既能应对审稿人的追问,也能让合作科室的医生真正理解模型到底在哪个场景下有用。希望这篇文章能让你少走一点我当年走过的弯路。