统计思维(实例8)——假设检验

经典假设检验

这章解决的基本问题是,在一个样本中观察到的效应是否也会出现在更大规模的总体中。例如,在NSFG样本中,第一胎和其他胎的妊娠期长度不同,这种效应是真实反映了美国妇女的生育情况,还是偶然出现在这个样本中而已。

这个问题有几种表示方法:Fisher原假设检验、Neyman-Pearson决策理论和贝叶斯推理,大部分人在实践中使用的都是这3中方法。这里介绍这些方法的一个子集,称为经典假设检验(classical hypothesis testing)。

经典假设检验的目的是回答一个问题:“给定一个样本和一个直观效应,这个效应是偶然出现的概率为多少?”,回答这个问题的步骤如下:

  • 第一步,选择一个检验统计量(test statistic),对直观效应进行量化。

  • 第二步,定义原假设(null hypothesis)。原假设是系统的一个模型,所基于的假设是直观效应不为真。

  • 第三步,计算p值。p值是在原假设为真时,直观效应出现的概率。

  • 最后,解释结果。如果p值很低,我们称这个效应是统计显著(statistically significant)的,即不太可能偶然发生。在这种情况下,我们推断,这个效应在大规模总体中出现的可能性更大。

假设检验

本文用HypothesisTest表示一个经典假设检验结果,定义如下:

class HypothesisTest(object):
    
    def __init__(self, data):
        self.data = data
        self.MakeModel()
        self.actual = self.TestStatistic(data) 
       
    def PValue(self, iters=1000):
        self.test_stats = [self.TestStatistic(self.RunModel())                            
                            for _ in range(iters)]
        
        count = sum(1 for x in self.test_stats if x >= self.actual)        
        return count / iters   
             
    def TestStatistic(self, data):
        raise UnimplementedMethodException()
            
    def MakeModel(self):
        pass
        
    def RunModel(self):
        raise UnimplementedException()

HypothesisTest是一个抽象父类,完整定义了一些方法,并为其他方法预留接口。基于HypothesisTest的子类继承了__init__和PValue,实现了TestsStatistic和RunModel,可选择是否定义MakeModel。

举个简单的例子,假设投掷一枚硬币250次,结果得到140次正面和110次反面。基于这个结果,我们怀疑这个硬币质地不均匀,落地时正面朝上的可能性更大。为了检验这个假设,我们计算出当质地均匀时出现这种结果的概率。

class CoinTest(HypothesisTest):

    def TestStatistic(self, data):
        heads, tails = data
        test_stat = abs(heads - tails)        
        return test_stat 
               
    def RunModel(self):
        heads, tails = self.data
        n = heads + tails
        sample = [random.choice('HT') for _ in range(n)]
        hist = Hist(sample)
        data = hist['H'], hist['T']        
        return data

参数data是一对整数,即结果为正面和反面的次数。检验统计量是两者的差值30。计算PValue的结果为0.07,也就是如果硬币质地均匀的,预期有7%的可能性看到正面和反面的差值达到30的情况。

如果p值小于1%,那么效应不太可能是随机产生的;如果p值大于10%,那么效应可以合理解释为随机现象。位于1%和10%之间的p值应看作边缘值。

检验均值差

在全国家庭增长调查数据中,我们看到第一胎比其他胎的妊娠时间稍长,出生体重略轻。现在,我们要看看这些效应是否统计显著。

这些例子的原假设是两组样本的分布相同。对这个原假设建模,一个方法是置换(permutation),即从两组中取值混排,把两个组当成一个大组。

class DiffMeansPermute(HypothesisTest):
    
    def TestStatistic(self, data):
        group1, group2 = data
        test_stat = abs(group1.mean() - group2.mean())
        return test_stat
        
    def MakeModel(self):
        group1, group2 = self.data
        self.n, self.m = len(group1), len(group2)
        self.pool = np.hstack((group1, group2))
        
    def RunModel(self):
        np.random.shuffle(self.pool)
        data = self.pool[:self.n], self.pool[self.n:]
        return data

data是一对序列,每组一个序列。检验统计量是两组序列的均值差。

我们将妊娠时间数据抽取为NumPy数组,传递给DiffMeansPermute,计算出p值,计算结果约为0.17,即我们预期有17%的可能性看到妊娠时间差达到所观测的差值。因此,这个效应不是统计显著的。

图1 原假设下,妊娠期时间均值差CDF

检验相关性

