本文定位:bridging study(桥接研究)与 follow-on CDx(follow-on companion diagnostic,后续伴随诊断)统计方法的学习笔记。前两章先铺背景——精准医学与传统医学有何不同、为什么要”搬运证据”(概念框架),以及 CDx 试验怎么设计(入组、对照、设盲三个取舍);有了背景再进入方法。方法部分分两条腿走概念 讲清每个量是什么、公式是什么;worked example 搭一个已知真值的生成模型(DGP,data-generating process),从它派生所有下游数据集,逐节用公式估计再和已知真值对比,验证 bridging / follow-on 机制能否把被抽样偏差扭曲的疗效/一致性还原回真相。所有插图均由本文的 R 代码生成。

核心论点:bridging study 和 follow-on CDx 是同一类统计问题——用 assay 间的 concordance 把疗效证据从一个 assay 定义的人群搬运到另一个 assay 定义的人群——区别只在缺失数据的结构不同。

1 概念框架

先分清精准医学和传统医学。 传统医学按”平均病人”给药:同一诊断下大家吃同一种药、同一剂量,疗效是全人群的平均效应,个体差异靠事后调整。精准医学换了前提——先用一个 biomarker(如某基因突变状态)把人群切成疗效/风险不同的亚群,再对亚群分别决策。药不再是”对这个病有效”,而是”对这个 biomarker 阳性的人有效”。

这一换,就逼出一个传统医学大多能绕开、精准医学却绕不开的问题:biomarker 阳性/阴性得靠一个检测工具判定,而工具不完美、还可能换。疗效证据是对”某工具筛出的人群”成立的,工具一变,人群跟着变,证据能不能接着用就成了统计问题。本文全部内容都在处理这一个问题。

1.1 临床决策问题与术语

精准医学的统计问题不是从名词开始的,而是从一个很实际的问题开始:医生手里拿到的是某个检测平台给出的阳性/阴性结果,治疗证据却可能来自另一个检测平台定义的人群

两个典型例子:

  • 肺癌靶向治疗:非小细胞肺癌里,EGFR 突变、ALK 融合等 biomarker 决定患者是否进入靶向治疗路径。pivotal trial 可能用中心实验室 PCR/NGS assay 入组,但真正上市、报销或院内常规使用的是另一个 CDx 平台。
  • 免疫治疗配体检测:PD-L1 表达常用不同抗体、评分算法和 cut-off 定义阳性。两个 assay 都在测”同一个临床概念”,但并不保证对每个患者给出相同分类。

(同类还有 HER2 的 IHC/FISH 分层、BRCA/HRD 与 PARP 抑制剂的 panel 差异——机制一样,不再展开。)

这些例子的共同点:biomarker 是生物学目标,assay/CDx 是测它的工具。治疗效果通常不是对”工具”本身成立,而是对这个工具筛出来的人群成立;一旦工具换了,人群也跟着变了。

那为什么偏偏是精准医学?糖尿病、心血管也测 biomarker、也换试剂啊。 差别在有没有一把公共的尺。传统病的指标是连续、可溯源的物理量——血糖 mg/dL、HbA1c、LDL 都锚定到统一参考方法,换个仪器只要校准到同一把尺,结果就一致、人群几乎不动,换工具只需重新校准,不必搬运证据。精准医学的麻烦是常常没有这把公共尺:突变有/无靠某个 assay 判,borderline 样本各 assay 会吵,而且没有”真相”可校——只能拿两个 assay 互比一致率(PPA/NPA)。

更精确地说,bridging/follow-on 问题要三条同时成立,传统病通常缺一条,精准病常常三条齐活:

  1. 测量是无金标准的定性分类(而非可溯源的连续量)——没有绝对真值可校,只能 assay 互比。
  2. 疗效在这个分类上强异质(predictive)——marker+ 大获益、marker− 几乎无效,药效在 cut-off 处近乎翻转;换 assay 就换了”谁获益”。
  3. assay 与药监管绑定(companion)——label 写”仅限某 assay 识别的人群”,逼新检测证明能接上旧证据。

反过来,传统病一旦这三条凑齐,照样撞上同样的统计:肌钙蛋白换代改了”心梗阳性”的定义、心血管风险评分换代改了该吃他汀的人群、HLA-B*5701 门控 abacavir——这些本质就是 CDx 问题。所以精准医学不是发明了新问题,而是把一个被传统病靠”连续+可溯源+药诊解耦”压住的问题,推到了台面正中央。

四个 assay 角色(贯穿全文)

  • CTA (clinical trial assay):pivotal trial 里实际入组用的 assay。
  • CDx (companion diagnostic):最终要上市/使用的伴随诊断。CDx 是检测 biomarker 的手段(device/platform),不是 biomarker 本身;监管验证的是这个设备的性能。FDA 的定义强调:CDx 提供的是对应治疗产品安全、有效使用所必需的信息 (U.S. Food and Drug Administration 2014)
  • FCD (follow-on CDx):新的后续伴随诊断,想替代已批准的检测。
  • CCD (comparator CDx):已经批准、拿来做参照的伴随诊断。

1.2 统计上真正难的地方

换一个 assay 后,统计问题立刻变成四件事:

  • 分类不完全一致:CTA+ 不等于 CDx+,FCD+ 也不等于 CCD+。PPA/NPA(阳性/阴性一致率)不是形式指标,而是在问”旧证据覆盖了新阳性人群里的多少人”;当比较对象不是公认参考标准时,FDA 也明确建议成对报告 PPA/NPA 及其双侧 95% CI,而不是只报总体一致率 (U.S. Food and Drug Administration 2007)
  • 疗效被混合和稀释:如果真 marker+ 获益大、真 marker− 获益小,那么 assay+ 人群的表观疗效取决于这个 assay 的 PPV(阳性预测值)。检测越差,阳性人群里混进的真阴性越多,疗效越被稀释。
  • 关键格子缺失:富集试验只入组 CTA+ 患者。事后用最终 CDx 重测时,CTA−/CDx+ 这格人可能有临床意义,却没有随机化疗效数据。这不是普通亚组分析,而是缺失数据和外推问题。
  • 一致性需要参照物:FCD 对 CCD 的 PPA 是 90% 到底够不够?如果 CCD 自己重复检测的一致性也只有 92%,评判标准就不应是”完美一致”,而是”新检测是否不明显差于参照检测自己的重复性”,这就是 NI(非劣效性)思路。

所以本文后面的所有公式都在回答同一个问题:如何把一个 assay 定义下的疗效证据,合理搬运到另一个 assay 定义的人群上。先把名词和场景说清楚,再用一个已知真值的 DGP 看这些搬运公式什么时候能还原真相、什么时候只能靠假设。

1.3 三种路径与验证场景

pivotal / bridging / follow-on 是三条开发路径,全文最重要的区分——唯一差别是”最终上市 CDx”与”试验里实际入组用的 assay”的关系。真实开发里不是”大多数项目都同时做 bridging 和 follow-on”,而是按时间顺序落到其中一条:

par(family = "cjk", mar = c(1, 1, 3, 1))
plot(NA, xlim = c(0, 12), ylim = c(0, 7), axes = FALSE, xlab = "", ylab = "",
     main = "三种验证场景", cex.main = 1.6)
draw_box(1.8, 6, "Pivotal", "gray30", w = 2.6, h = 0.85, cex = 1.15)
draw_box(5.0, 6, "Pivotal trial", "gray40", w = 3.0, h = 0.85, cex = 1.05)
draw_box(9.4, 6, "最终 CDx\n直接入组", "#2E86AB", w = 4.0, h = 0.95, cex = 1.05)
draw_arr(6.6, 6, 7.3, 6)
draw_box(1.8, 4, "Bridging", "gray30", w = 2.6, h = 0.85, cex = 1.15)
draw_box(5.0, 4, "Pivotal trial", "gray40", w = 3.0, h = 0.85, cex = 1.05)
draw_box(9.4, 4, "CTA 入组\n最终 CDx 不同", "#A23B72", w = 4.0, h = 0.95, cex = 1.05)
draw_arr(6.6, 4, 7.3, 4)
draw_arr(9.4, 3.5, 9.4, 2.55, "bridging analysis", cex = 1.0)
draw_box(1.8, 2, "Follow-on", "gray30", w = 2.6, h = 0.85, cex = 1.15)
draw_box(5.0, 2, "已批准\ncomparator CDx", "gray40", w = 3.0, h = 0.95, cex = 0.95)
draw_box(9.4, 2, "新 follow-on\nCDx (FCD)", "#C73E1D", w = 4.0, h = 0.95, cex = 1.05)
draw_arr(6.6, 2, 7.3, 2)

  • Pivotal(最理想,co-development):药物 pivotal trial 入组时就用最终要上市的 CDx,上市 CDx 就是入组 assay。疗效证据直接属于 CDx+ 人群,统计上最干净,通常不需要 bridging。FDA 的总体原则也是尽可能同步开发并同期批准/许可治疗产品与其 CDx (U.S. Food and Drug Administration 2014, 2016)
  • Bridging(做不到同步时):trial 开始时最终 CDx 还没定型,只能用 central lab assay、LDT(laboratory-developed test,实验室自建检测)、prototype、旧 cut-off、旧 platform 入组(即 CTA)。申报时最终 CDx 与 CTA 不一致,需 bridging analysis 把原 trial 疗效证据接到 CDx 定义的人群上 (U.S. Food and Drug Administration 2016; Song et al. 2024)
  • Follow-on(药物/CDx 已上市后):另一个新检测(FCD)想拿同一适应症,不重新证明 biomarker-drug biology,而是证明自己与已批准的 comparator CDx(CCD)足够一致,间接支持 FCD+ 人群疗效 (Li 2016)

一句话:bridging 解决”试验 assay 与最终 CDx 不一致”;follow-on 解决”新 CDx 能否替代已批准 CDx”。真实临床使用还更复杂——院内 NGS panel、LDT、商业综合 panel、不同样本类型,监管批准/报销/临床实际检测不总完全重合。两者各自的定义与做法,都放在后面「从生成模型到 follow-on 与 bridging」一章里展开(同一地基、两种搬运)。


2 试验设计:入组、对照、设盲

前面(§1.3)按”CDx 与入组 assay 的关系”分了三条路径。这一章换到设计层面:一个 CDx 试验具体怎么搭?

先给两组定义。伴随诊断 (companion diagnostic, CDx) 是为对应治疗产品安全有效使用提供必要信息的体外诊断设备 (U.S. Food and Drug Administration 2014)。围绕它:

  • 两类主张Predictive(预测性)——预测治疗对结局的差异效应,需 treatment × CDx 交互证据(marker+ 和 marker− 的疗效不同);Selection(选择性)——只筛出用药亚群(通常 biomarker+),在选中人群证明有意义的疗效即可,不必量化 marker− 那侧。
  • 两类验证Analytical(分析验证)——精密度、准确度、干扰/稳健性;Clinical(临床验证)——识别/选择患者、预测差异获益的能力。

主张类型决定要拿什么证据;但落到怎么搭试验,有三个彼此独立的取舍,外加一条贯穿的纪律。这几件事最容易混,先用一张表钉清楚:

三个取舍 + 一条纪律

常听到的概念

其实属于

谁入组(富集只 CDx+ / 全员含 CDx−)

取舍一 · 入组

随机化、要不要并行对照臂

取舍二 · 对照

open-label、双盲/三盲、独立评估 BICR

取舍三 · 设盲

揭盲前锁定 CDx 分析计划、防事后 post-hoc

纪律 · 分析预设

  • 取舍一 · 入组:谁进组?富集(只 CDx+,删 CDx−) vs 全员(含 CDx−)。
  • 取舍二 · 对照:治疗组配不配并行对照?随机对照试验(randomized controlled trial, RCT) vs 单臂。
  • 取舍三 · 设盲:瞒不瞒处理信息?双盲/三盲 vs open-label(外加独立评估 BICR)。
  • 纪律 · 分析预设:CDx 的 assay/cutoff/分析计划必须在揭盲看数据前锁定,不做事后 post-hoc(这不是可选项,是纪律)。

三个取舍相互独立(一条怎么定,不限制另外两条):富集人群里既能 RCT 也能单臂;RCT 既能双盲也能 open-label;单臂则必然 open-label(但仍保留 BICR)。下面逐条讲。

2.1 取舍一 · 入组谁:富集(删 CDx−)还是全员

理论上,predictive 主张要 marker-by-treatment 设计:CDx 检测后,CDx+ 与 CDx− 两个分层里都要做 1:1 随机化(治疗 vs 对照),这样才能估交互。可真做的时候(尤其肿瘤靶向药),CDx− 分层通常被整条筛除,既不入组也不随机(下图红叉):

par(family = "cjk", mar = c(0.5, 0.5, 3, 0.5))
plot(NA, xlim = c(0, 14), ylim = c(-0.6, 6), axes = FALSE, xlab = "", ylab = "",
     main = "Predictive 设计(理论) vs 富集现实:CDx− 常被筛除", cex.main = 1.3)
draw_box(1.7, 3, "符合条件患者", "gray30", w = 3.0, h = 0.9, cex = 1.05)
draw_box(5.0, 3, "CDx 检测", "gray40", w = 2.2, h = 0.9, cex = 1.05); draw_arr(3.3, 3, 3.85, 3)
# CDx+ 支路:实际入组、随机
draw_box(8.4, 4.5, "CDx+  1:1 随机", "#2E86AB", w = 3.2, h = 0.9, cex = 1.0)
draw_arr(6.15, 3, 6.75, 4.4)
for (yy in c(5.2, 3.8)) draw_box(12.2, yy, if (yy > 4.5) "治疗 T=1" else "对照 T=0", "gray55", w = 2.6, h = 0.62, cex = 0.95)
draw_arr(10.05, 4.6, 10.85, 5.2); draw_arr(10.05, 4.4, 10.85, 3.85)
# CDx- 支路:富集设计里筛除 —— 淡蓝(未入组)+ 划红叉
draw_box(8.4, 1.5, "CDx−  1:1 随机", "#AED6F1", w = 3.2, h = 0.9, cex = 1.0, txt = "gray20", border = "#7FB3D5")
draw_arr(6.15, 3, 6.75, 1.6)
for (yy in c(2.2, 0.8)) draw_box(12.2, yy, if (yy > 1.5) "治疗 T=1" else "对照 T=0", "#AED6F1", w = 2.6, h = 0.62, cex = 0.95, txt = "gray20", border = "#7FB3D5")
draw_arr(10.05, 1.6, 10.85, 2.2); draw_arr(10.05, 1.4, 10.85, 0.85)
segments(6.55, 2.25, 13.6, 0.45, col = "#C73E1D", lwd = 3)
segments(6.55, 0.45, 13.6, 2.25, col = "#C73E1D", lwd = 3)
text(10.0, -0.25, "富集(靶向)设计:CDx− 筛除,不入组、不随机", col = "#C73E1D", font = 2, cex = 0.95)

CDx− 被筛除有两条硬理由:

  • 伦理:给预期无效的 marker− 患者上靶向药、或把他们放进对照臂承担风险与延误,站不住脚。
  • 效率:marker− 会稀释总体疗效、抬高所需样本量。

于是绝大多数 CDx 试验退化成富集(靶向)设计:只入组阳性患者(实际是 CTA+),只在阳性人群里随机治疗 vs 对照。拿到的是 selection 主张(“阳性人群有效”),不是完整的 predictive/交互主张——图中下半条支路的数据从来没产生过。

这正是全文所有麻烦的根源。 因为 CDx−(以及后面会遇到的 CTA−/CDx+ 那一格)根本没被随机化、没有疗效数据,想把疗效证据搬到另一个 assay 定义的人群上,就只能对这些没观测到的格子做假设——这就是后面讲 bridging 时 \(\delta_{01}\) 不可识别的由来。

那到底什么时候能删 CDx−、什么时候不能? 取决于你要哪类主张:

  • 能删(富集就够):目标是 selection 主张(“该给谁用”),且有强生物学理由 + 既往证据表明 marker− 基本不获益——典型是药物直接靶向该突变产物:marker− 连靶点都没有,不可能获益,让他们上药纯担风险、进对照臂还延误治疗,伦理上也不该随机化。:伊马替尼(BCR-ABL 融合,CML)、曲妥珠单抗(HER2 扩增,乳腺癌)、维莫非尼(BRAF V600E,黑色素瘤)、拉罗替尼/恩曲替尼(NTRK 融合,tumor-agnostic)——都只入组阳性、直接删 marker−。代价:只拿到阳性人群疗效,并留下缺失格子。
  • 不能删(要全员/两层):目标是 predictive 主张、必须证明疗效随 marker 不同(treatment × marker 交互);或 marker− 是否获益本身存疑、cutoff 还没定、biomarker 的预测价值正是待验证的问题。这些情况下删掉 marker− 等于假设了本该由数据回答的东西:抗 EGFR 单抗(西妥昔单抗、帕尼单抗)治结直肠癌——正因为入组了全部患者,才发现 KRAS 突变者不但不获益、甚至更差,只有野生型获益;若一开始就富集野生型,根本无从证明突变型不该用。又如连续型 marker(如 PD-L1 TPS)cutoff 尚未确定时,得靠全员数据才能把阈值定在哪。

还有第三条折中路:全员入组、把 CDx 留到分析阶段才劈(prospective–retrospective 设计)。 全员随机化时不看 CDx,但每人都测,等分析计划锁定后再按 CDx 分层分析。合法与否只看一点——“预设”:assay、cutoff、分析计划必须在看到 biomarker 与结局的关系之前就冻结;反面是事后 post-hoc(跑完看了数据才挑 cutoff、挑分法),等于 data dredging,监管不认。:上面 KRAS 的故事正是这么落地的——Karapetis、Amado 等(2008)用的是已完成的全员 RCTKRAS 状态从存档肿瘤样本回测,但 treatment × KRAS 的分析是预先设定的;所以尽管是”回溯”分析,监管仍然采信,并据此把抗 EGFR 单抗的适应症限定到 KRAS 野生型。

这里的”锁定/冻结”指分析层面的预设(对应上表的”纪律 · 分析预设”)——在主分析揭盲、看 biomarker–结局关系之前把 CDx 分析定死,防挑数据;它不是对医生/患者设盲,设盲是取舍三的事,具体到(§2.3)再展开。

2.2 取舍二 · 对照怎么设:RCT 还是单臂(删对照)

取舍一确定的是”入组谁”;这一节是另一个独立的问题:入组之后,治疗组要不要配一个并行对照臂?这跟删不删 CDx− 完全是两回事——富集人群里照样可以做 RCT,也可以删掉对照臂做单臂试验(single-arm trial, SAT)(见下方单臂示意图)。精准医疗里相当一部分获批走的正是单臂。下面把这条取舍讲清:为什么能单臂、什么时候必须 RCT。

RCT:金标准,但在精准医疗里有门槛。 RCT 能估比较性获益——即”用了这个药,比不用(或用现有标准治疗)好多少”。因为治疗组和对照组来自同一批随机分配的患者、预后分布一致,两组的差异才能干净地归因于药物,而不是病情本身或人群差异。度量上通常用 PFS(progression-free survival,无进展生存期)或 OS(overall survival,总生存期)的风险比 (hazard ratio, HR):HR<1 表示试验药相对对照把”进展/死亡”的瞬时风险降低了多少(如 HR=0.6 ≈ 风险降 40%)。这种”相对某个对照”的比较性证据,是完全批准(full approval)和比较性主张的金标准——单臂试验给不出,因为它没有并行对照。

PFS/OS 具体怎么算(两臂各算一套)。 它们都是事件时间终点,从随机化那天(t=0)起算,逐患者记一个 (时间, 是否事件) 对:OS = 到任何原因死亡的时间;PFS = 到疾病进展(影像按 RECIST 判定肿瘤增大/新病灶)或死亡、谁先算谁。到数据截止还活着 / 无进展 / 失访的患者不是没结果,而是删失 (censored)——只知道”至少活/无进展到这天”,之后未知。每个臂把本臂所有患者的这些 (时间, 事件/删失) 汇总成一条 Kaplan–Meier 生存曲线(曲线每遇一个事件下一个台阶,删失只做标记不下台阶),曲线降到 0.5 处即中位 PFS/OS。两臂比较:log-rank 检验看曲线整体是否不同,Cox 模型给出 HR(两臂瞬时事件风险之比)。所以实验组、对照组各有自己的 PFS 曲线和 OS 曲线,“疗效”就是两条曲线之间的差(HR 或中位差)。

RCT 的好处(为什么是金标准):

  • 可比性:随机化让两臂预后分布一致,组间差异能干净归因于药,而非人群或病情差异。
  • 比较性主张:直接回答”比现有治疗好多少”,支撑完全批准与优效/非劣标签。
  • 硬终点的相对量:OS/PFS 的 HR 有并行对照做参照,不必依赖历史/自然史假设。
  • 可设盲:有对照才盲得起来,压住主观评估与安慰剂效应偏倚(见取舍三)。

RCT 的坏处(代价与陷阱——多数不是精准医疗独有,是所有 RCT 的通病):

  • 样本与成本:要两臂各自够人数;marker+ 罕见时凑不齐(这条精准医疗尤其突出)。
  • 伦理:疗效巨大又无有效替代时,把人随机到对照臂难以接受。
  • crossover 稀释 OS任何允许交叉的 RCT 共有,下图):对照组进展后换用试验药会抬高对照组生存、抹平两臂 OS 差距。
  • OS 受后线治疗混杂任何随访够长的 RCT 都有):OS 到死亡为止,中间夹多线其他药,难把生存差异归因给最初这一个。
  • assay 依赖(精准医疗特有):CDx 必须入组时就能用,否则落回 bridging。

crossover 为什么稀释 OS。 这不是精准医疗的特殊问题,是所有允许交叉的 RCT 的结构性代价。OS 只算到死亡:对照组病人进展后换到有效试验药,虽然晚了几个月,最终也吃上了药、也活长了 → 两臂 OS 拉不开。PFS 只算到第一次进展,而进展正是触发换药的事件、发生在换药之前 → PFS 不被污染。所以有 crossover 风险时,主要终点常从 OS 退到 PFS。下图用模拟 Kaplan–Meier 曲线对比——同一个实验组,只改对照组是否 crossover

