百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

为什么以及如何在多重假设检验中调整 P 值

toyiye 2024-06-21 12:36 10 浏览 0 评论

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

低于某个阈值的 P 值通常用作选择相关特征的方法。下面的建议建议如何正确使用它们。

当我们在多个特征上重复测试模型时,就会发生多重假设检验,因为获得一个或多个错误发现的概率会随着测试次数的增加而增加。例如,在基因组学领域,科学家们经常想测试数千个基因中的任何一个是否对感兴趣的结果具有显着不同的活性。

本文[1]中,我们将介绍几种通过调整模型 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 个非空特征标记为重要!使用它的主要优点是当我们知道数据可能包含大量相关特征时。然而,在那种情况下,我们可能还想考虑过滤掉相关的特征,这样我们就不需要测试所有的特征。

Reference

[1]Source: https://towardsdatascience.com/why-and-how-to-adjust-p-values-in-multiple-hypothesis-testing-2ccf174cdbf8

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码