B 你用 N 赌一个 p 值 / Sample Size Estimation

你用 N 赌一个 p 值 / Sample Size Estimation

Dec 16, 2024

一个临床医生和一个统计师在走廊里相遇。

“这个试验需要多少人?” 医生问。

“那要看你想证明什么。” 统计师说。

“证明药有效啊。”

“多少算有效?”

医生想了三秒,给了一个数字。统计师在脑子里跑了一遍公式,又给了一个数字。

这场对话每天都在发生。但它底下藏着的逻辑,远比一句"样本量不够"要复杂。样本量估计(sample size estimation)不是在报表里填一个数字,而是把整个试验设计写成一个等式——然后把你的预算、你的野心、你愿意承担的犯错概率,全部扔进这个等式的一边,看另一边跳出多少 N。

你手里的钱(N),去赌一个可以承受的错误概率(α 和 β)。问题是,这场交易里有太多人想偷你的 N:脱落率(dropout rate)、多重比较(multiplicity)——你每防住一个,就得加点钱。

四种关系,四种方程

在开始之前,先搞清楚我们到底想证明什么。

临床上常见的比较设计有四种,英文标准名称写在这里,因为中文翻译常常混用:

  • Superiority trial(优效性试验):证明 A 比 B 好。
  • Non-inferiority trial(非劣效性试验):证明 A 不比 B 差太多,差在可接受范围内。
  • Equivalence trial(等效性试验):证明 A 和 B 在某个范围内等效。
  • Bioequivalence study(生物等效性研究):证明仿制药和原研药在药代动力学参数上等价。

四种关系的数学表达各不相同,公式也因此不一样。

先讲最常见的 superiority trial。另外三种的逻辑差异主要在界值(margin)的设定上,原理相通,本文不展开。

两类错误:你可以犯错,但不能白犯错

这是整个样本量估计的理论地基。如果这块没理解,后面的公式就是一堆符号。

Type I error (α)

也叫 significance level(显著性水平)。

定义:$H_0$ 为真时,你错误地拒绝了 $H_0$。

翻译成人话:药其实没用,但你的数据"看起来"像是有用。你被数据骗了。

典型值是 0.05(双侧,two-sided)。也就是说,你愿意接受在 20 次试验里被数据骗 1 次的概率。

(我自己学到这里的时候,总觉得 0.05 是个任意的数字——为什么不是 0.04 或 0.06?后来才明白,它确实是任意的。Fisher 当年说"it is convenient to take this point as a limit",翻译过来就是"我觉得这个数字挺方便"。方便而已。)

Type II error (β)

定义:$H_1$ 为真时,你错误地没有拒绝 $H_0$。

翻译成人话:药真的有用,但你的数据没检测出来。你把一颗好药扔了。

典型值是 0.2。这个数字更像是一种行业的默契——愿意接受 20% 的概率遗漏真实效应。

Power (1 − β)

也叫 statistical power(统计效能)。

定义:当真实效应存在时,试验检测到它的概率。

Power = 0.80 意味着如果药真的有用,你有 80% 的把握用数据证明它有用。剩下 20% 是你主动选择的"错过了"。

直觉上,α 是你"宁可错杀"的容忍度——你怕把没用的药当成有用。β 是你"宁可放过"的容忍度——你怕把有用的药当成没用。这两个恐惧是对抗性的:想把 α 压得更低,N 就得涨;想把 power 推得更高,N 也得涨。

你在同时对抗两种恐惧。而恐惧的货币单位,是 N。

用两个正态分布来直观理解:$H_0$ 下的分布(假设效应为 0)和 $H_1$ 下的分布(假设效应为 δ)。α 是你在 $H_0$ 分布尾巴上划掉的那一块面积;β 是你在 $H_1$ 分布上被 $H_0$ 的拒绝线"吞掉"的那一块面积。两块面积都要小,唯一的办法是把两个分布拉远——要么增大 δ,要么增大 N。δ 是天生的(效应量不由你定),所以 N 成了你唯一能拧的旋钮。

影响 N 的那些参数

现在把参数一个一个拉出来。

Effect size(效应量)

记为 δ。英文标准叫法是 clinically meaningful difference,或者 minimum clinically important difference (MCID)。

这不是"真实效应有多大"——因为真实效应你不知道。这是"你希望在样本中检测到的临床上最小的有意义差异"。

举个例子:降压药的 MCID 可能是收缩压下降 5 mmHg。如果你设 δ = 2 mmHg,N 会大到让你破产;如果你设 δ = 20 mmHg,N 会小到连你自己都不信。所以 δ 的设定本质上是一个临床判断,不是统计判断。

