跳轉到

8.4 Fisher 精確檢定與列聯表#

本節目標#

  • 知道 Fisher 1935 的「女士品茶」故事如何催生了精確檢定
  • 理解精確檢定的數學:超幾何分配(呼應 3.4 節)與「固定邊際、條件分布」
  • 手算一個 \(2\times2\) 表裡單一格子的機率,並用 r.fisher_test 驗證
  • 學會什麼時候該用 Fisher、什麼時候用卡方
  • 會讀懂輸出的勝似比(odds ratio)與信賴區間

1935 年:女士品茶#

1935 年,劍橋的一間茶室裡,一位科學家 Muriel Bristol 說:她能分辨一杯茶是先倒奶再倒茶,還是先倒茶再倒奶。她的朋友、統計學家 Ronald Fisher 覺得這是個可以量化的事。

實驗設計:準備 8 杯茶(4 杯先奶、4 杯先茶),隨機打亂,由她盲測猜哪 4 杯是「先奶」的。

Fisher 的問題是:如果她其實完全靠猜,猜對 4 杯的機率有多大?

這就是 Fisher 檢定的起點,而且他的推理方式非常 Fisher:

不要問「資料有多不尋常」,要問「在所有與觀察到的一樣可能(邊際固定)的結果中,觀察到的這個結果有多不尋常」。

這與 7.4 節的卡方檢定形成對比:卡方是「用漸近分配去近似抽樣分布」,Fisher 則是「直接列舉所有可能結果,算出精確的機率」。

為什麼需要精確?#

卡方檢定的 \(\chi^2\) 統計量服從漸近的卡方分配——那個近似在期望次數大時很好,但期望次數小時(例如格裡只有 1、2 個人)會失準。Fisher 的方法沒有近似,所以叫精確檢定。


超幾何:回到 3.4 節#

在 \(2\times2\) 表裡,若行、欄的總數(邊際)固定,那麼四個格子裡其實只有一個是自由的。固定邊際下的格子次數服從超幾何分配——正是 3.4 節推導的那個。

看女士品茶:總共 8 杯,其中「先奶」4 杯。如果 Bristol 猜對 \(x\) 杯,那麼:

$$ P(X = x) = \frac{\binom{4}{x}\binom{4}{4-x}}{\binom{8}{4}}

$$ 手算每一種可能:

import rstatistics as r

m, n, k = 4, 4, 4
xx = 3
p = r.dhyper(xx, m, n, k)
print("P(X = 3) =", p, " (超幾何 C(4,3)C(4,1)/C(8,4) =", r.choose(4,3)*r.choose(4,1)/r.choose(8,4), ")")
print("全部可能格子的機率:")
tot = 0.0
for v in range(0, 5):
    pv = r.dhyper(v, m, n, k)
    tot += pv
    print(f"  X = {v}: P = {pv:.6f}")
print("  總和 =", round(tot, 6))
P(X = 3) = 0.22857142857142917  (超幾何 C(4,3)C(4,1)/C(8,4) = 0.22857142857142856 )
全部可能格子的機率:
  X = 0: P = 0.014286
  X = 1: P = 0.228571
  X = 2: P = 0.514286
  X = 3: P = 0.228571
  X = 4: P = 0.014286
  總和 = 1.0

手算 \(P(X = 3) = \frac{4 \times 4}{70} = \frac{16}{70} = 0.228571\) ,與 r.dhyper 一致。

現在注意:** \(X = 3\) (猜對 3 杯)並不是最不可能的——最不可能的是 \(X = 0\) 與 \(X = 4\) (各 0.014286)。所以「猜對 3 杯」其實沒有很稀奇**(p ≈ 0.229)。這正是 Fisher 檢定要表達的。

Fisher 檢定的 p 值怎麼來#

雙邊 p 值的定義:把所有機率不超過觀察值機率的結果加總。

觀察到 \(X = 3\) ,機率 \(0.228571\) 。所有 \(P \le 0.228571\) 的格子是 \(X = 0, 1, 3, 4\) ( \(X=2\) 的機率 0.514286 太大,不算):

