跳轉到

7.3 兩樣本 t 檢定與 Welch 檢定#

本節目標#

  • 分辨「兩組獨立樣本」與「同一批受試者的前後測」兩種設計
  • 理解 pooled(合併變異數) 與 Welch(不等變異數) 兩種 t 檢定的差異
  • 先用 r.var_test 檢查變異數是否同質,再決定該用哪一版
  • 用模擬親眼看到:變異數不等時用錯檢定會嚴重扭曲第一類錯誤率
  • 掌握 paired=True 配對設計,正確利用同一受試者提供的配對資訊

問題:新舊兩條生產線,效率差多少?#

上一節的單樣本 t 檢定只能回答「一組資料的平均是否等於某個值」。實務上更常遇到的問題是:兩組資料的平均差多少?

兩條螺絲生產線,新線(供應商 A)與舊線(供應商 B),各抽一批零件量測外徑(mm):

A: 12.1  12.4  11.9  12.3  12.0  12.2  11.8  12.5  12.1  12.0  11.9  12.3
B:  9.8  11.6  12.9  10.4  11.2  13.1   9.5  12.4  10.8  11.7

一眼看過去:\(\bar{x}_A = 12.125\)、\(\bar{x}_B = 11.340\),新線平均外徑大了 0.785 mm。但 A 組的資料集中得可怕(12 個值擠在 11.8–12.5 之間),B 組卻從 9.5 散到 13.1。這個「變異數差很多」的現象,正是本節的核心。


理論:pooled 與 Welch 的分水嶺#

1908:Student 的兩樣本檢定(假設等變異數)#

Student(Gosset)在 1908 年只處理了單樣本。兩樣本的情形後來沿用一個自然的做法:把兩組的樣本變異數「合併」成一個共同變異數 \(s_p^2\):

$$ s_p^2 = \frac{(n_1-1)s_1^2 + (n_2-1)s_2^2}{n_1+n_2-2}, \qquad s_p = \sqrt{s_p^2\left(\frac{1}{n_1}+\frac{1}{n_2}\right)}

$$ 檢定統計量

$$ T = \frac{\bar{X}_1-\bar{X}2}{s_p} \;\sim\; t

$$ 這就是 pooled t 檢定(var_equal=True)。它成立的前提是:\(\sigma_1^2 = \sigma_2^2\)。用上式把兩個變異數混在一起,正等於宣告「兩組來自同一個變異數的母體」。

1947:Welch 的不等變異數檢定#

Welch 在 1947 年《Biometrika》論文〈The generalization of "Student's" problem when several different population variances are involved〉中指出:真實研究裡兩組變異數很少真的相等,於是提出不假設等變異數的版本。

Welch 檢定各組用自己的標準誤:

$$ s_W = \sqrt{\frac{s_1^2}{n_1}+\frac{s_2^2}{n_2}}, \qquad T = \frac{\bar{X}_1-\bar{X}_2}{s_W}

$$ 但麻煩在於:\(T\) 不再服從精確的 \(t\) 分配。Welch 用 Welch–Satterthwaite 近似自由度解決:

$$ \nu \approx \frac{\left(\frac{s_1^2}{n_1}+\frac{s_2^2}{n_2}\right)^2} {\frac{(s_1^2/n_1)^2}{n_1-1}+\frac{(s_2^2/n_2)^2}{n_2-1}}

$$ 這個自由度通常不是整數(例如 9.4654),也通常小於 pooled 的 \(n_1+n_2-2\)。自由度假設小 = 尾巴更厚 = p 值更大 = 更保守。

兩者的取捨#

pooled(var_equal=True) Welch(var_equal=False,預設)
假設 \(\sigma_1^2 = \sigma_2^2\) 不需要
標準誤 合併後的 \(s_p\) 各自 \(s_1^2/n_1 + s_2^2/n_2\)
自由度 \(n_1+n_2-2\)(整數) Welch–Satterthwaite(小數)
變異數真的相等時 略為保守 幾乎無損
變異數不等時 第一類錯誤率可能嚴重失真 幾乎不受影響

