4. Bootstrap基本算法与重采样机制

非参数Bootstrap的核心思想可以用一句话概括:把样本当作总体,从样本中有放回地反复抽样,用这些“重采样样本”的统计量分布去近似原统计量的抽样分布。这句话里有两个关键点:一是“把样本当作总体”,这对应经验分布;二是“有放回地抽样”,这决定了重采样样本与原始样本的关系。理解这两点,才能正确实现Bootstrap。

经验分布:Bootstrap的“总体替身”

设原始样本为 x = (x₁, x₂, …, xₙ),来自某个未知分布 F。我们想估计某个统计量 θ = T(F) 的抽样分布,但 F 未知,无法直接抽样。Bootstrap的做法是构造经验分布 F̂ₙ:它在每个观测值 xᵢ 上放置 1/n 的概率质量。也就是说,F̂ₙ 是一个离散分布,取值就是样本中的 n 个点,每个点的概率相等。

从 F̂ₙ 中抽取一个样本,等价于从原始 n 个观测值中独立地、有放回地抽取 n 次。每次抽取时,每个原始观测值被选中的概率都是 1/n。由于是有放回,同一个观测值可能被抽中多次,也可能一次都没被抽中。一个重采样样本记为 x* = (x*₁, x*₂, …, x*ₙ),其中每个 x*ᵢ 都来自原始样本。

经验分布 F̂ₙ 是Bootstrap的理论支点:它用样本的离散均匀分布替代未知的 F。样本量 n 越大,F̂ₙ 越接近 F,Bootstrap近似通常越好。

一次重采样的完整拆解

下面用一个具体的小样本手动走一遍。假设我们测量了5名学生的某次测验成绩(单位:分):

x = (72, 85, 90, 78, 88)   n = 5

我们关心的统计量是样本均值 θ̂ = x̄。原始样本均值为 (72+85+90+78+88)/5 = 82.6。现在执行一次Bootstrap重采样:

  1. 建立抽样池:把5个观测值放入池中,每个值对应一个“球”,共5个球,每个球被抽中的概率为 1/5 = 0.2。
  2. 有放回抽5次:每次抽一个球、记录数值、放回池中,再抽下一次。假设这次抽到的索引序列为 (2, 5, 2, 1, 4),对应数值为 (85, 88, 85, 72, 78)。注意索引2出现了两次,索引3(数值90)一次都没出现——这正是有放回抽样的典型特征。
  3. 计算重采样统计量:x̄* = (85+88+85+72+78)/5 = 81.6。
  4. 记录结果:把 x̄* = 81.6 存入Bootstrap统计量集合。

重复上述过程 B 次(例如 B = 1000 或 B = 10000),得到 B 个重采样均值 x̄*₁, x̄*₂, …, x̄*_B。这 B 个值的经验分布,就是原均值 x̄ 抽样分布的Bootstrap近似。基于这个分布,可以计算标准误(B 个值的样本标准差)、百分位置信区间(取2.5%和97.5%分位数)等。

从原始样本到Bootstrap统计量分布 原始样本 x(n = 5) 72 85 90 78 88 每个观测值概率 1/5,构成经验分布 F̂ₙ 抽样池:有放回抽 5 次 72 85 90 78 88 抽后放回,同一值可重复出现 第 1 次重采样样本 x*(索引 2,5,2,1,4) 85 88 85 72 78 85 出现两次,90 未被抽中 → x̄* = 81.6 Bootstrap 统计量集合 x̄*₁ = 81.6, x̄*₂ = 84.2, x̄*₃ = 79.8, … 重复 B 次(如 B = 1000) B 个重采样统计量的经验分布 → 推断 原始 x̄ = 82.6 • 标准误 ≈ B 个值的标准差 • 95% 百分位区间 ≈ [2.5%, 97.5%] 分位数 • 偏差 ≈ B 个值的均值 − 原始统计量

算法伪代码与实现要点

把上面的过程写成通用伪代码,结构非常清晰:

输入: 原始样本 x[1..n], 统计量函数 T, 重采样次数 B
输出: B 个 Bootstrap 统计量

for b = 1 to B:
    x_star = 空数组
    for i = 1 to n:
        j = 从 {1, 2, …, n} 中等概率有放回抽取一个索引
        x_star.append(x[j])
    theta_star[b] = T(x_star)

返回 theta_star[1..B]

实现时有几个容易出错的细节值得注意。第一,抽样必须独立且等概率,每次抽取都从完整的 n 个索引中选,不能“抽一个少一个”。第二,重采样样本的容量应等于原样本容量 n,这是标准非参数Bootstrap的约定;容量不同会改变统计量的方差尺度。第三,统计量函数 T 应保持与原始分析完全一致,包括缺失值处理、加权方式等,否则Bootstrap分布与目标统计量不匹配。第四,B 的选择:估计标准误时 B = 200 通常已够用,百分位置信区间建议 B ≥ 1000,需要更稳定的分位数时可取 B = 10000。

用代码手动实现一次

下面用Python的NumPy手动实现,不依赖任何Bootstrap库,便于看清每一步:

import numpy as np

x = np.array([72, 85, 90, 78, 88])
n = len(x)
rng = np.random.default_rng(2024)

# 一次重采样
idx = rng.integers(0, n, size=n)   # 有放回抽 n 个索引
x_star = x[idx]
theta_star_1 = x_star.mean()
print(idx, x_star, theta_star_1)
# 示例输出: [1 4 1 0 3] [85 88 85 72 78] 81.6

# B 次重采样
B = 1000
theta_star = np.empty(B)
for b in range(B):
    idx = rng.integers(0, n, size=n)
    theta_star[b] = x[idx].mean()

se_boot = theta_star.std(ddof=1)
ci = np.percentile(theta_star, [2.5, 97.5])
print(se_boot, ci)

上述输出中的具体数值取决于随机种子,属于示例结果,不是固定实测值。读者运行时应关注三件事:idx 中是否有重复索引、x_star 是否只包含原始样本中的值、theta_star 的分布是否大致以原始统计量为中心。若 idx 出现越界或 x_star 出现原始样本之外的值,说明抽样逻辑有误。

重采样机制带来的两个直觉

其一,重采样样本的均值围绕原始统计量波动。 因为每次抽样都是从原始样本中等概率抽取,重采样样本的期望均值等于原始样本均值。所以 B 个 x̄* 的分布中心应接近 x̄ = 82.6,其离散程度反映 x̄ 的抽样不确定性。

其二,重采样样本的构成与原始样本不同。 一个有放回样本中,平均约有 1 − 1/e ≈ 63.2% 的原始观测值至少出现一次,其余约36.8%不出现。这个比例是Bootstrap的固有性质,也解释了为什么重采样样本之间会有差异——正是这种差异承载了抽样变异的信息。

Bootstrap不是“凭空造数据”,它的一切信息都来自原始样本。若原始样本本身有严重偏差、样本量过小或统计量不光滑(如极值、中位数在小样本下),Bootstrap分布可能失真。此时应结合样本量、统计量性质和诊断图判断结果是否可信。

掌握这套机制后,后续的标准误估计、百分位区间、BCa区间都只是在这 B 个统计量上做不同的加工。先把“经验分布—有放回抽样—统计量集合”这条链路走通,是理解一切Bootstrap变体的基础。