$$ p = 0.014286 + 0.228571 + 0.228571 + 0.014286 = 0.485714

$$ 如果 Bristol 猜對 4 杯( \(X = 4\) ),機率只有 0.014286,那麼所有機率 \(\le 0.014286\) 的結果只有 \(X = 0\) 和 \(X = 4\) 兩個, \(p = 0.028571\) ——顯著。

這是 Fisher 檢定與卡方檢定的根本差異:卡方是「看統計量落在多尾的機率」,Fisher 是「看有多少種可能的表,機率不比我這張更小」。


跑 r.fisher_test#

import rstatistics as r

# 女士品茶:列 = 是否先倒奶,欄 = 是否猜對
tab = [[3, 1], [1, 3]]
print("列聯表(列=是否先倒茶,欄=是否猜對):")
for row in tab:
    print("  ", row)
print()

h = r.fisher_test(tab)
print(h)
print("  h.method         =", h.method)
print("  h.p_value        =", h.p_value)
print("  h.estimate       =", h.estimate)
print("  h.conf_int       =", h.conf_int)
print("  h.alternative    =", h.alternative)
print("  h.null_name      =", h.null_name)
列聯表(列=是否先倒茶,欄=是否猜對):
   [3, 1]
   [1, 3]


    Fisher's Exact Test for Count Data

data:  x
p-value = 0.4857
alternative hypothesis: true odds ratio is not equal to 1
95 percent confidence interval:
   0.2117329 621.9337505
sample estimates:
odds ratio 
  6.408309 

  h.method         = Fisher's Exact Test for Count Data
  h.p_value        = 0.48571428571428693
  h.estimate       = [6.40830886700578]
  h.conf_int       = [0.2117329153055029, 621.9337505454196]
  h.alternative    = two.sided
  h.null_name      = odds ratio

輸出行行解讀:

輸出行 意義
Fisher's Exact Test for Count Data 方法名。Exact 表示沒有任何漸近近似
p-value = 0.4857 就是上面手算的 \(0.485714\) 。不顯著:這個小樣本無法排除「她只是運氣好」
alternative hypothesis: true odds ratio is not equal to 1 零假設是「勝似比 = 1」(即兩變數獨立)
95 percent confidence interval: 勝似比的 95% 信賴區間
sample estimates: odds ratio 6.408309 勝似比的點估計

勝似比(odds ratio)#

\(2\times2\) 表 \(\begin{pmatrix} a & b \\ c & d \end{pmatrix}\) 的勝似比:

$$ \text{OR} = \frac{a \cdot d}{b \cdot c}

$$ 本例 \(\text{OR} = \frac{3 \times 3}{1 \times 1} = 9\) ——但估計值給的是 6.408,為什麼?

因為 6.408 是條件最大概然估計(conditional MLE),不是簡單的比值。Fisher 檢定把邊際固定當成條件,所以估計的是「在邊際固定這個限制下,最可能的那個 OR」。這個估計法在 \(2\times2\) 小樣本裡比朴素比值 \(ad/bc\) 更穩健。區間 \([0.2117, 621.93]\) 極寬,正是小樣本下 OR 估計不穩定的直接證據。

順帶一提: \(r.fisher_test\) 的 or_ 參數可以檢定勝似比不等於 1以外的假設(例如某個有理論根據的 OR 值)。

單邊方向#

import rstatistics as r

tab = [[3, 1], [1, 3]]
print("alternative='less'   :", r.fisher_test(tab, alternative="less").p_value)
print("alternative='greater':", r.fisher_test(tab, alternative="greater").p_value)
alternative='less'   : 0.9857142857142859
alternative='greater': 0.24285714285714333

'less' 對應「x 的 OR 小於 1」(關聯方向為負),'greater' 對應「OR 大於 1」。因為觀察到的 OR 是 6.4(大於 1),所以 greater 方向的 p 值(0.243)小於 less(0.986)。