Welch 檢定幾乎永遠是安全的選擇:變異數真的相等時它只損失極少檢定力,變異數不等時它救你。所以 R 的 t.test 從 R 3.x 起預設就是 Welch。

1924:Fisher 的 F 檢定與「先檢後選」的流派#

另一條路線是先做 F 檢定(r.var_test)檢查 \(H_0: \sigma_1^2/\sigma_2^2 = 1\),若拒絕就改用 Welch,不拒絕就用 pooled。F 檢定的統計量是 Fisher 在 1924 年引進的:

$$ F = \frac{s_1^2}{s_2^2} \sim F_{n_1-1,\;n_2-1} \quad (H_0 \text{ 為真時}) ]

現在的統計學界已不太推薦「先檢後選」(pretest),因為兩階段程序本身的錯誤率不是 5%(第一階段的 F 檢定也會犯錯),而且在真實研究裡變異數通常「略有差異」,而 Welch 對此完全免疫。實務建議:直接用 Welch,但仍可用 r.var_test 描述你的資料(「兩組變異數是否同質」本身是個有價息的描述性結果)。


動手:跑一遍兩種檢定#

第一步:先看變異數#

import rstatistics as r
A = [12.1,12.4,11.9,12.3,12.0,12.2,11.8,12.5,12.1,12.0,11.9,12.3]
B = [9.8,11.6,12.9,10.4,11.2,13.1,9.5,12.4,10.8,11.7]

print(r.var_test(A, B))