suppressMessages(library(survival))
set.seed(7)
par(family = "cjk", mfrow = c(1, 2), mar = c(4.4, 4.6, 3.2, 1))
ln2 <- log(2); cens <- 48; n <- 300
rmed <- function(n, m) rexp(n, ln2 / m)
mk <- function(t) data.frame(t = pmin(t, cens), ev = as.integer(t <= cens))
t_trt <- rmed(n, 30)     # 实验组 OS 中位 ~30
t_c0  <- rmed(n, 18)     # 对照 无 crossover 中位 ~18
t_c1  <- rmed(n, 27)     # 对照 有 crossover: 换药后 OS 被抬到 ~27, 逼近实验组
kmplot <- function(dt, dc, main) {
  d <- rbind(cbind(dt, arm = "trt"), cbind(dc, arm = "ctrl"))
  f <- survfit(Surv(t, ev) ~ arm, data = d)
  hr <- summary(coxph(Surv(t, ev) ~ (arm == "trt"), data = d))$coef[2]  # exp(coef)=HR 实验 vs 对照
  plot(f, col = c("#C73E1D", "#2E86AB"), lwd = 3, xlim = c(0, cens),
       xlab = "随机化后月数", ylab = "生存概率 S(t)", cex.lab = 1.3, cex.axis = 1.15, mark.time = FALSE)
  title(main = main, cex.main = 1.35)
  legend("topright", c("实验组", "对照组"), col = c("#2E86AB", "#C73E1D"), lwd = 3, bty = "n", cex = 1.15)
  abline(h = 0.5, lty = 3, col = "gray60")
  text(1, 0.10, sprintf("HR = %.2f", hr), pos = 4, cex = 1.25, font = 2)
}
kmplot(mk(t_trt), mk(t_c0), "OS:无 crossover(两臂拉得开)")
kmplot(mk(t_trt), mk(t_c1), "OS:有 crossover(对照被抬高、差距抹平)")

左图无 crossover,两臂拉得开、HR≈0.63 显示获益;右图对照组进展后换药,OS 曲线被抬到几乎与实验组重合、HR≈0.94,“看着没效”——但药其实一样有效,只是 OS 这把尺子被 crossover 稀释了。PFS(算到进展、在换药之前)在两种情形下都能保住获益,这就是它常被选作主要终点的原因。

RCT 更适合人群较大、有真实对照药的场景——例:曲妥珠单抗(HER2+ 乳腺癌)、维莫非尼(BRAF V600E 黑色素瘤,BRIM-3 拿到 OS 获益)、克唑替尼确证性 RCT(PROFILE 1007/1014)。

单臂试验(SAT):为什么可以,什么时候用。 单臂没有对照,人人服药,主要终点是客观缓解率(ORR)+ 缓解持续时间(DoR),与预设的历史/自然史阈值比。它站得住脚的核心统计理由是:肿瘤缩小是可归因于药的事件——肿瘤不会自己缩,所以”客观缓解”这种终点不像 PFS/OS 那样严重依赖预后和自然史,不必靠并行随机对照来归因。适用条件:marker 罕见(如 NTRK 融合 <1%,无法凑够随机样本)、预期疗效巨大、未满足的临床需求大、走加速批准(accelerated approval)。例:拉罗替尼/恩曲替尼(NTRK 融合,tumor-agnostic 篮子试验,按 ORR 获批)、帕博利珠单抗(MSI-H/dMMR,跨瘤种单臂)、奥希替尼(EGFR T790M,AURA 单臂加速批准)、克唑替尼最初的单臂加速批准(PROFILE 1001)。

par(family = "cjk", mar = c(0.5, 0.5, 3, 0.5))
plot(NA, xlim = c(0, 14), ylim = c(-0.6, 6), axes = FALSE, xlab = "", ylab = "",
     main = "单臂试验(SAT):富集入组,只有治疗臂,无并行对照", cex.main = 1.3)
draw_box(1.7, 3, "符合条件患者", "gray30", w = 3.0, h = 0.9, cex = 1.05)
draw_box(5.0, 3, "CDx 检测", "gray40", w = 2.2, h = 0.9, cex = 1.05); draw_arr(3.3, 3, 3.85, 3)
draw_box(8.2, 3, "CDx+ 入组\n(富集)", "#2E86AB", w = 2.6, h = 1.0, cex = 0.95); draw_arr(6.15, 3, 6.85, 3)
# 只有治疗臂
draw_box(11.7, 4.2, "治疗 T=1", "#2E86AB", w = 2.6, h = 0.7, cex = 0.95); draw_arr(9.55, 3.2, 10.35, 4.0)
text(11.7, 5.15, "终点:ORR / DoR", cex = 0.9, col = "gray20", font = 3)
# 对照臂被删 —— 灰掉 + 红叉
draw_box(11.7, 1.8, "对照 T=0", "gray80", w = 2.6, h = 0.7, cex = 0.95); draw_arr(9.55, 2.8, 10.35, 2.0)
segments(10.3, 2.2, 13.2, 1.4, col = "#C73E1D", lwd = 3)
segments(10.3, 1.4, 13.2, 2.2, col = "#C73E1D", lwd = 3)
text(7.0, -0.3, "单臂:删去对照臂;疗效以 ORR 与预设的历史/自然史阈值比,不做并行随机化",
     col = "#C73E1D", font = 2, cex = 0.92)

怎么选。 现实里常见的是先后接力:先单臂拿加速批准、再补确证性 RCT(克唑替尼、奥希替尼都走这条)。一句话决策规则:marker 越罕见、效应越戏剧化、越无替代药 → 越倾向单臂;人群越大、越需要比较 PFS/OS、越有现成对照 → 越必须 RCT。

各自要盯的问题。

  • RCT 要注意:marker− 通常被筛除(见上图),拿到的是 selection 而非完整 predictive;对照组 crossover 会稀释 OS;OS 受后线治疗混杂,常用 PFS 替代;assay 要提前锁定并在入组时可用;多个 biomarker 亚组带来多重性问题。
  • 单臂要注意:没有对照,只能证明药物活性(ORR),不能直接估比较性获益;历史对照有偏(患者选择、标准治疗随时间变好、支持治疗进步都会抬高表观疗效);ORR 作为替代终点的可信度要够,通常还需确证性试验;无法把预后因素和预测因素分开;盲态只能靠 BICR(见取舍三)。

2.3 取舍三 · 要不要设盲:open-label、双/三盲与 BICR

金标准 RCT 讲三盲:①病人盲(不知自己在治疗组还是对照组)、②医生/研究者盲、③独立评估与数据分析盲(影像/病理由独立评审判读、统计分析也盲态)。要害是:设盲得靠对照才盲得起来——你得有”治疗 vs 安慰剂/对照”这层可瞒;没有对照,前两盲根本无从谈起。所以设盲和取舍二(对照)相关,但不是同一件事:

  • 有对照的 RCT:可做双盲/三盲(安慰剂对照);也可以是 open-label RCT——仍随机、仍有对照,但因伦理或操作(如手术 vs 药)瞒不住,病人/医生开放。
  • 单臂:人人吃药,必然 open-label——①②两盲天生做不到;但第③盲保得住:缓解由盲态独立中心评审(BICR,blinded independent central review)判读,挡住开放标签下的评估偏倚。

三盲各防一种偏倚,单臂能保住哪层一目了然:

三盲各防一种偏倚,单臂能保住哪层

盲谁

防的偏倚

单臂能否做到

① 病人

安慰剂效应、依从/退出差异

✗(人人吃药)

② 医生/研究者

用药、剂量调整、主观评估倾向

③ 独立评估 + 分析(BICR / 统计)

评估者、分析者偏倚

✓(靠 BICR)

别和”分析预设”混。 取舍一提过的”揭盲前锁定 CDx 分析计划”,那个”揭盲”指试验末数据库/治疗码揭盲的时间点,要求 biomarker 分析在此之前冻结——是分析纪律(防 post-hoc 挑数据),和”病人/医生知不知道谁在治疗组”无关。一句话:设盲管”知不知道处理”,分析预设管”看数据的先后顺序”,两回事。

回到本文框架。 worked example 里的疗效是随机臂之差 \(\delta = \mathrm{E}[Y\mid T{=}1] - \mathrm{E}[Y\mid T{=}0]\);换成单臂时,被搬运的量就从”治疗效应差”变成”assay+ 人群的应答率”。但底层逻辑完全一样:不完美 assay 让阳性人群混进真阴性,应答率(或疗效)被 PPV 稀释,换一个 assay 就得 bridging 把它搬到新人群上。所以后文所有公式对 RCT 和单臂都成立——区别只在”疗效”这个词具体指风险比(HR)还是应答率(ORR)。


3 地基(DGP):先造一个已知真相的世界

这一章是地基(DGP,Data Generating Process,数据生成过程)——后面 bridging、follow-on 两章都站在它上面。地基其实有两层,要一起搭

  • 概念地基——一套贯穿全文的记号,和一条主恒等式。记号只有三家:真值 \(\mu\)(按真实 marker 分)、FCD+ 人群疗效 \(\eta\)、CCD+ 人群疗效 \(\theta\)。主恒等式一句话——任何 assay 判为阳性的那群人,其疗效都是”两块真疗效积木”按该 assay 的 PPV 混出来的。把这一句吃透,后面 bridging、follow-on 全是它的特例。
  • 模拟地基——一个自己偷偷知道全部真相的世界,让上面那些抽象记号落成能对撞验证的真实数字\(\eta\)\(\theta\) 到底是多少、混合公式对不对,都能在这个世界里数出来核对。

两层穿插着搭(这正是本章比”光有公式”或”光有数据”更值的地方):每立一个概念,随即用模拟把它兑现成数字、当场验证。动手之前,先把整条方法线要解决的问题摆出来。

两个具体问题(都是同一件事的两种形态:疗效证据能不能跨 assay 搬运):

  1. follow-on:一个新检测 FCD 想拿到和已批准 CCD 一样的适应症。问——FCD 和 CCD 判人到底有多一致?能不能借 CCD 已确立的疗效证据、反推”FCD 判阳的人群”疗效可接受,而不必重做一遍临床试验?
  2. bridging:试验当年用 CTA 入组,最终上市换成了 CDx。问——原试验在 CTA+ 人群拿到的疗效,还算不算 CDx+ 人群的?难点在于富集试验只留下了 CTA+ 的人,CTA−/CDx+ 那一格根本没有病人,这块疗效怎么补?

为什么先搭一个模拟器,而不是直接上真实数据? 因为这两个问题的要命之处是真相不可观测:真实 marker 状态 \(G\) 看不到、CTA−/CDx+ 那格没有病人、缺失格子的疗效 \(\delta_{01}\) 无从测量。在真实数据里,你永远没法验证一个 bridging 估计到底对不对——手里没有标准答案可对。所以本章的办法是:先造一个自己偷偷知道全部真相的世界(下面的”生成模型” DGP),在里面按各种真实设计抽样、跑公式,再把结果和那个只有”上帝视角”才知道的真值对撞。公式能还原真相,才敢把它用到真实数据上;还原不了的地方,恰好暴露出”哪一步只能靠假设、不能靠数据”。

顺序:先搭地基——最小符号、“已知真相的世界”、自洽验证,并把”不完美 assay 会稀释疗效”这个两种搬运都要用的通用事实立起来(结局层)→ 再讲 bridging(本文主角:自己试验有疗效,要把它从 CTA+ 人群搬到 CDx+,难在缺失格子)→ 最后讲 follow-on(换一种缺失结构:自己不做疗效试验,靠与已批准 CCD 的一致性间接搬运)。bridging 和 follow-on 是同一类问题、只是缺失结构不同(详见末章「综述」对照表);同一个生成器在末章「进阶」继续复用。

3.1 最小符号与生成模型

worked example 的地基是一个已知真相的世界。先搭最小的两层,全部围绕潜在真实 marker 状态 \(G\)

  • 真相层\(G\) 是每个人潜在的真实 marker 状态,二值——\(G{=}1\) 真阳性、\(G{=}0\) 真阴性。只需一个参数 \(\Pr(G{=}1)=\text{prev}_G\)(真实患病率),\(\Pr(G{=}0)=1-\text{prev}_G\) 随之确定。
  • assay 层:每个检测(CTA、最终 CDx、comparator CCD、pre-screen)是 \(G\)噪声分类器,各有敏感度 \(\Pr(\text{assay}{=}1\mid G{=}1)\) 和特异度 \(\Pr(\text{assay}{=}0\mid G{=}0)\);给定 \(G\) 条件独立(CCD 两次重复也独立)。

第三层”结局层”(连续端点 \(Y\)、疗效 \(\delta\)先不加——讲到疗效混合时再补(见 §「加结局层:疗效混合」)。好处:一切一致性和混合效应都由共同原因 \(G\) 自动自洽诱导,不用手拼一堆可能矛盾的边际概率。

上面两层、以及由它们直接导出的基础符号,严格写出来如下(Pr(·) 是概率,竖线 | 读作”在……已知的条件下”):

本文基础符号(真相层 + assay 层):严格定义

符号

严格定义

层 / 说明

G

真实 marker 状态:G=1 真阳、G=0 真阴

真相层 · 潜在不可观测,需参考标准才知道

prevG(π)

Pr(G=1),真实患病率(1−π = Pr(G=0))

真相层 · 唯一的患病率参数

T

治疗臂:T=1 治疗、T=0 对照

结局层 · §「结局层」引入结局 Y 时才用到

assay A

任一检测(G 的噪声分类器);本文 A ∈ {CTA, FCD(=最终 CDx), CCD, Pre}

assay 层 · A 是占位符,代入具体检测即得该检测的量

sensA /
specA

敏感度 sensA = Pr(A=1|G=1);
特异度 specA = Pr(A=0|G=0)

assay 层 · 在 TRUTH 里直接给定(生成参数)

PPVA /
NPVA

阳性预测值 PPVA = Pr(G=1|A=1);
阴性预测值 NPVA = Pr(G=0|A=0)

assay 层 · 由 sens/spec 与患病率 π 经 Bayes 导出,非自由参数

这一层的量都是”标签或概率”(下一节结局层才出现可正可负的疗效实数):\(G\)\(T\) 是二值标签 \(\{0,1\}\)\(\pi\)\(sens/spec\)\(PPV/NPV\) 都是 \(0\)\(1\) 的概率。本文 DGP 的具体取值——\(\pi=0.30\);四个检测 \(sens/spec\) 依次 CTA 0.85 / 0.90、CDx(=FCD) 0.95 / 0.96、CCD 0.93 / 0.94、Pre 0.80 / 0.75;由此贝叶斯算出 \(PPV_f\approx0.91\)\(PPV_c\approx0.87\)

为什么 \(\pi=0.30\) 它本质只是 DGP 的一个固定已知参数,取任何值都能验证公式;选 0.30 图两点:一是真实——很多肿瘤 biomarker 的患病率就落在 20–40%;二是教学清晰——特意避开 0.5(太对称,掩盖患病率对 PPV 的影响)和极端值(罕见 marker 会让 PPV 崩到很低、例子失真),这样”不完美 assay 稀释疗效”的效应既清楚可见、又不夸张(\(\pi=0.30\) 下 CDx 这么好的检测 \(PPV_f\) 也才 \(0.91\)、不到 1,稀释一眼看得见)。四个检测的 \(sens/spec\) 同理特意拉开档次(CDx 最好、Pre 最差),好让”assay 越好、\(PPV\) 越高、稀释越少”的对比一目了然。

下标就是”哪一个检测”,不用猜\(A\) 只是占位符,代入具体检测就得到那个检测的量——本文固定 \(f\) = FCD(最终 / 新 CDx,是 FCD 的缩写,不是字母”f”某检测)、\(c\) = CCD(comparator CDx)。于是 \(PPV_f=\Pr(G{=}1\mid FCD{=}1)\)\(PPV_c=\Pr(G{=}1\mid CCD{=}1)\)\(sens_A\)\(spec_A\) 同理。

而且 \(PPV\) 不是可以随手假设的旋钮。 它由贝叶斯从三样东西钉死:该检测的 \(sens\)\(spec\),加上这个 marker 的真实患病率 \(\pi\)\(PPV=\dfrac{sens\cdot\pi}{sens\cdot\pi+(1-spec)(1-\pi)}\)。要害在 \(\pi\)不同 marker 在人群里的分布差别极大,罕见 marker(\(\pi\) 小)即便检测很准,\(PPV\) 也可能很低(阳性里混进大量假阳)。所以 \(PPV\)marker 特异的、可行范围有限——一旦锁定某个具体 marker 的 assay,它的 \(PPV\) 基本被 \((sens,spec,\pi)\) 定死,不能拍脑袋给。(这条贝叶斯关系,§「贝叶斯」那节还会用模拟验证。)

结局层的 \(Y\)\(\mu\)\(\delta_{\text{pos}}/\delta_{\text{neg}}\)\(\eta\)\(\theta\) 到 §「加结局层」再严格定义(见那里的”三级台阶”表);派生的一致性量 PPA/NPA、搬运权重 \(p\) 在 §「生成器、真值算子与自洽验证」一节就地给出;全部符号另见文末〈记号表〉。

TRUTH <- list(
  prev_G = 0.30,                                     # Pr(G=1)
  assays = list(
    CTA = c(sens = 0.85, spec = 0.90),               # clinical trial assay (入组用)
    CDx = c(sens = 0.95, spec = 0.96),               # 最终 CDx / FCD (最好)
    CCD = c(sens = 0.93, spec = 0.94),               # comparator CDx (follow-on 参照)
    Pre = c(sens = 0.80, spec = 0.75)                # pre-screen (粗筛, 最差)
  ),
  endpoint = list(mu_G1_T0 = 0, delta_pos = 0.60,    # 真 marker+: 对照均值0, 治疗 +0.60
                  mu_G0_T0 = 0, delta_neg = 0.05,    # 真 marker-: 药物几乎无效
                  sd = 1.0)
)

角色 → 数据列映射(角色定义见 §1.1,此处只说代码里落到哪列):CDx 列 = FCD(新检测/最终 CDx)——注意别被列名误导,FCD 在代码里就是 CDxCCD1/CCD2 = CCD 的两次独立重复;CTA 列 = CTAPre 列 = 粗筛。

3.2 生成器、真值算子与自洽验证(先只验 assay 层)

两样东西配成一对——一个造数据、一个给标准答案:

  • generate_population() — 模拟器:先按 prevalence 抽真状态 \(G\),再让每个 assay 当 \(G\) 的噪声分类器(sens/spec 决定翻转概率、给定 \(G\) 独立)各自出结果。它也能顺带生成结局 \(Y\)with_endpoint=TRUE),但那一层这一节先不用。给样本量 \(n\),吐一个数据集。
  • 闭式真值算子 — 不抽样,直接从 TRUTH 算理论真值,当”标准答案”。这一节先用三个纯 assay 层(不涉及疗效)的:
    • assay_prev():某 assay 的边际阳性率 \(\Pr(\text{assay}{=}1)\)
    • true_concordance():两 assay 的真 PPA/NPA——泛化定义 PPA = \(\Pr(B{+}\mid A{+})\)(A 判阳的人里 B 也判阳的比例)、NPA = \(\Pr(B{-}\mid A{-})\),在共同 \(G\) 下用联合概率算,不靠样本。
    • true_p_cta_given_cdx()\(p=\Pr(CTA{+}\mid CDx{+})\),bridging 的关键权重。

第四个算子 true_delta_in_assay_pos() 属结局层,等加了疗效再定义。套路统一:模拟估计 → 和闭式真值对撞 → 验证公式。

generate_population <- function(n, truth = TRUTH, with_endpoint = FALSE) {
  G <- rbinom(n, 1, truth$prev_G)
  call_assay <- function(oc) ifelse(G == 1, rbinom(n, 1, oc["sens"]), rbinom(n, 1, 1 - oc["spec"]))
  df <- data.frame(G = G, CTA = call_assay(truth$assays$CTA), CDx = call_assay(truth$assays$CDx),
                   CCD1 = call_assay(truth$assays$CCD), CCD2 = call_assay(truth$assays$CCD),
                   Pre = call_assay(truth$assays$Pre))
  if (with_endpoint) {                                   # 结局层:讲疗效时才用到
    ep <- truth$endpoint; T_arm <- rbinom(n, 1, 0.5)
    mu <- ifelse(G == 1, ep$mu_G1_T0 + T_arm*ep$delta_pos, ep$mu_G0_T0 + T_arm*ep$delta_neg)
    df$T_arm <- T_arm; df$Y <- rnorm(n, mu, ep$sd)
  }
  df
}

# assay 层算子:边际阳性率; 两 assay 真 PPA/NPA; true p=Pr(CTA+|CDx+)。(δ 算子在结局层再定义)
assay_prev <- function(oc, prev = TRUTH$prev_G) oc["sens"]*prev + (1-oc["spec"])*(1-prev)
true_concordance <- function(ocA, ocB, prev = TRUTH$prev_G) {
  pA1 <- function(g) if (g == 1) ocA["sens"] else 1-ocA["spec"]
  pB1 <- function(g) if (g == 1) ocB["sens"] else 1-ocB["spec"]
  jt <- function(a,b) sum(sapply(0:1, function(g){
    pa <- if(a==1) pA1(g) else 1-pA1(g); pb <- if(b==1) pB1(g) else 1-pB1(g)
    pa*pb*(if(g==1) prev else 1-prev)}))
  c(PPA = unname(jt(1,1)/(jt(1,1)+jt(1,0))), NPA = unname(jt(0,0)/(jt(0,0)+jt(0,1))))
}
true_p_cta_given_cdx <- function(truth = TRUTH) {
  oc_c <- truth$assays$CTA; oc_d <- truth$assays$CDx; prev <- truth$prev_G
  num <- sum(sapply(0:1, function(g){pc<-if(g==1)oc_c["sens"] else 1-oc_c["spec"]; pd<-if(g==1)oc_d["sens"] else 1-oc_d["spec"]; pc*pd*(if(g==1)prev else 1-prev)}))
  den <- sum(sapply(0:1, function(g){pd<-if(g==1)oc_d["sens"] else 1-oc_d["spec"]; pd*(if(g==1)prev else 1-prev)}))
  unname(num/den)
}