Standard deviation (σ)

结局指标的变异程度。通常来自文献或 pilot study(预试验)。

σ 越大,N 越大。这个直观:数据越嘈杂,你需要越多的耳朵才能听见信号。

Allocation ratio(分配比)

试验组与对照组的样本比例,最常见 1:1。在某些情况下可以调整——比如试验组药物很贵,你希望在保证 power 的前提下减少试验组人数——但代价是总 N 会变大。

Hypothesis type

双侧检验(two-sided test)还是单侧检验(one-sided test)。双侧更保守,也是监管机构的默认选择。除非你有非常充分的理由只用单侧。

连续结局的公式推导

对于两独立样本 t 检验(two independent samples t-test),连续结局(continuous outcome),每组所需样本量为:

$$N_{per\_arm} = \frac{2(z_{1-\alpha/2} + z_{1-\beta})^2 \sigma^2}{\delta^2}$$

其中:

  • $z_{1-\alpha/2}$:双侧显著性水平对应的标准正态分位数(α = 0.05 时,$z_{0.975} = 1.96$)
  • $z_{1-\beta}$:power 对应的标准正态分位数(β = 0.2 时,$z_{0.8} = 0.84$)
  • $\sigma^2$:结局的方差(variance)
  • $\delta$:效应量(effect size),即两组均值之差的期望值

这个公式的结构很清楚:分子是 $(z_{1-\alpha/2} + z_{1-\beta})^2 \sigma^2$,你越想"稳"(z 越大),或者数据越"吵"(σ² 越大),分子就越大。分母是 $\delta^2$,效应越小,分母越小,N 就暴增——这是一个平方反比关系。

如果 wants 把 δ 从 5 压到 2.5,N 不是翻倍,是翻四倍。

二分类结局的公式

对于两独立样本比例检验(two independent samples proportions test),二分类结局(binary outcome):

$$N_{per\_arm} = \frac{(z_{1-\alpha/2} + z_{1-\beta})^2 \cdot [p_1(1-p_1) + p_0(1-p_0)]}{(p_1 - p_0)^2}$$

其中:

  • $p_0$:对照组的预期事件比例(expected event rate in the control group)
  • $p_1$:试验组的预期事件比例(expected event rate in the treatment group)
  • $p_1 - p_0$:两组率差(rate difference),即 δ

二分类结局有一个隐藏陷阱:方差 $p(1-p)$ 在 p = 0.5 时最大。如果你不知道事件率大概是多少,保守策略是按 0.5 算——但这样 N 会大得离谱。所以 pilot study 对于估计 p 至关重要。

我第一次手算二分类样本量的时候,把 p₀ 随便写了个 0.1,p₁ 写了个 0.05,出来的 N 看起来还挺温柔。后来老板看了一眼说"你这 p₀ 哪来的?"。我说"估计的"。他说"依据?"。我没说话。

pilot 数据或者系统综述(systematic review)是 p₀ 的唯一合法来源。拍脑袋的 p₀ 等于拍脑袋的 N。

谁偷走了你的 N:脱落率

你算好了 N = 200。开始招募。三个月后,跑了 30 个。

这就是 dropout rate(脱落率)。

更精确一点:dropout 泛指任何未完成试验的受试者,包含两种不同的退出机制——withdrawal(主动退出)和 loss to follow-up(失访,即联系不上了)。前者的数据可能还收得到一部分,后者直接蒸发。统计师对它们的态度略有不同,但在 N 的调整公式里,都算进同一个 d。

调整公式简单到让人不安:

$$N_{adjusted} = \frac{N_{planned}}{1 - d}$$

其中 d 为预期脱落率。

$N_{planned} = 200$,预期 d = 20%,则 $N_{adjusted} = 200 / 0.8 = 250$。多招 50 个人。

常见脱落率参考(不同疾病领域差异很大,不要照搬):

  • 短期急性病试验:5-10%
  • 长期慢性病试验(6 个月以上):15-25%
  • 肿瘤试验:可能更高,取决于终点和随访时长

脱落率的设定有一个容易被忽视的心理陷阱:统计师倾向于往高了估,给自己留安全垫;申办方(sponsor)倾向于往低了估,因为每多一个受试者都是钱。最终的数字通常是两股力量在会议上较量出来的。

统计师会说"按经验这类试验脱落率 20%"。申办方说"我们的 site 管理能力强,15% 就够了"。最后大家握手说 18%。

这个数字不是一个客观估计,是一份经过谈判的契约。药监局的审评员看到的时候,也会在心里默默加上自己的 discount。

