條件式邏輯迴歸
配對之後為什麼不能用一般邏輯迴歸、strata() 到底在條件掉什麼、把配對變項放回模型會發生什麼事(答案是靜默地什麼都不發生),以及 1:1 配對時它與 McNemar 檢定的四個數值關係。
這個模型在解決什麼問題
配對(matching)是設計階段用來對付干擾的手段。做法是替每個 case 挑一或多個在指定變項上 (年齡、性別、就診年份、同一家醫院)與他相同或相近的 control,讓那些變項在設計上就不可能造成偏差。
但配對是一個要付利息的決定。 資料一旦配對,各個配對組(matched set)內部的人就不再彼此獨立,
而一般邏輯迴歸假設全部觀察值獨立。
把配對好的資料丟進 glm(..., family = binomial),等於把配對這件事整個丟掉。
條件式邏輯迴歸(conditional logistic regression)的做法是:只在配對組內部比較, 不讓不同組的人互相比較。技術上是把每一組自己的截距(也就是「這一組整體有多容易成為 case」) 從概似函數裡條件掉——這也是它叫「條件式」的原因。
一個有 個 control 的配對組,對概似函數的貢獻是「這一組裡偏偏是這個人成為 case」的機率:
這一條式子把本頁後面三件事全部解釋掉了,值得先記住它長什麼樣子:分子只有 case, 分母是整組人,而組別本身完全沒有出現在式子裡。
這一頁的例子
datasets::infert 是一份繼發性不孕症的配對病例對照研究,隨 R 本體一起出貨,不必安裝任何套件。
248 列、83 個配對組、每組恰好一個 case,
配對變項是年齡、產次與教育程度。暴露是自發性流產(spontaneous)與人工流產(induced)的次數。
同一份資料也是病例對照研究那一章的例子。 那一章講的是設計本身;這一頁只講模型。
library(survival)
data(infert)
# 正確的模型:分層項就是配對組
cond <- clogit(case ~ spontaneous + induced + strata(stratum), data = infert)
summary(cond)
# 錯誤的模型:右邊完全一樣,只是把配對丟掉
naive <- glm(case ~ spontaneous + induced, data = infert, family = binomial)
exp(cbind(OR = coef(naive), confint.default(naive)))
# 把配對變項放回模型,看看會發生什麼事
clogit(case ~ spontaneous + induced + age + parity + strata(stratum), data = infert)驗證環境:R 4.6.0 + survival 3.8.6。clogit() 是 survival 套件的函式,內部呼叫的是 coxph(),所以報表看起來會像存活分析——這是實作方式,不是筆誤。
import numpy as np
import statsmodels.api as sm
from statsmodels.discrete.conditional_models import ConditionalLogit
inf = sm.datasets.get_rdataset("infert").data
X = inf[["spontaneous", "induced"]]
cond = ConditionalLogit(inf["case"], X, groups=inf["stratum"]).fit()
print(cond.summary())
print(np.exp(cond.params)) # 條件 OR
# 忽略配對的版本,用來對照
naive = sm.Logit(inf["case"], sm.add_constant(X)).fit()
print(np.exp(naive.params))statsmodels 有 ConditionalLogit,估計值與 R 一致,但診斷與後續工具遠不如 survival 完整;配對分析建議在 R 端做。
忽略配對會發生什麼事
兩個模型的右邊一模一樣,唯一的差別是有沒有 strata(stratum)。
這樣比較才乾淨:任何差異都只能歸給「有沒有尊重配對」,不會混進「多放了一個共變項」。
figures/scripts/B2-10-conditional-logistic.R| 變項 | 條件式 OR | 忽略配對 OR | 倍數 | 條件式 SE | 忽略配對 SE |
|---|---|---|---|---|---|
自發性流產次數(spontaneous) | 7.285 | 3.311 | 2.20 | 0.352 | 0.212 |
人工流產次數(induced) | 4.092 | 1.519 | 2.69 | 0.361 | 0.206 |
自發性流產的 OR 從條件式的 7.285 掉到 3.311,差了一倍以上,而且是往 1 的方向掉。 人工流產同樣往 1 掉(4.092 → 1.519)。 這個方向不是巧合:配對讓 case 與 control 在配對變項上長得像, 如果把那些人混在一起當成獨立樣本比較,暴露與結果的關聯就會被稀釋。
strata() 到底在做什麼
回到上面那條概似函數。把它看成一個比賽:組裡每個人依 拿到一個分數, 式子問的是「分數這樣分配之下,偏偏是 case 那個人中選的機率」。
如果一整組人的暴露完全相同,每個人的分數就一樣,那個機率永遠是 , 不管 是多少。 這樣的組對概似函數是一個常數,對估計的貢獻是 0,不是「很少」。
這件事可以直接量。本頁主模型(spontaneous + induced,兩個都是次數)底下,
一組要完全沒有貢獻,條件是組內每個人的兩個次數都一樣。
83 個配對組裡有
7 組符合,
剩下 76 組
(228 列)才帶得動估計。
把那 7 組整個刪掉再配一次:
| 變項 | 全部 83 組的係數 | 只用 76 組的係數 | 全部組的 SE | 只用那些組的 SE |
|---|---|---|---|---|
| 自發性流產次數 | 1.985876 | 1.985876 | 0.3524 | 0.3524 |
| 人工流產次數 | 1.409012 | 1.409012 | 0.3607 | 0.3607 |
係數與標準誤的最大差距分別是 1.1e-15 與 5.6e-16——那是浮點數的機器精度,不是真的差異。 那 7 組完全沒有進到答案裡。
下面這張圖畫的不是主模型,而是一個看得懂的簡化版:
clogit(case ~ spont_any + strata(stratum))。它和主模型差兩件事——
暴露壓成二元(有過/沒有過自發性流產),而且 induced 不在模型裡。
條件放寬了,符合「整組相同」的組數就多了:
28 組整組相同、
55 組有變異。
道理一樣,只是這個版本畫得出來(一格塗色或不塗色),而次數畫不出來。
在這個簡化模型上,全部組與只用有變異的組配出來的係數都是
1.653873。
figures/scripts/B2-10-conditional-logistic.R配對變項不可以再放進模型
這是配對分析最容易犯、也最難自己發現的錯。看看把配對變項加回去會怎樣:
| 加進模型的變項 | 它自己的係數 | 它的標準誤 | spontaneous 的係數 | 配適後 log-likelihood | R 講了什麼 |
|---|---|---|---|---|---|
age | NA | 0 | 1.98587552 | -64.20223692 | (什麼都沒講) |
parity | NA | 0 | 1.98587552 | -64.20223692 | (什麼都沒講) |
education | NA | 0 | 1.98587552 | -64.20223692 | (什麼都沒講) |
age + parity + education | NA | 0 | 1.98587552 | -64.20223692 | (什麼都沒講) |
沒有加任何配對變項時,spontaneous 的係數是
1.98587552,
log-likelihood 是 -64.20223692。
上表每一列都與它完全相同:最大變化量是
0(係數)與
0(log-likelihood)。
不是「接近」,是 0。
原因還是那條概似函數。配對變項在組內是常數, 裡屬於它的那一項 在分子與分母同時出現、同時約掉,所以它根本沒有辦法改變任何東西。 資料裡沒有任何資訊可以用來估它。
1:1 配對時,它與 McNemar 的關係
配對成 1:1(每個 case 一個 control)而暴露是二元時,條件式邏輯迴歸會退化成一個手算得出來的東西。
把每一對按「case 有沒有暴露」乘「control 有沒有暴露」分成四格:
| control 有暴露 | control 沒暴露 | |
|---|---|---|
| case 有暴露 | 17(一致) | 38(b) |
| case 沒暴露 | 8(c) | 20(一致) |
對角線上那 37 對,兩個人的暴露一樣, 依上一節的道理對估計沒有貢獻。剩下 46 對不一致對(discordant pairs) 就是全部的資訊,而條件 OR 正好是這兩格的比值:
實際跑出來:38 除以
8 等於
4.750,
而 clogit() 給的 OR 是 4.750。
兩者相差 1.8e-8,
那是最佳化演算法的收斂容差,不是真的差異。
反過來說,這也是為什麼多數情況下不該只做 McNemar。 McNemar 只吃一個二元暴露,沒有辦法校正其他變項,也處理不了 1:M 配對或連續型暴露。 條件式邏輯迴歸做得到全部這些,而在 McNemar 適用的那個特例上又給出同一個答案。
常見誤用
| 誤用 | 為什麼錯 |
|---|---|
| 配對資料用一般邏輯迴歸 | 配對組內不獨立;估計往 1 偏,而 p 值不會警告你 |
| 用 p 值變化判斷有沒有漏掉配對 | p 值兩個方向都可能動,看設計文件才可靠 |
| 把配對變項當共變項放進模型 | 組內是常數,係數回 NA 而且完全沒有警告 |
看到那一欄是 NA 就把 strata() 拿掉 | 退回錯誤模型;正確做法是把配對變項移出模型 |
沒有檢查 coef() 有沒有 NA 就報表 | 這是唯一會提醒你變項被丟掉的地方 |
| 以為配對組數越多、配得越緊越好 | 過度配對會讓組內暴露也一致,那些組貢獻是零 |
| 用列數當樣本量講檢定力 | 有效資訊量是「組內有變異」的組數 |
| 說「條件式邏輯迴歸等於 McNemar」而不指名檢定 | 未校正 McNemar 才等於 score 檢定,預設是有校正的 |
| 只報 McNemar 就結束 | 校正不了其他變項,也處理不了 1:M 與連續暴露 |
| 把配對病例對照的 OR 當成 RR 讀 | 病例對照的分母是研究者挑的,見病例對照研究 |
| 把 1:M 的資料硬拆成 1:1 來做 McNemar | 丟掉其他 control 的資訊;直接用 clogit() 即可 |
| 配對變項在分析階段「再校正一次」以求保險 | 不會更保險,只會讓報表出現看不懂的空欄 |
重跑本頁的所有數字
/opt/homebrew/bin/Rscript figures/scripts/B2-10-conditional-logistic.R讀讀看這張圖
答案取自產生本頁圖表的同一份統計輸出,不是另外打上去的。
同一份配對資料,conditional logistic 與忽略配對的一般 logistic 給出不同的 odds ratio。忽略配對做了什麼?
看答案與解析
正確答案: 得到 7.29——這是 conditional 模型的值,忽略配對後估計會被拉低到不及它的一半
conditional 模型給 7.29,忽略配對的一般 logistic 給 3.31,掉了一半以上。忽略配對不是「少校正一點」而已:配對變項決定了誰會被選進來當對照,忽略它等於忽略選案機制,估計因此被往一的方向拉。4.09 是 conditional 模型裡另一項(每多一次人工流產),不是同一個對比。在配對研究裡,配對是設計的一部分,分析必須跟著它走。
83 個配對組裡,有 7 組組內每個人的暴露完全相同。它們對 conditional logistic 的估計有什麼貢獻?
看答案與解析
正確答案: 這 7 組完全沒有貢獻,條件概似對它們是一個常數
組內暴露完全一致的 7 組對條件概似只貢獻一個常數,等於不存在——conditional logistic 只用得上組內有差異的 76 組。83 是總組數,把它當成有效樣本數會高估這份資料能支撐多少參數。這是為什麼「我有兩百多筆資料」在配對研究裡不是有效樣本數的說法:該數的是有差異的組,不是列。
把每組取一個 case 與一個 control 湊成 1:1 配對之後,McNemar 檢定實際用到的是哪些配對?
看答案與解析
正確答案: 不一致配對,共 46 對——只有兩人暴露不同的配對帶得動估計
McNemar 檢定與由配對算出來的 odds ratio 都只用得上不一致的 46 對。一致的 37 對再多,對估計值一點影響也沒有,它們只是讓樣本數看起來比較大——檢定力取決於不一致配對的數目,不是總配對數 83。看到配對研究報出一個很大的 n,要問的是其中有多少對是不一致的。
用到這個方法的章節
素材來源與授權
本頁為原創內容