关于程序员:为什么以及如何在多重假设检验中调整-P-值

8次阅读

共计 4792 个字符,预计需要花费 12 分钟才能阅读完成。

动动发财的小手,点个赞吧!

低于某个阈值的 P 值通常用作抉择相干特色的办法。上面的倡议倡议如何正确应用它们。

当咱们在多个特色上反复测试模型时,就会产生多重假设检验,因为取得一个或多个谬误发现的概率会随着测试次数的减少而减少。例如,在基因组学畛域,科学家们常常想测试数千个基因中的任何一个是否对感兴趣的后果具备显着不同的活性。

在本文中,咱们将介绍几种通过调整模型 p 值来解释多重假设检验的风行办法:

  1. False Positive Rate (FPR)
  2. Family-Wise Error Rate (FWER)
  3. False Discovery Rate (FDR)

并解释何时应用它们是有意义的。

本文档能够用下图概括:

测试数据

咱们将创立一个模仿示例,以更好地了解对 p 值的各种操作如何导致不同的论断。要运行此代码,咱们须要装置有 pandas、numpy、scipy 和 statsmodels 库的 Python。

出于本示例的目标,咱们首先创立一个蕴含 1000 个特色的 Pandas DataFrame。其中 990 个 (99%) 的值将从均值 = 0 的正态分布生成,称为 Null 模型。(在上面应用的函数 norm.rvs() 中,应用 loc 参数设置均值。)其余 1% 的特色将从均值 = 3 的正态分布生成,称为非空模型。咱们将应用这些来示意咱们想要发现的乏味特色。

import pandas as pd
import numpy as np
from scipy.stats import norm
from statsmodels.stats.multitest import multipletests

np.random.seed(42)

n_null = 9900
n_nonnull = 100

df = pd.DataFrame({
    'hypothesis': np.concatenate((['null'] * n_null,
        ['non-null'] * n_nonnull,
    )),
    'feature': range(n_null + n_nonnull),
    'x': np.concatenate((norm.rvs(loc=0, scale=1, size=n_null),
        norm.rvs(loc=3, scale=1, size=n_nonnull),
    ))
})

对于 1000 个特色中的每一个,如果咱们假如它是从 Null 散布生成的,则 p 值是察看到该值至多一样大的概率。

P 值能够从累积散布(来自 scipy.stats 的 norm.cdf())计算,它示意取得等于或小于察看值的值的概率。而后计算 p 值,咱们计算 1 – norm.cdf() 以找到大于察看到的概率:

df['p_value'] = 1 - norm.cdf(df['x'], loc = 0, scale = 1)
df

False Positive Rate

第一个概念称为误报率,定义为咱们标记为“显着”的原假如的一小部分(也称为 I 类谬误)。咱们之前计算的 p 值能够依据其定义解释为误报率:当咱们对 Null 散布进行采样时,它们是取得至多与指定值一样大的值的概率。

出于阐明目标,咱们将利用一个常见的(神奇的)p 值阈值 0.05,但能够应用任何阈值:

df['is_raw_p_value_significant'] = df['p_value'] <= 0.05
df.groupby(['hypothesis', 'is_raw_p_value_significant']).size()

请留神,在咱们的 9900 个原假如中,有 493 个被标记为“重要”。因而,误报率为:FPR = 493 / (493 + 9940) = 0.053。

FPR 的次要问题是,在实在场景中,咱们无奈先验地晓得哪些假如为零,哪些不为零。而后,原始 p 值自身(误报率)的用处无限。在咱们的例子中,当非空特色的比例十分小时,大多数标记为重要的特色将是空的,因为它们的数量更多。具体来说,在 92 + 493 = 585 个标记为真(“正”)的特色中,只有 92 个来自咱们的非空散布。这意味着大多数或大概 84% 的报告重要特色 (493 / 585) 是误报!

那么,咱们能做些什么呢?有两种常见的办法能够解决这个问题:咱们能够计算 Family-Wise Error Rate (FWER) 或 False Discovery Rate (FDR),而不是误报率。这些办法中的每一种都将一组原始的、未经调整的 p 值作为输出,并生成一组新的“调整后的 p 值”作为输入。这些“调整后的 p 值”示意 FWER 和 FDR 下限的估计值。它们能够从 multipletests() 函数中取得,它是 statsmodels Python 库的一部分:

def adjust_pvalues(p_values, method):
   return multipletests(p_values, method = method)[1]

Family-Wise Error Rate

Family-Wise Error Rate 是谬误地回绝一个或多个原假如的概率,或者换句话说,将真 Null 标记为 Non-null,或者看到一个或多个误报的概率。

当只有一个假如被测验时,这等于原始 p 值(假阳性率)。然而,测试的假如越多,咱们就越有可能失去一个或多个误报。有两种风行的预计 FWER 的办法:Bonferroni 和 Holm 程序。只管 Bonferroni 和 Holm 程序都没有对运行测试对单个特色的依赖性做出任何假如,但它们会过于激进。例如,在所有特色都雷同的极其状况下(雷同模型反复 10,000 次),不须要校对。而在另一个极其,没有特色相干,则须要某种类型的校对。

Bonferroni 程序

Bonferroni 程序是校对多重假设检验的最风行办法之一。这种办法之所以风行,是因为它非常容易计算,甚至能够手工计算。此过程将每个 p 值乘以执行的测试总数,或者如果此乘法会使它超过 1,则将其设置为 1。

