大于2x2列联表的Fisher精确检验

21 投票
2 回答
20360 浏览
提问于 2025-04-18 17:41

你好,scipy库里有Fisher精确检验的实现,但它只适用于2x2的列联表。我想在大于2x2的表格上进行这个检验,比如5x2或5x3的表格。

我知道在R语言里有一个fisher.test的函数可以完成这个工作,但我想在我的Python代码中实现。

有没有人知道在Python中有没有可以处理更大表格的Fisher精确检验的实现?

另外,我也不确定在大于2x2的表格上做Fisher精确检验是否合适。

谢谢!

2 个回答

3

通过一些简单的计算,我们可以把2 x 2的超几何概率公式扩展到任何大小的r x c表格。例如,2 x 3的表格的概率可以这样计算:

(N1!N2!M1!M2!M3!) / (N!a!b!c!d!e!f!)

这里,N1和N2是每一行的总和,M1、M2、M3是每一列的总和,N是所有数据的总和,而a、b、c、d、e、f则是每个单元格里的数字。

你可以通过深度优先搜索(dfs)来实现费舍尔精确检验。代码如下:

import math

def _dfs(mat, pos, r_sum, c_sum, p_0, p):

    (xx, yy) = pos
    (r, c) = (len(r_sum), len(c_sum))

    mat_new = []

    for i in range(len(mat)):
        temp = []
        for j in range(len(mat[0])):
            temp.append(mat[i][j])
        mat_new.append(temp)

    if xx == -1 and yy == -1:
        for i in range(r-1):
            temp = r_sum[i]
            for j in range(c-1):
                temp -= mat_new[i][j]
            mat_new[i][c-1] = temp
        for j in range(c-1):
            temp = c_sum[j]
            for i in range(r-1):
                temp -= mat_new[i][j]
            mat_new[r-1][j] = temp
        temp = r_sum[r-1]
        for j in range(c-1):
            temp -= mat_new[r-1][j]
        if temp <0:
            return
        mat_new[r-1][c-1] = temp

        p_1 = 1
        for x in r_sum:
            p_1 *= math.factorial(x)
        for y in c_sum:
            p_1 *= math.factorial(y)

        n = 0
        for x in r_sum:
            n += x
        p_1 /= math.factorial(n)

        for i in range(len(mat_new)):
            for j in range(len(mat_new[0])):
                p_1 /= math.factorial(mat_new[i][j])
        if p_1 <= p_0 + 0.00000001:
            #print(mat_new)
            #print(p_1)
            p[0] += p_1
    else:
        max_1 = r_sum[xx]
        max_2 = c_sum[yy]
        for j in range(c):
            max_1 -= mat_new[xx][j]
        for i in range(r):
            max_2 -= mat_new[i][yy]
        for k in range(min(max_1,max_2)+1):
            mat_new[xx][yy] = k
            if xx == r-2 and yy == c-2:
                pos_new = (-1, -1)
            elif xx == r-2:
                pos_new = (0, yy+1)
            else:
                pos_new = (xx+1, yy)
            _dfs(mat_new, pos_new, r_sum, c_sum, p_0, p)


def fisher_exact(table):

    row_sum = []
    col_sum = []

    for i in range(len(table)):
        temp = 0
        for j in range(len(table[0])):
            temp += table[i][j]
        row_sum.append(temp)
    
    for j in range(len(table[0])):
        temp = 0
        for i in range(len(table)):
            temp += table[i][j]
        col_sum.append(temp)

    mat = [[0] * len(col_sum)] * len(row_sum)
    pos = (0, 0)

    p_0 = 1

    for x in row_sum:
        p_0 *= math.factorial(x)
    for y in col_sum:
        p_0 *= math.factorial(y)

    n = 0
    for x in row_sum:
        n += x
    p_0 /= math.factorial(n)

    for i in range(len(table)):
        for j in range(len(table[0])):
            p_0 /= math.factorial(table[i][j])

    p = [0]
    _dfs(mat, pos, row_sum, col_sum, p_0, p)

    return p[0]

你可以测试这段代码,比如:

print(fisher_exact([[1,24],[5,20],[14,11],[11,14]]))

这样会得到结果:

0.0001228337404686859

这个结果和R语言给出的结果是一样的。虽然可能有更优雅的方法,但这段代码确实能给出正确的结果。

24

是的,进行大于2x2的表格的Fisher精确检验是可以的。

目前在Python中没有特别干净、经过广泛测试的解决方案。一个解决办法是使用rpy2,然后从Python调用R的函数:

import numpy as np
import rpy2.robjects.numpy2ri
from rpy2.robjects.packages import importr
rpy2.robjects.numpy2ri.activate()

stats = importr('stats')
m = np.array([[4,4],[4,5],[10,6]])
res = stats.fisher_test(m)
print 'p-value: {}'.format(res[0][0])
>> p-value: 0.668165917041

另一个解决办法是深入研究R实现所使用的C代码,并直接调用那段代码。这里有一个链接,指向某人的GitHub项目,他们回到了最初的Fortran实现,并从Python调用了它。

撰写回答