算子和生成器都齐了,当场”对表”验一次自洽——但先只验 assay 层(一致性 + \(p\),还不碰疗效):同一批真值用两条独立路子各算一遍——闭式(直接拿参数套公式)vs 模拟 200 万人再数比例(经验值)。两列几乎相等(“差”列≈0)就说明公式和模拟器都没写错、DGP 自洽——之后各节才敢直接拿闭式真值当基准。

big <- generate_population(2e6)
tc  <- true_concordance(TRUTH$assays$CTA, TRUTH$assays$CDx)   # 命名向量 c(PPA=, NPA=)
closed <- c(tc[["PPA"]], tc[["NPA"]], true_p_cta_given_cdx())  # [[ ]] 取值、不带元素名
emp <- c(mean(big$CDx[big$CTA==1]==1),                         # Pr(CDx+|CTA+) = PPA
         mean(big$CDx[big$CTA==0]==0),                         # Pr(CDx−|CTA−) = NPA
         mean(big$CTA[big$CDx==1]==1))                         # Pr(CTA+|CDx+) = p
ft_show(data.frame(
  `` = c("PPA_{CTA,CDx} = Pr(CDx+|CTA+)",
           "NPA_{CTA,CDx} = Pr(CDx−|CTA−)",
           "p = Pr(CTA+|CDx+)"),
  `闭式真值` = round(closed, 4),
  `模拟 2M 估计` = round(emp, 4),
  `` = round(abs(closed - emp), 4),
  check.names = FALSE),
  title = "概念真值表(assay 层):闭式真值 vs 模拟估计")
概念真值表(assay 层):闭式真值 vs 模拟估计

闭式真值

模拟 2M 估计

PPACTA,CDx = Pr(CDx+|CTA+)

0.7540

0.7532

0.0008

NPACTA,CDx = Pr(CDx−|CTA−)

0.8993

0.8994

0.0001

p = Pr(CTA+|CDx+)

0.7829

0.7831

0.0002

✓ DGP(assay 层)自洽,可以拿它当后面各节的”标准答案”。

3.3 加”结局层”:疗效混合

问题:一个不完美的 assay 判为阳性的那群人,他们的表观疗效,比真 marker+ 人群的疗效低多少? 这是 bridging 和 follow-on 都要用的通用事实——两种搬运搬的都是”assay 定义人群的疗效”,而它天生被 assay 的不完美稀释。所以放在地基里先讲清楚,回答它要用到疗效,正好把之前搁置的第三层补上。

到这里才需要疗效,把第三层补上。结局层:连续端点 \(Y\),均值只取决于治疗臂 \(T\) 和真实 \(G\);真 marker+ 疗效 \(\delta_{\text{pos}}\)、真 marker− 疗效 \(\delta_{\text{neg}}\) 各自给定——generate_population(..., with_endpoint = TRUE) 生成的正是这一层。同时补上属结局层的第四个算子:

true_delta_in_assay_pos <- function(oc, truth = TRUTH) {   # assay+ 人群真疗效 = PPV 加权 δ
  prev <- truth$prev_G
  ppv <- oc["sens"]*prev / (oc["sens"]*prev + (1-oc["spec"])*(1-prev))
  unname(ppv*truth$endpoint$delta_pos + (1-ppv)*truth$endpoint$delta_neg)
}

它一上来就讲故事:真 marker+ 疗效 0.60,但 CDx+ 人群只 0.551(不完美 assay 把真阴性混进来、稀释),CTA+ 人群更低 0.482(CTA 更差)——bridging 要把 CTA+ 观测的疗效往 CDx+ 调,正是它存在的意义。

正式写出来前,先立只有两块的”积木”。把人按 (治疗臂 \(T\), 真实 marker 状态 \(G\)) 分成四格,每格的结局均值记 \(\mu_{TG}\)

四格结局均值 μ(行=治疗臂 T,列=真 marker G)

真 marker+ (G=1)

真 marker− (G=0)

治疗 T=1

μ11

μ10

对照 T=0

μ01

μ00

这四格从哪来?一张图看清——先按真 marker \(G\) 把人分两支(两支都留),每支再 1:1 随机成治疗 / 对照,就落成四格 \(\mu_{TG}\)

par(family = "cjk", mar = c(0.5, 0.5, 3, 0.5))
plot(NA, xlim = c(0, 14), ylim = c(-0.9, 6), axes = FALSE, xlab = "", ylab = "",
     main = "四格 μ 从哪来:真 marker G × 治疗臂 T", cex.main = 1.3)
draw_box(1.3, 3, "患者", "gray30", w = 1.9, h = 0.85, cex = 0.95)
draw_box(3.9, 3, "看真实 marker G\n(上帝视角)", "gray45", w = 2.7, h = 1.0, cex = 0.85)
draw_arr(2.25, 3, 2.55, 3)
draw_box(6.9, 4.4, "真 marker+\nG=1", "#2E86AB", w = 2.3, h = 0.9, cex = 0.9)
draw_box(6.9, 1.4, "真 marker−\nG=0", "#AED6F1", w = 2.3, h = 0.9, cex = 0.9, txt = "gray20", border = "#7FB3D5")
draw_arr(5.25, 3.2, 5.75, 4.3); draw_arr(5.25, 2.8, 5.75, 1.5)
draw_box(10.0, 5.1, bquote(paste("T=1 → ", mu["11"])), "#2E86AB", w = 2.7, h = 0.6, cex = 0.9)
draw_box(10.0, 3.7, bquote(paste("T=0 → ", mu["01"])), "#2E86AB", w = 2.7, h = 0.6, cex = 0.9)
draw_box(10.0, 2.1, bquote(paste("T=1 → ", mu["10"])), "#AED6F1", w = 2.7, h = 0.6, cex = 0.9, txt = "gray20", border = "#7FB3D5")
draw_box(10.0, 0.7, bquote(paste("T=0 → ", mu["00"])), "#AED6F1", w = 2.7, h = 0.6, cex = 0.9, txt = "gray20", border = "#7FB3D5")
draw_arr(8.05, 4.6, 8.65, 5.1); draw_arr(8.05, 4.2, 8.65, 3.7)
draw_arr(8.05, 1.6, 8.65, 2.1); draw_arr(8.05, 1.2, 8.65, 0.7)
brace(11.55, 3.7, 5.1); text(12.5, 4.4, bquote(delta[pos]), col = "#2E86AB", cex = 1.25, font = 2)
brace(11.55, 0.7, 2.1); text(12.5, 1.4, bquote(delta[neg]), col = "#3B7DB0", cex = 1.25, font = 2)
text(7, -0.6, "μ 下标 =(治疗臂 T, 真 marker G)。每 marker 内「治疗 − 对照」= 疗效:δpos = μ11 − μ01(真+)、δneg = μ10 − μ00(真−)",
     col = "gray25", cex = 0.8, font = 3)

μ 的两位下标就是这么来的:第一位 = 治疗臂 \(T\)(哪一支:治疗 / 对照)、第二位 = 真 marker \(G\)(哪一列:真+ / 真−)。每一的”治疗 − 对照”就是那个真实人群的治疗效应——全文只有这两块积木

\[\delta_{\text{pos}}=\mu_{11}-\mu_{01}\ (\text{真 marker+ 的疗效}),\qquad \delta_{\text{neg}}=\mu_{10}-\mu_{00}\ (\text{真 marker− 的疗效}).\]

这些 \(\mu\) 是什么数?\(Y\) 想成一个连续疗效读数(比如肿瘤缩小量、某评分的变化,单位随意),\(\mu_{TG}\) 就是某一格人 \(Y\)平均值——一个实数,可正可负,不是百分比。本文把对照臂均值设成基准 0,代入 DGP 的参数就是:\(\mu_{01}=0\)(marker+ 对照)、\(\mu_{11}=0.60\)(marker+ 用药,平均比对照高 0.60)、\(\mu_{00}=0\)(marker− 对照)、\(\mu_{10}=0.05\)(marker− 用药,几乎没动)。相减即疗效:\(\delta_{\text{pos}}=0.60-0=0.60\)\(\delta_{\text{neg}}=0.05-0=0.05\)。个体的 \(Y\) 再在这均值上下按正态分布(均值 \(\mu\)、标准差 \(1\))抖动,所以某个 marker+ 用药者可能 \(Y=1.3\)、也可能 \(Y=-0.2\)记住两类量别混\(\mu\)\(\delta\)\(\eta\)\(\theta\)疗效读数或其差,实数、可正可负;而 \(\pi\)\(sens/spec\)\(PPV\)\(PPA\)\(p\)\(c\)概率或比率,取值都在 \(0\)\(1\) 之间

有真 \(G\) 时,FCD+ 人群的表观疗效 \(\eta\) 就是这两块积木的加权平均,权重是该 assay 的阳性预测值 \(PPV_f=\Pr(G{=}1\mid FCD{=}1)\)(“判为阳性的人里真的是 marker+ 的比例”):

\[\eta = PPV_f\,\delta_{\text{pos}} + (1-PPV_f)\,\delta_{\text{neg}}.\]

这条恒等式和”FCD”没什么特殊关系。 把”按真 \(G\) 分”换成”按任意 assay A 的判读分”,同一条推导照走——A 判阳人群的疗效 \(=PPV_A\,\delta_{\text{pos}}+(1-PPV_A)\,\delta_{\text{neg}}\)两块真疗效积木 \(\delta_{\text{pos}}\)\(\delta_{\text{neg}}\) 永远不变,变的只有权重 \(PPV_A\) 这就是「参考标准可得框架 (reference-standard-available framework)」的全部内容:给每个 assay 一套按”臂 \(T\) × 该 assay 判读”索引的四格均值,它 A+ 人群的臂间差就落成上面这个混合。

一张表把最容易混的 \(\mu\)\(\delta\)\(\eta\)\(\theta\))一次钉死——横看是”三级台阶”:层 → 相减得积木 → 按 PPV 加权;第 ③ 级那一行竖看,又正好是同一框架的三个实例(\(\mu\) 供积木,\(\eta\)\(\theta\) 只换 PPV 权重)

μ / δ / η(θ):横看是三级台阶,③ 行竖看是 μ/η/θ 三实例

台阶

符号

是什么 / 按什么索引

关系式 / 例

① 层

μTG

一格人的结局均值(和 Y 同单位);按 臂 T × 真 marker G 索引

μ11, μ01,
μ10, μ00

② 相减 → 得积木

δpos,
δneg

同一真实人群 治疗−对照;全文仅此两块真疗效积木

δpos = μ11−μ01
δneg = μ10−μ00

③ 按 PPV 加权 → 混积木

η(FCD+),
θ(CCD+)

assay+ 人群的表观疗效;按 该 assay 判读 索引,权重 = 该 assay 的 PPV

η = PPVf·δpos+(1−PPVf)·δneg
θ = PPVc·δpos+(1−PPVc)·δneg

读法\(\eta\) 正是本文主角 bridging 要还原的量(FCD 就是最终 CDx);\(\theta\)follow-on 要”借”的量(CCD 已确立疗效,\(PPV_c=\Pr(G{=}1\mid CCD{=}1)\))。两者做法完全对称——follow-on 章把 \(\theta\) 拿去用,估计、混合、稀释和这里的 \(\eta\) 一模一样,到那儿没有新东西。数下标最好记:\(\mu_{TG}\)两个下标(治疗臂 \(T\) × 真 marker \(G\)——要两件事才定得下一个均值);\(\delta_{\text{pos}}/\delta_{\text{neg}}\) 只剩一个下标(真 marker——臂差已把 \(T\) 吃掉);\(\eta\)\(\theta\) 一个真-marker 下标都没有——恰恰因为看不见真 marker,才不得不用 PPV 把两块积木糊成一个。

所以 \(\eta\) 本身就是一种 \(\delta\)(assay+ 人群那一版的疗效),\(\theta\) 也是(换 CCD 而已)。后面 bridging 还是这同一招,只换了”按什么分组、用什么权重”:把 CDx+ 人群按 CTA 状态再切成两格、按占比加权——积木仍是 \(\delta_{\text{pos}}\)\(\delta_{\text{neg}}\) 那两块。(届时会出现 \(\delta_{11}\)\(\delta_{01}\)\(\delta_{CDx}\) 这样的记号,两位数字指 (CTA, CDx) 格子、不是 μ 那种 (臂, 真 marker)——到 bridging 章正式定义,这里知道”同一招”即可,不用现在就懂。)吃透这里的 μ→δ→η(外加孪生 θ)就够,后文不再添新”积木”。 一张图看懂这个”加权混合”(以最好的 assay CDx 为例,\(PPV_f\approx0.91\)):

par(family = "cjk", mar = c(0.5, 0.5, 3, 0.5))
plot(NA, xlim = c(0, 1), ylim = c(0, 1), axes = FALSE, xlab = "", ylab = "",
     main = "疗效混合:assay+ 人群的表观疗效被 PPV 稀释", cex.main = 1.45)
PPV <- 0.91; dpos <- 0.60; dneg <- 0.05; eta <- PPV*dpos + (1-PPV)*dneg
xof <- function(d) 0.08 + (d/0.65)*0.84
xL <- 0.08; xR <- 0.92; sp <- xL + (xR-xL)*(1-PPV)   # 左=marker−、右=marker+,与下方数轴同序
text(0.5, 0.93, "① assay+ 人群的构成(按真实 marker 状态)", cex = 1.05, font = 2)
rect(xL, 0.72, sp, 0.84, col = "#AED6F1", border = "white", lwd = 2)
rect(sp, 0.72, xR, 0.84, col = "#2E86AB", border = "white", lwd = 2)
text((xL+sp)/2, 0.90, sprintf("真 marker− %.0f%%", 100*(1-PPV)), col = "#3B7DB0", font = 2, cex = 0.82)
text((sp+xR)/2, 0.78, bquote(paste("真 marker+  ", PPV[f], "≈", .(round(100*PPV)), "%")), col = "white", font = 2, cex = 0.95)
text(0.5, 0.5, "② 各自真实疗效,按上面比例加权 → 表观疗效 η", cex = 1.05, font = 2)
segments(xof(0), 0.30, xof(0.65), 0.30, lwd = 2, col = "gray50")
for (d in c(0,0.2,0.4,0.6)) { segments(xof(d),0.29,xof(d),0.31,lwd=1.5,col="gray50"); text(xof(d),0.245,d,cex=0.9,col="gray40") }
text(0.5, 0.16, "疗效 δ(治疗 vs 对照)", cex = 1.0, col = "gray30")
points(xof(dneg), 0.30, pch = 15, col = "#3B7DB0", cex = 2.4)
points(xof(dpos), 0.30, pch = 15, col = "#2E86AB", cex = 2.4)
points(xof(eta),  0.30, pch = 19, col = "#A23B72", cex = 3.0)
text(xof(dneg), 0.38, sprintf("真−\nδ=%.2f", dneg), col = "#3B7DB0", font = 2, cex = 0.9)
text(xof(dpos), 0.38, sprintf("真+\nδ=%.2f", dpos), col = "#2E86AB", font = 2, cex = 0.9)
text(xof(eta),  0.40, sprintf("η≈%.2f", eta), col = "#A23B72", font = 2, cex = 1.05)
text(0.5, 0.05, expression(eta == PPV[f] * delta[pos] + (1 - PPV[f]) * delta[neg]), cex = 1.2)

PPV 越大(assay 越好),\(\eta\) 越靠近 \(\delta_{\text{pos}}\)、稀释越少;反过来,assay 越差、混进的真阴性越多,表观疗效越被拖低。

下面把这套混合用三条独立的路各算一遍,看对不对得上——而且顺手把 \(\eta\)(CDx+)和它的孪生 \(\theta\)(CCD+)一起算,当场验证”换个 assay、同一台机器”(下面以 CDx 为例讲三条路,CCD 完全一样):

  • 混合公式:套上式 \(PPV_f\,\delta_{\text{pos}}+(1{-}PPV_f)\,\delta_{\text{neg}}\),其中 \(PPV_f\) 从模拟样本里数出来mean(sub$G==1),即”CDx 判阳的人里真 \(G{=}1\) 的比例”)。
  • 直接估计:完全不碰公式,在 CDx+ 这群人里直接量 \(Y\) 的差——治疗组 \(Y\) 均值 \(-\) 对照组 \(Y\) 均值(就是 delta_hat)。这正是真实试验会观测到的”表观疗效”本身。
  • 闭式真值:就是前面那个算子 true_delta_in_assay_pos()——不抽样,先用贝叶斯从参数精确算出 \(PPV_f=\dfrac{\text{sens}\cdot\text{prev}}{\text{sens}\cdot\text{prev}+(1{-}\text{spec})(1{-}\text{prev})}\)(这里 sens/spec 取 CDx 的),再套同一条混合公式。
pop <- generate_population(3e5, with_endpoint = TRUE)
mix_check <- function(assay_col, oc) {
  sub <- pop[pop[[assay_col]] == 1, ]
  ppv <- mean(sub$G == 1)                                # 从样本数出的 PPV
  c(PPV        = round(ppv, 4),
    `混合公式` = round(ppv*TRUTH$endpoint$delta_pos + (1-ppv)*TRUTH$endpoint$delta_neg, 4),
    `直接估计` = round(delta_hat(sub), 4),                # 只量 Y,不预设任何模型
    `闭式真值` = round(true_delta_in_assay_pos(oc), 4))   # 不抽样,从参数精确算
}
tab <- rbind(`η(FCD+ = CDx+)` = mix_check("CDx",  TRUTH$assays$CDx),
             `θ(CCD+)`        = mix_check("CCD1", TRUTH$assays$CCD))
ft_show(tab, title = "同一台机器换个 assay:η、θ 各自三条路都对齐", rowname = "人群疗效")
同一台机器换个 assay:η、θ 各自三条路都对齐

人群疗效

PPV

混合公式

直接估计

闭式真值

η(FCD+ = CDx+)

0.9112

0.5512

0.5578

0.5508

θ(CCD+)

0.8717

0.5295

0.5304

0.5280

✓ 两行、各自三路对齐\(\eta\)(CDx+)≈0.55、\(\theta\)(CCD+)≈0.53——CCD 比 CDx 略差、\(PPV\) 稍低,混进的真阴性更多、疗效被稀释得更狠,完全在意料之中。这正是概念地基兑现成数字\(\eta\)\(\theta\) 不是抽象记号,而是模拟世界里数得出、对得上的真实量。有个微妙点值得点破:混合公式闭式真值其实都用了那条混合公式(只是 \(PPV\) 一个来自样本、一个来自参数),它俩对上只说明”样本 PPV≈真 PPV”。真正独立的裁判是「直接估计」——它只老实量 \(Y\)、不预设任何混合模型;它也和闭式真值对上,才证明”assay+ 疗效 = 真+/真− 按 PPV 混合”这个模型本身没说错。η 和 θ 用的是同一个 mix_check,只换了 assay 列——框架的对称性,在代码里就是同一个函数。

这里有个容易滑过、却很关键的点。 这里的混合、以及后面 bridging 的搬运,本质是同一句话:合并人群的疗效 = 各子群疗效按人数占比加权平均。当疗效用的是均值差 \(\delta\) 时,这句话是恒等式、永远成立——因为”一群人的平均值 = 各子群平均值按占比加权”本就是平均数的定义,两臂各自取平均再相减,这个加权关系原样传下来。

“按占比加权平均”并非对任何疗效度量都成立——只在疗效是均值型(而非比值型)时才精确。 最典型的反例是 Cox 模型的风险比 HR:即便药在两个子群里 HR 都恰好等于 \(0.6\),把两个 \(0.6\) 按占比加权也得不到合并人群的 HR(合并后通常朝 \(1\) 漂)。原因是比值不是期望、不服从”按占比加权”,这个性质叫不可塌缩 (non-collapsible)。所以本文统一用连续端点的均值差,是为了让搬运恒等式精确成立、教学干净;真实试验里疗效常报成 HR,这时”看似能线性搬运、其实得先换到可塌缩的尺度(均值差 / 某时点生存率差 / RMST)“本身就是 bridging 会踩的坑。完整展开见末章 「补充:为什么用连续端点」

3.4 地基全景:一张图把三层与正交的治疗臂串起来

整章的量,串成一张图。两条线都从真相 \(G\) 出发:往左,\(G\) 被各 assay 有噪声地测量(每个 assay 一副 \(sens/spec\)),派生出 \(PPV/NPV\)\(PPA/NPA\)、搬运权重 \(p\);往右,\(G\) 和随机的治疗臂 \(T\) 正交地共同决定四格结局均值 \(\mu_{TG}\),同列(同一 marker、沿 \(T\))相减得两块积木 \(\delta_{\text{pos}}\)\(\delta_{\text{neg}}\)。两条线在最底部汇合:任何 assay+ 人群的疗效,都是这两块积木、按左边那条线算出的 \(PPV\) / \(p\) 加权——这就是 \(\eta\)(FCD+)、\(\theta\)(CCD+),以及通向第 4 章 bridging 的 \(\delta_{CDx}\)

par(family = "cjk", mar = c(0.5, 0.5, 3, 0.5))
plot(NA, xlim = c(0, 14), ylim = c(0, 11), axes = FALSE, xlab = "", ylab = "",
     main = "地基全景:真相 → assay 与结局(治疗臂 T 正交),最终在疗效混合处汇合",
     cex.main = 1.2)

# 多行框:每行可为普通字符串或 plotmath 表达式(下标走 X[sub],与表/正文一致的真下标)
cellbox <- function(x, y, w, h, lines, fill, tcol, cex = 0.86, border = "white", lwd = 2) {
  rect(x-w/2, y-h/2, x+w/2, y+h/2, col = fill, border = border, lwd = lwd)
  n <- length(lines)
  ys <- y + (h/2 - 0.12) - (seq_len(n) - 0.5) * (h - 0.24) / n
  fnt <- if (identical(tcol, "white")) 2 else 1
  for (i in seq_len(n)) text(x, ys[i], lines[[i]], col = tcol, cex = cex, font = fnt)
}

