連續變項要不要切成分組
切成四分位會損失多少檢定力、為什麼有時候切完反而更顯著(那不是好消息)、「最佳切點」搜出來的效果量為什麼一定偏大、P for trend 把 1/2/3/4 當數值時假設了什麼,以及有些變項根本切不出四分位。
這個決定每天都在發生
年齡切成「≥ 65 歲」、BMI 切成四類、CRP 切成「高/低」、腫瘤標記切成四分位—— 臨床論文裡到處都是。理由通常是「這樣比較好講」「臨床上就是這樣分的」「reviewer 要求」。
方法學文獻對這件事的立場一面倒:不要切。但那個立場是有前提的,而前提常常沒被講出來。 這一頁用兩份資料把前提找出來——因為它們剛好落在相反的兩邊。
兩份資料,兩個方向
| 資料 / 變項 | 結果 | 模型 | 非線性檢定 |
|---|---|---|---|
A. MASS::birthwt 的 lwt(產婦體重,磅) | 189 人,低出生體重 59 例 | logistic | χ² 2.49,df 3,p 0.478 |
B. survival::rotterdam 的 pgr(黃體素受體,fmol/l) | 2982 人,復發或死亡 1713 例 | Cox | χ² 9.45,df 3,p 0.024 |
A 的樣條沒有偵測到偏離直線;B 偵測到了。這一個差別會把後面所有結果的方向反過來。
library(MASS); library(survival); library(splines)
data(birthwt, package = "MASS")
bw <- birthwt
bw$race_f <- factor(bw$race, levels = 1:3, labels = c("White", "Black", "Other"))
# 1. 保留連續
glm(low ~ lwt + smoke + race_f, data = bw, family = binomial)
# 2. 切成四分位,最低的一組當參考組
bw$lwt_q <- cut(bw$lwt, quantile(bw$lwt, 0:4/4), include.lowest = TRUE)
glm(low ~ lwt_q + smoke + race_f, data = bw, family = binomial)
# 3. P for trend:把 1/2/3/4 當成一個數值變項
bw$lwt_q_num <- as.integer(bw$lwt_q)
glm(low ~ lwt_q_num + smoke + race_f, data = bw, family = binomial)
# 4. 樣條:不用線性假設,也不用切
sp <- ns(bw$lwt, knots = quantile(bw$lwt, c(.25, .5, .75)),
Boundary.knots = quantile(bw$lwt, c(.05, .95)))
glm(low ~ sp + smoke + race_f, data = bw, family = binomial)驗證環境:R 4.6.0 + MASS 7.3.65 + survival 3.8.6。cut() 的 include.lowest = TRUE 不能省,否則最小值會變成 NA。
import numpy as np, pandas as pd
import statsmodels.api as sm
import statsmodels.formula.api as smf
bw = sm.datasets.get_rdataset("birthwt", "MASS").data
# 1. 保留連續
smf.logit("low ~ lwt + smoke + C(race)", data=bw).fit()
# 2. 四分位(qcut 依分位切,labels=False 給 0..3)
bw["lwt_q"] = pd.qcut(bw["lwt"], 4, labels=False)
smf.logit("low ~ C(lwt_q) + smoke + C(race)", data=bw).fit()
# 3. P for trend:同一個 0..3 直接當數值
smf.logit("low ~ lwt_q + smoke + C(race)", data=bw).fit()pandas 的 qcut() 對應 R 的 cut(quantile(...));遇到重複切點時 pandas 預設也會丟 ValueError,訊息是 Bin edges must be unique。
方向 A:線性大致成立時,切組就是純虧
lwt 是產婦最後一次月經時的體重(磅),結果是新生兒低出生體重,
模型另外校正抽菸與族裔。三種做法的同一個關聯:
| 做法 | 報出來的效果量 | z | p | AIC |
|---|---|---|---|---|
| 保留連續(每 10 磅) | OR 0.876 | -2.10 | 0.036 | 225.01 |
| 四分位(Q4 vs Q1) | OR 0.412(0.16–1.04) | -1.88 | 0.060 | 227.27 |
| P for trend | 每上升一個四分位 OR 0.768 | -1.74 | 0.082 | 226.88 |
同一份資料、同一個關聯:保留連續時達到統計顯著;切成四分位之後未達; P for trend 也未達。 從 2.10 掉到 1.88 再到 1.74, 而切組的兩個版本 AIC(227.27 與 226.88)都比連續版本的 225.01 高。
四個組的細節在這裡,順便看一件事:
| 四分位 | 體重範圍(磅,含上界) | n | 事件 | OR | 95% CI | p |
|---|---|---|---|---|---|---|
| Q1 | 80–110 | 53 | 25 | 1(參考) | — | — |
| Q2 | 110–121 | 43 | 10 | 0.334 | 0.13–0.85 | 0.021 |
| Q3 | 121–140 | 46 | 12 | 0.486 | 0.20–1.19 | 0.113 |
| Q4 | 140–250 | 47 | 12 | 0.412 | 0.16–1.04 | 0.060 |
點估計不是單調的。 最低的是 Q2(OR 0.334), Q3 又回升到 0.486。
但不要把這個起伏歸給「切組製造的雜訊」。 同一份資料的樣條擺的是同一個形狀: 從 94 磅一路下降到約 125 磅的谷底 (0.456),在約 150 磅回升到 0.658, 之後才再往下。連續與切組兩種做法看到的是同一個上上下下。
能說的只有這句:非線性檢定未偵測到偏離直線 (χ² 2.49、df 3、 p 0.478),而信賴區間寬到分不出「真的有起伏」與「其實是一條直線」。 在這種情況下選線性項,是因為它比較省參數,不是因為已經證明關係是直線。
而 P for trend 正是假設它單調而且等距的(下一節)。
方向 B:切完變強,代表線性假設本來就錯了
pgr 是乳癌組織的黃體素受體濃度,右偏得很厲害:中位數 41、
平均 162、最大值 5004 fmol/l。
結果是 recurrence-free survival,模型校正腫瘤大小與停經狀態。
| 做法 | 報出來的效果量 | z | p | AIC |
|---|---|---|---|---|
| 保留連續(每 100 fmol/l) | HR 0.979 | -2.25 | 0.024 | 25393.55 |
| 四分位(Q4 vs Q1) | HR 0.812(0.71–0.93) | -2.95 | 0.003 | 25388.96 |
| P for trend | 每上升一個四分位 HR 0.934 | -3.14 | 0.002 | 25389.16 |
| 樣條(3 個內部 knot) | 見圖 B | — | — | 25390.10 |
方向整個反過來: 從 2.25 上升到 2.95,再到 3.14。 如果只看這一份資料,很容易得出「切組比較敏感」的結論。
它其實在告訴你別的事。 線性模型在這裡本來就配不上資料: 樣條 vs 線性的非線性檢定 χ² 9.45、 df 3、p 0.024。
先講清楚真實的形狀,因為它跟直覺不一樣:低值區本來就是平的—— 從 0 到約 63 fmol/l,樣條貼在 1 附近,在 23 fmol/l 處還微升到 1.053;下降主要發生在約 23 到 491 fmol/l 之間, 之後轉平。所以問題不是「線性模型在低值區的斜率不夠陡」。
問題是一條直線被要求同時服務 0 到 5004 fmol/l 的整個全距:
- 一半的病人擠在 0 到 41 fmol/l (中位數就是 41,四分位切點是 0 / 4 / 41 / 198)。 在這一段直線幾乎分不開任何人——連跨過整個四分位距 (4 到 198 fmol/l), 線性模型也只給 HR 0.960。
- 而斜率是被右尾少數幾個極高值的人定下來的:他們離其他人最遠, 對一個單一線性係數的影響也最大。
於是同一個係數必須同時代表「0 與 41 之間」與 「198 與 5004 之間」, 而樣條顯示這兩段的形狀根本不同。切成四分位至少把最高的四分之一單獨切出來、 讓它自己有一個估計,所以「看起來」變強了。
AIC 也指向同一件事:線性 25393.55 是三者中最差的, 四分位 25388.96 與樣條 25390.10 都比它低。 四分位與樣條之間只差 1.15——這個差距太小, 不足以拿來選模型。選樣條的理由不是 AIC,是它不需要憑空決定三個切點、 不把組內的變異當成沒發生過,而且畫得出一條可以讀的曲線。
figures/scripts/B2-11-categorisation.RP for trend 到底在算什麼
P for trend 在臨床論文裡幾乎是四分位表格的標配,但它的算法比多數人以為的粗糙:
把四分位編號 1、2、3、4 當成一個數值變項丟進模型,然後看那一個係數的 p 值。
於是它假設了兩件事:
- 單調:效果隨組別往同一個方向走。
- 等距:從 Q1 到 Q2 的 log-odds(或 log-hazard)變化量, 等於從 Q3 到 Q4 的變化量。
第二條是關鍵,而且幾乎沒有人檢查。四分位在原始尺度上不是等距的—— 方向 B 的四組中位數是 0、16、99、413 fmol/l, 最後一組是前一組的 4.2 倍。把它們當成 1、2、3、4 等距,等於宣稱「效果隨排名等速變化」, 那是一個關於組的序號的假設,不是關於原始變項的假設。
「最佳切點」:一個一定會偏大的效果量
比切四分位更常見、也更糟的做法:掃過所有可能的切點,挑 p 值最小的那個當「最佳切點」。
拿方向 A 的 lwt 實際跑一遍。在第 10 到第 90 百分位之間,
共有 48 個相異值可以當切點,每一個都跑一次校正後的 logistic:
| 切點 | OR | p | |
|---|---|---|---|
| 搜出來的「最佳」切點 | 105 磅 | 3.053 | 0.005(未校正) |
| 同一個對比,連續模型說的 | — | 1.712 | 0.036 |
第二列是這樣算的:切點兩側的平均體重分別是 97.19 與 137.76 磅, 把這個差距代進連續模型,得到的 OR 是 1.712。 搜出來的那個 3.053 是它的 1.78 倍。
有些變項根本切不出四分位
切組還有一個實務上會擋住你的地方:分位數切點必須互不相同。
同一份 rotterdam 資料裡的 nodes(陽性淋巴結數)就切不出來:
| 百分位 | 0% | 25% | 50% | 75% | 100% |
|---|---|---|---|---|---|
nodes 的切點 | 0 | 0 | 1 | 4 | 34 |
前兩個切點都是 0,因為 1436 位病人
(48.2%)沒有陽性淋巴結。
cut() 直接停下來報錯:'breaks' are not unique
(英文語系下的訊息;中文語系的 R 會看到翻譯過的版本)。
這不是 R 的毛病,是四分位這個概念對零膨脹的變項沒有意義—— 沒有辦法把一半都落在同一個值上的資料切成四等份。 同一個零膨脹在樣條那一頁造成的是另一種災情(預設 knot 被擠在一起), 處理方式是手動指定 knot;而切組在這裡連跑都跑不起來。
臨床上常見的零膨脹變項:淋巴結數、住院天數、每日抽菸支數、急診就診次數、過去一年住院次數。
那什麼時候可以切?
常見誤用
| 誤用 | 為什麼錯 |
|---|---|
| 把連續變項切成二分再做主要分析 | 損失最多的一種切法;等於丟掉組內所有變異 |
| 用資料搜出「最佳切點」再照樣報 p 值 | 效果量被選擇性放大,p 值沒有校正搜尋 |
| 切完比較顯著就採用切組版本 | 那是線性假設不成立的訊號,該換樣條不是換切法 |
| P for trend 顯著就說「有劑量反應關係」 | 該檢定假設單調且等距,兩者都沒被檢查 |
| 四分位的 CI 跨過 1 卻描述效果大小 | 應寫「未偵測到差異」並附區間 |
| 只報 Q4 vs Q1,不報各組人數與事件數 | 讀者無法判斷估計是幾個人撐起來的 |
| 在不同論文之間比較四分位的 OR | 切點來自各自的資料分佈,定義的對比不同 |
| 零膨脹變項硬切分位數 | 切點會重複,切出來的組也沒有臨床意義 |
| 切組之後宣稱「不需要假設線性」 | 對,但代價是檢定力;樣條同樣不需要線性假設,卻不必付這個代價 |
| 主要分析用切組、次要分析才放連續 | 順序反了;連續版本才是資訊完整的那個 |
所以,下一步
如果你讀到這裡的理由是「我的資料看起來不像直線」,
那要去的地方是限制性立方樣條與劑量反應曲線——
那一頁用的是同一份 rotterdam 資料,示範怎麼在不切組的情況下放掉線性假設、
knot 要放在哪裡、以及畫出來的劑量反應曲線怎麼讀。
重跑本頁的所有數字
/opt/homebrew/bin/Rscript figures/scripts/B2-11-categorisation.R讀讀看這張圖
答案取自產生本頁圖表的同一份統計輸出,不是另外打上去的。
同一個模型、同一個係數,母親體重的 odds ratio 被報成三個不同的數字。這說明什麼?
看答案與解析
正確答案: 每十磅是 0.876——OR 的大小取決於「每多少」,看到 OR 一定要先問單位
0.987、0.876、0.672 是同一個係數換三種單位的結果:每一磅、每十磅、每一個四分位距。三個都對,而且是同一件事——重點在於 OR 的大小沒有單位就沒有意義。每一磅那個版本看起來幾乎等於一,很容易被讀成「沒有效果」;每一個四分位距那個最遠離一,但它也不比另外兩個更「真」,只是換算的區間比較大。報告連續變項的 OR 時必須寫清楚每多少,否則讀者無法判斷效果大小。
用 quantile() 對 nodes 切四分位會直接以「breaks 不唯一」報錯。原因是什麼?
看答案與解析
正確答案: 有 1436 人的 nodes 是零,最低的幾個分位點因此相同
1436 人的 nodes 是零,將近全體的一半,於是最低的幾個分位點彼此相同,cut() 找不到唯一的切點就直接拒絕執行。1713 是事件數、2982 是總人數,兩者都不會讓分位點重疊——樣本大反而讓分位數更穩定。分位數切點在有大量重複值的變項上會失敗,而這種變項在臨床資料裡非常常見:零顆淋巴結、零次住院、零支菸。遇到這種分布,該想的是零要不要自成一組,而不是換一個切法硬切。
讀一張 spline 曲線圖時,第一個該找的是什麼?
看答案與解析
正確答案: 曲線通過一的位置 102——所有的比值都是相對於它
這張圖的參考點是 102,也就是曲線通過一的地方,而圖上每一個縱軸數值都是「相對於體重 102 磅的人」。121 是 lwt 的中位數、130 是平均數:參考點可以選成它們,但不是非得如此,這張圖就沒有。參考點的選擇不改變曲線的形狀,只把整條曲線上下平移,卻決定了所有數字是相對於誰——所以讀 spline 圖的第一個動作,是找出曲線通過一的那個位置在哪裡。
用到這個方法的章節
素材來源與授權
本頁為原創內容