$$
```text

    F test to compare two variances

data:  x and y
F = 0.030969, num df = 11, denom df = 9, p-value = 2.393e-06
alternative hypothesis: true ratio of variances is not equal to 1
95 percent confidence interval:
 0.007916333 0.111114654
sample estimates:
ratio of variances 
 0.03096928 

F = 0.031:\(s_A^2/s_B^2\) 只有 3%,p 值 2.4e-06,變異數絕對不相等。所以本題必須用 Welch。(順帶一提,r.var_test 的 p 值用的是雙尾,所以「B 組變異數比較大」這個方向資訊要看 ratio of variances 才會知道。)

第二步:pooled 版(錯誤的選擇)#

print(r.t_test(A, B, var_equal=True))
    Two Sample t-test

data:  x and y
t = 2.1662, df = 20, p-value = 0.04255
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 0.02906846 1.54093154
sample estimates:
mean of x mean of y 
   12.125    11.340 

p = 0.0426 < 0.05 → 「顯著差異」。

第三步:Welch 版(正確的選擇)#

print(r.t_test(A, B))
    Welch Two Sample t-test

data:  x and y
t = 1.979, df = 9.4654, p-value = 0.07761
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 -0.1056184  1.6756184
sample estimates:
mean of x mean of y 
   12.125    11.340 

p = 0.0776 > 0.05 → 不顯著。

兩個結論不一樣:這就是重點#

pooled Welch
t 值 2.1662 1.979
df 20 9.4654
p 值 0.04255 0.07761
95% CI [0.029, 1.541] [−0.106, 1.676]
結論 顯著 不顯著

同一份資料,選錯檢定會讓結論從「顯著」翻成「不顯著」——這在實務上就是「宣布新製程有效」與「宣布沒證據」的差別。

手算驗證:\(s_A^2 = 0.0475\)、\(s_B^2 = 1.6372\)。

  • pooled:\(s_p^2 = (11\times0.0475 + 9\times1.6372)/20 = 0.8423\),\(s_p = \sqrt{0.8423(1/12+1/10)} = 0.36239\),\(T = 0.785/0.36239 = 2.1662\),\(df = 20\)。
  • Welch:\(s_W = \sqrt{0.0475/12 + 1.6372/10} = 0.39666\),\(T = 0.785/0.39666 = 1.979\),自由度 \(\nu = 9.4654\)。

關鍵:Welch 的標準誤(0.3967)比 pooled(0.3624)大,因為它承認 B 組的變異數大;於是 t 值變小、df 變小、p 值變大。pooled 版則是把 B 組的大變異數攤平到 A 組身上,假裝兩組一樣穩,於是低估了不確定性。

單尾版本#

若我們只關心「新線是否比較粗」(\(H_1: \mu_A - \mu_B > 0\)):

print(r.t_test(A, B, alternative='greater'))
    Welch Two Sample t-test

data:  x and y
t = 1.979, df = 9.4654, p-value = 0.03881
alternative hypothesis: true difference in means is greater than 0
95 percent confidence interval:
 0.06193356        Inf
sample estimates:
mean of x mean of y 
   12.125    11.340 

p = 0.0388 < 0.05 變成顯著了——但這是方向事先就有的(品管規定的公差上限),不是看到資料才選的方向。信賴區間也從雙邊變成單邊 [0.0619, ∞)。


動手:配對設計(paired=True)#

問題:同一批人量兩次#

另一種常見設計是配對(paired):每個受試者提供兩個值,兩個值之間有自然的配對關係。

例子:12 位高血壓患者,記錄服降壓藥前與服藥後的收縮壓(mmHg):

before: 148 132 141 155 129 138 150 144 136 147 133 142
after:  132 124 128 140 120 130 138 135 126 136 125 130

這 12 對資料不是獨立的:每位患者服藥前的血壓高低,會強烈影響他服藥後的血壓高低(體質、年齡、原有疾病)。每位患者本身就是自己的對照組。

為什麼配對能省很多變異數?#

未配對的 Welch 檢定把「人與人之間的體質差異」也算進標準誤;配對檢定則先把每位患者前後相減,只留下「服藥前後的變化」這個訊號:

before = [148,132,141,155,129,138,150,144,136,147,133,142]
after  = [132,124,128,140,120,130,138,135,126,136,125,130]

d = [before[i] - after[i] for i in range(12)]
print("平均下降", r.mean(d), " 差值的標準差", r.sd(d))
print("組內 sd(before)", r.sd(before), " 組內 sd(after)", r.sd(after))
平均下降 10.916666666666666  差值的標準差 2.7455197664338145
組內 sd(before) 7.944409126148802  組內 sd(after) 6.095204274797947

差值的標準差只有 2.75,遠小於各組內的 7.94 / 6.10。標準誤變小,檢定力就上去。

正確做法:配對 t 檢定#

print(r.t_test(before, after, paired=True))
    Paired t-test

data:  x and y
t = 13.774, df = 11, p-value = 2.787e-08
alternative hypothesis: true mean difference is not equal to 0
95 percent confidence interval:
  9.172247 12.661087
sample estimates:
mean difference 
       10.91667 

平均降壓 10.92 mmHg(95% CI:9.17 至 12.66),p = 2.8e-08,證據極強。

手算:\(T = \bar{d}/(s_d/\sqrt{12}) = 10.9167/(2.7455/\sqrt{12}) = 13.774\),\(df = 12-1 = 11\)。配對 t 檢定在數學上就是「對差值做單樣本 t 檢定」。

錯誤做法:當成兩個獨立樣本#

print(r.t_test(before, after))          # 忘了 paired=True
    Welch Two Sample t-test

data:  x and y
t = 3.7766, df = 20.618, p-value = 0.001136
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
  4.89859 16.93474
sample estimates:
mean of x mean of y 
 141.2500  130.3333 

p = 0.00114 而非 2.8e-08,CI 寬了將近兩倍([4.90, 16.93] vs [9.17, 12.66])。結論方向一樣,但證據強度差了一個數量級,估計精度也差很多。忘了 paired=True 是實務上最常見的統計錯誤之一。

判斷準則:兩組資料是「同一批受試者先後兩次量測」或「同一對象的兩個部件」,就是配對;是「兩批不同的受試者」或「兩台不同機器」,就是獨立。獨立資料硬套 paired=True 會出錯(長度不同時直接報錯),配對資料忘了 paired=True 只是浪費檢定力。


動手:模擬——用錯檢定會怎樣?#

上面用真實資料看到了「結論翻轉」。現在用模擬量化風險有多大:\(H_0\) 為真(兩組平均相同)時,兩種檢定的第一類錯誤率各是多少?

def sim(n1, n2, sd1, sd2, N=5000, seed=11):
    g = r.set_seed(seed)
    rp = rw = 0
    for i in range(N):
        x = g.rnorm(n1, 0.0, sd1)
        y = g.rnorm(n2, 0.0, sd2)
        if r.t_test(x, y, var_equal=True).p_value < 0.05:
            rp += 1
        if r.t_test(x, y).p_value < 0.05:
            rw += 1
    return rp / N, rw / N

for (n1, n2, sd1, sd2) in [(30,10,1,3), (15,15,1,4), (10,30,1,3)]:
    p_pool, p_welch = sim(n1, n2, sd1, sd2)
    print(f"n1={n1}, n2={n2}, sd1={sd1}, sd2={sd2}")
    print(f"  pooled 拒絕率 = {p_pool:.4f}")
    print(f"  Welch  拒絕率 = {p_welch:.4f}")
n1=30, n2=10, sd1=1, sd2=3
  pooled 拒絕率 = 0.2122
  Welch  拒絕率 = 0.0514
n1=15, n2=15, sd1=1, sd2=4
  pooled 拒絕率 = 0.0558
  Welch  拒絕率 = 0.0490
n1=10, n2=30, sd1=1, sd2=3
  pooled 拒絕率 = 0.0034
  Welch  拒絕率 = 0.0480

這三個情境是本節最值得記住的部分:

情境 A(\(n_1=30, n_2=10\)):pooled 的第一類錯誤率是 21.2%!#

目標是 5%,pooled 卻拒絕了 21.2% 的時間——每五次就有一次假陽性。原因是:\(s_p\) 把 B 組(\(\sigma=3\))的大變異數攤到 A 組(\(\sigma=1\))身上,而 A 組樣本多、B 組樣本少,於是 A 組那個被低估的標準誤主導了整個檢定。Welch 則維持 5.14%,幾乎完美。

情境 B(兩組樣本數相同):pooled 只略微偏高#

\(n_1=n_2=15\) 時,pooled 的拒絕率 5.58%,只比 5% 高一點。這解釋了為什麼 pooled t 檢定在教科書裡流行了一百年——很多研究的兩組樣本數恰好相近,錯用造成的偏差不大,問題就被掩蓋了。

情境 C(\(n_1=10, n_2=30\)):pooled 變得過度保守#

拒絕率只有 0.34%,遠低於 5%。同樣是「變異數不等 + 樣本數不等」,只是把大小組對調,pooled 就從過度自由翻成過度保守。這說明 pooled 的第一類錯誤率不是單純的「偏高」,而是方向會隨 \(n_1\) 與 \(n_2\) 的對比而改變——完全不可預期。Welch 三個情境都穩穩地停在 5% 附近。

結論:只要你能預期兩組變異數可能不同,就永遠用 Welch。 這不是「比較保守所以比較安全」的模糊建議,而是一個在模擬中被反覆驗證的具體結論。


與 R 對照#

R 的 t.test 與 var.test 是這些檢定的原廠實作:

A <- c(12.1,12.4,11.9,12.3,12.0,12.2,11.8,12.5,12.1,12.0,11.9,12.3)
B <- c(9.8,11.6,12.9,10.4,11.2,13.1,9.5,12.4,10.8,11.7)
var.test(A, B)
t.test(A, B, var.equal = TRUE)
t.test(A, B)
    F test to compare two variances

data:  A and B
F = 0.030969, num df = 11, denom df = 9, p-value = 2.393e-06
alternative hypothesis: true ratio of variances is not equal to 1
95 percent confidence interval:
 0.007916333 0.111114654
sample estimates:
ratio of variances 
 0.03096928 

    Two Sample t-test

data:  A and B
t = 2.1662, df = 20, p-value = 0.04255
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 0.02906846 1.54093154
sample estimates:
mean of x mean of y 
   12.125    11.340 

    Welch Two Sample t-test

data:  A and B
t = 1.979, df = 9.4654, p-value = 0.07761
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 -0.1056184  1.6756184
sample estimates:
mean of x mean of y 
   12.125    11.340 

與 rstatistics 的輸出數值一字不差。參數名也對應:R 的 var.equal = TRUE 對 Python 的 var_equal=True;R 的 t.test 預設 var.equal = FALSE,即 Welch。

配對版本:

before <- c(148,132,141,155,129,138,150,144,136,147,133,142)
after  <- c(132,124,128,140,120,130,138,135,126,136,125,130)
t.test(before, after, paired = TRUE)
    Paired t-test

data:  before and after
t = 13.774, df = 11, p-value = 2.787e-08
alternative hypothesis: true mean difference is not equal to 0
95 percent confidence interval:
  9.172247 12.661087
sample estimates:
mean difference 
       10.91667 

模擬部分,R 用 set.seed(11) 重跑同一組情境:

run <- function(n1,n2,sd1,sd2,N=5000){
  set.seed(11)
  rp<-0; rw<-0
  for(i in 1:N){
    x<-rnorm(n1,0,sd1); y<-rnorm(n2,0,sd2)
    if(t.test(x,y,var.equal=TRUE)$p.value<0.05) rp<-rp+1
    if(t.test(x,y)$p.value<0.05) rw<-rw+1
  }
  c(rp/N, rw/N)
}
run(30,10,1,3); run(15,15,1,4); run(10,30,1,3)
[1] 0.2122 0.0514
[1] 0.0558 0.0490
[1] 0.0034 0.0480

與 Python 版逐位元相同——rstatistics 的亂數與 R 的 set.seed 位元等價,所以連模擬都能完全重現。


小結#

  • pooled t 檢定(var_equal=True)假設 \(\sigma_1^2 = \sigma_2^2\),把兩個變異數合併,df = \(n_1+n_2-2\);Welch 檢定(預設)不需這個假設,df 由 Welch–Satterthwaite 近似得到(常是小數)。
  • 變異數不等時,pooled 會低估標準誤,第一類錯誤率可能高達 21% 或低到 0.3%,方向不可預期;Welch 穩穩維持 5%。
  • 永遠用 Welch(除非你有明確理由相信等變異數);r.var_test 仍值得跑,但它是「描述」而非「選擇檢定的關卡」。
  • 配對設計(paired=True)把每個受試者當自己的對照,消掉個體差異:降壓例子中 p 值從 1.1e-03 變成 2.8e-08,CI 寬度減半。
  • 忘了 paired=True 不會讓結論方向反轉,但會嚴重低估證據強度與估計精度。

練習#

  1. 對本節的 A、B 兩組資料,把 var_equal=True 換成 alternative='greater' 再算一次。p 值變成多少?為什麼單尾 p 值恰是雙尾的一半?提示:\(t = 2.1662\)、\(df = 20\);因為 A 組平均比 B 組大,\(H_1: \mu_A > \mu_B\) 的極端機率全部落在右尾,雙尾把它一半分給左尾。
  2. 用 r.var_test 的 estimate 估計 \(\sigma_B^2/\sigma_A^2\)。若真實比值是 30,樣本估計值離它多遠?提示:ratio of variances = 0.030969 是 \(s_A^2/s_B^2\) 的估計,所以 \(\sigma_B^2/\sigma_A^2 \approx 1/0.030969 \approx 32.3\)。
  3. 把情境 A 的模擬改成 sd2=2(變異數比 4 而非 9),pooled 的拒絕率變成多少?說明為什麼變異數差距縮小後,pooled 的問題也變輕。提示:變異數比越小,\(s_p\) 的「攤平誤差」越小。
  4. 用配對資料 before/after 計算:若只取前 6 位患者做配對檢定,p 值會變成多少?把同樣的 6 位患者當成兩個獨立樣本來檢定,p 值又是多少?比較兩者。提示:配對的優勢來自差值的小變異;樣本變少後 df = 5,\(t_5\) 尾巴很厚。
  5. 設計一個你實習/工作上看過的情境,說明它該用 pooled、Welch 還是配對,並寫出對應的 r.t_test 呼叫。提示:先問「兩組是否同一批對象的前後兩次」;再問「變異數有理由相等嗎」。