# ① 真相层 G(根)
draw_box(7, 10.2, "真相层 G ∈ {0,1}\nπ = Pr(G=1)(真实患病率)", "#6A4C93", w = 4.4, h = 1.0, cex = 0.95)

# ② assay 层(G 的噪声分类器)+ ③ 结局层 μ(G × T 四格)
draw_box(3.1, 7.4, "assay 层 A = G 的噪声分类器\nCTA · CDx(=FCD) · CCD · Pre", "#2E86AB", w = 4.8, h = 1.1, cex = 0.88)
cellbox(10.9, 7.4, 4.8, 1.1, list(
  expression(paste("结局层 ", mu[TG], " 四格结局均值")),
  "行 = 治疗臂 T,列 = 真 marker G"), "#A23B72", "white", cex = 0.88)
draw_arr(5.9, 9.75, 3.9, 8.0, cex = 0.82); text(4.9, 8.95, expression(paste(sens[A], ", ", spec[A])), cex = 0.82, col = "gray30", font = 3)
draw_arr(8.1, 9.75, 9.9, 8.0, "决定真实疗效", cex = 0.82, adj_y = 0.30)

# ④ 治疗臂 T(正交轴)
draw_box(12.4, 9.9, "治疗臂 T ∈ {0,1}\n随机、与 G 独立(正交)", "#E8871E", w = 3.0, h = 0.95, cex = 0.78)
draw_arr(11.9, 9.4, 11.2, 8.0, "另一维", cex = 0.8, adj_y = 0.25)

# ⑤ assay 分支 → 闭式导出
cellbox(3.1, 4.5, 5.7, 1.6, list(
  "闭式导出(算子精确算,不抽样)",
  expression(paste(PPV[A], ", ", NPV[A], ":Pr(G=1|A=1)、Pr(G=0|A=0)")),
  "PPA / NPA(assay 间一致性)",
  "p = Pr(CTA+ | CDx+)(bridging 搬运权重)"), "#E3F1F7", "gray15", border = "#2E86AB", lwd = 1.6)
draw_arr(3.1, 6.85, 3.1, 5.35)

# ⑥ 结局分支 → 两块积木
cellbox(10.9, 4.5, 5.7, 1.6, list(
  "两块积木(同列、治疗 − 对照)",
  expression(paste(delta[pos], " = ", mu["11"], " − ", mu["01"], "(真 marker+ 疗效)")),
  expression(paste(delta[neg], " = ", mu["10"], " − ", mu["00"], "(真 marker− 疗效)"))),
  "#F7E6EF", "gray15", border = "#A23B72", lwd = 1.6)
draw_arr(10.9, 6.85, 10.9, 5.35, "沿 T 相减", cex = 0.8, adj_y = 0.25)

# ⑦ 汇合:疗效混合(PPV / p 当权重)
cellbox(7, 1.5, 10.6, 2.0, list(
  "疗效混合:两块积木,按「该人群的 PPV / p」加权",
  expression(paste(eta, " = ", PPV[f], " · ", delta[pos], " + (1−", PPV[f], ") · ", delta[neg], " → FCD(=CDx)+ 人群疗效")),
  expression(paste(theta, " = ", PPV[c], " · ", delta[pos], " + (1−", PPV[c], ") · ", delta[neg], " → CCD+ 人群疗效")),
  expression(paste(delta[CDx], " = p · ", delta["11"], " + (1−p) · ", delta["01"], " → bridging 目标(第 4 章)"))),
  "#FBF3DE", "gray15", cex = 0.86, border = "#E8871E", lwd = 1.6)
draw_arr(9.9, 3.7, 8.6, 2.5)                               # 积木 → 混合
draw_arr(4.3, 3.7, 5.4, 2.5, "PPV / p 作权重", cex = 0.82, adj_y = 0.30)  # 权重跨分支 → 混合

图里的量分三种身份,正对应本章”设参数 → 算真值 → 模拟对撞”的三步:设定——\(\pi\)、各 assay 的 \(sens/spec\)\(\delta_{\text{pos}}\)\(\delta_{\text{neg}}\),是你在 TRUTH 里直接给的参数;闭式导出——\(PPV/NPV\)\(PPA/NPA\)\(p\),以及 \(\eta/\theta\) 的真值,由算子不抽样、纯从参数精确算模拟估计——以上每个量在生成数据里数出的经验版。本章 §「生成器、真值算子与自洽验证」做的正是把后两者对撞、确认相等,DGP 才够格当”标准答案”。\(T\)\(G\) 正交是关键\(T\) 随机分配、独立于 marker,所以 \(\mu\) 才需要 \(T\)\(G\) 两个下标,而疗效永远是在某个 marker 分组内、沿 \(T\) 相减得到的。

4 Bridging:缺失格与疗效搬运

地基搭好,先讲本文主角 bridging自己试验里就有疗效数据(不管是 RCT 的臂间差还是单臂的应答率),难点是把它从”入组用的 CTA 定义的人群”搬到”最终 CDx 定义的人群”,而两者不重合、还缺了一整格数据。(第五章的 follow-on 是同一套骨架换一种缺失结构——自己不做疗效试验、改靠与已批准 CCD 的一致性间接搬运。)

4.1 为什么需要 bridging,怎么分步做

动机前面讲过:bridging 出现在最终 CDx 与 pivotal trial 入组 assay 不一致时。监管指导把它描述为:用原临床试验样本和预设统计分析计划比较 CTA 与候选 CDx,并据此把 CTA 的临床性能外推到最终 CDx;后续方法学工作进一步系统讨论了缺失样本、local test、pre-screening 和低阳性率 biomarker 等问题 (U.S. Food and Drug Administration 2016; Song et al. 2024)

先说清这里 \(\delta\) 的下标,别被第三章绊到。 第三章的 \(\delta_{\text{pos}}/\delta_{\text{neg}}\)真实 marker 分组、只有一个标签;这里的 \(\delta\)两位数字,但这两位是 (CTA 状态, CDx 状态)不是 \(\mu_{TG}\) 那种 (治疗臂 \(T\), 真 marker \(G\))。读法:\(\delta_{ab}\) = 在”CTA\(=a\) 且 CDx\(=b\)“这一格里的疗效(治疗 − 对照)——所以 \(\delta_{11}\) 是 (CTA+ & CDx+)、\(\delta_{01}\) 是 (CTA− & CDx+)。两点要记住:其一,\(\delta\)\(\delta_{\text{pos}}\) 一样本身已经是臂间差、不带治疗臂下标,两位数字只标”落在哪个观察得到的格子”,跟 \(T\) 无关(\(T\) 已被”治疗 − 对照”这步吃掉);其二,这里改按 CTA/CDx 分组、而非第三章的真 \(G\),是因为真实试验里真 marker \(G\) 看不到,只能按观察得到的 assay 判读来切人群。

进入”怎么做”,三步:

  1. 一致性 (concordance):用试验筛查/入组样本,估最终 CDx 与 CTA 的一致性(PPA/NPA);由此得搬运权重 \(p=\Pr(CTA{+}\mid CDx{+})\)
  2. 疗效 (efficacy):在观察得到的 (CTA+ & CDx+) 格子里估疗效 \(\delta_{11}\),据此推 CDx+ 人群的临床性能 \(\delta_{CDx}\)(selection test 的关注量)。
  3. 缺失格敏感性 (sensitivity — 结构性缺失):(CTA− & CDx+) 那格试验里从没入组\(\delta_{01}\) 不可识别,用敏感性参数 \(c\) 假设它(\(\delta_{01}=c\,\delta_{11}\)),并给出 \(\delta_{CDx}\)\(c\) 的依赖。核心、必做

此外还有一个可选、触发式的分析,跟第 3 步是两回事:

  1. 缺失 CDx 结果的敏感性 (sensitivity — 样本可用性缺失,optional):第 3 步缺的是”一整格人群从没入组”;这一步缺的是”入组了、但个别样本拿不到 / 样本不足 / CDx 结果 invalid/未测”——是测量层面的缺失。做法:先量缺失/无效率,只有超过阈值(如 >10%)才触发在 MAR(随机缺失)假设下做多重插补、重估一致性与疗效;不超阈值就不做。

(标准 bridging 的结构性缺失就第 3 步这一处、一个 \(c\);末章「进阶」里 pre-screening、多 CTA 会引入更多结构性缺失格,第 3 步那类敏感性就要做多份。第 4 步则始终是那个独立的、可选的测量缺失分析。本文 worked example 两步都走:第 3 步见「worked:三步在 DGP 上走一遍」,第 4 步——含插补的内部机制、MAR 与 MNAR 的后果——见「第 4 步:入组了,却没有 CDx 判读」及随后两节。)操作上,用最终 CDx 重测可用筛查样本,尽量含 CTA− 筛查样本和全部 CTA+ 入组样本(富集设计下尤其重要)。

4.2 标准 bridging:缺失结构

问题:CDx+ 人群疗效 \(\delta_{CDx}\) 想估,可里面一整块(CTA−/CDx+)试验里根本没人——缺的这块怎么补? 富集设计只入组 CTA+;回溯性贴 CDx 后,CTA−/CDx+ 亚组未观察到

par(family = "cjk", mar = c(0.5, 0.5, 3, 0.5))
plot(NA, xlim = c(0, 16), ylim = c(-0.5, 7.4), axes = FALSE, xlab = "", ylab = "",
     main = "标准 bridging 全树:CTA × CDx × 臂 = 8 个末端,缺失打叉", cex.main = 1.2)
lb <- "#AED6F1"; lbb <- "#7FB3D5"                                  # 未入组:淡蓝 + 深边
xm <- function(x, y, col, hw = 0.9, hh = 0.23) {                    # 叉:标"不存在"
  segments(x-hw, y-hh, x+hw, y+hh, col = col, lwd = 2.4); segments(x-hw, y+hh, x+hw, y-hh, col = col, lwd = 2.4) }
arm <- function(y, col, tx) draw_box(10.0, y, tx, col, w = 1.8, h = 0.46, cex = 0.72,
  txt = if (identical(col, lb)) "gray30" else "white", border = if (identical(col, lb)) lbb else "white")
draw_box(1.2, 3.65, "筛查\n人群", "gray30", w = 1.8, h = 0.9, cex = 0.85)
# ① 先按 CTA 分(入组用)
draw_box(3.8, 5.45, "CTA+\n入组", "#2E86AB", w = 1.9, h = 0.8, cex = 0.85)
draw_box(3.8, 1.85, "CTA−\n没入组", lb, w = 1.9, h = 0.8, cex = 0.85, txt = "gray20", border = lbb)
draw_arr(2.1, 3.8, 2.85, 5.3); draw_arr(2.1, 3.5, 2.85, 2.0)
# ② 再回贴 CDx → 四格 a,b,c,d
draw_box(6.6, 6.25, bquote(paste(bold("a"), "  CTA+&CDx+")), "#2E86AB", w = 2.7, h = 0.6, cex = 0.8)
draw_box(6.6, 4.65, bquote(paste(bold("c"), "  CTA+&CDx−")), "gray55", w = 2.7, h = 0.6, cex = 0.8)
draw_box(6.6, 2.65, bquote(paste(bold("b"), "  CTA−&CDx+")), lb, w = 2.7, h = 0.6, cex = 0.8, txt = "gray20", border = lbb)
draw_box(6.6, 1.05, bquote(paste(bold("d"), "  CTA−&CDx−")), lb, w = 2.7, h = 0.6, cex = 0.8, txt = "gray20", border = lbb)
draw_arr(4.75, 5.6, 5.25, 6.25); draw_arr(4.75, 5.3, 5.25, 4.65)
draw_arr(4.75, 2.0, 5.25, 2.65); draw_arr(4.75, 1.7, 5.25, 1.05)
# ③ 每格再分治疗 / 对照 → 8 个末端
arm(6.6, "#2E86AB", "治疗 T=1"); arm(5.9, "gray60", "对照 T=0")
arm(5.0, "gray60", "治疗 T=1"); arm(4.3, "gray72", "对照 T=0")
arm(3.0, lb, "治疗 T=1"); arm(2.3, lb, "对照 T=0")
arm(1.4, lb, "治疗 T=1"); arm(0.7, lb, "对照 T=0")
draw_arr(7.95, 6.3, 9.1, 6.6); draw_arr(7.95, 6.2, 9.1, 5.9)
draw_arr(7.95, 4.7, 9.1, 5.0); draw_arr(7.95, 4.6, 9.1, 4.3)
draw_arr(7.95, 2.7, 9.1, 3.0); draw_arr(7.95, 2.6, 9.1, 2.3)
draw_arr(7.95, 1.1, 9.1, 1.4); draw_arr(7.95, 1.0, 9.1, 0.7)
brace(11.0, 5.9, 6.6); text(12.6, 6.25, bquote(paste(delta[11], " 可估")), col = "#2E86AB", cex = 0.9, font = 2)
brace(11.0, 2.3, 3.0); text(12.6, 2.65, bquote(paste(delta["01"], " 缺失")), col = "gray30", cex = 0.9, font = 2)
text(11.5, 4.65, "c,d = CDx−(不搬)", col = "gray45", cex = 0.75, font = 3)
segments(2.9, 0.25, 14.2, 3.45, col = "#C73E1D", lwd = 3); segments(2.9, 3.45, 14.2, 0.25, col = "#C73E1D", lwd = 3)  # 红叉:CTA− 整支
xm(10.0, 5.9, "#E8871E"); xm(10.0, 4.3, "#E8871E")                  # 橙叉:单臂才删对照(CTA+ 支的对照 T=0)
segments(0.5, 7.2, 1.0, 7.2, col = "#C73E1D", lwd = 3); text(1.15, 7.2, "红叉 = CTA− 富集未入组(取舍一)", col = "#C73E1D", cex = 0.72, font = 2, adj = 0)
xm(0.75, 6.8, "#E8871E", 0.25, 0.13); text(1.15, 6.8, "橙叉 = 单臂才删对照(取舍二;RCT 下对照在、δ11 可估)", col = "#E8871E", cex = 0.72, font = 2, adj = 0)
text(8, -0.3, expression(paste("目标 CDx+ = a+b。RCT:a 两臂都在 → ", delta[11], " = 治疗 − 对照可估;单臂:删对照(橙叉) → δ 用应答率(前提对照≈0)。b 随 CTA− 没入组 → ", delta["01"], " 缺失。p = a/(a+b),", delta[CDx], " = p·", delta[11], " + (1−p)·", delta["01"])),
     col = "gray20", cex = 0.76, font = 2)

\[\delta_{CDx} = p\,\delta_{11} + (1-p)\,\delta_{01},\qquad p=\Pr(CTA{+}\mid CDx{+})\]

  • \(\delta_{11}\):(CTA+ & CDx+) 疗效——观察到。这里默认 RCT:CTA+ 入组者被随机分到治疗 / 对照,所以 (CTA+ & CDx+) 这格里两个臂都在\(\delta_{11}=\) 治疗 − 对照直接量得到(若改单臂设计则没有对照臂,“疗效”改用单臂应答率、不是臂差)。\(\delta_{01}\):(CTA− & CDx+) 疗效——缺失,因为 CTA− 整支富集设计里没入组。
  • 这是把 CDx+ 人群按 CTA 状态劈开的全期望恒等式。\(p\) 可估,\(\delta_{01}\) 只能靠假设/敏感性分析。
  • 和 §地基「疗效混合」是同一套积木\(\delta_{\text{pos}}=\mu_{11}-\mu_{01}\)\(\delta_{\text{neg}}=\mu_{10}-\mu_{00}\),就是地基那两块):\(\delta_{11}\)\(\delta_{01}\) 各自也都是 \(\delta_{\text{pos}}\)\(\delta_{\text{neg}}\) 按各自格子 PPV 混出来的(\(\delta_{01}\) 那格 PPV 更低、混进更多真−,所以 \(\delta_{01}<\delta_{11}\),这正是后面取 \(c=\delta_{01}/\delta_{11}\in(0,1)\) 的由来)。区别只在:那里按看不到的\(G\) 分(需参考标准),这里改按观察得到的 CTA 分。

“臂差 vs 单臂应答率” 上面 \(\delta_{11}=\) 治疗 − 对照,是个臂间差(对照差, contrast)——要有对照臂,答的是”用药比不用好多少”;对照替你扣掉了疾病自然史 / 安慰剂那一部分,所以这个差能干净地归因于药。单臂应答率(如 ORR,objective response rate,客观缓解率)没有对照,只在治疗组里量”缓解”的比例——是个绝对水平(level),答的是”治了有多少人缓解”;它扣不掉自然史,得拿历史 / 预设阈值当参照。单臂之所以立得住,靠的是”肿瘤不会自己缩、客观缓解可归因于药”(何时能单臂、历史对照的偏倚,详见 §「取舍二」)。

为什么单臂”只量治疗组”也能当疗效?把”臂”这一维显式写出来就清楚了(平时省掉、一滑而过,反而糊)。严格讲,每个格子里有两个臂均值,用一个带治疗臂下标的记号:\(\mu_{T;\,11}\) = 治疗臂 \(T\in\{1,0\}\)、在 (CTA+ & CDx+) 格里的结局均值。于是臂差 \(\delta_{11}=\mu_{1;11}-\mu_{0;11}\)(治疗 − 对照,都在这一格)。平时把 \(T\) 这维省掉、直接写 \(\delta_{11}\),是因为差分已经把它吃掉——但省掉不等于不存在,对照项 \(\mu_{0;11}\) 一直在那儿。

单臂设计里没有对照臂,量到的只有 \(\mu_{1;11}\)(治疗组的应答水平)。它能顶替疗效,靠的正是一个假设:对照项 \(\mu_{0;11}\approx 0\)——ORR 这类客观缓解终点,不治疗几乎无人自发缓解(肿瘤不自己缩),所以 \(\delta_{11}=\mu_{1;11}-\mu_{0;11}\approx\mu_{1;11}\),“治疗组应答率”就≈“臂差”。这个假设不写出来就滑过去了,其实是单臂能成立的关键前提。(本文 DGP 恰好也把对照臂均值设成 \(0\)——\(\mu_{01}=\mu_{00}=0\),见地基——所以那里 \(\delta_{\text{pos}}=\mu_{11}\)\(\delta_{\text{neg}}=\mu_{10}\),治疗组水平直接就等于疗效,与单臂同构。)

一句话对比:臂差是”相对某对照的差值”,应答率是”治疗组里的绝对水平”(后者当疗效用,前提是对照水平≈0)。但对 bridging 而言两者是一回事:应答率 = “是否缓解”这个 \(0/1\) 变量的均值,和均值差一样属均值型、可按人数占比线性混合——所以把本章的 \(\delta\) 一律替换成 ORR,搬运恒等式 \(\delta_{CDx}=p\,\delta_{11}+(1-p)\,\delta_{01}\) 照样精确成立、公式一个都不用改。唯一不能这样换的是比值型的 HR(风险比不可塌缩,混合会偏,见末章 「补充:为什么用连续端点」)。

CDx × CTA 的 2×2 一致性表——格里 \(a,b,c,d\)落入该格的病人数(样本数),不是概率:如 \(a\) = 既 CTA+ 又 CDx+ 的人数、\(d\) = 既 CTA− 又 CDx− 的人数,四格加起来 = 总样本量。

CDx × CTA 2×2 一致性表

CTA+

CTA-

CDx+

a

b

CDx-

c

d

两个一致率直接从表读出(行 = CDx,列 = CTA):

\[PPA=\Pr(CDx{+}\mid CTA{+})=\frac{a}{a+c},\qquad NPA=\Pr(CDx{-}\mid CTA{-})=\frac{d}{b+d}\]

即 PPA 是 \(CTA{+}\) 那列里 \(CDx{+}\) 的比例、NPA 是 \(CTA{-}\) 那列里 \(CDx{-}\) 的比例。还有第三个常用指标 OPA(overall percent agreement,总体一致率)——两检测判读完全一致的比例:

\[OPA=\Pr(CDx{=}CTA)=\frac{a+d}{a+b+c+d}\]

但 OPA 由患病率主导(把 PPA、NPA 按边际比例混成一个数),某一类占绝大多数时会虚高,所以监管一致性以 PPA、NPA 分开报为主、OPA 只作总览。本文的搬运公式用 PPA/NPA 加权重 \(p\),不依赖 OPA——列出只为记号完整。

\(a,b,c,d\) 和疗效 \(\delta\) 对上(这里最容易晕)。同一张 CTA×CDx 网格、两个层\(a,b,c,d\) 数的是人数(谁落哪格),\(\delta\) 是那格里的疗效(治疗 − 对照的 \(Y\) 均值差)。把疗效层也摆成同布局的 2×2(行 = CDx、列 = CTA,和上面计数表一格对一格):

同格的疗效 δ(下标 = CTA,CDx;与上表 a,b,c,d 一格对一格)

CTA+

CTA-

CDx+

δ11

δ01

CDx-

δ10

δ00

一格对一格:人数 \(a\) 那格的疗效是 \(\delta_{11}\)\(b\) 格是 \(\delta_{01}\)\(c\) 格是 \(\delta_{10}\)\(d\) 格是 \(\delta_{00}\)bridging 只搬 CDx+ 那一行\(\delta_{11}\)\(\delta_{01}\)):\(\delta_{11}\)(配人数 \(a\))观察得到、\(\delta_{01}\)(配人数 \(b\))缺失。按人数加权就是搬运公式——

\[\delta_{CDx}=\frac{a\,\delta_{11}+b\,\delta_{01}}{a+b}=p\,\delta_{11}+(1-p)\,\delta_{01},\qquad p=\frac{a}{a+b}=\Pr(CTA{+}\mid CDx{+}).\]

(CDx− 行的 \(\delta_{10}\)\(\delta_{00}\) 不是搬运目标,本文不用。)