什麼時候該用 Fisher?——期望次數判準#

卡方檢定的近似在期望次數小時會失準。標準建議是:任何格子的期望次數小於 5,就用 Fisher。

rstatistics 的 chisq_test_table 不直接印出期望次數,但可以自己算(期望次數 = 行總數 × 欄總數 / 總數):

import rstatistics as r

def expected(table):
    rows = [sum(r) for r in table]
    cols = [sum(table[i][j] for i in range(len(table))) for j in range(len(table[0]))]
    total = sum(rows)
    return [[rows[i] * cols[j] / total for j in range(len(cols))] for i in range(len(rows))]

for name, tb in [("[[36,16],[24,24]]", [[36,16],[24,24]]),
                 ("[[15,5],[5,15]]", [[15,5],[5,15]]),
                 ("[[3,2],[1,0]]", [[3,2],[1,0]])]:
    e = expected(tb)
    mn = min(min(r) for r in e)
    print(f"{name} 的期望次數 = {[[round(v,3) for v in row] for row in e]}   最小 = {mn:.3f}")
[[36,16],[24,24]] 的期望次數 = [[31.2, 20.8], [28.8, 19.2]]   最小 = 19.200
[[15,5],[5,15]] 的期望次數 = [[10.0, 10.0], [10.0, 10.0]]   最小 = 10.000
[[3,2],[1,0]] 的期望次數 = [[3.333, 1.667], [0.667, 0.333]]   最小 = 0.333
  • 第一個表:最小期望 19.2 → 卡方可靠
  • 第二個表:最小期望 10.0 → 卡方可靠
  • 第三個表:最小期望 0.333 → 卡方不可靠,必須用 Fisher

三個表並排比較#

import rstatistics as r

big = [[15, 5], [5, 15]]
print("較大的表 [[15,5],[5,15]]:")
print(r.fisher_test(big))
print()
print(r.chisq_test_table(big))
print()
print(r.chisq_test_table(big, correct=False))
print()

small = [[3, 2], [1, 0]]
print("小期望次數的表 [[3,2],[1,0]]:")
print(r.fisher_test(small))
print()
print(r.chisq_test_table(small))
print()
print(r.chisq_test_table(small, correct=False))
較大的表 [[15,5],[5,15]]:

    Fisher's Exact Test for Count Data

data:  x
p-value = 0.003848
alternative hypothesis: true odds ratio is not equal to 1
95 percent confidence interval:
  1.794006 48.338244
sample estimates:
odds ratio 
  8.418596 



    Pearson's Chi-squared test with Yates' continuity correction

data:  x
X-squared = 8.1, df = 1, p-value = 0.004427



    Pearson's Chi-squared test

data:  x
X-squared = 10, df = 1, p-value = 0.001565


小期望次數的表 [[3,2],[1,0]]:

    Fisher's Exact Test for Count Data

data:  x
p-value = 1
alternative hypothesis: true odds ratio is not equal to 1
95 percent confidence interval:
  0.00000 77.90627
sample estimates:
odds ratio 
         0 



    Pearson's Chi-squared test with Yates' continuity correction

data:  x
X-squared = 4.2525e-32, df = 1, p-value = 1



    Pearson's Chi-squared test

data:  x
X-squared = 0.6, df = 1, p-value = 0.4386

三點觀察#

  1. 大表時,Fisher 與卡方結論一致:在 \([[15,5],[5,15]]\) 上 Fisher 的 p = 0.003848,卡方(校正後)p = 0.004427,不校正則 0.001565。都顯著,結論相同。

  2. 小表時,卡方會出狀況: \([[3,2],[1,0]]\) 上,

  3. Fisher 給 p = 1(最誠實的答案:這點資料什麼都無法證明);
  4. 卡方不校正給 p = 0.4386,看起來像「有點顯著」——但這是近似的產物,不是真的;
  5. 卡方有 Yates 校正給 p = 1,與 Fisher 一致,但這是因為這個表太极端( \(X^2 = 4.25\times10^{-32}\) )觸發了退化。

  6. Yates 校正的兩難:R 的 chisq.test 對 \(2\times2\) 表預設套用 Yates 連續性校正,理由正是「小樣本時卡方近似會高估顯著性」。但校正只是「彌補近似」,不是「精確」。如果你在意正確性,就用 Fisher——它不需要任何近似。

