“卡方”到底是什么
为什么要叫“卡方”?因为原名是“chi-squared”,一半是音译,一半是意译。其中,chi 是希腊字母 的读音,其实读音更像是“开”,而不是“卡”。square表示平方,因此在英语中,卡方分布写作 distribution。
卡方分布
在理解卡方检验之前,应当理解卡方分布。卡方分布是一种连续概率分布。
如果一个随机变量服从标准正态分布,即,那么就服从自由度为1的卡方分布。记作 或者
而如果都服从标准正态分布,那么它们的平方和服从自由度为的卡方分布,记作:
或者写作。
对于非负自变量的自由度为的卡方分布的概率密度函数 (简称"pdf"):
(1)为什么非负?因为根据定义,卡方分布的自变量是一个平方和。
(2)这里的是一个函数。关于这个函数具体是什么,以及上门的概率密度函数如何推导,这里不展开,只需要知道有这么个函数即可。实在是好奇的,可以参考这里。
(3)卡方分布的均值为,而标准差为。
(4)自由度越大,该函数图像越对称。
(5)为什么这里需要正态分布,我的理解是,如果零假设为真,那么观测值和期望值之间的波动程度,应该是正态分布的,或者说“噪声”应该是正态分布的。
卡方检验
卡方检验有两个用途:
拟合优度检验 chi-squared test goodness of fit
独立性检验 chi-squared test of independence
拟合优度检验
某新闻说某个篮球明星的原地两连投的单次命中率是0.8,根据历次比赛的数据汇总得到下面的表格:
一次两连投中的命中次数 | 0 | 1 | 2 |
---|---|---|---|
观察次数 | 5 | 82 | 251 |
意思是说,在比赛中,有5次两连投是一次都没中,有82次是在两连投中命中1次。现在,我们来用卡方检验验证新闻说的0.8的命中率是否正确。零假设如下:
:两连投的成功次数符合二项分布,且概率为
(1)先根据零假设计算“期望”的命中次数分布:
由于总的观察次数为 ,于是在成立的前提下,可以计算每种两连投结果的期望次数:
0次命中:
1次命中:
2次命中:
一次两连投中的命中次数 | 0 | 1 | 2 |
---|---|---|---|
实际的观察次数 (observed) | 5 | 82 | 251 |
期望的观察次数 (expected) | 13.52 | 108.16 | 216.32 |
显然,期望的观察次数和实际的观察次数是有偏差的,那么问题在于这个偏差是否大到具有统计显著性,进而可以否定零假设。
(2)我们来构造卡方检验统计量(chi-squared test statistic):
这个值是把表里每个格子的实际值和期望值进行对比。为什么要用平方?目的在于规避正负号的影响。为什么要除以期望值?目的在于消除数量绝对值的影响。例如你预算3块钱的水,商家加价50元,那么这个波动是你无法忍受的,而你预算20万的车,商家加价50元,则变得可以忍受。也就是说,除以期望值目的在于聚焦于变化率,而不是变化量。
之后,把这些“变化率”加总得到。而计算自由度有一个公式:
其中 R 表示行数,C 表示列数。对于本例:
从另一个角度解释为什么:前面的定义是如果是个符合标准正态分布的相加,则自由度是,但是这里自有两个格子可以自由变化,第三个格子可以用总观察数减出来,例如 。
因此,真正自由的只有2个格子,所以自由度是2。
好了,将格子的数据代入,求出检验统计量:
(3)根据自由度为2的卡方分布,找到检验统计量对应的位置:
不难理解,随着统计量增大,表示预期的分布和实际的分布的差异也就越来越大。
另外,由于通常意义上,p值是越小越能推翻零假设,那么显然我们需要用右侧的面积来表示p值,这里用Python计算来代替查表:
from scipy.stats import chisquare
observed = [5, 82, 251]
expected = [13.52, 108.16, 216.32]
output = chisquare(observed, expected)
print('statistic: {:.2f}, pvalue: {:.4f}'.format(output.statistic, output.pvalue))
输出:statistic: 17.26, pvalue: 0.0002
由于p值很小(假设我们的显著性水平的0.05),那么我们可以推翻零假设。
进一步的,我们来探索下,该运动员的两连投的成功次数分数是否真的符合二项分布。零假设:
:两连投的成功次数符合二项分布。
既然符合二项分布,那么我们需要先估算一下最合理的概率,那当然是用总命中数除以总投篮数来计算了:
然后,用该概率值重复之前的计算,也就是先计算出一个期望的表格:
一次两连投中的命中次数 | 0 | 1 | 2 |
---|---|---|---|
实际的观察次数 (observed) | 5 | 82 | 251 |
期望的观察次数 (expected) | 6.26 | 79.48 | 252.26 |
注意,这里的,这是因为,我们每从数据估计一个参数,那么我们就损失一个自由度。这里用了一个平均命中的概率,因此自由度只有。
这时候,在使用 Python 进行计算时,注意调整默认的自由度:
# 如果根据数据推断均值,则损失一个额外的自由度,因此 ddof = 1
# 自由度 = number of cells - 1 - ddof
observed = [5, 82, 251]
expected = [6.26, 79.48, 252.26]
output = chisquare(observed, expected, ddof=1)
print(output.statistic, output.pvalue)
这里的 ddof 就是额外损失的自由度,本意是“delta degree of freedom”
输出:statistic: 0.34, pvalue: 0.56
可以看到p值很大,因此不足以推翻零假设,也就是说该运动员的投篮命中次数可能真的是二项分布。
独立性检验
下面表格表示喝酒频率和与警察发生麻烦的频数。
以第一列为例,表示从不喝酒的人中,4992人不发生麻烦,71人会发生麻烦。
现在的问题是,能否从以下数据推断说喝酒频率和与警察发生麻烦这两个事件相互独立?
Never | Occasional | Frequent | Row total | |
---|---|---|---|---|
Trouble with police | 71 | 154 | 398 | 623 |
No trouble with police | 4992 | 2808 | 2737 | 10537 |
Column total | 5063 | 2962 | 3135 | 11160 |
我们的零假设应该如何设计?如果要说明两者相互独立,那么上表的分布应该满足乘法公式。也就是说两个独立事件一起发生的概率等于分别发生的概率之积。
于是我们有:
发生麻烦的总人数除以总人数
不喝酒的总人数除以总人数
进一步,根据总人数算出不喝酒而发生麻烦的人数的期望(下标表示零假设):
用类似的算法,计算每一个格子在零假设成立的情况下的值,写在原表数据下的括号里:
Never | Occasional | Frequent | Row total | |
---|---|---|---|---|
Trouble with police | 71 (282.6) |
154 (165.4) |
398 (175.0) |
623 |
No trouble with police | 4992 (4780.4) |
2808 (2796.6) |
2737 (2960.0) |
10537 |
Column total | 5063 | 2962 | 3135 | 11160 |
仔细观察可以看出,其实每个格子就是对应的:
另外可以看到,零假设下的各个格子的行列之和与原来相同。这不是偶然的,我们用字母代替计算一下就知道了:
Never | Occasional | Frequent | Row total | |
---|---|---|---|---|
Trouble with police | a | b | c | a+b+c |
No trouble with police | d | e | f | d+e+f |
Column total | a+d | b+e | c+f | a+b+c+d+e+f |
于是第一列的两个格子应该是:
对于其他格子、行的总和,都一样,这里不多说了。
好,继续分析。我们直接用上表计算卡方统计量和p值:
from scipy.stats import chisquare
observed = [71, 154, 398, 4992, 2808, 2737]
expected = [282.6, 165.4, 175.0, 4780.4, 2796.6, 2960.0]
output = chisquare(observed, expected)
print('statistic: {:.2f}, pvalue: {:.4f}'.format(output.statistic, output.pvalue))
这部分计算方法和拟合优度是一样的,就不赘述了。计算发现这个p值非常小,接近0,因此我们可以推翻零假设。也就是说,喝酒的频率和被警察找麻烦的并不是独立的,而是相关的。
费希尔精确检验 Fisher's exact test
关于独立性检验,有一个比卡方检验更精准的检验,叫 fisher's exact test。它通过直接计算否定零假设的概率,也就直接得到了一个准确的p值。有一个经典的女士品茶的统计学故事。有一个女士号称可以区分出一杯茶是先倒入了奶还是先倒入了茶。统计学家 Fisher 为了验证她的说法,做了一个实验。拿了8杯茶,4杯是先茶后奶,4杯是先奶后茶。
女士判断先奶后茶 | 女士判断先茶后奶 | 总数 | |
---|---|---|---|
实际先奶后茶 | 4 | 0 | 4 |
实际先茶后奶 | 0 | 4 | 4 |
总数 | 4 | 4 | 8 |
实验结果是全部说对了。那么问题是,这是否具有统计显著性呢?比如说一个人猜对了一次硬币,他的预测能力靠谱吗?
我们假设女士的判断是完全随机的,这个是我们的零假设。那么8杯里面抽中4杯全对的概率是:
如果显著性水平是0.01,那么我们不能推翻零假设,即不敢确定这位女士真有这个识别能力。如果显著性水平定在0.05,则我们可以认为她确实有这个识别能力。
如果让实验结果有更大的说服力呢?一个简单的办法就是增加茶的数量,比如我们设定为两种茶各10杯,要求10杯都判断正确,那么p值为多少呢?
这个算起来比较麻烦,这里我写一个 python 脚本来计算:
def factorial(n:int):
"""阶乘函数"""
s = 1
while n > 1:
s *= n
n -= 1
return s
def C(m:int, n:int):
"""组合数公式。C(m, n)表示$C_m^n$"""
return factorial(m) / (factorial(m-n)*factorial(n))
print('p-value = {:.4f}%'.format(C(10,10)/C(20,10)*100))
计算结果:
这个p值就小得很夸张了,基本可以断定零假设不成立了。
那么,回到实验本身,如果女士只选对了三杯,那么在零假设的前提下,这个发生的概率是多少?
这个概率比较大了,原大于通常使用的显著性水平 0.05,因此我们没有办法推翻零假设。为什么要这样乘呢?这个是因为一共有这么多种取法。你把所有可能的取法罗列处理,就是16种,然后除以总的取法数,就是随机取到这样结果的概率。
比较 Fisher's exact test 和 chi-squared test,可以参考这篇文章。
条件 | chi-squared test | Fisher's exact test |
---|---|---|
最小样本量 | 大 | 小 |
准确度 | 近似 | 精确 |
列联表 | 任意维度 | 一般是2x2 |
解释 | pearson residuals | odds ratio |
一般来说,两者都适用的情况下,应该优先选择 Fisher's exact test,因为它是精确值。如果实验观察的数量很小(小于10),应该不使用 chi-squared test。
下面使用一个脚本来计算:
from scipy.stats import fisher_exact
# 列联表
contigency_table = [[0, 4], [4, 0]]
# alternative 表示备择假设
# alternative='less' 表示零假设是相等,备择假设是
oddsratio, pvalue = fisher_exact(contigency_table, alternative='less')
print(pvalue)
# 输出:0.014285714285714268
参考
- An Introduction to the Chi-Square Distribution
- Chi-square tests: Goodness of Fit for the Binomial Distribution
- Chi-square Tests of Independence (Chi-square Tests for Two-Way Tables)
- Python Programming: scipy.stats.chisquare
- Python Programming: scipy.stats.fisher_exact