Bayes 估 \(p=\Pr(CTA{+}\mid CDx{+})\)\(p=\dfrac{PPA\cdot prev}{PPA\cdot prev+(1{-}NPA)(1{-}prev)}\),其中 \(prev=\Pr(CTA{+})\) 须对应意向使用人群。下面核对这条 Bayes 式与直接估计一致:

PPA_t <- 0.90; NPA_t <- 0.85; prev <- 0.30; n <- 1e4
a <- n*prev*PPA_t; b <- n*(1-prev)*(1-NPA_t)   # 用设定的 PPA/NPA/prev 造一张表
ft_show(data.frame(`p(Bayes 式)` = round((PPA_t*prev)/(PPA_t*prev+(1-NPA_t)*(1-prev)), 4),
           `p(直接从表)` = round(a/(a+b), 4), check.names = FALSE),
        title = "Bayes 估 p = 直接从表算")
Bayes 估 p = 直接从表算

p(Bayes 式)

p(直接从表)

0.72

0.72

4.3 worked:三步在 DGP 上走一遍(一致性 → 疗效 → 敏感性)

把 §4.1 的三步在已知真相的世界里逐一跑通,每步都从模拟数据估出、再和闭式真值对撞——看清楚哪些量数据能给、最终 \(\delta_{CDx}\) 到底卡在哪一步的假设上。

步骤 1 · 一致性 → 搬运权重 \(p\) 用一份代表性筛查样本(CTA、CDx 都测、含 CTA−、未富集)估 PPA/NPA,再 Bayes 得 \(p=\Pr(CTA{+}\mid CDx{+})\)

scr <- generate_population(3e5)                               # 代表性筛查样本(未富集,含 CTA−)
PPA_hat <- mean(scr$CDx[scr$CTA==1]==1)                       # Pr(CDx+|CTA+)
NPA_hat <- mean(scr$CDx[scr$CTA==0]==0)                       # Pr(CDx−|CTA−)
p_hat   <- mean(scr$CTA[scr$CDx==1]==1)                       # Pr(CTA+|CDx+) = 搬运权重
tc <- true_concordance(TRUTH$assays$CTA, TRUTH$assays$CDx)
ft_show(data.frame(
  `` = c("PPA_{CTA,CDx}", "NPA_{CTA,CDx}", "p = Pr(CTA+|CDx+)"),
  `估计` = round(c(PPA_hat, NPA_hat, p_hat), 4),
  `真值` = round(c(tc[["PPA"]], tc[["NPA"]], true_p_cta_given_cdx()), 4),
  check.names = FALSE), title = "步骤 1:一致性与搬运权重 p —— 估计 vs 真值")
步骤 1:一致性与搬运权重 p —— 估计 vs 真值

估计

真值

PPACTA,CDx

0.7558

0.7540

NPACTA,CDx

0.9005

0.8993

p = Pr(CTA+|CDx+)

0.7855

0.7829

步骤 2 · 疗效 → 观察格 \(\delta_{11}\) 富集试验只入组 CTA+,在观察得到的 (CTA+ & CDx+) 格里直接估 \(\delta_{11}\)(治疗 − 对照):

trial <- generate_population(2e5, with_endpoint = TRUE)       # 富集试验(分析只用 CTA+)
d11_hat <- delta_hat(subset(trial, CTA==1 & CDx==1))          # 观察格疗效
big2 <- generate_population(3e6, with_endpoint = TRUE)        # god 视角:算真值用
true_d11 <- delta_hat(subset(big2, CTA==1 & CDx==1))
ft_show(data.frame(`δ_{11} 估计` = round(d11_hat,4), `δ_{11} 真值` = round(true_d11,4),
  check.names = FALSE), title = "步骤 2:观察格 (CTA+ & CDx+) 疗效 δ_{11} —— 估计 vs 真值")
步骤 2:观察格 (CTA+ & CDx+) 疗效 δ11 —— 估计 vs 真值

δ11 估计

δ11 真值

0.6103

0.5952

步骤 3 · 缺失格敏感性 → \(\delta_{CDx}\) (CTA− & CDx+) 那格没人、\(\delta_{01}\) 测不到,用敏感性参数 \(c=\delta_{01}/\delta_{11}\) 顶上,于是 \(\delta_{CDx}=(p+(1-p)c)\,\delta_{11}\)。把步骤 1、2 估到的 \(p\)\(\delta_{11}\) 代进去、扫 \(c\);再用 god 视角的真 \(c\) 验证能否还原真 \(\delta_{CDx}\)

true_d01 <- delta_hat(subset(big2, CTA==0 & CDx==1))          # god 视角:缺失格真疗效
true_dCDx <- true_delta_in_assay_pos(TRUTH$assays$CDx); c_true <- true_d01/true_d11
c_seq <- seq(0, 1, 0.02)
plot(c_seq, (p_hat+(1-p_hat)*c_seq)*d11_hat, type = "l", lwd = 2, col = "#2E86AB",
     xlab = expression(paste("敏感性 c = ", delta["01"], " / ", delta["11"])),
     ylab = expression(paste("bridged ", delta[CDx])),
     main = expression(paste("步骤 3:代入真 c 还原真 ", delta[CDx])))
abline(h = true_dCDx, lty = 2, col = "#C73E1D"); abline(v = c_true, lty = 3, col = "gray40")
points(c_true, (p_hat+(1-p_hat)*c_true)*d11_hat, pch = 19, col = "#C73E1D")
legend("topleft", legend = expression(paste("bridged(c)"), paste("真 ", delta[CDx]), paste("真 c")),
       col = c("#2E86AB","#C73E1D","gray40"), lty = c(1,2,3), lwd = 2, bty = "n", cex = 0.8)

ft_show(data.frame(`δ_{01} 真值` = round(true_d01,4), `c 真值` = round(c_true,4),
  `δ_{CDx}(代入真 c)` = round((p_hat+(1-p_hat)*c_true)*d11_hat,4), `δ_{CDx} 真值` = round(true_dCDx,4),
  check.names = FALSE), title = "步骤 3:代入真 c 还原真 δ_{CDx}")
步骤 3:代入真 c 还原真 δCDx

δ01 真值

c 真值

δCDx(代入真 c)

δCDx 真值

0.3935

0.6611

0.566

0.5508