小提醒: \(r.fisher_test\) 目前只支援 \(2\times2\) 表(其他尺寸會拋出 ValueError)。 \(r.chisq_test_table\) 則支援任意尺寸。


一個真實感的例子:抽菸與疾病#

import rstatistics as r

smoke = ["抽菸"] * 52 + ["不抽菸"] * 48
sick = ["患病"] * 36 + ["未患病"] * 16 + ["患病"] * 24 + ["未患病"] * 24
print("抽菸與疾病的列聯表:")
print(r.table(smoke, sick, "抽菸", "狀態"))
print()

tab2 = [[36, 16], [24, 24]]
print(r.fisher_test(tab2))
print()
print(r.chisq_test_table(tab2))
抽菸與疾病的列聯表:
     狀態
抽菸    患病 未患病
  不抽菸 24  24
  抽菸  36  16


    Fisher's Exact Test for Count Data

data:  x
p-value = 0.06627
alternative hypothesis: true odds ratio is not equal to 1
95 percent confidence interval:
 0.9220762 5.5305872
sample estimates:
odds ratio 
  2.231334 



    Pearson's Chi-squared test with Yates' continuity correction

data:  x
X-squared = 3.0866, df = 1, p-value = 0.07894

這次期望次數夠大(最小 19.2),兩種方法幾乎一致:Fisher p = 0.0663、卡方 p = 0.0789。兩者都在 0.05 與 0.10 之間,屬於「有點可疑但未達顯著」。這正是統計學最誠實的結論:資料不足以斷定關聯,但也不足以排除。

勝似比 \(2.23\) 表示抽菸者的患病機率比是非抽菸者的 2.23 倍;信賴區間 \([0.92, 5.53]\) 包含 1,所以在 5% 水準下無法拒絕獨立。


與 R 對照#

R 的 fisher.test 與 rstatistics 的 r.fisher_test 幾乎完全一致:

tab <- matrix(c(3,1,1,3), 2, 2, byrow=TRUE)
cat("P(X = 3) =", dhyper(3,4,4,4), "\n")
for (v in 0:4) cat(sprintf("  X = %d: P = %.6f\n", v, dhyper(v,4,4,4)))

print(fisher.test(tab))
cat("alternative='less'   :", fisher.test(tab, alternative="less")$p.value, "\n")
cat("alternative='greater':", fisher.test(tab, alternative="greater")$p.value, "\n")

big <- matrix(c(15,5,5,15),2,2,byrow=TRUE)
print(fisher.test(big))
print(chisq.test(big))
print(chisq.test(big, correct=FALSE))

small <- matrix(c(3,2,1,0),2,2,byrow=TRUE)
print(fisher.test(small))
print(chisq.test(small))
print(chisq.test(small, correct=FALSE))
cat("\n--- 期望次數檢查(chisq.test 的 expected)---\n")
print(chisq.test(big)$expected)
print(chisq.test(small)$expected)
P(X = 3) = 0.2285714 
  X = 0: P = 0.014286
  X = 1: P = 0.228571
  X = 2: P = 0.514286
  X = 3: P = 0.228571
  X = 4: P = 0.014286

    Fisher's Exact Test for Count Data

data:  tab
p-value = 0.4857
alternative hypothesis: true odds ratio is not equal to 1
95 percent confidence interval:
   0.2117329 621.9337505
sample estimates:
odds ratio 
  6.408309 

alternative='less'   : 0.9857143 
alternative='greater': 0.2428571 

    Fisher's Exact Test for Count Data

data:  big
p-value = 0.003848
alternative hypothesis: true odds ratio is not equal to 1
95 percent confidence interval:
  1.794006 48.338244