在全国家庭增长调查数据集中,新生儿体重和母亲年龄的相关性约为0.07。年龄较大的母亲似乎产下的孩子更重,但这种效应是偶然产生的吗?

我们选择Pearson相关性作为检验统计量,但Spearman相关性也是很好的选择。如果我们有理由预期正相关,那么就可以进行单侧检验。由于我们并没有任何相关证据,还是选择使用相关性的绝对值进行双侧检验。

检验的原假设是母亲年龄和新生儿体重之间没有相关性。我们可以将观察值混排进行模拟,在这个模拟世界中,母亲年龄和新生儿体重的分布仍保持不变,但这两个变量之间没有相关性。

class CorrelationPermute(HypothesisTest):

    def TestStatistic(self, data):
        xs, ys = data
        test_stat = abs(Corr(xs, ys))        
        return test_stat   
             
    def RunModel(self):
        xs, ys = self.data
        xs = np.random.permutation(xs)        
        return xs, ys

data是一对序列,TestStatistic计算Pearson相关性的绝对值,RunModel将xs进行混排,返回模拟数据。

实际数据的相关性为0.07,检验计算得到的p值为0。在1000次重复实验中,模拟得到的最大相关性为0.04。因此,虽然观察到的变量相关性很小,但这种相关性是统计显著的。

检验比例

假设你怀疑一个骰子有问题,将这个骰子掷了60次,得到如下结果:

你希望的结果是每个点数平均出现10次。在这个数据集中,3出现的次数较多,4较少。但是,这个差异是统计显著的吗?

为了计算这个假设,我们可以计算出每个值的预期频数、预期频数与观察频数的差值,以及差值绝对值的和。在这个示例中,差值绝对值的和为20。完全偶然出现这么大差值的概率是多少呢?

class DiceTest(HypothesisTest):
    
    def TestStatistic(self, data):
        observed = data
        n = sum(observed)
        expected = np.ones(6) * n /6
        test_stat = sum(abs(observed - expected))        
        return test_stat     
           
    def RunModel(self):
        n = sum(self.data)
        values = [1, 2, 3, 4, 5, 6]
        rolls = np.random.choice(values, n ,replace=True)
        hist = Hist(rolls)
        freqs = hist.Freqs(values)        
        return freqs

上述代码中使用的数据表示一列频数,观察值为[8, 9, 19, 5, 8, 11],预期频数都是10。检验统计量是差值绝对值的和。

原假设是骰子没有问题,因此从values中随机抽取样本进行模拟。计算得到的p值为0.13,因此,这个直观效应不是统计显著的。

卡方检验

前面,我们使用偏差总和作为检测统计量。但是,检测比例时,人们更多使用的是卡方统计量。

其中Oi是观察到的频数,Ei是预期频数。

使用卡方统计量计算的p值为0.04,明显小于使用偏差和的值0.13。将这两个检验放在一起考虑,无法排除骰子有问题的可能性,也不能肯定骰子一定有问题。

这个示例说明了一个重要问题:p值取决于检验统计量的选择和原假设模型,有时这些因素决定了一个效应是否统计显著。

卡方检验(chi-squared test)可以证明两个群组之间存在差异,但不能揭示这个差异是什么。

误差

在经典假设检验中,如果p值低于某个阈值(常用阈值为5%),那么我们就认为一个效应是统计显著的。这个过程产生两个问题:

  • 如果一个效应的确是偶然产生的,那么我们将它误判为统计显著的概率是多少?这个概率就是误报率(false positive rate)。

  • 如果一个效应不是偶然的,那么假设检验失败的概率是多少?这个概率称为漏报率(false negative rate).

功效

误报率受实际效应大小的影响,而通常我们无法得知实际效应的大小,因此误报率较难计算。一个办法是计算一个假定效应大小的误报率。

我们以妊娠时间计算进行模拟,计算结果为:如果妊娠时间均值的实际差异为0.78周,那么我们预期如果使用整个规模的样本就行实验,结果有70%的可能性为误报。

人们经常使用另一种方式描述这个结果:如果实际差异为0.78周,那么我们预期检验通过的可能性只有30%。这个“正确通过率”称为检验的功效,有时也称为“敏感度”。这个值反映了一个检验检测出指定大小效应的能力。

通常,假设检验失败并不说明两个群组之间不存在差异,而是说,如果差异的确存在的话,这个差异太小,以至于无法在这种规模的样本中检测到。


参考文献:

    统计思维. Allen B.Downey. 金迎 译


版权声明:本文为shandianke原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。