✓ 三步都对上:步骤 1 的 PPA/NPA/\(p\)、步骤 2 的 \(\delta_{11}\) 都贴合真值(数据能给);步骤 3 把这两步估到的 \(p\)\(\delta_{11}\) 一代,红点正落在真值线上。唯一数据给不出的,是缺失格的 \(\delta_{01}\)(即 \(c\)——它是全流程唯一不可识别、只能靠假设的量。\(\delta_{CDx}\) 一般低于富集试验直接估计的疗效,收缩程度取决于 \(p\) 和对 \(c\) 的假设;实践中 \(c\) 取小值做保守估计。

4.4 第 4 步:入组了,却没有 CDx 判读

前三步处理的缺失是一整格人群缺席。这一节是另一种缺失:人试验里,CDx 那一格却是空的——样本没采到、采到了量不足、DNA 降解让判读成 INVALID、或者根本没送测。这就是 §4.1 列的第 4 步:独立于第 3 步、可选、触发式。临床 bridging 的方法学综述也把 sample retainment / missing data 单列为核心难点 (Song et al. 2024)

本节的疗效改用单臂应答率(记 \(R_1\) = 观察格 CTA+ & CDx+ 里的应答率,例如 CR/CRh 率),因为真实的 CDx 敏感性分析多半长这样。搬运恒等式一个字都不用改(§4.2 已说明率是均值型、可线性混合):

\[R_{CDx}=p\,R_1+(1-p)\,R_2,\qquad R_2=c\,R_1 \quad\Longrightarrow\quad R_{CDx}=\bigl(p+(1-p)c\bigr)R_1\]

4.4.1 两种缺失,差在有没有可借的信息

par(family = "cjk", mfrow = c(1, 2), mar = c(0.6, 0.6, 3.0, 0.6))
lb <- "#AED6F1"; lbb <- "#7FB3D5"; org <- "#E8871E"

## 左:结构缺失——整格没人
plot(NA, xlim = c(0, 10), ylim = c(0, 8), axes = FALSE, xlab = "", ylab = "",
     main = "结构缺失(第 3 步):整格没人", cex.main = 1.15)
text(3.9, 7.0, "CTA+", col = "gray25", font = 2, cex = 0.95)      # 列头
text(7.3, 7.0, "CTA−", col = "gray25", font = 2, cex = 0.95)
text(1.95, 5.5, "CDx+", col = "gray25", font = 2, cex = 0.95, adj = 1)   # 行头
text(1.95, 3.3, "CDx−", col = "gray25", font = 2, cex = 0.95, adj = 1)
rect(2.2, 4.4, 5.6, 6.6, col = "#2E86AB", border = "white", lwd = 2)
rect(5.6, 4.4, 9.0, 6.6, col = lb, border = lbb, lwd = 2)
rect(2.2, 2.2, 5.6, 4.4, col = "gray62", border = "white", lwd = 2)
rect(5.6, 2.2, 9.0, 4.4, col = "gray80", border = "white", lwd = 2)
segments(5.8, 4.6, 8.8, 6.4, col = adjustcolor("#C73E1D", 0.5), lwd = 3)  # 叉先画,文字压上面
segments(5.8, 6.4, 8.8, 4.6, col = adjustcolor("#C73E1D", 0.5), lwd = 3)
text(3.9, 5.5, "a  观察到", col = "white", font = 2, cex = 0.95)
rect(6.25, 5.22, 8.35, 5.78, col = adjustcolor("white", 0.88), border = NA)   # 叉上垫白底
text(7.3, 5.5, "b  没入组", col = "#C73E1D", font = 2, cex = 0.95)
text(3.9, 3.3, "c", col = "white", font = 2, cex = 0.95)
text(7.3, 3.3, "d", col = "gray25", font = 2, cex = 0.95)
text(5.6, 2.0, "c、d 是 CDx− 行,不是搬运目标", col = "gray45", cex = 0.78, font = 3)
text(5.0, 1.4, "b 格的人一行数据都没有:没有协变量、没有结局。", col = "gray20", cex = 0.9, font = 2)
text(5.0, 0.6, "→ 疗效不可识别,只能用敏感性参数 c 顶上", col = "#C73E1D", cex = 0.9, font = 2)

## 右:测量缺失——行都在,只缺一格
plot(NA, xlim = c(0, 10), ylim = c(0, 8), axes = FALSE, xlab = "", ylab = "",
     main = "测量缺失(第 4 步):只缺一格", cex.main = 1.15)
hdr <- c("协变量 X", "CTA", "CDx", "结局")
xs  <- c(1.0, 3.6, 5.4, 7.2); xe <- c(3.6, 5.4, 7.2, 9.2)
for (j in seq_along(hdr)) {
  rect(xs[j], 6.4, xe[j], 7.2, col = "gray35", border = "white", lwd = 1.6)
  text((xs[j] + xe[j])/2, 6.8, hdr[j], col = "white", font = 2, cex = 0.82)
}
cdx_val <- c("+", "?", "−", "+", "?", "+")
for (i in 1:6) {
  yb <- 6.4 - i * 0.72; yt <- yb + 0.72
  gone <- cdx_val[i] == "?"
  for (j in seq_along(hdr)) {
    fill <- if (j == 3 && gone) adjustcolor(org, 0.28) else "white"
    rect(xs[j], yb, xe[j], yt, col = fill, border = "gray70", lwd = 1.2)
  }
  text((xs[1] + xe[1])/2, (yb + yt)/2, "年龄 / 分层 / 疾病状态", col = "gray30", cex = 0.72)
  text((xs[2] + xe[2])/2, (yb + yt)/2, "+", col = "#2E86AB", font = 2, cex = 0.95)
  text((xs[3] + xe[3])/2, (yb + yt)/2, cdx_val[i],
       col = if (gone) org else "#2E86AB", font = 2, cex = if (gone) 1.15 else 0.95)
  text((xs[4] + xe[4])/2, (yb + yt)/2, if (i %% 2) "应答" else "未应答", col = "gray30", cex = 0.72)
}
text(5.0, 1.4, "同一行的其它列都在:可以用它们学到的关系", col = "gray20", cex = 0.9, font = 2)
text(5.0, 0.6, "→ 把这一格的条件分布补出来(多重插补)", col = org, cex = 0.9, font = 2)

分水岭就是”有没有可借的信息”。 结构缺失那格的人从没入组,数据里一行都没有,\(\delta_{01}\)(率版本的 \(R_2\)不可识别,只能靠 \(c\)。测量缺失的人整行都在——人口学、临床分层、CTA 判读、结局全有,只缺 CDx 一格;于是可以用”有判读的人”身上学到的关系,把这一格补上。

插补补的是分布,不是真值。 谁也不知道那个人到底是 CDx+ 还是 CDx−;能做的是按条件概率一个,抽很多次(多重插补),看结论在这些抽法之间晃多少。晃动幅度本身就是答案的一部分。这正是多重插补相对于单值填补的关键:让缺失值不确定性进入最终推断 (Rubin 1987; Little and Rubin 2019)

4.4.2 触发:先量缺失率,超过阈值才做

实务顺序是:按队列量”缺失或无效”的比例 → 超过阈值(常取 10%)才启动多重插补,并在观察值 + 插补值的合并数据上重估一致性与疗效;不超阈值就用有判读的人直接分析。阈值不是统计定理,是工作量与风险的折衷;把它写进 SAP 的作用是在看到结果之前就定好做不做,避免”结果不好看才补一个敏感性分析”。

4.4.3 三档缺失机制

\(R\) 为缺失指示(\(R=1\) 表示这个人的 CDx 判读缺失),\(Z\) 为待缺失的 CDx 判读,\(X\) 为观测到的协变量:

缺失机制三档(R = 缺失指示,Z = 待缺失的 CDx 判读,X = 观测协变量)

机制

定义

读法

丢弃缺失者

MAR 插补能修吗

MCAR

Pr(R=1 | Z, X) = Pr(R=1)

缺失与任何东西无关

无偏

不必修

MAR

Pr(R=1 | Z, X) = Pr(R=1 | X)

缺不缺可以看 X 决定;X 给定之后,不再多看一眼那个要缺的判读

一般有偏(X 的分布被筛过)

能修(前提:X 进了插补模型)

MNAR

Pr(R=1 | Z, X) 仍依赖 Z

缺不缺还取决于判读本身(如 CDx− 的样本更容易 invalid)

有偏

修不掉

MAR 是假设,不是可检验的事实。 缺失值那部分不出现在观测数据的似然里,所以一个 MAR 模型和某个 MNAR 模型可以对观测数据给出完全相同的拟合——数据分不开它们。这样看,第 4 步和第 3 步同属”靠假设”,区别在假设的位置和成本:\(c\) 是一个完全没有数据支撑的旋钮,MAR 则借到了 \(X\) 的信息,还能把插补带来的变异量出来。因此主分析必须陈述可识别假设,并用敏感性分析探索不可检验部分 (National Research Council 2010; Little and Rubin 2019)

机制往哪偏也可以推:样本量不足、肿瘤含量低导致的判读无效,很可能与 marker 状态本身相关——那就落在 MNAR 一档。本节最后会把这种情形的后果量出来。

4.4.4 插补模型放哪些变量

实务做法:人口学 + 临床基线(年龄、性别、种族、风险分层、疾病状态)。两个刻意的选择:

  1. 不放先前的 CDx 判读。 那等于把待插补量的一个近似副本喂进模型,插补值会近乎复制它;一致性表随之被这份人造相关撑起来,PPA 和权重 \(p\) 抬高,敏感性分析看不出缺失的影响。
  2. CTA 判读放不放,是一个有方向的取舍。 放进去是 MAR 的正统做法——凡与缺失、或与待插补量相关的观测变量都该进模型,这样才修得掉偏倚。不放,插补分布就向”含大量 CTA− 阴性材料的整体”回归,插出的 CDx 偏阴,PPA 与 \(p\) 偏低,结论偏保守。下面的 worked example 把两条路的偏倚各自量出来。

协变量本身若有零星缺失,实务里常直接填一个”未报告”类别(分类变量)或中位数(连续变量),不再单独建一层插补模型——“未报告”在基线表里本来就是一个真实类别。

4.4.5 CTA− 那一列不插补

缺失发生在入组的 CTA+ 人群;一致性表里的 CTA− 一列通常来自外部阴性材料(专为估 NPA 收集、判读齐全)。这些人要留在模型框里,因为 NPA 得靠它们;但不给它们插补——给一列本来齐全的判读插补,等于直接改写 NPA。真有 CTA− 个体缺 CDx 判读,做法是剔除并把剔除数报出来。

4.5 一次插补 = 两次抽样

micelogreg(logistic regression 插补)核心只有三步:

\[\text{① 拟合(只用有判读的人):}\quad \operatorname{logit}\Pr(\text{CDx}{=}1\mid X)=X^{\top}\hat\beta\]

\[\text{② 抽系数:}\quad \beta^{*}=\hat\beta+L^{\top}z,\qquad L^{\top}L=\widehat{\operatorname{Cov}}(\hat\beta),\quad z\sim N(0,I)\]

\[\text{③ 抽判读:}\quad \pi_i=\operatorname{expit}\!\bigl(x_i^{\top}\beta^{*}\bigr),\qquad \text{CDx}_i^{\text{imp}}=\mathbf{1}\{u_i\le\pi_i\},\quad u_i\sim U(0,1)\]

两次抽样各管一件不确定性:② 管参数不确定性——完整案例就那么多人,回归系数本身是估出来的;③ 管个体残差不确定性——同样 \(\pi=0.7\) 的两个人,一个可以是阳、一个可以是阴。少任何一步都会把不确定性做小:只做 ③(把 \(\hat\beta\) 当真值,文献里叫 improper imputation)低估插补方差;两步都不做、直接填 \(\pi>0.5\) 的那一类,则所有插补数据集完全相同,多重插补退化成单值填补,跨插补方差是 0。三种填法的差别下面会量。

4.5.1 插补值进入这条链的三个位置

每个完整数据集都要把第 1–3 步重跑一遍。插补的判读不是只改一个计数,它同时进三处:

  1. 2×2 表 → PPA、NPA → 经 Bayes 得权重 \(p\)
  2. \(R_1\) 的分母:谁算进双阳格(CTA+ & CDx+);
  3. \(R_1\) 的分子:这些人应答与否(结局是观察到的,但算不算进分子取决于插补的判读)。

三处的偏倚方向可以互不相同,净效应要算——这也是为什么不能只看 PPA 一处就下结论。

4.5.2 \(c=1\) 那一行是个恒等式

\[R_{CDx}(c)=p\,R_1+(1-p)\,c\,R_1 \quad\xrightarrow{\ c=1\ }\quad p\,R_1+(1-p)\,R_1=R_1\]

\(p\) 整个消掉:\(c=1\) 那一行就是双阳应答率本身,与主分析对得上,可当交叉校验读。反过来说,若敏感性分析只做 \(c=1\) 一档,一致性那一整套(PPA、NPA、\(p\) 以及 \(p\) 的方差)算了也会被代数消掉——不是算错,是这一档的必然结果。

4.5.3 m 取多大,取决于怎么汇总

Rubin 规则下 \(m=20\sim50\) 通常已足够(相对效率 \((1+\gamma/m)^{-1}\)\(\gamma\) 为缺失信息比例;具体所需次数仍取决于 FMI 与目标精度)(Rubin 1987)。而实务表格常用五数概括报这 \(m\) 个估计(Min/Q1/中位/均值/Q3/Max),那就得把 \(m\) 提到几百上千:四分位数很快稳住,而 Min、Max 是极值统计量,\(m\) 越大越往外走、不收敛。所以 \(m=1000\) 服务的是”极值那两列别乱跳”;即便如此,那两列的读法也只能是”这 1000 次里最坏/最好的一次”,不是界限。下面把这条漂移画出来。

4.6 worked:缺失 CDx 的插补在 DGP 上走一遍

在已知真相的世界里,“如果没有缺失”这个对照是能拿到的——真实数据里永远拿不到。整节的验证策略就是拿它当基准:全数据估计 vs 丢弃缺失者 vs 两种插补,看谁回得去。

先给这一节的 DGP 加两样东西:与真 marker 相关的基线协变量(插补模型要用),和一个二值应答(单臂率)。assay 参数、患病率、疗效都沿用 TRUTH,不新设。

gen_mi <- function(n, truth = TRUTH) {
  G  <- rbinom(n, 1, truth$prev_G)
  ca <- function(oc) ifelse(G == 1, rbinom(n, 1, oc["sens"]), rbinom(n, 1, 1 - oc["spec"]))
  ep <- truth$endpoint                                   # 单臂:只有治疗组
  Y  <- rnorm(n, ifelse(G == 1, ep$mu_G1_T0 + ep$delta_pos, ep$mu_G0_T0 + ep$delta_neg), ep$sd)
  data.frame(G = G, CTA = ca(truth$assays$CTA), CDx = ca(truth$assays$CDx),
             AGE  = rnorm(n, 60 + 8 * G, 7),             # 协变量①:真 marker+ 年龄偏大
             RISK = rbinom(n, 1, 0.25 + 0.35 * G),       # 协变量②:真 marker+ 更常落在高危层
             RESP = as.integer(Y > 1.0))                 # 二值应答(CR/CRh 式)
}

# 一次试验 = 富集入组的 CTA+ n1 人(判读 + 结局齐全)+ 外部阴性材料 n0 人(只为估 NPA)
make_trial <- function(n1 = 150, n0 = 100) {
  pool <- gen_mi(4e4)
  neg  <- pool[pool$CTA == 0, ][seq_len(n0), ]; neg$RESP <- NA_integer_   # 阴性材料无结局
  rbind(pool[pool$CTA == 1, ][seq_len(n1), ], neg)
}

PREV_CTA <- unname(assay_prev(TRUTH$assays$CTA))   # 意向使用人群的 Pr(CTA+),Bayes 权重用

把第 1–3 步压成一个函数——这就是每份插补数据集要重跑的全部

bridged_rate <- function(cdx, cta, resp, c_values, prev = PREV_CTA, idx = seq_along(cdx)) {
  cdx <- cdx[idx]; cta <- cta[idx]; resp <- resp[idx]
  A <- sum(cdx == 1 & cta == 1); B <- sum(cdx == 1 & cta == 0)      # 2×2 四格
  C <- sum(cdx == 0 & cta == 1); D <- sum(cdx == 0 & cta == 0)
  ppa <- A/(A + C); npa <- D/(B + D)                                # 步骤 1:一致性
  R1  <- if (A > 0) mean(resp[cdx == 1 & cta == 1]) else NA_real_    # 步骤 2:观察格应答率
  p   <- (ppa*prev)/(ppa*prev + (1 - npa)*(1 - prev))                # Bayes 权重
  c(PPA = ppa, NPA = npa, p = p, R1 = R1, A = A,                     # 步骤 3:扫 c
    setNames((p + (1 - p)*c_values)*R1, paste0("c", c_values)))
}

缺失机制两种:MAR 只看协变量(年龄大、高危层更容易缺),MNAR 额外还看判读本身(CDx− 更容易无效)。插补按上面拆的三步写,mode 控制抽几层:

zage      <- function(a) (a - 60)/10
miss_mar  <- function(d) d$CTA == 1 &
  runif(nrow(d)) < plogis(-3.05 + 1.1*zage(d$AGE) + 0.9*d$RISK)
miss_mnar <- function(d) d$CTA == 1 &
  runif(nrow(d)) < plogis(-4.3 + 1.1*zage(d$AGE) + 0.9*d$RISK + 3.0*(d$CDx == 0))

impute_once <- function(d, miss, use_cta, mode = "proper") {
  f   <- if (use_cta) CDx ~ AGE + RISK + CTA else CDx ~ AGE + RISK
  fit <- glm(f, data = d[!miss, ], family = binomial)               # ① 只用有判读的人
  b   <- coef(fit)
  bst <- if (mode == "proper")                                      # ② 抽系数 β*
           b + t(chol(summary(fit)$cov.unscaled)) %*% rnorm(length(b)) else b
  pr  <- plogis(as.vector(model.matrix(delete.response(terms(fit)), d[miss, ]) %*% bst))
  if (mode == "deterministic") as.integer(pr > 0.5)                 # 确定性填补:不抽
  else as.integer(runif(length(pr)) <= pr)                          # ③ 抽判读
}

draw_imp <- function(d, miss, use_cta, m, c_values, mode = "proper")
  t(vapply(seq_len(m), function(i) {
    cdx <- d$CDx; cdx[miss] <- impute_once(d, miss, use_cta, mode)
    bridged_rate(cdx, d$CTA, d$RESP, c_values)                      # 每份完整数据集重跑整条链
  }, numeric(5 + length(c_values))))

4.6.1 一次插补的解剖

set.seed(7)
d_one <- make_trial(); miss_one <- miss_mar(d_one)
M <- 1000; C_V <- c(1, 0.5, 0)
mi_pro <- draw_imp(d_one, miss_one, TRUE, M, C_V, "proper")
mi_imp <- draw_imp(d_one, miss_one, TRUE, M, C_V, "improper")
mi_det <- draw_imp(d_one, miss_one, TRUE,  20, C_V, "deterministic")

par(family = "cjk", mfrow = c(1, 3), mar = c(4.2, 4.2, 3.4, 0.8))

## (a) 抽系数:β* 曲线族
fit1 <- glm(CDx ~ AGE + RISK + CTA, data = d_one[!miss_one, ], family = binomial)
ag   <- seq(38, 88, length.out = 120)
Xg   <- cbind(1, ag, 0, 1)                                   # RISK=0、CTA=1 的切片
V    <- t(chol(summary(fit1)$cov.unscaled))
plot(NA, xlim = range(ag), ylim = c(0, 1), main = "① 拟合 + ② 抽系数 β*",
     xlab = "年龄(RISK=0、CTA+ 切片)", ylab = "Pr(CDx+)", cex.main = 1.1, cex.lab = 1.05)
for (k in 1:40)
  lines(ag, plogis(as.vector(Xg %*% (coef(fit1) + V %*% rnorm(4)))),
        col = adjustcolor("gray55", 0.45), lwd = 1)
lines(ag, plogis(as.vector(Xg %*% coef(fit1))), col = "#2E86AB", lwd = 3)
obs <- d_one[!miss_one & d_one$CTA == 1, ]
points(obs$AGE, jitter(obs$CDx, amount = 0.04), pch = 16, cex = 0.5,
       col = adjustcolor("#2E86AB", 0.5))
rug(d_one$AGE[miss_one], col = "#E8871E", lwd = 2, side = 1)
legend("bottomright", c("完整案例拟合", "40 条 β* 抽样", "待插补者年龄"),
       col = c("#2E86AB", "gray55", "#E8871E"), lwd = c(3, 1, 2), cex = 0.85,
       bg = adjustcolor("white", 0.88), box.col = NA)

## (b) 三种填法的跨插补散布
xr <- range(c(mi_pro[, "c0.5"], mi_imp[, "c0.5"], mi_det[, "c0.5"]))
hist(mi_pro[, "c0.5"], breaks = 30, col = adjustcolor("#2E86AB", 0.55), border = "white",
     freq = FALSE, xlim = xr, main = "三种填法:1000 次插补的桥接率",
     xlab = expression(paste("桥接应答率 ", R[CDx], "  (c = 0.5)")),
     ylab = "密度", cex.main = 1.1, cex.lab = 1.05)
lines(density(mi_imp[, "c0.5"]), col = "forestgreen", lwd = 2.4)
abline(v = mi_det[1, "c0.5"], col = "#E8871E", lwd = 3)
legend("topright", c(sprintf("proper(抽 β*+判读)SD %.4f", sd(mi_pro[, "c0.5"])),
                     sprintf("improper(只抽判读)SD %.4f", sd(mi_imp[, "c0.5"])),
                     "确定性填补 SD = 0"),
       col = c("#2E86AB", "forestgreen", "#E8871E"), lwd = c(6, 2.4, 3), cex = 0.8,
       bg = adjustcolor("white", 0.88), box.col = NA)

## (c) 五数概括随 m 漂移
ms <- unique(round(10^seq(log10(10), log10(M), length.out = 40)))
v  <- mi_pro[, "c0.5"]
fv <- t(vapply(ms, function(mm) c(min(v[1:mm]), quantile(v[1:mm], c(.25, .5, .75), names = FALSE),
                                  max(v[1:mm])), numeric(5)))
matplot(ms, fv, type = "l", log = "x", lty = c(2, 1, 1, 1, 2), lwd = c(2, 1.8, 2.6, 1.8, 2),
        col = c("#C73E1D", "gray45", "#2E86AB", "gray45", "#C73E1D"),
        ylim = range(fv) + c(-0.001, 0.008),          # 顶部留出 legend 的位置
        main = "五数概括随 m 的走势", xlab = "插补次数 m(对数轴)",
        ylab = expression(paste("桥接应答率 ", R[CDx], "  (c = 0.5)")),
        cex.main = 1.1, cex.lab = 1.05)
legend("topleft", c("Min / Max(一直往外爬)", "Q1 / Q3", "中位数"),
       col = c("#C73E1D", "gray45", "#2E86AB"), lty = c(2, 1, 1), lwd = c(2, 1.8, 2.6),
       cex = 0.8, bg = adjustcolor("white", 0.9), box.col = NA)

左图是”抽系数”这一步的样子:完整案例给一条拟合曲线(蓝),\(\beta^{*}\) 每抽一次就换一条(灰)——灰带的宽度就是参数不确定性,待插补者(橙色刻度)的 \(\pi_i\) 每轮都落在不同曲线上。

中图是三种填法的后果:proper 的散布最宽,improper 窄一点(这份数据上方差小 12%,缺失者更多、协变量更多时差距会更大),确定性填补是一根竖线——不确定性被抹成 0,多重插补名存实亡

右图是 \(m\) 的作用:中位数和四分位数在 \(m\approx 50\) 之后就基本不动了,而 Min/Max 两条虚线一直在往外爬。要它们稳,就得把 \(m\) 提到上千;即便如此,稳的也只是”抖动小一些”,不是”到界了”。

4.6.2 四种读数对撞

单次试验的差异会被抽样噪声吃掉(\(n=150\) 时 PPA 的抽样标准差约 0.035,比要看的偏倚还大),所以重复 150 次试验、每次都和同一次试验的全数据估计配对相减,看平均偏倚:

mi_rep <- function(mech, R = 150, m = 20, cf = 0.5) {
  keys <- c("PPA", "p", "R1", paste0("c", cf))
  t(vapply(seq_len(R), function(i) {
    d <- make_trial(); mi <- mech(d)
    full <- bridged_rate(d$CDx, d$CTA, d$RESP, cf)[keys]                     # 基准:没有缺失
    cc   <- bridged_rate(d$CDx[!mi], d$CTA[!mi], d$RESP[!mi], cf)[keys]      # 丢弃缺失者
    mx   <- colMeans(draw_imp(d, mi, FALSE, m, cf))[keys]                    # 插补:只用协变量
    mc   <- colMeans(draw_imp(d, mi, TRUE,  m, cf))[keys]                    # 插补:+CTA 判读
    c(rate = mean(mi[d$CTA == 1]), full = full, cc = cc - full,
      mx = mx - full, mc = mc - full)
  }, numeric(1 + 4*4)))
}
set.seed(11)
rep_mar  <- mi_rep(miss_mar)
rep_mnar <- mi_rep(miss_mnar)

bias_tbl <- function(res, cf = "c0.5") {
  g <- function(k) sapply(c("PPA", "p", "R1", cf), function(q) mean(res[, paste0(k, ".", q)]))
  se <- function(k) sd(res[, paste0(k, ".", cf)])/sqrt(nrow(res))
  data.frame(
    `做法` = c("丢弃缺失者(complete case)", "插补:仅协变量", "插补:协变量 + CTA 判读"),
    `ΔPPA` = sprintf("%+.4f", c(g("cc")[1], g("mx")[1], g("mc")[1])),
    `Δp`   = sprintf("%+.4f", c(g("cc")[2], g("mx")[2], g("mc")[2])),
    `ΔR_{1}` = sprintf("%+.4f", c(g("cc")[3], g("mx")[3], g("mc")[3])),
    `Δ桥接率 (c=0.5)` = sprintf("%+.5f", c(g("cc")[4], g("mx")[4], g("mc")[4])),
    `MC 标准误` = sprintf("%.5f", c(se("cc"), se("mx"), se("mc"))),
    check.names = FALSE)
}
ft_show(bias_tbl(rep_mar), title = "MAR 机制下的配对偏倚(150 次重复试验,每次 m = 20)",
        footers = sprintf(paste("基准 = 同一次试验的全数据估计:PPA %.4f、桥接率 %.4f;",
                                "平均缺失率 %.1f%%(入组 CTA+ 中)。Δ = 该做法 − 全数据。"),
                          mean(rep_mar[, "full.PPA"]), mean(rep_mar[, "full.c0.5"]),
                          100*mean(rep_mar[, "rate"])))
MAR 机制下的配对偏倚(150 次重复试验,每次 m = 20)

做法

ΔPPA

Δp

ΔR1

Δ桥接率 (c=0.5)

MC 标准误

丢弃缺失者(complete case)

-0.0204

-0.0047

-0.0032

-0.00364

0.00149

插补:仅协变量

-0.0208

-0.0047

-0.0020

-0.00261

0.00056

插补:协变量 + CTA 判读

+0.0009

+0.0000

-0.0024

-0.00211

0.00048

基准 = 同一次试验的全数据估计:PPA 0.7523、桥接率 0.3030; 平均缺失率 16.6%(入组 CTA+ 中)。Δ = 该做法 − 全数据。

三行读下来:

  • 丢弃缺失者确实偏:PPA 偏低约 0.020。缺失依赖年龄和风险层,而这两样又与真 marker 相关,所以剩下的人不是原样本的缩小版。
  • 插补只用协变量,PPA 的偏倚几乎没修掉(-0.0208):待插补者全是 CTA+,而模型是在”CTA+ 与外部阴性材料混在一起”的样本上拟合的,插出的判读因此偏阴。这正是前面说的保守方向
  • 把 CTA 判读放进模型,偏倚回到噪声量级(+0.0009):这是 MAR 的正统做法。

最值得留意的是这四列之间的传递。 PPA 偏 0.020,经 Bayes 换成权重 \(p\) 只剩 0.005,再乘 \((1-c)R_1\) 进桥接率——PPA 这一路对最终偏倚的贡献很小。写成一行链式导数就清楚了:

\[\frac{\partial R_{CDx}}{\partial\,\text{PPA}}=\underbrace{(1-c)\,R_1}_{\text{结局那一路}}\times\underbrace{\frac{\partial p}{\partial\,\text{PPA}}}_{\text{Bayes 那一路}},\qquad \frac{\partial p}{\partial\,\text{PPA}}=\frac{prev\,(1-NPA)(1-prev)}{\bigl[\text{PPA}\cdot prev+(1-NPA)(1-prev)\bigr]^{2}}\]

b0 <- bridged_rate(d_one$CDx, d_one$CTA, d_one$RESP, 0.5)      # 这份数据的各量
dp_dppa <- PREV_CTA*(1 - b0[["NPA"]])*(1 - PREV_CTA) /
           (b0[["PPA"]]*PREV_CTA + (1 - b0[["NPA"]])*(1 - PREV_CTA))^2
slope <- (1 - 0.5)*b0[["R1"]] * dp_dppa
cat(sprintf("dp/dPPA = %.3f ;  (1-c)*R1 = %.3f ;  合起来 dR_CDx/dPPA = %.4f\n",
            dp_dppa, (1 - 0.5)*b0[["R1"]], slope))
## dp/dPPA = 0.203 ;  (1-c)*R1 = 0.140 ;  合起来 dR_CDx/dPPA = 0.0284
w_R1 <- b0[["p"]] + (1 - b0[["p"]])*0.5                        # R1 那一路的系数
cat(sprintf("→ PPA 偏 %.3f 只搬来 %.4f;R1 偏 %.4f 搬来 %.4f;合计 %.4f,实测 %.4f\n",
            abs(mean(rep_mar[, "cc.PPA"])), abs(mean(rep_mar[, "cc.PPA"]))*slope,
            abs(mean(rep_mar[, "cc.R1"])),  abs(mean(rep_mar[, "cc.R1"]))*w_R1,
            abs(mean(rep_mar[, "cc.PPA"]))*slope + abs(mean(rep_mar[, "cc.R1"]))*w_R1,
            abs(mean(rep_mar[, "cc.c0.5"]))))
## → PPA 偏 0.020 只搬来 0.0006;R1 偏 0.0032 搬来 0.0028;合计 0.0034,实测 0.0036

两级衰减都在这条式子里:\(p\) 是 PPA 的一个平缓函数(斜率 0.20),\((1-c)R_1\) 又把它再压一次(0.14),合起来把 PPA 的扰动缩到约 1/35。这条链天生对一致性的偏倚不敏感——所以这类敏感性分析常常得出”缺失带来的偏倚可忽略”,不是插补神奇,而是估计量的结构把这一路的扰动衰减掉了

另一路没有这层保护。 \(R_1\) 的系数是 \(p+(1-p)c\approx0.89\)——\(R_1\) 偏多少,桥接率几乎照搬。上面那行输出把实测的桥接率偏倚 0.0036 拆开了:PPA 那一路只占 0.0006,其余来自 \(R_1\)看一致性表判断”缺失影响大不大”会看错重点,真正要盯的是缺失有没有动 \(R_1\)(即有没有把系统性偏应答/偏不应答的人补进双阳格)。

4.6.3 汇总口径:五数概括 vs Rubin 规则

\(m\) 个估计怎么变成一个结论?两种口径,回答的问题不同:

set.seed(13)
m_r <- 200; B_r <- 200; n_one <- nrow(d_one)
th <- numeric(m_r); U <- numeric(m_r)
for (i in seq_len(m_r)) {
  cdx <- d_one$CDx; cdx[miss_one] <- impute_once(d_one, miss_one, TRUE, "proper")
  th[i] <- bridged_rate(cdx, d_one$CTA, d_one$RESP, 0.5)[["c0.5"]]     # 第 i 份的点估计
  U[i]  <- var(vapply(seq_len(B_r), function(b)                        # 第 i 份的组内方差
    bridged_rate(cdx, d_one$CTA, d_one$RESP, 0.5,
                 idx = sample.int(n_one, n_one, TRUE))[["c0.5"]], numeric(1)), na.rm = TRUE)
}
Wbar <- mean(U); Bv <- var(th); Tot <- Wbar + (1 + 1/m_r)*Bv           # Rubin 规则
ft_show(data.frame(
  `` = c("θ̄(m 个估计的均值)", "W̄(组内方差:抽样不确定性)",
           "B(组间方差:插补不确定性)", "T = W̄ + (1+1/m)·B(总方差)",
           "缺失信息比例 FMI = (1+1/m)B / T", "Rubin 95% 区间", "五数概括的 Min–Max 跨度"),
  `` = c(sprintf("%.4f", mean(th)), sprintf("%.3e", Wbar), sprintf("%.3e", Bv),
           sprintf("%.3e", Tot), sprintf("%.1f%%", 100*(1 + 1/m_r)*Bv/Tot),
           sprintf("[%.4f, %.4f](宽 %.4f)", mean(th) - 1.96*sqrt(Tot),
                   mean(th) + 1.96*sqrt(Tot), 2*1.96*sqrt(Tot)),
           sprintf("[%.4f, %.4f](宽 %.4f)", min(th), max(th), max(th) - min(th))),
  check.names = FALSE),
  title = "同一份数据的两种汇总:Rubin 规则 vs 五数概括",
  footers = sprintf("m = %d 次插补;每份插补数据集用 %d 次自助重抽算它自己的组内方差。", m_r, B_r))
同一份数据的两种汇总:Rubin 规则 vs 五数概括

θ̄(m 个估计的均值)

0.2450

W̄(组内方差:抽样不确定性)

1.336e-03

B(组间方差:插补不确定性)

3.462e-05

T = W̄ + (1+1/m)·B(总方差)

1.371e-03

缺失信息比例 FMI = (1+1/m)B / T

2.5%

Rubin 95% 区间

[0.1724, 0.3176](宽 0.1451)

五数概括的 Min–Max 跨度

[0.2301, 0.2573](宽 0.0272)

m = 200 次插补;每份插补数据集用 200 次自助重抽算它自己的组内方差。

两者答的不是同一个问题。 \(\bar W\) 是”这 150 个人本来就有的抽样不确定性”,\(B\) 是”插补法引入的额外不确定性”;这里 \(B\) 只占总方差的 2.5%。所以:

  • 五数概括的跨度(宽 0.0272)只反映 \(B\) 那一份。“跨插补的估计只在窄窄一段里晃”读作结论对缺失不敏感,是对的。
  • 但它不是置信区间——Rubin 区间宽 0.1451,是它的 5.3 倍。要报不确定性,走 Rubin(或”先自助再插补”);五数概括只当敏感性读。

4.6.4 当机制其实是 MNAR

把缺失改成还依赖判读本身(CDx− 更容易无效),同一套方法再跑一遍:

MNAR 机制下的配对偏倚(同样 150 次重复试验)

做法

ΔPPA

Δp

ΔR1

Δ桥接率 (c=0.5)

MC 标准误

丢弃缺失者(complete case)

+0.0542

+0.0113

+0.0014

+0.00319

0.00089

插补:仅协变量

+0.0343

+0.0074

-0.0056

-0.00380

0.00061

插补:协变量 + CTA 判读

+0.0593

+0.0123

-0.0088

-0.00585

0.00070

缺失概率额外依赖 CDx 判读本身。基准 = 全数据估计:PPA 0.7537、 桥接率 0.3065;平均缺失率 13.4%。

PPA 这次偏(缺的多是 CDx−,剩下的人阳性比例虚高),而且插补没有修掉它——正统做法(协变量 + CTA)的偏倚 +0.0593 和丢弃缺失者 +0.0542 一个量级。原因直白:插补模型是从观测到的那部分学关系的,而 MNAR 下这部分关系本身已经被筛过,学过来再套到缺失者身上,等于把偏倚照搬一遍。

桥接率那一列还出现了符号翻转(PPA 偏正、桥接率偏负):插补进入三处,\(R_1\) 那两处(分母、分子)把偏阴的、应答更少的人补进双阳格,压低了 \(R_1\),方向与 \(p\) 那一路相反。这正是前面说”净效应要算、不能凭一处推断”的实例。

要处理 MNAR,只能再加一个假设——例如给插补的 logit 加一个偏移量 \(\Delta\)、扫它的取值(\(\delta\)-adjustment / pattern-mixture)。那就又回到了第 3 步 \(c\) 的位置上:一个数据说不出话的旋钮。

4.6.5 这一步能给什么、不能给什么

# c=1 恒等式的自检:p 必须整个消掉,否则表格会与主分析的双阳应答率不一致。
chk <- bridged_rate(d_one$CDx, d_one$CTA, d_one$RESP, c(1, 0.5, 0))
stopifnot(isTRUE(all.equal(chk[["c1"]], chk[["R1"]])), chk[["c0"]] < chk[["c1"]])
cat(sprintf("✓ c=1 时桥接率 %.6f = 双阳应答率 R1 %.6f(p 消掉);c=0 时降到 %.6f\n",
            chk[["c1"]], chk[["R1"]], chk[["c0"]]))
## ✓ c=1 时桥接率 0.280000 = 双阳应答率 R1 0.280000(p 消掉);c=0 时降到 0.219754

能给:在 MAR 假设下,把”这些人若都有判读、结论会怎样”量出来,并给出结论对缺失的敏感度;顺带得到一个 \(c=1\) 的交叉校验。

不能给:MAR 本身的检验(数据分不开 MAR 与 MNAR);MNAR 下的无偏估计;以及——它替代不了把缺失率降下来(采样流程、复测机制)。第 3 步的 \(c\) 和第 4 步的 MAR 是同一类东西的两个位置:统计能做的是把假设摆到明处、把它的后果算清楚,不是把假设消掉。

5 Follow-on:换一种缺失结构(一致性搬运)

bridging 讲完,换 follow-on——同一套骨架、又一种缺失结构。区别:bridging 自己试验里有疗效数据、只是人群错位;follow-on 连疗效试验都不做,药和 CCD 都已上市,新检测 FCD 只想证明”我和已批准的 CCD 判人足够一致”,从而间接继承 CCD 那套疗效证据。所以 follow-on 的主战场从”补疗效缺失格”变成”量一致性”——疗效那层用的正是地基里已讲的稀释事实。用外部 concordance study 支持 follow-on CDx 临床验证的统计框架可参见 Li (2016)

5.1 follow-on 定位与一致性符号

问题:FCD 和 CCD 判人到底有多一致(follow-on 目标 1)? 要回答它,先得有一把量”两个检测一致不一致”的尺子,并把符号定清楚。

承接 §1.3 的 follow-on 定位:要证 目标 1——FCD 与已批准 CCD 分类足够一致;目标 2——借 CCD 的疗效证据说明 FCD+ 人群疗效可接受(用地基里的疗效混合)。路径分直接(用 FCD 重做试验,或重测原 pivotal 样本)与间接(CCD 已确立疗效 + 外部 FCD-vs-CCD 一致性研究)。本章顺序:先把符号定清楚 → 一致性估计 + NI 检验(目标 1)→ 无参考的 \(p_c\) 重加权 → 样本量 → 3-way 补充。

目标 2 里被”借”的量,正是地基里立过的 \(\theta\)——当时已和 \(\eta\) 一起用模拟验证过三路对齐,这里正式派上用场,没有新符号:\(\theta=PPV_c\,\delta_{\text{pos}}+(1{-}PPV_c)\,\delta_{\text{neg}}\)\(PPV_c=\Pr(G{=}1\mid CCD{=}1)\)——还是地基那两块积木,只是权重换成 CCD 的 PPV。\(\theta\) 是 CCD 已经确立、可以借用的疗效;follow-on 的逻辑就是:若 FCD 与 CCD 判人足够一致(目标 1),则 \(\eta\approx\theta\),于是 FCD+ 人群可继承 CCD 的疗效结论。(记号统一,地基也说过:\(\mu\)\(G\)\(\eta\)FCD\(\theta\)CCD 索引疗效,三者都是同两块积木的混合,只是分组不同。)

先把符号定清楚。comparator CDx (CCD) 测两次(CCD1、CCD2)+ follow-on CDx (FCD),可把”FCD-vs-CCD 一致性”和”CCD 自身重复一致性”做基准比较。

记号约定:每个检测的结果编码成 0/1——“= 1” 表示该检测把样本判为阳性(biomarker+),“= 0” 判为阴性。所以 \(CCD1{=}1\) 是”第一次 CCD 判这个样本阳性”,\(FCD{=}0\) 是”新检测判它阴性”。

PPA / NPA 是什么:两个检测对同一批样本的一致率PPA(阳性一致率)= 一个检测判阳的样本里、另一个也判阳的比例,写成 \(\Pr(\text{乙}{=}1\mid \text{甲}{=}1)\)NPA(阴性一致率)= 判阴里另一个也判阴的比例 \(\Pr(\text{乙}{=}0\mid \text{甲}{=}0)\)。数越大,两个检测越像。注意它们有方向性:交换条件检测与被比较检测,数值一般会变 (U.S. Food and Drug Administration 2007)

下标读法:下标两个字母 = “以谁为参照 + 跟谁比”,其中 \(C1\) = CCD1、\(C2\) = CCD2、\(F\) = FCD;第一个字母是参照(条件放在它上面),第二个是被比较的检测。于是:

  • \(C1F\):以 CCD1 为参照,看 FCD 与它多一致(\(\Pr(FCD\mid CCD1)\));
  • \(C2F\):以 CCD2 为参照,看 FCD;
  • \(C1C2\):以 CCD1 为参照,看 CCD2——这是 CCD 自己两次重复的一致,用作基准;
  • \(C2C1\):反过来,以 CCD2 为参照看 CCD1。

\[PPA_{C1F}=\Pr(FCD{=}1\mid CCD1{=}1),\quad PPA_{C1C2}=\Pr(CCD2{=}1\mid CCD1{=}1)\] \[NPA_{C1F}=\Pr(FCD{=}0\mid CCD1{=}0),\quad NPA_{C1C2}=\Pr(CCD2{=}0\mid CCD1{=}0)\] \[PPA_{C2F}=\Pr(FCD{=}1\mid CCD2{=}1),\quad PPA_{C2C1}=\Pr(CCD1{=}1\mid CCD2{=}1)\] \[NPA_{C2F}=\Pr(FCD{=}0\mid CCD2{=}0),\quad NPA_{C2C1}=\Pr(CCD1{=}0\mid CCD2{=}0)\]

逻辑:FCD 相对 CCD(\(C1F\)\(C2F\))的一致性,不应明显差于 CCD 自己两次重复(\(C1C2\)\(C2C1\))。这就是 NI(非劣效性)式一致性检验的立意——拿”CCD 的重复性”当及格线。

正式写成四个单侧非劣效性假设(PPA、NPA 各两个,分别以 CCD1、CCD2 为参照)。每个 \(\xi\) 是”CCD 自一致 − FCD-对-CCD 一致”的差距,\(\delta_1\)(PPA 侧)、\(\delta_0\)(NPA 侧)是可接受 margin:

\[H_0:\ \xi_{ppa1}=PPA_{C1C2}-PPA_{C1F}\ \ge\ \delta_1 \quad\text{vs}\quad H_a:\ \xi_{ppa1}<\delta_1\] \[H_0:\ \xi_{ppa2}=PPA_{C2C1}-PPA_{C2F}\ \ge\ \delta_1 \quad\text{vs}\quad H_a:\ \xi_{ppa2}<\delta_1\] \[H_0:\ \xi_{npa1}=NPA_{C1C2}-NPA_{C1F}\ \ge\ \delta_0 \quad\text{vs}\quad H_a:\ \xi_{npa1}<\delta_0\] \[H_0:\ \xi_{npa2}=NPA_{C2C1}-NPA_{C2F}\ \ge\ \delta_0 \quad\text{vs}\quad H_a:\ \xi_{npa2}<\delta_0\]

差距落到 margin 之下(拒绝 \(H_0\))= FCD 不比 CCD 自身重复性明显更差。

5.2 无参考标准:计数表、估计量与 \(p_c\) 重加权

问题:现实里没有金标准(真 \(G\) 看不到),而且入组还按 CCD1 富集过——这时候怎么估一致性才不被富集带偏? 尤其是 CCD2 一侧的量,直接从富集样本里算会有系统性偏差。

先摆计数记号,再给 CCD1-条件化估计量,最后是富集下要的 \(p_c\) 重加权。

计数表(符号记号):

CCD1 × CCD2 × FCD 计数表(符号)

CCD1+

CCD1−

CCD2+

CCD2−

Total

CCD2+

CCD2−

Total

FCD+

a₁

b₁

m₁

a₀

b₀

r₁

FCD−

c₁

d₁

m₀

c₀

d₀

r₀

Total

n₁₁

n₁₀

n₁

n₀₁

n₀₀

n₀

CCD1+ 分层内按 (CCD2, FCD) 记 \(a_1,b_1,c_1,d_1\)(行 = FCD、列 = CCD2),边缘计数:

\[m_1 = a_1 + b_1 \quad(\text{FCD+ 数})\] \[n_{11} = a_1 + c_1 \quad(\text{CCD2+ 数})\] \[n_1 = n_{11} + n_{10} \quad(\text{CCD1+ 总数})\]

CCD1− 分层内同理记 \(a_0,b_0,c_0,d_0\),边缘计数:

\[r_0 = c_0 + d_0 \quad(\text{FCD− 数})\] \[n_{00} = b_0 + d_0 \quad(\text{CCD2− 数}),\qquad n_0 = n_{01} + n_{00} \quad(\text{CCD1− 总数})\]

CCD1-条件化的估计量可直接从计数算,不受分层入组偏差影响,每个一行:

\[\hat{PPA}_{C1F}=m_1/n_1\quad(\text{CCD1+ 里 FCD 也判阳的比例})\] \[\hat{PPA}_{C1C2}=n_{11}/n_1\quad(\text{CCD1+ 里 CCD2 也判阳的比例})\] \[\hat{NPA}_{C1F}=r_0/n_0\quad(\text{CCD1− 里 FCD 也判阴的比例})\] \[\hat{NPA}_{C1C2}=n_{00}/n_0\quad(\text{CCD1− 里 CCD2 也判阴的比例})\]

富集入组(按 CCD1 抽)扭曲样本内 CCD1 分层比例。\(p_c=\Pr(CCD1{=}1)\)(真实人群 prevalence,非样本比例)重加权可还原 CCD2-条件化的四个量。用计数表的记号(\(a,b,c,d\) 见上),四式为:

\[\hat{PPA}_{C2F}=\frac{p_c\,a_1 n_0+(1{-}p_c)a_0 n_1}{p_c(a_1{+}c_1)n_0+(1{-}p_c)(a_0{+}c_0)n_1}\] \[\hat{PPA}_{C2C1}=\frac{p_c(a_1{+}c_1)n_0}{p_c(a_1{+}c_1)n_0+(1{-}p_c)(a_0{+}c_0)n_1}\] \[\hat{NPA}_{C2F}=\frac{(1{-}p_c)d_0 n_1+p_c\,d_1 n_0}{(1{-}p_c)(b_0{+}d_0)n_1+p_c(b_1{+}d_1)n_0}\] \[\hat{NPA}_{C2C1}=\frac{(1{-}p_c)(b_0{+}d_0)n_1}{(1{-}p_c)(b_0{+}d_0)n_1+p_c(b_1{+}d_1)n_0}\]

分子分母同除 \(n_1 n_0\) 即看出:这四式都是用真实 CCD1 prevalence \(p_c=\Pr(CCD1{=}1)\)(来自外部流行病学数据或假设,不是样本里的比例)去重新加权两个 CCD1 分层内的样本比例——把富集扭曲的分层权重换回真实权重。下面用 worked example 把这四式与已知真值对撞,验证它们成立:

pc <- assay_prev(TRUTH$assays$CCD)                              # p_c = Pr(CCD1=1)
tc_fcd  <- true_concordance(TRUTH$assays$CCD, TRUTH$assays$CDx) # FCD vs CCD 真值
tc_c2c1 <- true_concordance(TRUTH$assays$CCD, TRUTH$assays$CCD) # CCD 自重复真值
pool <- generate_population(3e5)
enr  <- rbind(pool[pool$CCD1==1,][1:2000,], pool[pool$CCD1==0,][1:2000,])   # 富集 1:1
e1 <- subset(enr, CCD1==1); e0 <- subset(enr, CCD1==0); n1 <- nrow(e1); n0 <- nrow(e0)
a1<-sum(e1$CDx==1&e1$CCD2==1); b1<-sum(e1$CDx==1&e1$CCD2==0); c1<-sum(e1$CDx==0&e1$CCD2==1); d1<-sum(e1$CDx==0&e1$CCD2==0)
a0<-sum(e0$CDx==1&e0$CCD2==1); b0<-sum(e0$CDx==1&e0$CCD2==0); c0<-sum(e0$CDx==0&e0$CCD2==1); d0<-sum(e0$CDx==0&e0$CCD2==0)
rw <- c(
  PPA_C2F  = (pc*a1*n0+(1-pc)*a0*n1)/(pc*(a1+c1)*n0+(1-pc)*(a0+c0)*n1),
  PPA_C2C1 = (pc*(a1+c1)*n0)/(pc*(a1+c1)*n0+(1-pc)*(a0+c0)*n1),
  NPA_C2F  = ((1-pc)*d0*n1+pc*d1*n0)/((1-pc)*(b0+d0)*n1+pc*(b1+d1)*n0),
  NPA_C2C1 = ((1-pc)*(b0+d0)*n1)/((1-pc)*(b0+d0)*n1+pc*(b1+d1)*n0))
naive <- c(
  PPA_C2F  = with(subset(enr, CCD2==1), mean(CDx==1)),
  PPA_C2C1 = with(subset(enr, CCD2==1), mean(CCD1==1)),
  NPA_C2F  = with(subset(enr, CCD2==0), mean(CDx==0)),
  NPA_C2C1 = with(subset(enr, CCD2==0), mean(CCD1==0)))
truth <- c(tc_fcd["PPA"], tc_c2c1["PPA"], tc_fcd["NPA"], tc_c2c1["NPA"])
ft_show(data.frame(
  `` = c("PPA_{C2F}", "PPA_{C2C1}", "NPA_{C2F}", "NPA_{C2C1}"),
  `真值` = round(unname(truth),4),
  `naive(富集偏差)` = round(unname(naive),4),
  `p_{c} 重加权` = round(unname(rw),4),
  check.names = FALSE),
  title = "p_{c} 重加权(4 个 CCD2 条件化量):naive 偏离、重加权贴回真值")
pc 重加权(4 个 CCD2 条件化量):naive 偏离、重加权贴回真值

真值

naive(富集偏差)

pc 重加权

PPAC2F

0.8309

0.8639

0.8050

PPAC2C1

0.8162

0.8953

0.8017

NPAC2F

0.9319

0.9048

0.9306

NPAC2C1

0.9131

0.8284

0.9108

四式都成立:naive(直接在富集样本里条件化)因富集偏离真值,\(p_c\) 重加权都贴回真值。CI(置信区间)用 Wilson 或 bootstrap;检验按接受准则取单/双侧。

5.3 worked:估计四量 + NI 检验

问题:FCD 到底够不够一致、够到能替代 CCD 吗? 工具齐了,用一份代表性样本把四个一致性量估出来、对撞真值,再跑 NI 检验(判”FCD-对-CCD”不比”CCD 自身重复”差):

conc <- generate_population(1200)                              # 代表性一致性研究
c1pos <- subset(conc, CCD1 == 1); c1neg <- subset(conc, CCD1 == 0)
est <- c(mean(c1pos$CDx==1), mean(c1pos$CCD2==1),
         mean(c1neg$CDx==0), mean(c1neg$CCD2==0))
truth_conc <- c(true_concordance(TRUTH$assays$CCD, TRUTH$assays$CDx)[["PPA"]],
                true_concordance(TRUTH$assays$CCD, TRUTH$assays$CCD)[["PPA"]],
                true_concordance(TRUTH$assays$CCD, TRUTH$assays$CDx)[["NPA"]],
                true_concordance(TRUTH$assays$CCD, TRUTH$assays$CCD)[["NPA"]])
ft_show(data.frame(
  `一致性量` = c("PPA_{C1F}", "PPA_{C1C2}", "NPA_{C1F}", "NPA_{C1C2}"),
  `估计` = round(est, 4), `真值` = round(truth_conc, 4),
  check.names = FALSE),
  title = "一致性估计 vs 真值(CCD1 条件化)")
一致性估计 vs 真值(CCD1 条件化)

一致性量

估计

真值

PPAC1F

0.8197

0.8309

PPAC1C2

0.8388

0.8162

NPAC1F

0.9329

0.9319

NPAC1C2

0.9209

0.9131

四个量的估计都逼近真值。按 §NI 假设框架跑检验,以 \(\xi_{ppa1}\) 为代表(其余三个同套路,bootstrap 上界 \(<\delta_1\) 则拒绝 \(H_0\)):

xi_boot <- boot_diff_paired(c1pos$CCD2==1, c1pos$CDx==1); delta1 <- 0.10
ft_show(round(c(xi_point = mean(c1pos$CCD2==1) - mean(c1pos$CDx==1),
  ci_upper_95 = unname(quantile(xi_boot, 0.95)), margin = delta1,
  reject_H0 = unname(quantile(xi_boot, 0.95) < delta1)), 4),
  title = "单侧 NI 检验(PPA)")
单侧 NI 检验(PPA)

xi_point

0.0191

ci_upper_95

0.0492

margin

0.1000

reject_H0

1.0000

估计逼近真值;FCD(最好的 assay)对 CCD 正确拒绝 \(H_0\)

5.4 样本量 / power

问题:这个一致性研究要招多少人,才有足够把握(power)断定 FCD 合格? 意向使用人群随机抽样可公式推导,但实际是非随机抽样(无闭式),用模拟:

par(family = "cjk", mar = c(0.5, 0.5, 3, 0.5))
plot(NA, xlim = c(0, 10), ylim = c(0, 7), axes = FALSE, xlab = "", ylab = "",
     main = "样本量模拟流程", cex.main = 1.5)
steps <- c("指定各分层样本量", "假设 PPA/NPA 与 phi", "模拟 CCD2、FCD 结果",
           "估 PPA/NPA + bootstrap CI", "单侧 NI 检验", "重复 N 次 → 估 power")
ys <- 6.4 - (seq_along(steps) - 1) * 1.12
for (i in seq_along(steps)) draw_box(5, ys[i], steps[i], "#2E86AB", w = 6.0, h = 0.72, cex = 1.05)
for (i in 1:(length(steps) - 1)) draw_arr(5, ys[i] - 0.36, 5, ys[i + 1] + 0.36)

power_at <- function(n_per, n_sim = 200, delta1 = 0.10, alpha = 0.05, B = 800) {
  rej <- logical(n_sim)
  for (s in seq_len(n_sim)) {
    d <- generate_population(2*n_per); cp <- subset(d, CCD1==1)
    if (nrow(cp) < 5) { rej[s] <- NA; next }
    rej[s] <- quantile(boot_diff_paired(cp$CCD2==1, cp$CDx==1, B = B), 1-alpha) < delta1
  }
  mean(rej, na.rm = TRUE)
}
n_grid <- c(100, 200, 400, 800, 1600); pw <- sapply(n_grid, power_at)
plot(n_grid, pw, type = "b", pch = 19, col = "#2E86AB", lwd = 2, ylim = c(0, 1),
     xlab = "每分层目标 n", ylab = "power", main = "PPA NI 检验 power vs 样本量")
abline(h = 0.8, lty = 3, col = "gray50")

power 随 \(n\) 单调上升,达 0.8 所需 \(n\) 一目了然。

5.5 补充:3-way 联合表与 phi

最后补一个 follow-on 一致性的技术点:给定 \(G\) 条件独立,但 CCD1+ 分层内 FCD 与 CCD2 仍有残差正相关(CCD1+ 不能完全确定 \(G\))。用 phi 量化,验证下面这个锚点 \(P_{a1}=\varphi_1\sqrt{PPA_{C1C2}PPA_{C1F}(1{-}PPA_{C1C2})(1{-}PPA_{C1F})}+PPA_{C1C2}PPA_{C1F}\)

3-way 联合表(FCD 一次 + CCD 两次),用一致性量和两个锚点 \(P_{a1},P_{d0}\) 参数化:

FCD × CCD1 × CCD2 联合概率表(Pa1、Pd0 为锚点)

CCD1+/CCD2+

CCD1+/CCD2-

CCD1-/CCD2+

CCD1-/CCD2-

FCD+

Pa1

PPAC1F − Pa1

1 − NPAC1C2 − NPAC1F + Pd0

NPAC1C2 − Pd0

FCD-

PPAC1C2 − Pa1

1 − PPAC1C2 − PPAC1F + Pa1

NPAC1F − Pd0

Pd0

合计

PPAC1C2

1 − PPAC1C2

1 − NPAC1C2

NPAC1C2

a1p <- mean(c1pos$CCD2==1 & c1pos$CDx==1); pC2 <- mean(c1pos$CCD2==1); pF <- mean(c1pos$CDx==1)
phi1 <- (a1p - pC2*pF) / sqrt(pC2*(1-pC2)*pF*(1-pF))
ft_show(c(`φ_{1}` = round(phi1, 4), `P_{a1} 经验值` = round(a1p, 4),
  `P_{a1} 公式值` = round(phi1*sqrt(pC2*pF*(1-pC2)*(1-pF)) + pC2*pF, 4)),
  title = "φ 与锚点 P_{a1}:经验值 = 公式值")
φ 与锚点 Pa1:经验值 = 公式值

φ1

0.5868

Pa1 经验值

0.7705

Pa1 公式值

0.7705

联合表公式自洽。phi>0 提醒:“给定 \(G\) 条件独立” ≠ “分层内无关”——任何 follow-on 的样本量模拟都得带上这个 phi(见前面「样本量 / power」)。


6 综述与核验状态

统一结论:只要给对参数(真 \(p\)、真 \(c\)、正确权重),bridging / follow-on 机制能把被抽样偏差扭曲的疗效和一致性还原回 DGP 的已知真相。风险全在那几个不可识别的参数——\(\delta_{01}\)(缺失亚组疗效)、\(c\)、真实 prevalence——它们是假设出来的,不是估出来的。

bridging 与 follow-on:数据和方法论一模一样吗? 不一样。一句话:底层生成机制一样,但观测到的数据不同,方法论共用一套骨架、估计对象却不同。 分维度对照:

bridging vs follow-on:同一类问题,不同缺失结构

维度

bridging

follow-on

数据来源

药的 pivotal 试验本身

单独的一致性研究 + 已批准 CCD 的历史疗效

有无自带疗效数据

有(试验里 CTA+ 人群的 T、Y)

无(FCD 上没有 T、Y)

观测变量

assay 结果 + 治疗臂 T + 结局 Y

只有 assay 结果(CCD1/CCD2/FCD)

缺失结构

CTA−/CDx+ 那格没入组 → δ01

富集抽样扭曲 CCD1 分层比例 + 无金标准

疗效从哪来

在自己试验里搬(CTA+ → CDx+)

从 CCD 借(外推到 FCD+ 人群)

核心公式

δCDx = p·δ11 + (1−p)·δ01

PPA/NPA 的 NI 检验 + pc 重加权

不可识别参数

δ01(用敏感性参数 c 假设)

真实 prevalence pc、分层内残差相关 φ

共用的骨架:都是「assay 间 concordance + PPV 混合 + 一个不可识别参数」,把疗效/一致性从一个 assay 定义的人群搬到另一个。真正的分水岭是数据里有没有随机化疗效——bridging 有,只需搬运;follow-on 没有,只能 CCD 的疗效并测自己与 CCD 的一致性。每加一层 assay 选择(多 CTA、pre-screening,见末章「进阶」),就多依赖一个猜测——这是反对多层 pre-screening 的根本原因。

提醒:本文 worked example 里两者共用同一个 DGP(同一批 \(G\)、assay、TRUTH),那是教学简化——好让”同一类问题”一眼可见、并能用同一套真值对撞验证;真实世界里两者的数据集并不相同(如上表)。

核验状态一览:各量三条路对齐 / 公式经真值验证成立

位置

结论

§ 混合疗效 η

✓ 混合公式 = 直接估计 = 真值

§ NPA/PPA

✓ NPA = d/(b+d) 直接从表读出

§ 贝叶斯 p

✓ Bayes 估 p = 直接估计

§ 联合表参数化

✓ 边际自洽 (φ>0)

§ pc 重加权

✓ pc 重加权还原真值 (pc = 真实 CCD1 prevalence)

§ standard bridging

✓ 真 c 处还原真 δCDx

§ 第 4 步 MAR 插补

✓ 正统 MAR 插补(含 CTA 判读)回到全数据估计;只用协变量偏保守;MNAR 下修不掉

§ c=1 恒等式

✓ c=1 时 p 代数消掉,桥接率 = 双阳应答率(stopifnot 自检)

§ double bridging

✓ 全程限定 CDx+ 后还原真值

§ multi-CTA 一致性权重

✓ 按阳性/阴性人数加权,汇总一致性还原真值

核验表最后两行(double bridging、multi-CTA)属末章「进阶」的两种额外情形。

6.1 补充:为什么用连续端点(效应尺度与可塌缩性)

全文疗效统一用连续端点 \(Y\) 的均值差 \(\delta\),是一个刻意的选择。原因:贯穿全文的两条搬运恒等式——疗效混合 \(\eta=PPV\,\delta_{\text{pos}}+(1{-}PPV)\,\delta_{\text{neg}}\)、bridging 的 \(\delta_{CDx}=p\,\delta_{11}+(1{-}p)\,\delta_{01}\)——都是全期望,只有当 \(\delta\) 是一个可塌缩 (collapsible) 的期望型量时才精确成立:均值差、某固定时点的生存概率差、或 RMST(restricted mean survival time,限制平均生存时间)差。

而现实肿瘤试验汇报疗效的通用货币是 Cox 模型给的 HR(hazard ratio)——HR 不可塌缩:两个亚组各自的 HR 按 \(p\) 线性加权 \(\neq\) 合并人群的 HR(风险比是比值不是期望,混合会系统性偏)。所以真实 bridging 里 \(\delta_{11}\) 常常就是一个 HR,但线性混合不能直接架在 HR 上——要么换到可塌缩尺度(均值差 / 生存概率差 / RMST),要么放弃精确、做数值近似。

一句话:本文选连续端点是为了让搬运恒等式精确成立、教学干净;换成 HR 时,“看似能线性搬运、其实要挑对尺度”本身就是 bridging 会踩的坑。(具体某篇 bridging 方法论文用哪种尺度,以该文为准;Cox 的内部估计 partial likelihood 与本文主题无关,略。)

调参入口:改「统一生成模型」里的 TRUTH(prevalence、各 assay sens/spec、marker+/− 真疗效),全文所有真值和估计会一致联动。


7 进阶:额外的选择偏差层

标准 bridging 只有一个缺失亚组 (CTA−/CDx+)。两种情形引入更多:Case I pre-screening(双重 bridging);Case II 多个 CTA。共同教训:每加一层 assay 选择,CDx 疗效估计离直接观测更远、不确定性更大。

7.1 Case I:Pre-Screening → Double Bridging

为加快入组/降成本,CTA 前加 biomarker 粗筛 (pre-screen)。多一个上游缺失:本该 CTA+ 却被粗筛排除的患者。

par(family = "cjk", mar = c(0.5, 0.5, 3, 0.5))
plot(NA, xlim = c(0, 13.5), ylim = c(0, 9), axes = FALSE, xlab = "", ylab = "",
     main = "Pre-screen → CTA → CDx:双重 bridging", cex.main = 1.45)
draw_box(1.2, 4.5, "Pre-screen", "gray30", w = 2.3, h = 0.8, cex = 0.95)
draw_box(4.1, 6.6, "Pre+", "#2E86AB", w = 1.9, h = 0.8, cex = 1.0)
draw_box(4.1, 2.2, "Pre-\n(未入组)", "gray60", w = 2.4, h = 0.9, cex = 0.9)
draw_arr(2.35, 4.6, 3.0, 6.2); draw_arr(2.35, 4.4, 3.0, 2.6)
draw_box(7.0, 7.7, "CTA+ (入组)", "#2E86AB", w = 2.5, h = 0.75, cex = 0.92)
draw_box(7.0, 5.4, "CTA-\n(未入组)", "#C73E1D", w = 2.5, h = 0.9, cex = 0.88)
draw_arr(5.3, 6.7, 5.85, 7.4); draw_arr(5.3, 6.5, 5.85, 5.7)
draw_box(7.0, 2.2, "CTA+", "#A23B72", w = 1.9, h = 0.75, cex = 0.92)
draw_arr(5.3, 2.2, 6.0, 2.2)
draw_box(10.7, 8.1, "CDx+ 观察 (delta_11)", "#2E86AB", w = 3.9, h = 0.7, cex = 0.9)
draw_box(10.7, 7.2, "CDx-", "gray55", w = 3.8, h = 0.6, cex = 0.85)
draw_arr(8.25, 7.85, 8.75, 8.1); draw_arr(8.25, 7.6, 8.75, 7.25)
draw_box(10.7, 5.4, "CDx+ 缺失 (stage-1)", "#C73E1D", w = 3.9, h = 0.7, cex = 0.9)
draw_arr(8.25, 5.4, 8.65, 5.4)
draw_box(10.7, 2.2, "CDx+ 缺失 (stage-2)", "#A23B72", w = 3.9, h = 0.7, cex = 0.9)
draw_arr(7.95, 2.2, 8.65, 2.2)
text(6.75, 0.5, "stage-1: Pre+&CTA+ → 完整 CTA+     stage-2: CTA+ → CDx+",
     cex = 0.95, col = "gray30", font = 3)

调整变两阶段:Stage 1 从观测的 Pre+/CTA+ 还原完整 CTA+ 队列;Stage 2 从 CTA+ 还原 CDx+ 人群。

\[\delta_{CTA+}=(p_1+(1{-}p_1)c_1)\,\delta'_{11},\qquad \delta_{CDx+}=(p+(1{-}p)c_2)\,\delta_{CTA+}\]

关键:两阶段所有子群都要先限定在 CDx+ 内,再按 Pre / CTA 细分。因为 estimand(\(\delta_{CDx+}\))始终活在 CDx+ 人群里\(\delta'_{11}\) 指的是 (Pre+ & CTA+ & CDx+) 的疗效,不是整个 (Pre+ & CTA+) 的疗效——若不限定 CDx+,会用整个 CTA+ 的疗效冒充 CTA+&CDx+ 的疗效、系统性低估。下面严格限定 CDx+:

big3 <- generate_population(4e6, with_endpoint = TRUE); cdxpos <- subset(big3, CDx == 1)
# stage 1: 在 CTA+&CDx+ 内按 Pre 还原
d11p <- delta_hat(subset(cdxpos, Pre==1 & CTA==1)); d01p <- delta_hat(subset(cdxpos, Pre==0 & CTA==1))
p1 <- mean(subset(cdxpos, CTA==1)$Pre == 1)
delta_CTApos_s1 <- p1*d11p + (1-p1)*d01p; true_d11c <- delta_hat(subset(cdxpos, CTA==1))
# stage 2: 在 CDx+ 内按 CTA 还原
p_true <- true_p_cta_given_cdx(); d01_s2 <- delta_hat(subset(cdxpos, CTA==0))
delta_CDx_s2 <- p_true*delta_CTApos_s1 + (1-p_true)*d01_s2
ft_show(data.frame(`p_{1}` = round(p1,4), `c_{1}` = round(d01p/d11p,4),
  `阶段1 还原` = round(delta_CTApos_s1,4), `阶段1 真值` = round(true_d11c,4),
  `阶段2 还原` = round(delta_CDx_s2,4), `δ_{CDx} 真值` = round(true_delta_in_assay_pos(TRUTH$assays$CDx),4),
  check.names = FALSE),
  title = "Double bridging:两阶段依次还原真值")
Double bridging:两阶段依次还原真值

p1

c1

阶段1 还原

阶段1 真值

阶段2 还原

δCDx 真值

0.7942

0.9592

0.5928

0.5928

0.5494

0.5508

两阶段依次还原真值。每阶段一个缺口、一个敏感性参数——两阶段的双重收缩。变体:部分 pre-screening(只部分人群走粗筛,只有受影响子集需 double-bridging)、患者层面 pre-screening 信息(保留则能显式识别 Pre+/Pre− 分支,提高第一阶段透明度)。实践结论直白:尽量不用 pre-screening——它让最终 CDx 疗效估计更低更不精确。

7.2 Case II:多个 CTA

保险/site 偏好/国别监管导致多个 CTA,各自与 CDx 一致性不同、偏差程度不同。假设:每人只按一个 CTA 入组;各 CTA 患者选择表现类似。

par(family = "cjk", mar = c(0.5, 0.5, 3, 0.5))
plot(NA, xlim = c(0, 13), ylim = c(0, 8), axes = FALSE, xlab = "", ylab = "",
     main = "两个 CTA:并行 bridging", cex.main = 1.5)
draw_box(1.2, 4, "样本", "gray30", w = 1.9, h = 0.8, cex = 1.0)
draw_box(4.0, 6, "CTA1", "#2E86AB", w = 1.9, h = 0.78, cex = 1.0)
draw_box(4.0, 2, "CTA2", "#3AA0C0", w = 1.9, h = 0.78, cex = 1.0)
draw_arr(2.2, 4.2, 2.95, 5.7); draw_arr(2.2, 3.8, 2.95, 2.3)
draw_box(7.0, 7, "CTA1+", "#2E86AB", w = 1.9, h = 0.7, cex = 0.95); draw_box(7.0, 5, "CTA1-", "#C73E1D", w = 1.9, h = 0.7, cex = 0.95)
draw_arr(5.0, 6.1, 5.95, 6.9); draw_arr(5.0, 5.9, 5.95, 5.1)
draw_box(10.6, 7, "CDx+ 观察", "#2E86AB", w = 2.8, h = 0.66, cex = 0.9)
draw_box(10.6, 5, "CDx+ 缺失 → bridge", "#C73E1D", w = 3.3, h = 0.66, cex = 0.82)
draw_arr(8.0, 7, 9.0, 7); draw_arr(8.0, 5, 8.9, 5)
draw_box(7.0, 3, "CTA2+", "#3AA0C0", w = 1.9, h = 0.7, cex = 0.95); draw_box(7.0, 1, "CTA2-", "#C73E1D", w = 1.9, h = 0.7, cex = 0.95)
draw_arr(5.0, 2.1, 5.95, 2.9); draw_arr(5.0, 1.9, 5.95, 1.1)
draw_box(10.6, 3, "CDx+ 观察", "#3AA0C0", w = 2.8, h = 0.66, cex = 0.9)
draw_box(10.6, 1, "CDx+ 缺失 → bridge", "#C73E1D", w = 3.3, h = 0.66, cex = 0.82)
draw_arr(8.0, 3, 9.0, 3); draw_arr(8.0, 1, 8.9, 1)

方法 A(加权一致性):先汇总 PPA/NPA/prevalence → 汇总 \(p\) → 一次 bridging。 方法 B(加权疗效):各 CTA 内分别 bridging → 按样本量合并。

truth2 <- TRUTH
truth2$assays$CTA1 <- c(sens = 0.85, spec = 0.90)     # 阳性率约 0.33
truth2$assays$CTA2 <- c(sens = 0.90, spec = 0.50)     # 特异度差, 阳性率约 0.62 (强对比)
gen_cta <- function(n, oc) { d <- generate_population(n, truth = truth2, with_endpoint = TRUE)
  d$CTAx <- ifelse(d$G==1, rbinom(n,1,oc["sens"]), rbinom(n,1,1-oc["spec"])); d }
n1c <- 40000; n2c <- 40000; d1 <- gen_cta(n1c, truth2$assays$CTA1); d2 <- gen_cta(n2c, truth2$assays$CTA2)
bridge_one <- function(d, oc) {
  prev <- truth2$prev_G; ocd <- truth2$assays$CDx
  num <- sum(sapply(0:1, function(g){pc<-if(g==1)oc["sens"] else 1-oc["spec"]; pd<-if(g==1)ocd["sens"] else 1-ocd["spec"]; pc*pd*(if(g==1)prev else 1-prev)}))
  den <- sum(sapply(0:1, function(g){pd<-if(g==1)ocd["sens"] else 1-ocd["spec"]; pd*(if(g==1)prev else 1-prev)}))
  p <- num/den
  p*delta_hat(subset(d, CTAx==1 & CDx==1)) + (1-p)*delta_hat(subset(d, CTAx==0 & CDx==1))
}
dB1 <- bridge_one(d1, truth2$assays$CTA1); dB2 <- bridge_one(d2, truth2$assays$CTA2)
ft_show(data.frame(`方法 B(加权疗效)` = round(dB1*n1c/(n1c+n2c) + dB2*n2c/(n1c+n2c), 4),
           `δ_{CDx} 真值` = round(true_delta_in_assay_pos(truth2$assays$CDx, truth2), 4),
           check.names = FALSE),
        title = "多 CTA 方法 B(加权疗效)还原真值")
多 CTA 方法 B(加权疗效)还原真值

方法 B(加权疗效)

δCDx 真值

0.5557

0.5508

汇总一致性的加权(一个易错点):跨 CTA 汇总 PPA/NPA 时,PPA 要按各队列的 CTA 阳性人数加权、NPA 按阴性人数加权,不能按总样本量 \(n_i/n\)——因为 PPA 是”阳性里判阳的比例”,权重自然是阳性人数。下面对比两种权重:

ppa1 <- true_concordance(truth2$assays$CTA1, truth2$assays$CDx)["PPA"]
ppa2 <- true_concordance(truth2$assays$CTA2, truth2$assays$CDx)["PPA"]
pos1 <- sum(d1$CTAx==1); pos2 <- sum(d2$CTAx==1)
ft_show(data.frame(`按阳性人数(对)` = round(unname((pos1*ppa1+pos2*ppa2)/(pos1+pos2)), 4),
           `按总样本量(错)` = round(unname(ppa1*n1c/(n1c+n2c) + ppa2*n2c/(n1c+n2c)), 4),
           check.names = FALSE),
        title = "汇总一致性:按阳性人数 vs 按总样本量")
汇总一致性:按阳性人数 vs 按总样本量

按阳性人数(对)

按总样本量(错)

0.5454

0.5951

方法 B 还原真值;汇总一致性按阳性人数加权得到正确值,按总样本量加权则明显偏离。(注意区分:方法 B 最后按 \(n_i/n\) 合并疗效是另一回事——那是”各 CTA 使用份额”的建模选择、需业务判断,不是这里的加权问题。)


8 记号表(希腊字母与关键符号)

查阅用。\(\delta_{\text{pos}}/\delta_{\text{neg}}\) 是本文为”两块积木”起的简写(等价于两个 \(\mu\) 相减)。

记号表(希腊字母与关键符号)

符号

定义

备注

T

治疗臂 =1 / 对照臂 =0

G

真实 marker 状态:1 阳 / 0 阴

需参考标准才可观测

μTG

结局均值,按 (治疗臂 T, 真 marker G) 索引的四格

最底层原料

δpos = μ11 − μ01

真 marker+ 人群的治疗效应(积木一)

本文简写

δneg = μ10 − μ00

真 marker− 人群的治疗效应(积木二)

本文简写

η

FCD+ 人群疗效 = PPVf·δpos + (1−PPVf)·δneg

按 FCD 索引

θ

CCD+ 人群疗效 = PPVc·δpos + (1−PPVc)·δneg

按 CCD 索引;follow-on 借用的量

δCDx

CDx+ 人群疗效(bridging 估计目标)

δ11, δ01

(CTA+ & CDx+) / (CTA− & CDx+) 人群疗效

观察到 / 缺失

δ'11, δ'01, δCTA+

double bridging 第一阶段:(Pre± & CTA+ & CDx+) 疗效、还原出的 CTA+ 队列疗效

进阶

c, c1, c2

敏感性参数:δ01 = c·δ11(及两阶段版本),c ∈ (0,1)

缺失格不可识别

p

Pr(CTA+ | CDx+)——bridging 搬运权重

p1

Pr(Pre+ | CTA+)——double bridging 第一阶段权重

进阶

pc

Pr(CCD1=1)——真实人群 CCD1 prevalence(重加权用)

非样本比例

prev, π

Pr(CTA+)——意向使用人群的 marker prevalence

Bayes 估 p 用

PPVf, NPVf

FCD 的阳性/阴性预测值 Pr(G=1|FCD=1)、Pr(G=0|FCD=0)

PPVc, NPVc

CCD 的阳性/阴性预测值(同构定义,assay 换成 CCD)

PPA, NPA, OPA

阳性/阴性/总体一致率(OPA=(a+d)/n 判读完全一致的比例,本文只列不用);下标两字母 = 参照 + 被比(C1=CCD1、C2=CCD2、F=FCD)

ξppa1, …

NI 检验的差距,如 ξppa1 = PPAC1C2 − PPAC1F

δ1, δ0

NI 检验 PPA 侧 / NPA 侧的可接受 margin

与疗效 δ 无关,是检验边界

φ1, φ2

φ 系数:CCD1+ / CCD1− 分层内 FCD 与 CCD2 的相关

Pa1, Pd0

3-way 联合表的两个概率锚点

R1, R2, RCDx

率版本的疗效:观察格 (CTA+ & CDx+) 应答率、缺失格应答率 (=c·R1)、桥接后 CDx+ 人群应答率

与 δ 版本同构,公式不变

R(缺失指示)

缺失指示:R=1 表示该个体的 CDx 判读缺失(第 4 步用)

测量层缺失

X, Z

X = 观测协变量(插补模型的自变量);Z = 待插补的 CDx 判读

MAR 定义里的两个角色

m, β*

m = 插补次数;β* = 从 Cov(β̂) 抽出的一套系数(插补的第二次抽样)

插补内部

W̄, B, T, FMI

Rubin 规则:组内方差均值、组间方差、总方差 T = W̄+(1+1/m)B、缺失信息比例

汇总口径:五数概括只含 B 那一份

(完整框架里还有按 FCD 索引的分量 \(\eta_{11},\eta_{01},\dots\) 和按 CCD 索引的 \(\theta_{11},\theta_{01},\dots\)——本文只用它们的差 \(\eta=\eta_{11}-\eta_{01}\)\(\theta=\theta_{11}-\theta_{01}\),分量本身不必单独记。)


9 统计缩写速查

统计缩写速查

缩写

英文全称

中文含义

用在本文哪里

PPA / NPA / OPA

positive / negative / overall percent agreement

阳性/阴性/总体一致率

assay 间一致性评价(OPA 仅列出、不用)

PPV / NPV

positive / negative predictive value

阳性/阴性预测值

assay+ 人群疗效混合

NI

non-inferiority

非劣效性

FCD 相对 CCD 的一致性检验

CI

confidence interval

置信区间

一致性估计的不确定性

RCT

randomized controlled trial

随机对照试验

试验设计语境

MI

multiple imputation

多重插补

第 4 步:缺失 CDx 判读的插补

MCAR / MAR / MNAR

missing completely at random / at random / not at random

完全随机缺失 / 随机缺失 / 非随机缺失

第 4 步:缺失机制三档

FMI

fraction of missing information

缺失信息比例

Rubin 规则里插补方差占总方差的比例


10 参考文献

Li, Meijuan. 2016. “Statistical Methods for Clinical Validation of Follow-on Companion Diagnostic Devices via an External Concordance Study.” Statistics in Biopharmaceutical Research 8 (3): 355–63. https://doi.org/10.1080/19466315.2016.1202859.
Little, Roderick J. A., and Donald B. Rubin. 2019. Statistical Analysis with Missing Data. 3rd ed. John Wiley & Sons. https://doi.org/10.1002/9781119482260.
National Research Council. 2010. The Prevention and Treatment of Missing Data in Clinical Trials. The National Academies Press. https://doi.org/10.17226/12955.
Rubin, Donald B. 1987. Multiple Imputation for Nonresponse in Surveys. John Wiley & Sons. https://doi.org/10.1002/9780470316696.
Song, Changhong, Xiaoqin Xiong, Sunghee Kim, Zhiheng Xu, Dandan Xu, and Bipasa Biswas. 2024. “Statistical Considerations for Some Issues in Clinical Bridging Studies Evaluating Companion Diagnostic Devices.” Journal of Biopharmaceutical Statistics 34 (3): 441–52. https://doi.org/10.1080/10543406.2023.2220398.
U.S. Food and Drug Administration. 2007. Statistical Guidance on Reporting Results from Studies Evaluating Diagnostic Tests: Guidance for Industry and FDA Staff. U.S. Department of Health; Human Services, Food; Drug Administration. https://www.fda.gov/media/71147/download.
U.S. Food and Drug Administration. 2014. In Vitro Companion Diagnostic Devices: Guidance for Industry and Food and Drug Administration Staff. U.S. Department of Health; Human Services, Food; Drug Administration. https://www.fda.gov/media/81309/download.
U.S. Food and Drug Administration. 2016. Principles for Codevelopment of an in Vitro Companion Diagnostic Device with a Therapeutic Product: Draft Guidance for Industry and Food and Drug Administration Staff. U.S. Department of Health; Human Services, Food; Drug Administration. https://www.fda.gov/media/99030/download.

作者:胡杨博士 | 首次发布:2026-08-25 | 最后更新:2026-08-26 16:40 EDT