df['p_value_bonf'] = adjust_pvalues(df['p_value'], 'bonferroni')
df.sort_values('p_value_bonf')

Holm 程序

Holm 的程序提供了比 Bonferroni 的程序更弱小的修改。惟一的区别是 p 值并未全副乘以测试总数(此处为 10000)。相同,每个排序的 p 值逐步乘以递加序列 10000、9999、9998、9997、…、3、2、1。

df['p_value_holm'] = adjust_pvalues(df['p_value'], 'holm')
df.sort_values('p_value_holm').head(10)

咱们能够本人验证这一点:此输入的最初 10 个 p 值乘以 9991:7.943832e-06 * 9991 = 0.079367。Holm 修改也是 R 语言中 p.adjust() 函数默认调整 p 值的办法。

如果咱们再次利用 0.05 的 p 值阈值,让咱们看看这些调整后的 p 值如何影响咱们的预测:

df['is_p_value_holm_significant'] = df['p_value_holm'] <= 0.05
df.groupby(['hypothesis', 'is_p_value_holm_significant']).size()

这些后果与咱们对原始 p 值利用雷同阈值时的后果大不相同!当初,只有 8 个特色被标记为“重要”,并且所有 8 个都是正确的——它们是从咱们的非空散布生成的。这是因为即便一个特色被谬误标记的概率也只有 0.05 (5%)。

然而,这种办法有一个毛病:它无奈将其余 92 个非空特色标记为重要。尽管确保没有空特色滑入是十分严格的,但它只能找到 8%(100 个中的 8 个)非空特色。这能够看作是采纳与误报率办法不同的极其办法。

还有更多的两头立场吗?答案是“是”,两头立场是谬误发现率。

False Discovery Rate

如果咱们能够承受一些误报,但捕捉超过个位数百分比的真阳性怎么办?兴许咱们能够承受一些误报,只是没有那么多以至于它们吞没了咱们标记为重要的所有特色——就像 FPR 示例中的状况一样。

这能够通过将谬误发现率(而不是 FWER 或 FPR)管制在指定的阈值程度(例如 0.05)来实现。谬误发现率定义为所有标记为阳性的特色中误报的分数:FDR = FP / (FP + TP),其中 FP 是误报的数量,TP 是真阳性的数量。通过将 FDR 阈值设置为 0.05,咱们示意咱们能够承受在咱们标记为正的所有特色中有 5%(均匀)的误报。

有几种办法能够管制 FDR,这里咱们将介绍如何应用两种风行的办法:Benjamini-Hochberg 和 Benjamini-Yekutieli 程序。这两个程序尽管比 FWER 程序更简单,但很类似。他们依然依赖于对 p 值进行排序,将它们与特定数字相乘,而后应用截止规范。

Benjamini-Hochberg 程序

Benjamini-Hochberg (BH) 程序假设每个测试都是独立的。例如,如果被测试的特色互相关联,就会产生相干测试。让咱们计算 BH 调整后的 p 值,并将其与咱们之前应用 Holm 校对的 FWER 后果进行比拟:

df['p_value_bh'] = adjust_pvalues(df['p_value'], 'fdr_bh')
df[['hypothesis', 'feature', 'x', 'p_value', 'p_value_holm', 'p_value_bh']] \
    .sort_values('p_value_bh') \
    .head(10)

df['is_p_value_holm_significant'] = df['p_value_holm'] <= 0.05
df.groupby(['hypothesis', 'is_p_value_holm_significant']).size()

df['is_p_value_bh_significant'] = df['p_value_bh'] <= 0.05
df.groupby(['hypothesis', 'is_p_value_bh_significant']).size()

BH 程序当初正确地将 100 个非空特色中的 33 个标记为重要特色——比 Holm 修改后的 8 个特色有所改进。然而,它也将 2 个有效特色标记为重要。因而,在标记为重要的 35 个特色中,不正确特色的比例为:2 / 33 = 0.06,即 6%。

请留神,在这种状况下,咱们的 FDR 率为 6%,只管咱们的指标是将其管制在 5%。FDR 将均匀管制在 5% 的利率:有时可能较低,有时可能较高。

Benjamini-Yekutieli 程序

无论测试是否独立,Benjamini-Yekutieli (BY) 程序都会管制 FDR。同样,值得注意的是,所有这些程序都试图在 FDR(或 FWER)上建设下限,因而它们可能更激进或更激进。让咱们将 BY 过程与下面的 BH 和 Holm 过程进行比拟:

df['p_value_by'] = adjust_pvalues(df['p_value'], 'fdr_by')
df[['hypothesis', 'feature', 'x', 'p_value', 'p_value_holm', 'p_value_bh', 'p_value_by']] \
    .sort_values('p_value_by') \
    .head(10)

df['is_p_value_by_significant'] = df['p_value_by'] <= 0.05
df.groupby(['hypothesis', 'is_p_value_by_significant']).size()

BY 程序对 FDR 的管制更为严格;在这种状况下,甚至比 Holm 管制 FWER 的过程更重要,它只将 7 个非空特色标记为重要!应用它的次要长处是当咱们晓得数据可能蕴含大量相干特色时。然而,在那种状况下,咱们可能还想思考过滤掉相干的特色,这样咱们就不须要测试所有的特色。

本文由 mdnice 多平台公布

正文完
 0