说到底,脱落率不是"安全垫"——是你对自己执行力的诚实估计。说 10% 的脱落率,就等于说"我相信我的 site 能把 90% 的人留到最后"。如果你根本没这个信心,就不要往试验方案里写这个数字。

另一个偷 N 的人:多重比较

Multiplicity(多重性)是把安静的统计变成烧钱的战争的元凶之一。

核心直觉很简单:如果你做 20 次独立的假设检验,即使全是噪声,你期望有一次 p < 0.05。这就是 family-wise error rate (FWER)(族错误率)膨胀。

不控制多重性的后果:你设计了一个三期试验,收了主要终点(primary endpoint)和五个次要终点(secondary endpoints)。如果每个都用 α = 0.05,总的一类错误概率会远高于 0.05。等于你本来想用 5% 的门槛,结果自己偷偷把门槛加宽了。

所以需要修正。

Bonferroni correction

$$\alpha_{adjusted} = \frac{\alpha}{k}$$

最保守的方法。如果有 k 个检验,把 α 除以 k。k = 5 时,每个检验用 α = 0.01。

保守的好处是稳妥——谁都挑不出毛病。坏处是 power 掉得很快。

Bonferroni-Holm procedure

逐步法。把 k 个检验的 p 值从小到大排序,第一个用 α/k,第二个用 α/(k−1),以此类推。比 Bonferroni 稍不保守,但控制 FWER 的逻辑一样。

Hochberg procedure

另一个逐步法,方向和 Holm 相反——从最大的 p 值开始比较,如果不显著就往下走。在某些场景下比 Holm 更有 power。

Šidák correction

$$\alpha_{adjusted} = 1 - (1 - \alpha)^{1/k}$$

数学上比 Bonferroni 稍精确一些,但在 k 不大的时候两者数值非常接近。

多重比较如何影响样本量?α 变小 → $z_{1-\alpha/2}$ 变大 → N 变大。

举个例子:α = 0.05 时 $z_{0.975} = 1.96$。做了 Bonferroni 调整(k = 3),α_adjusted = 0.0167,$z_{0.9917} \approx 2.39$。仅这一项,N 增加约 $(2.39/1.96)^2 \approx 1.49$ 倍。

你在和 invisible enemies 打仗。每多一个 endpoint,就多一个需要被惩罚的 α。每多一层惩罚,N 就膨胀一圈。这时候你回头看当初决定"多放几个次要终点应该没事"的自己——

像在看一个不认识的人。

附录

讲完理论,交点代码。以下是 R 和 Python 的实现。

R

# 连续结局,两独立样本 t 检验
library(pwr)

pwr.t.test(
  d = 5 / 10,         # effect size (Cohen's d) = delta / sigma
  sig.level = 0.05,
  power = 0.80,
  type = "two.sample",
  alternative = "two.sided"
)

# 二分类结局
pwr.2p.test(
  h = ES.h(p1 = 0.30, p2 = 0.20),  # Cohen's h
  sig.level = 0.05,
  power = 0.80,
  alternative = "two.sided"
)

Python

from statsmodels.stats.power import TTestIndPower, NormalIndPower

# 连续结局
power_analysis = TTestIndPower()
n_per_arm = power_analysis.solve_power(
    effect_size=5/10,
    alpha=0.05,
    power=0.80,
    alternative='two-sided'
)
print(f"每组需要: {n_per_arm:.0f}")

# 调整脱落率
d = 0.2
n_adjusted = n_per_arm / (1 - d)
print(f"调整脱落率后每组: {n_adjusted:.0f}")

# Bonferroni 调整(假设 3 个 co-primary endpoints)
k = 3
alpha_adj = 0.05 / k
n_bonf = power_analysis.solve_power(
    effect_size=5/10,
    alpha=alpha_adj,
    power=0.80,
    alternative='two-sided'
)
print(f"Bonferroni调整后每组: {n_bonf:.0f}")

算出那个数字之后,把结果写在方案里。

IRB 会审核。监管机构会把它当成你试验的硬件参数之一。

但你心里清楚——这个数字,是你用一堆假设换来的:δ 不会比预期小,σ 不会比预期大,脱落不会比预期多,α 和 β 是你自己选的值。每一个假设都站在 N 的背上,像行李。

样本量估计不是猜一个数字,是把你对试验的所有信念——乐观的、保守的、妥协的——压缩成一个 N。

这个 N 的背后,不是"样本量够不够"的问题,而是"你敢不敢用这个数字去打一场你输得起的仗"。

TouchingFish.top