花拾录
← 返回知识库

把非独立样本当独立样本,统计量膨胀了好几个数量级

编程语言导入2026/09/220 阅读0 评论

拿到一个 t 值高达 38 的回测结果,你第一反应是什么?兴奋?还是——先数一数,这个样本量是不是在骗你?这篇文章讲的,就是那种"样本量看起来很大、实际上很小"的经典陷阱。

现象

用全样本做回测,t 值高得离谱:

t-statistic = 38.4
样本量     = 12,500
p-value    = 0.0000

38 的 t 值意味着"这个结果几乎不可能是巧合",直觉上你会觉得找到了铁证。但问题在于:这 12,500 条信号,真的是 12,500 个彼此独立的观测吗?

根因

不是。 同一时间点上的所有对象,共享了共同的市场因子(宏观环境、行业波动、情绪周期……)。它们在同一个瞬间一起涨、一起跌,本质上是"高度相关"的,不是独立的观测。

把成千上万条信号当成独立样本,会把微弱信号严重说大。池化之后的 t 值可能膨胀 9~25 倍。也就是说,一个真实的 t 值可能是 2 左右(还勉强),但被错当独立样本一算,就变成了 30 多。

站得住的显著性检验,样本量不该按"有多少行"来算,而该按"有多少个彼此独立的时刻"来算:

# 错误:把所有行当独立样本
t = ttest_1samp(df["ret"], 0)        # 行数 = 12500,t = 38

# 正确:先按时间点聚合,再检验
daily = df.groupby("date")["ret"].mean()   # 时刻数 = 250
t = ttest_1samp(daily, 0)                  # 有效样本量 = 250

有效样本量从 12,500 掉到 250,t 值立刻回到理性范围——这才是"诚实"的统计量。

解决

两件事必须同时做:

第一,结论必须同时报告"按时间点聚合"的口径。同一时点的所有信号压缩成一个值(比如均值或中位数),再对这张"按时刻"的表做检验。

第二,用配对按时间点超额作为唯一诚实的显著性检验。做法是:对每一个时间点,分别计算"信号组的表现"和"同时点非信号组的表现",两者相减得到逐时点的超额,再对这条超额序列做检验。

# 逐时点配对超额
excess = (group_by_date(df, signals=True)["ret"]
          - group_by_date(df, signals=False)["ret"])
t = ttest_1samp(excess.dropna(), 0)   # 有效样本量 = 时刻数

这样做的好处是,它把"共同因子的涨跌"从两头同时扣掉了——不管市场当天是涨是跌,你比较的都是"同一时刻信号组相对非信号组的差"。剩下的才是信号本身贡献的部分。

延伸与预防

这个陷阱的通用形态是:样本之间是否共享了某个未被建模的共同因素。只要存在这种共同冲击,样本就不再独立,直接做检验一定会高估显著性。

预防的思路:

  • 先问"独立性从哪来"。做任何统计检验前,先确认观测之间是否彼此影响、是否共享时点、是否来自同一个簇。
  • 选择合适的聚合单位。时间序列按时刻聚合,分组实验按组聚合,面板数据用聚类稳健标准误(cluster-robust standard error)。
  • 报告有效样本量,而不是行数。一张表里写着 12,500 行,但真正独立的时刻只有 250 个,这个差距必须让读者看到。

一句话:样本量不是"有多少行",而是"有多少个彼此独立的时刻"。 记住这句,能挡掉一大半自欺欺人的"显著发现"。

评论(0)

  • 还没有评论,来抢沙发~

相关文章