sample estimates:
odds ratio 
  8.418596 


    Pearson's Chi-squared test with Yates' continuity correction

data:  big
X-squared = 8.1, df = 1, p-value = 0.004427


    Pearson's Chi-squared test

data:  big
X-squared = 10, df = 1, p-value = 0.001565


    Fisher's Exact Test for Count Data

data:  small
p-value = 1
alternative hypothesis: true odds ratio is not equal to 1
95 percent confidence interval:
  0.00000 77.90627
sample estimates:
odds ratio 
         0 


    Pearson's Chi-squared test with Yates' continuity correction

data:  small
X-squared = 4.2525e-32, df = 1, p-value = 1

Warning message:
In chisq.test(small) : Chi-squared approximation may be incorrect

    Pearson's Chi-squared test

data:  small
X-squared = 0.6, df = 1, p-value = 0.4386

Warning message:
In chisq.test(small, correct = FALSE) :
  Chi-squared approximation may be incorrect

--- 期望次數檢查(chisq.test 的 expected)---
     [,1] [,2]
[1,]   10   10
[2,]   10   10
          [,1]      [,2]
[1,] 3.3333333 1.6666667
[2,] 0.6666667 0.3333333
Warning message:
In chisq.test(small) : Chi-squared approximation may be incorrect

對照重點#

項目 R rstatistics
精確檢定 fisher.test(tab) r.fisher_test(tab)
指定勝似比 fisher.test(tab, odds.ratio = 2) r.fisher_test(tab, or_=2.0)
單邊 alternative="less"/"greater" 同名參數
關閉信賴區間 conf.int=FALSE conf_int=False
卡方對照 chisq.test(tab) r.chisq_test_table(tab)
期望次數 chisq.test(tab)$expected 需自算

R 的一個實用提醒:當期望次數太小時,R 會印出

Warning message:
In chisq.test(small) : Chi-squared approximation may be incorrect

這個警告就是 R 對你說「改用 fisher.test」。rstatistics 目前不會印這種警告,所以要自己檢查期望次數。


小結#

  • Fisher 1935 由「女士品茶」實驗發展出精確檢定:在固定邊際的條件下,用超幾何分配算出每一種可能結果的機率。
  • p 值定義為「所有機率不超過觀察值的結果的機率總和」;這比「統計量多尾」更直觀,也不需要任何近似。
  • r.fisher_test 給出 p 值、勝似比(conditional MLE)與其信賴區間;or_ 可檢定非 1 的勝似比。
  • 判準:任何格子的期望次數 < 5 → 用 Fisher;否則卡方就夠了。
  • 大樣本時兩者結論一致;小樣本時只有 Fisher 是精確的,卡方的 p 值是近似的產物。
  • 注意:Fisher 給的是勝似比(機率比)不是風險比(比例比),兩者在常見病例率下數值相近但概念不同。

練習#

  1. 女士品茶若 Bristol 猜對 4 杯( \(X = 4\) ),Fisher 的雙邊 p 值會是多少?手算(用 r.dhyper 驗證)並說明為什麼比 \(X = 3\) 的 p 值小。
  2. 用 r.dhyper 列出「10 人中抽 5 人,其中 3 人是病例」的完整機率分布(m = 5, n = 5, k = 5),確認總和為 1。
  3. 造三個 \(2\times2\) 表,讓最小期望次數分別約為 4、1、0.2。對每個表同時跑 r.fisher_test 與 r.chisq_test_table,比較 p 值;找出「卡方與 Fisher 結論相反」的情況。
  4. 把 [[15,5],[5,15]] 擴大成 \(3\times3\) 的表(例如年齡三層 × 治療三組)。r.fisher_test 會怎樣反應?r.chisq_test_table 的自由度變成多少?
  5. 承 4,對 \(3\times3\) 的表用 r.chisq_test_table(不帶 correct)並用手算驗證期望次數。當某格期望次數 < 5 時,你會怎麼處理這個表?提示:可以合併列。