邏輯迴歸與勝算比
OR 到底是什麼比值、結果不罕見的時候它為什麼一定會誇大、校正後的 OR 與粗 OR 差在哪裡(不只是干擾)、完全分離長什麼樣子怎麼修,以及 EPV 至少 10 這個規矩的來歷與它被質疑的地方。
這個模型在解決什麼問題
臨床研究裡第二大類的結果變項是二元的:有沒有低出生體重、有沒有院內感染、 三十天內有沒有再入院、術後有沒有併發症。
直接用線性迴歸跑 0/1 結果,會壞在兩個地方:預測值跑出 0–1 之外 (負的機率沒有意義),以及殘差的變異數隨預測機率變動(0/1 變項的變異數是 ,本來就不是常數)。
邏輯迴歸(logistic regression)的解法是先換一個尺度。把機率 換成勝算(odds), 再取對數,範圍就從 展開成整條實數線,然後在那個尺度上做線性模型:
於是每個 的意思是「其他變項固定時, 每增加一單位,log-odds 增加 」, 取指數之後 就是勝算比(odds ratio, OR)——這個模型會生出 OR 而不是 RR, 不是誰選的,是模型結構決定的。
這一頁的例子
沿用線性迴歸那一頁的 MASS::birthwt,
但把結果換成二元的 low:新生兒體重是否低於 2500 g。
189 位產婦中有 59 個低出生體重,盛行率 31.2%。
這個盛行率是刻意選的。 它遠遠不算罕見,所以 OR 與 RR 會在同一張表上明顯分家—— 那正是下一節要看的東西。
library(MASS)
data(birthwt, package = "MASS")
bw <- birthwt
bw$race_f <- factor(bw$race, levels = 1:3, labels = c("White", "Black", "Other"))
bw$smoke_f <- factor(bw$smoke, levels = 0:1, labels = c("No", "Yes"))
bw$ptl_any <- as.integer(bw$ptl > 0)
# 粗 OR:一個變項
fit_c <- glm(low ~ smoke_f, data = bw, family = binomial)
exp(cbind(OR = coef(fit_c), confint.default(fit_c)))
# 同一張 2x2 的 RR,要自己算——glm 不會給你
tb <- table(bw$smoke_f, bw$low)
(tb[2, 2] / sum(tb[2, ])) / (tb[1, 2] / sum(tb[1, ]))
# 校正後的 OR
fit <- glm(low ~ smoke_f + age + lwt + race_f + ht + ui + ptl_any,
data = bw, family = binomial)
summary(fit)
exp(cbind(OR = coef(fit), confint.default(fit))) # Wald,論文格式
# 完全分離長什麼樣子:把 ptl 當成四個 level 的因子
summary(glm(low ~ smoke_f + factor(ptl), data = bw, family = binomial))驗證環境:R 4.6.0 + MASS 7.3.65。confint() 對 glm 給的是 profile likelihood 區間,論文慣用的 Wald 區間要用 confint.default()。
import numpy as np
import statsmodels.api as sm
import statsmodels.formula.api as smf
bw = sm.datasets.get_rdataset("birthwt", "MASS").data
bw["race_f"] = bw["race"].map({1: "White", 2: "Black", 3: "Other"})
bw["smoke_f"] = bw["smoke"].map({0: "No", 1: "Yes"})
bw["ptl_any"] = (bw["ptl"] > 0).astype(int)
fit = smf.logit(
"low ~ C(smoke_f) + age + lwt + C(race_f) + ht + ui + ptl_any", data=bw).fit()
print(fit.summary())
print(np.exp(fit.params)) # OR
print(np.exp(fit.conf_int())) # 95% CI,Wald
# 分離時 statsmodels 會丟 PerfectSeparationWarning,比 R 的沉默好一點statsmodels 的 Logit 與 GLM(family=Binomial()) 結果相同;前者的報表較接近 R 的 summary()。
粗 OR 與 RR:同一張 2×2,兩個答案
先不跑模型,用手算。抽菸與低出生體重的 2×2:
| 低出生體重 | 正常 | 合計 | 風險 | 勝算 | |
|---|---|---|---|---|---|
| 抽菸 | 30 | 44 | 74 | 40.5% | 0.682 |
| 未抽菸 | 29 | 86 | 115 | 25.2% | 0.337 |
- 相對風險(risk ratio, RR)= 兩個風險相除 = 1.608(1.06–2.44)
- 勝算比(odds ratio, OR)= 兩個勝算相除 = 2.022(1.08–3.78)
同一張表,同一組人,兩個數字差了將近三成。如果把這個 OR 唸成「風險增加一倍」, 就把效果誇大了。 實際上風險從 25.2% 上升到 40.5%。
figures/scripts/B2-02-logistic.R道理是代數上的必然,不是巧合。給定 OR 與未暴露組風險 ,暴露組風險是
把本頁的 OR 固定住,換不同的基線風險:
| 未暴露組的風險 | 這個 OR 對應的 RR |
|---|---|
| 1% | 2.001 |
| 5% | 1.924 |
| 20% | 1.679 |
| 40% | 1.435 |
校正後的 OR
figures/scripts/B2-02-logistic.R| 變項 | 校正後 OR | 95% CI | p |
|---|---|---|---|
| Smoking during pregnancy | 2.33 | 1.048–5.187 | 0.038 |
| Age, per year | 0.96 | 0.894–1.037 | 0.318 |
| Mother's weight, per lb | 0.99 | 0.972–0.999 | 0.034 |
| Race: Black vs White | 3.36 | 1.184–9.548 | 0.023 |
| Race: Other vs White | 2.23 | 0.928–5.382 | 0.073 |
| History of hypertension | 6.29 | 1.585–24.954 | 0.009 |
| Uterine irritability | 2.04 | 0.822–5.047 | 0.125 |
| Previous preterm labour | 3.39 | 1.369–8.408 | 0.008 |
抽菸的 OR 從粗的 2.022 變成校正後的 2.331。
大部分教科書會說「差別來自干擾因子」。這裡有一半是,但另一半不是,而後者幾乎沒有人講:
其餘幾欄怎麼讀,與線性迴歸相同:連續變項的 OR 一定要寫單位 (年齡的 OR 0.963 是每一歲); 信賴區間跨過 1 的變項要寫成「本研究未偵測到關聯」而不是「無關」; 整體模型檢定(likelihood ratio 37.8,df = 8,p < 0.001) 問的是「全部係數同時為 0 嗎」。
完全分離
有時候 glm() 會給出一個看起來很戲劇化的係數,配一個荒謬的標準誤。這通常不是資料很強,
而是分離(separation):某個變項把結果完美(或幾乎完美)切開了。
這份資料裡就有現成的例子,不必人工製造。把「先前早產次數」ptl 當成四個 level 的因子:
| 先前早產次數 | 正常體重 | 低出生體重 |
|---|---|---|
| 0 | 118 | 41 |
| 1 | 8 | 16 |
| 2 | 3 | 2 |
| 3 | 1 | 0 |
最後一列只有 1 個人,而她沒有低出生體重的嬰兒。 這一格是 0,於是「早產 3 次」這個 level 在資料裡與「不會發生低出生體重」完全同義。 模型的反應是:
| 項 | 係數 | 標準誤 | p |
|---|---|---|---|
| (Intercept) | -1.28 | 0.23 | < 0.001 |
| smoke_fYes | 0.58 | 0.34 | 0.087 |
| ptl_f1 | 1.65 | 0.48 | < 0.001 |
| ptl_f2 | 0.52 | 0.94 | 0.582 |
| ptl_f3 | -13.86 | 882.74 | 0.987 |
EPV:每個變項至少 10 個事件
邏輯迴歸能放幾個變項,取決於事件數而不是樣本數。 本頁的模型有 59 個事件、8 個參數, EPV(events per variable)= 7.38。
常見誤用
| 誤用 | 為什麼錯 |
|---|---|
| 把 OR 讀成 RR,而結果並不罕見 | OR 一定比 RR 遠離 1,結果越常見差越多 |
| 世代研究 / RCT 有分母卻只報 OR | 可以直接報 RR 或風險差;報 OR 等於多要一個假設 |
| 把「校正後 OR 變大」全部歸給干擾 | OR 不可塌縮,條件 OR 本來就離 1 更遠 |
| 直接比較兩篇論文的 OR 大小 | 校正的變項不同,兩個 OR 定義的對比就不同 |
| 拿模型的 OR 去算 NNT 或族群層次的影響 | 需要邊際估計(標準化 / G-computation),不是條件 OR |
| 報連續變項的 OR 卻不寫單位 | 每一歲與每十歲的 OR 完全不同 |
| 出現分離仍照樣報那個巨大的 OR | 該估計值不存在;要合併類別或用 Firth |
把 fitted probabilities numerically 0 or 1 當雜訊略過 | 那通常就是分離的唯一警告 |
| 事件數不多卻放進大量共變項 | 過度配適,係數與區間都不可信 |
| 先看顯著性再決定放哪些變項 | 選擇後的 p 值與信賴區間失效 |
| 用 OR 大小當「重要性」排序 | 尺度取決於變項單位與分布,不是重要性 |
| 配對的病例對照用普通邏輯迴歸 | 要用條件式邏輯迴歸,見 病例對照研究 |
| 信賴區間跨過 1 還描述效果大小 | 應寫「未偵測到顯著關聯」並附上區間 |
重跑本頁的所有數字
/opt/homebrew/bin/Rscript figures/scripts/B2-02-logistic.R讀讀看這張圖
答案取自產生本頁圖表的同一份統計輸出,不是另外打上去的。
這份資料裡低出生體重並不罕見,粗 odds ratio 是 2.02。可以說「抽菸讓風險變成兩倍」嗎?
看答案與解析
正確答案: 不行,風險比其實是 1.61——結果不罕見時 OR 會比 RR 更遠離一
風險比是 1.61,odds ratio 是 2.02,兩者相差不小,因為這個結果一點也不罕見。OR 與 RR 只有在結果很罕見時才會接近,盛行率越高、兩者差距越大,而 OR 永遠偏離一更遠。把 OR 直接讀成「風險是幾倍」會誇大效果,而摘要裡經常就是這樣寫的。3.78 是 OR 的信賴區間上界,它描述的是不確定性,不是另一種效果量。
校正年齡、母親體重與族群之後,抽菸的 odds ratio 從 2.02 變成另一個數字。校正做了什麼?
看答案與解析
正確答案: 變成 2.33——校正可以往任何方向移動估計,這裡是往外
校正後的 OR 是 2.33,比粗估的 2.02 更遠離一。「校正會把數字往一拉」是很常見的直覺,但它不成立:移動的方向取決於共變項跟暴露、跟結果各自的關係,往外、往內、甚至換邊都可能。3.36 是同一個模型裡族群那一列,不是抽菸的。看到校正前後的兩個數字時,該問的是哪些變項進了模型、為什麼,而不是預期它往哪邊走。
抽菸組 74 人中有 30 人生下低出生體重的嬰兒。抽菸組的風險是多少?
看答案與解析
正確答案: 0.405——分母是同組的全部人數
風險的分母是全部的人,所以是 0.405。0.682 是同一組的 odds:分母只有沒發生事件的人,所以它永遠比風險大。事件不罕見時這兩個數字差很多,而 logistic regression 直接給你的是 odds 那一邊——這就是為什麼從 logistic 模型講「風險」之前必須先換算。0.252 是不抽菸組的風險,讀錯了列。
用到這個方法的章節
延伸觀看
StatQuest: Logistic Regression
Logistic Regression Details Pt1: Coefficients
醫學統計 EP14 羅吉斯迴歸
醫學統計 EP15 RR vs OR
【Hands-on】L9 R: Logistic Regression素材來源與授權
本頁為原創內容