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
三點觀察#
-
大表時,Fisher 與卡方結論一致:在 \([[15,5],[5,15]]\) 上 Fisher 的 p = 0.003848,卡方(校正後)p = 0.004427,不校正則 0.001565。都顯著,結論相同。
-
小表時,卡方會出狀況: \([[3,2],[1,0]]\) 上,
- Fisher 給 p = 1(最誠實的答案:這點資料什麼都無法證明);
- 卡方不校正給 p = 0.4386,看起來像「有點顯著」——但這是近似的產物,不是真的;
-
卡方有 Yates 校正給 p = 1,與 Fisher 一致,但這是因為這個表太极端( \(X^2 = 4.25\times10^{-32}\) )觸發了退化。
-
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 給的是勝似比(機率比)不是風險比(比例比),兩者在常見病例率下數值相近但概念不同。
練習#
- 女士品茶若 Bristol 猜對 4 杯( \(X = 4\) ),Fisher 的雙邊 p 值會是多少?手算(用
r.dhyper驗證)並說明為什麼比 \(X = 3\) 的 p 值小。 - 用
r.dhyper列出「10 人中抽 5 人,其中 3 人是病例」的完整機率分布(m = 5, n = 5, k = 5),確認總和為 1。 - 造三個 \(2\times2\) 表,讓最小期望次數分別約為 4、1、0.2。對每個表同時跑
r.fisher_test與r.chisq_test_table,比較 p 值;找出「卡方與 Fisher 結論相反」的情況。 - 把
[[15,5],[5,15]]擴大成 \(3\times3\) 的表(例如年齡三層 × 治療三組)。r.fisher_test會怎樣反應?r.chisq_test_table的自由度變成多少? - 承 4,對 \(3\times3\) 的表用
r.chisq_test_table(不帶correct)並用手算驗證期望次數。當某格期望次數 < 5 時,你會怎麼處理這個表?提示:可以合併列。