進階已經雙重審閱,尚未人工抽查

左截切與延遲進入

有人不是從第 0 天開始被觀察的。左截切與左設限差在哪、Surv(entry, exit, event) 這個兩端時間的寫法、不寫會把存活率往哪個方向偏多少,以及為什麼偏誤全部落在延遲進入的那一群人身上。

左截切與左設限,差在「有沒有進入分母」

這兩個詞長得像,中文譯名也常被混用,但它們講的是完全不同的兩件事,處理方式也完全不同。

左設限(left censoring)左截切(left truncation)
你知道的事事件已經發生了,只是時間不知道,只知道早於某個時點這個人在某個時點之前根本不可能被看到發生事件
典型情境第一次抽血就已經陽性,不知道何時感染的登錄資料的收案日晚於診斷日;以年齡當時間軸
缺的是什麼缺一個時間缺一段觀察
怎麼處理改用左設限/區間設限的概似式(Surv(time, time2, type = “interval2”)把進入時間寫進 Surv(),讓這個人在進入之前不被算進分母

設限與風險集合那一頁已經把「設限是人在資料裡、時間不完整;截切是人根本沒進來」講過,本頁不重複那一段。本頁處理的是左截切比較溫和、也比較常見的那個形態:人進得來,只是進得晚——而那個晚到的區間,正好是他不可能被觀察到發生事件的區間。

這一頁的例子:一份收案日晚於診斷日的骨髓瘤登錄

survival::myeloma 是 Mayo Clinic 的多發性骨髓瘤(multiple myeloma)病人資料,3882 人,追蹤到 2769 人死亡、1113 人(28.7%)在追蹤結束時仍存活。

關鍵在時間原點:這份資料的第 0 天是「診斷」,不是「收案」。 在 Mayo 當場診斷的人,兩者是同一天;在外院診斷、後來才轉介過來的人,兩者差了一段。這段差距就記在 entry 這個欄位裡。

人數占比
entry 為 0——診斷當天就進入觀察219456.5%
entry 大於 0——診斷之後才進入觀察168843.5%

只看整體的 entry 中位數會得到 0,因為超過一半的人是當場收案的,那個數字什麼都沒告訴你。要看的是延遲進入的那群人自己的分佈:中位延遲 166 天,四分位距 36 到 575 天,最長的一位是在診斷後 5414 天才進入觀察的。

寫法:把進入時間放進 Surv() 的第一個位置

Surv() 吃兩個時間就是在講這件事:這個人從什麼時候開始被觀察、到什麼時候結束。

library(survival)
data(cancer, package = "survival")   # myeloma 住在 cancer 這個 bundle 裡
# 注意:data(myeloma, package = "survival") 會警告「找不到這個資料集」

# 忽略延遲進入:每個人都被當成從第 0 天(診斷)就開始被觀察
fit_naive <- survfit(Surv(futime, death) ~ 1, data = myeloma)

# 正確:entry 是這個人真正進入風險集合的時間(診斷後第幾天)
fit_corr  <- survfit(Surv(entry, futime, death) ~ 1, data = myeloma)

summary(fit_naive)$table[c("median", "0.95LCL", "0.95UCL")]
summary(fit_corr)$table[c("median", "0.95LCL", "0.95UCL")]

# Cox 完全同一個寫法,把兩端時間放進 Surv() 就好
coxph(Surv(entry, futime, death) ~ year, data = myeloma)

驗證環境:R 4.6.0 + survival 3.8.6。myeloma 隨 survival 套件附帶,不必另外安裝。

不處理會偏多少、往哪個方向偏

同一份資料、同一批人、同一個事件定義,只差 Surv() 裡多不多一個時間欄位:

兩條 Kaplan-Meier 存活曲線疊在同一張圖上。橫軸是骨髓瘤診斷後的天數,從 0 到 3000,每 500 天一個刻度;縱軸是存活機率 0 到 1。藍線是忽略延遲進入的 Surv(futime, death),紅線是正確處理的 Surv(entry, futime, death),兩條各自帶一對虛線的 95% 信賴區間帶。藍線從一開始就整段落在紅線上方,到 3000 天都沒有交叉。一條棕色水平點線畫在存活機率 0.5,兩支垂直虛線從橫軸拉到那條線,紅色那支落在 764 天、藍色那支落在 1004 天,兩支之間有一支雙箭頭標著相差 240 天。圖上方兩行小字寫出資料集規模、延遲進入的人數與占比,以及第 1000 天兩種算法的存活率。圖下方是風險人數表兩列,每 500 天一格:忽略延遲進入那一列從 3882 開始遞減到 235,正確處理那一列從 2194 開始遞減到 219。
同一份資料的兩種寫法。藍線之所以整段偏高,不是因為它樂觀,是因為它的分母裡混進了 1688 位當時還不可能被觀察到死亡的人。產圖腳本 figures/scripts/B3-08-left-truncation.R
寫法中位存活(95% CI)S(500 天)S(1000 天)S(2000 天)
忽略延遲進入
survfit(Surv(futime, death) ~ 1, data = myeloma)
1004(952–1060)71.8%50.4%26.2%
正確處理延遲進入
survfit(Surv(entry, futime, death) ~ 1, data = myeloma)
764(728–811)63.2%41.0%19.2%
差(前者減後者)240 天8.5 個百分點9.4 個百分點7.0 個百分點

方向是固定的,不是這份資料剛好如此:忽略延遲進入一定會高估存活。 中位存活從 764 天被推到 1004 天,多了 240 天,是正確值的 1.31 倍;第 1000 天的存活率從 41.0% 被推到 50.4%,差 9.4 個百分點。

以百分點看,上表這三個時點裡以第 1000 天的落差最大(9.4 個百分點),到第 2000 天縮回 7.0 個百分點——那不是偏誤變小了,是兩條曲線都已經接近底部,能差的空間本來就變窄了。

機制:分子完全一樣,分母不一樣

要把這件事講清楚,最好的切入點是:兩種算法的事件數是同一個數字。

兩邊都是 2769 個死亡,一個不多一個不少——沒有任何一個事件被加進來或拿掉。變的只有每個事件發生時,KM 拿來當分母的那個風險集合有多大。分母被灌大,每一步的「這一刻死掉的比例」就被稀釋,乘起來的存活曲線就整條被抬高。

兩個上下排列的 panel。Panel A 標題是「每一種寫法認為誰正在被觀察」,橫軸是診斷後 0 到 3000 天,縱軸是風險集合人數 0 到約 4000。藍線(忽略進入時間)從 3882 起,一路單調下降到 3000 天的 235。紅線(延遲進入)從 2194 起,在最初一兩百天內先明顯上升到一個小高峰,之後才轉為下降,到 3000 天剩 219——先升後降不是畫錯,那正是延遲進入的樣子:有人在後面才進場。第 0 天處有一段金色的粗垂直線,標出兩條線之間 1688 人的落差,旁邊一行字寫著這些人在第 0 天還沒被轉介。Panel B 標題是「A 裡的那段落差——只有天真寫法把他們算成在風險集合裡」,畫的就是兩線的差值:一條金色曲線加填色面積,從 1688 起快速衰減、之後拖出一條長尾,第 500、1000、2000 天各標一個實心圓點,右上角列出這三個時點的人數與占天真風險集合的比例。圖最下方一行字說明這些人當時還沒被轉介,不可能被觀察到死亡。
Panel A 的紅線先升後降,是這張圖最該停下來看的地方——風險集合會變大,因為延遲進入的人陸續進場。Panel B 是兩條線的差,也就是天真寫法憑空多算的那群人。產圖腳本 figures/scripts/B3-08-left-truncation.R
時點(天)忽略延遲進入的分母正確處理延遲進入的分母還沒進入觀察占天真分母
038822194168843.5%
5002335185947620.4%
10001469122824116.4%
150092980512413.3%
20006025346811.3%
2500387352359.0%
3000235219166.8%

第 0 天的落差 1688 人,正好等於 entry 大於 0 的人數——不是近似,是同一群人。之後這個落差隨著大家陸續被轉介而縮小,但到第 2000 天,天真分母裡還有 11.3% 是不該在那裡的人。

偏誤不是平均攤在每個人身上

把樣本拆成兩群分開跑,會看到這一頁最有說服力的東西。

子群人數事件數忽略延遲進入的中位(95% CI)正確處理延遲進入的中位(95% CI)
在本院診斷、當場收案(entry 為 0)21941889777(729–823)777(729–823)0 天
在外院診斷、後來才轉介進來(entry 大於 0)16888801462(1339–1581)795(734–885)667 天

當場收案那一群,兩種寫法給出一模一樣的中位存活(777 天),連信賴區間的兩端都一樣。這不意外——他們的 entry 本來就是 0,兩個公式對他們說的是同一句話。

轉介那一群,兩種寫法差 667 天。整份分析的偏誤,百分之百落在這 1688 個人身上。

你這學期就會撞到的兩個情境

一、以年齡當時間軸的世代研究

在慢性病流行病學裡,時間軸常常不是「追蹤了幾年」,而是年齡——因為對心血管事件、失智、骨折這類結果來說,年齡是最強的那個時間尺度,用追蹤時間當軸再把年齡當共變項調整,是把最重要的東西降級處理。

一旦時間軸換成年齡,每個人都是左截切的:一位 62 歲收案的受試者,他從 0 歲到 62 歲那段沒有被你觀察,而且他能出現在你的收案名單裡,前提就是他活到了 62 歲。

# 時間軸 = 年齡(歲)。收案年齡是進入,結束追蹤時的年齡是離開。
# 用上面已經載入的 lung 當形狀示範:age 是收案年齡,time 是追蹤天數。
cohort <- data.frame(
  age_entry    = lung$age,
  futime_years = lung$time / 365.25,
  event        = as.integer(lung$status == 2),
  sex          = factor(lung$sex, levels = c(1, 2), labels = c("Male", "Female")),
  ecog         = lung$ph.ecog
)
cohort$age_exit <- cohort$age_entry + cohort$futime_years
coxph(Surv(age_entry, age_exit, event) ~ ecog + sex, data = cohort)

二、登錄/生物資料庫研究:收案日晚於疾病起始日

這是台灣醫學生做健保資料或院內登錄第一個會踩的坑。凡是資料的時間原點進入資料庫的時點不是同一天,中間那段就是左截切:

  • 院內癌症登錄以診斷日為原點,但病人是轉診進來才被建檔的
  • 生物資料庫以發病日為原點,但檢體是後來才送到、才被納入分析
  • 用某年開始的資料庫回溯研究「該年之前就已經診斷」的病人——這些人的原點在資料庫開始之前,他們能出現在資料裡就代表他們活到了資料庫啟用的那一天
  • 以「第一次處方」為原點,但資料庫只從某年開始有處方檔

寫法都一樣:entry = 原點到進入資料庫的天數,exit = 原點到事件或追蹤結束的天數。資料庫研究與真實世界資料那一章講的其他坑(申報資料不是為研究而存在、RECORD 要求交代哪些東西)與這一條是疊加的,不是替代的。

這一頁與其他頁的關係

  • 設限與截切的基礎、以及風險集合為什麼是分母——回到 設限與風險集合。那一頁的「三種設限」表格與本頁第一節的表格是一組的。
  • KM 曲線本身怎麼算、風險人數表為什麼一定要附——見 Kaplan-Meier 曲線。本頁的圖一之所以把兩列風險人數畫出來,就是因為那兩列正是兩條曲線分開的全部原因。
  • Cox 模型——見 Cox 比例風險模型。左截切在 coxph() 裡是完全相同的一行改動,partial likelihood 的每一個 risk set 會自動照 entry 篩過。
  • Immortal time bias——它與延遲進入是親戚,但不是同一件事,值得分清楚:
    • 延遲進入:那段時間任何人都沒在觀察他,錯在把不存在的觀察算成觀察。
    • Immortal time bias:那段時間你看得到他,但你用了一個在當時還不知道的狀態(有沒有接受移植、有沒有反應)去分組,於是把「保證活著」的時間記到了錯的組。 兩者的修法方向相反:前者要把時間移出風險集合,後者要把時間留在風險集合裡、但記到正確的那一組。 B3-01 用史丹佛換心資料走過 immortal time 的對照, 時間相依共變項與 landmark 分析則是它的正解。
  • 「能被納入」本身就是一種篩選——見 選擇偏誤Surv(entry, ...) 修的是分母,修不掉族群已經被篩過這件事。

常見誤用

誤用為什麼錯
收案日晚於原點,卻寫 Surv(time, event)不會報錯,只會把存活率系統性地高估;本頁的例子偏了 240 天
看整體 entry 中位數是 0 就認為不影響偏誤只落在延遲進入的人身上,整體分位數會把它藏起來
exit - entry 當追蹤時間代替兩端時間那是換掉時間原點,不是修正;臨床問題跟著變了
以年齡當時間軸卻只寫追蹤時間每個人都是左截切,這是該情境下的預設而非例外
把左截切當成左設限去處理(區間概似式)左設限缺的是一個時間,左截切缺的是一段觀察,兩者的概似式不同
Surv(entry, exit, event) 之後宣稱選擇偏誤也解決了只修好了分母;誰有機會被收案仍然是被篩選過的
Methods 只寫「使用 Kaplan-Meier 法」讀者無從判斷延遲進入有沒有被處理,而這是不可重現的關鍵一行
延遲進入處理好了就宣稱其他假設也滿足非資訊性設限、比例風險都是另外的假設,要各自檢查

重跑本頁的所有數字

/opt/homebrew/bin/Rscript figures/scripts/B3-08-left-truncation.R

讀讀看這張圖

答案取自產生本頁圖表的同一份統計輸出,不是另外打上去的。

同一份 myeloma 資料,忽略延遲進入與正確處理延遲進入,算出來的中位存活不一樣。該報哪一個?

看答案與解析

正確答案: 764 天——把每個人的進入時間放進風險集合之後算的

被轉介來的人必須先活到轉介那一天,才有機會出現在這份資料裡,而那段時間他們依定義不會死。把他們當成從診斷日就開始被觀察,等於把那段不可能死亡的時間送給存活曲線,於是中位數從 764 天被推高到 1004 天。728 是正確版本那個中位數的信賴區間下界:換一批人就會變,把它當成點估計不是保守,是報了另一個量。

這份資料 3882 人。要判斷延遲進入是不是一個需要處理的問題,該看哪一個數字?

看答案與解析

正確答案: 1688 人的進入時間大於零,這些人的觀察是從診斷之後才開始的

1688 人是延遲進入的,接近全體的一半——這個比例決定了忽略它會有多大的偏差。2194 是進入時間為零的人,他們沒有問題,但「其餘的人可以先不管」正好倒過來了;1113 是被設限的人數,而延遲進入與設限是兩件相反的事:設限是「之後看不到了」,延遲進入是「之前沒看到」。這件事在資料表上只是一個 entry 欄位,分析時沒有把它放進 Surv() 的話,程式不會有任何抱怨,結果照樣跑得出來。

整體來看,忽略與正確處理延遲進入的中位存活只差 240 天,有人因此說這個問題可以忽略。哪一個數字最能反駁?

看答案與解析

正確答案: 延遲進入那一群差了 667 天,偏差全部集中在他們身上

進入時間為零的人本來就沒有延遲進入的問題,兩種算法對他們給出完全一樣的中位數,差是 0;偏差全部來自延遲進入的那一群,他們差了 667 天。整體那個 240 天是兩個子群混在一起被稀釋之後的結果——一半的人完全沒有偏差,把他們平均進來,當然看起來還好。所以「整體看起來差不多」不能拿來當作可以忽略左設限的理由,該看的是受影響那一群有多大、偏多少。

延伸觀看

Censoring and Truncation [Survival Analysis 2/8]
ENzedstatistics· 14 min讀完本頁第一節、還沒往下看數字之前看它。它把 censoring 與 truncation 分成兩件事來講,正是本頁其餘部分建立在上面的那個區分。

素材來源與授權

本頁為原創內容

回報內容問題

這個站的統計內容由 AI 撰寫、AI 互審,人工只做抽查。你看得出來的錯,我們不一定看得出來。

寫得越具體越修得動,例如哪一句話跟哪本教科書/哪篇論文的說法不一致。

留了才回得了信;不留也會看。

一併送出的資訊

這些是自動帶上的,每一項都可以取消。