← 中文资源

简体中文版本

Bootstrap 置信区间估计

通过对样本有放回重采样,为任意统计量估计置信区间,并支持固定随机种子复现结果。

阅读英文原文

功能说明

Bootstrap 不假设数据服从正态分布,而是把已有样本当作总体,反复有放回抽样并计算统计量,用经验分布的分位数作为置信区间。它适合延迟分位数、转化率、评分均值这类分布偏斜的指标,也适合样本量只有几十条时快速给出不确定性范围。

函数接受任意统计量回调,默认均值,也可以传 statistics.median 或自定义分位数函数。返回值包含下界、上界和原始样本上的点估计,方便直接打印或写入监控报告。

关键代码

每次迭代按样本长度抽取同等数量的元素,允许重复,然后调用统计量函数。所有估计值排序后,按 1 减置信度再除以二得到尾部比例,取对应下标即为区间端点;上界下标做了越界保护。随机源使用独立的 random.Random(seed),不会影响全局随机状态,因此测试可以完全复现。

使用注意

迭代次数太少会让区间抖动,示例要求至少一百次,生产环境常用两千到一万次。Bootstrap 假设样本独立同分布,存在时间相关性或分组结构时应使用块采样或分层采样。样本量极小时区间会非常宽,这是真实不确定性而非缺陷。对最大值、最小值这类极值统计量,普通 Bootstrap 表现较差。传入的统计量必须能处理长度为样本量的序列,且不能修改输入。

验证方式

示例延迟数据均值为 20.35,打印结果形如 mean=20.35,区间应覆盖该值并明显向上偏斜,因为 40 和 55 两个长尾点拉高了上界。固定 seed 为 42 时两次运行输出完全一致;把 seed 改为其他值,区间端点会小幅变化。将统计量换成中位数后,区间明显收窄,说明长尾对中位数影响更小。

代码示例

import random
import statistics
from collections.abc import Callable, Sequence


def bootstrap_confidence_interval(
    sample: Sequence[float],
    *,
    statistic: Callable[[Sequence[float]], float] = statistics.mean,
    iterations: int = 2000,
    confidence: float = 0.95,
    seed: int | None = None,
) -> tuple[float, float, float]:
    if not sample:
        raise ValueError("sample must not be empty")
    if not 0 < confidence < 1:
        raise ValueError("confidence must be between 0 and 1")
    if iterations < 100:
        raise ValueError("use at least 100 iterations")
    rng = random.Random(seed)
    size = len(sample)
    estimates: list[float] = []
    for _ in range(iterations):
        resample = [sample[rng.randrange(size)] for _ in range(size)]
        estimates.append(statistic(resample))
    estimates.sort()
    alpha = (1 - confidence) / 2
    lower = estimates[int(alpha * iterations)]
    upper = estimates[min(iterations - 1, int((1 - alpha) * iterations))]
    return lower, upper, statistic(sample)


def main() -> None:
    latencies = [12.0, 15.5, 11.2, 40.0, 13.8, 14.1, 16.0, 12.9, 55.0, 13.0]
    lower, upper, observed = bootstrap_confidence_interval(
        latencies, iterations=4000, seed=42
    )
    print(f"mean={observed:.2f} 95% CI=({lower:.2f}, {upper:.2f})")

    median_lower, median_upper, _ = bootstrap_confidence_interval(
        latencies,
        statistic=statistics.median,
        iterations=4000,
        seed=7,
    )
    print(f"median CI=({median_lower:.2f}, {median_upper:.2f})")


if __name__ == "__main__":
    main()