限制平均存活時間(RMST)
RMST 是 Kaplan-Meier 曲線在 0 到 τ 之間的面積——「在前 τ 這段期間,平均活了多久」。這一頁講那塊面積怎麼看、τ 為什麼必須事先指定、換一個 τ 結論會變多少、差值與比值為什麼要一起報,以及怎麼把它講成一句對病人說得出口的話。
為什麼這個方法值得有自己的一頁
站上教存活分析的主線是 Kaplan-Meier 曲線加 Cox 比例風險模型,報出來的效果量是 hazard ratio。HR 有兩個代價, 臨床讀者每天都在付:
- 它要求比例風險假設成立。 兩組的風險比得在整段追蹤裡維持固定;不成立時,那個單一數字是 兩段不同效果的加權平均,不對應任何一個時點的真實風險比。怎麼檢查、四條補救路各要付什麼代價, 在比例風險假設與 Schoenfeld residuals。
- 它的單位不直觀。 「HR 0.78」講不出「所以呢」。要跟病人說「這個治療平均讓人多活多久」, HR 沒有辦法直接換算——它是瞬時風險的比值,不是時間。
限制平均存活時間(restricted mean survival time, RMST)兩件都不欠。它是曲線下的面積, 不需要任何比例假設;單位就是時間,天或月,讀者不必轉譯。
所以它已經不只是「PH 違反時的補救路四」。近年的腫瘤與心衰竭 RCT 常態性地把 RMST 差值放進 次要分析或附錄,理由正是上面兩條:曲線交叉或效果延遲出現時,HR 難以解釋,而 RMST 照樣算得出來、 也講得出口。這一頁是它在站上唯一的完整說明——PH 那一頁 現在只留一段轉介,不再自己發表一份 RMST 數字。
定義:Kaplan-Meier 曲線下的那塊面積
就是 Kaplan-Meier 估出來的存活曲線。把它從 0 積到 ,得到的量綱是時間:在前 這段期間,平均每個人活了多久。
KM 曲線是階梯函數,所以這個積分不需要任何數值方法——它就是一連串長方形的面積和, 每一階的高度乘上它持續的天數。這也是為什麼 RMST 不必假設任何分布形狀、也不必假設比例風險: 它只用到 KM 曲線本身。
本頁的例子是 survival::lung(NCCTG 晚期肺癌世代,資料集共 228 人)。
以 time, status, age, sex_f, ph.karno, wt.loss 這六個欄位做完整個案分析後,
進入分析的是 214 人、152 個死亡事件、62 筆設限。
分組變項是 ph.karno(Karnofsky 體能狀態評分):Karnofsky ≥ 80 分(n = 160,事件 105)
對 Karnofsky ≤ 70 分(n = 54,事件 47)。 取 365 天,也就是一年。
figures/scripts/B3-07-rmst.R怎麼算
survival 套件本體就做得到,不必安裝任何東西:summary(fit, rmean = tau) 會對每一個分層報出
[0, τ] 的曲線下面積與它的標準誤。
library(survival)
data(cancer, package = "survival")
lung$sex_f <- factor(lung$sex, levels = c(1, 2), labels = c("Male", "Female"))
# 完整個案分析的六個欄位,就是本頁分析真正用到的六個,不多也不少
ld <- lung[complete.cases(lung[, c("time", "status", "age", "sex_f",
"ph.karno", "wt.loss")]), ]
# levels 一定要顯式指定。不指定的話 R 按字母排序,"Karnofsky <= 70" 會排到
# 第一列,下面每一個 m[1] 就都是另一組了,而且不會有任何警告。
ld$karno_grp <- factor(
ifelse(ld$ph.karno >= 80, "Karnofsky >= 80", "Karnofsky <= 70"),
levels = c("Karnofsky >= 80", "Karnofsky <= 70")
)
fit <- survfit(Surv(time, status) ~ karno_grp, data = ld)
# 這就是全部:rmean = tau 讓 summary() 報出 [0, tau] 的曲線下面積。
# 欄位名在不同 survival 版本之間換過:舊版帶星號(*rmean),3.8 起不帶。
# 兩個都認,不要寫死其中一個——寫死的那一版會在升級之後靜靜地壞掉。
tb <- summary(fit, rmean = 365)$table
mcol <- intersect(c("rmean", "*rmean"), colnames(tb))
scol <- intersect(c("se(rmean)", "*se(rmean)"), colnames(tb))
m <- tb[, mcol]
se <- tb[, scol]
# 差值:兩個獨立的平均數相減,變異數相加
d <- m[1] - m[2]
se_d <- sqrt(se[1]^2 + se[2]^2)
c(diff = d, lcl = d - 1.96 * se_d, ucl = d + 1.96 * se_d,
p = 2 * (1 - pnorm(abs(d / se_d))))
# 比值:在 log 尺度上做,再指數回來(為什麼,見本頁「差值與比值」那一節)
r <- m[1] / m[2]
se_lr <- sqrt((se[1] / m[1])^2 + (se[2] / m[2])^2)
c(ratio = r, lcl = exp(log(r) - 1.96 * se_lr), ucl = exp(log(r) + 1.96 * se_lr))
# tau 敏感度:同一段程式跑一串 tau,就是本頁的圖二
sapply(c(180, 270, 365, 450, 550, 650), function(tau) {
x <- summary(fit, rmean = tau)$table
xc <- intersect(c("rmean", "*rmean"), colnames(x))
x[1, xc] - x[2, xc]
})驗證環境:R 4.6.0 + survival 3.8.6。summary(fit, rmean = tau) 隨 survival 套件本體提供,不必另外安裝。
import numpy as np
import statsmodels.api as sm
from lifelines import KaplanMeierFitter
from lifelines.utils import restricted_mean_survival_time
lung = sm.datasets.get_rdataset("cancer", "survival").data
d = lung[["time", "status", "age", "sex", "ph.karno", "wt.loss"]].dropna().copy()
d["event"] = (d["status"] == 2).astype(int)
d["grp"] = np.where(d["ph.karno"] >= 80, "high", "low")
def rmst_and_se(T, E, tau):
kmf = KaplanMeierFitter().fit(T, E)
# 傳 kmf(模型)而不是 kmf.survival_function_(DataFrame):
# 後者走 trapezoid 近似,得到的數字會比 R 的階梯積分略小。
point = restricted_mean_survival_time(kmf, t=tau)
# 標準誤要自己算。每個事件時間的貢獻 = (從該時點到 tau 的剩餘面積)^2
# 乘上 d / (n * (n - d))。這條式子的結果與 R 的 se(rmean) 逐位相同。
tb = kmf.event_table
tb = tb[tb.index <= tau]
S = kmf.survival_function_.loc[tb.index].values.ravel()
times = np.append(tb.index.values, tau)
rest = np.array([np.sum(np.diff(times[i:]) * S[i:]) for i in range(len(S))])
n_i, d_i = tb["at_risk"].values, tb["observed"].values
term = np.where(n_i - d_i > 0, d_i / (n_i * (n_i - d_i)), 0.0)
return point, np.sqrt(np.sum(rest**2 * term))
hi = rmst_and_se(d.loc[d.grp == "high", "time"], d.loc[d.grp == "high", "event"], 365)
lo = rmst_and_se(d.loc[d.grp == "low", "time"], d.loc[d.grp == "low", "event"], 365)
diff = hi[0] - lo[0]
se_d = np.hypot(hi[1], lo[1])
print(diff, diff - 1.96 * se_d, diff + 1.96 * se_d)lifelines 的 restricted_mean_survival_time(kmf, t=tau) 點估計與上面的 R 完全一致(要傳「模型」而不是 kmf.survival_function_,後者走 trapezoid 近似,數字會略小)。但它的 return_variance=True 回傳的不是估計值的變異數,而是限制後存活時間「分布」本身的變異數,拿它建信賴區間會得到寬到跨零的區間——標準誤必須自己算,就是下面那段。statsmodels 沒有 RMST。本頁報的數字全部來自 R。
τ 必須事先指定
RMST 一定要帶著 τ 才有意義——沒有 τ 的「RMST」不是一個數字。而 τ 是你選的, 這正是它唯一真正的軟肋。
figures/scripts/B3-07-rmst.R同一份資料、同一組人、同一個模型,只換 τ:
| τ(天) | Karnofsky ≥ 80 分 | Karnofsky ≤ 70 分 | 差值(天) | 95% CI | p |
|---|---|---|---|---|---|
| 180 | 165.1 | 144.5 | 20.6 | 4.8 – 36.5 | 0.011 |
| 270 | 229.9 | 188.4 | 41.5 | 15.3 – 67.8 | 0.002 |
| 365 | 284.9 | 221.6 | 63.3 | 26.1 – 100.6 | < 0.001 |
| 450 | 323.2 | 243.3 | 79.9 | 33.3 – 126.5 | < 0.001 |
| 550 | 356.5 | 265.8 | 90.7 | 33.1 – 148.2 | 0.002 |
| 650 | 382.3 | 280.1 | 102.3 | 35.9 – 168.6 | 0.003 |
τ 從 180 天換到 650 天,差值就從 20.6 天變成 102.3 天,是 5.0 倍。這不是兩個不同的結論,是同一個結論的兩種切法—— 但如果作者只報後者、而 τ 是看完曲線才決定的,讀者沒有辦法分辨那是臨床理由還是挑出來的。
方向也值得注意:在這份資料上差值隨 τ 幾乎單調變大,到 τ = 900 天達到 114.9 天 之後才走平,最後一格甚至微微回落到 114.7 天。原因看圖一就知道:兩條曲線在絕大部分的 追蹤期間沒有交叉,藍灰色那條在上面,每往右積一天就多累積一點面積;直到尾端兩條貼在一起並交錯, 差值才停止累積。曲線在中段就交叉的資料不會長這樣——那時差值會先變大再明顯縮回去甚至換號, 而 τ 的選擇就更關鍵。
τ 能拉到多遠
RMST 是兩條曲線各自的面積相減,所以 τ 不能超過任何一組還在被追蹤的時間。
| 組別 | 最後一筆觀察(天) |
|---|---|
| Karnofsky ≥ 80 分 | 1010 |
| Karnofsky ≤ 70 分 | 1022 |
上界由追蹤較短的那一組決定,也就是 Karnofsky ≥ 80 分的 1010 天, 而不是另一組的 1022 天。所以本頁可用的 τ 上界是 1010 天——圖二右側那條紅色虛線。
理由是階梯函數的行為:最後一個人離開之後,KM 曲線就不再往下走了。它停住不是因為風險消失, 是因為沒有人可以發生事件。把 τ 拉到那之後,多出來的面積是最後一階被水平延伸出來的長方形, 是外插,不是估計;而且兩組被延伸的長度不一樣,差值會被這個假象推著走。
差值與比值都要報
| 量 | 估計值 | 95% CI | 讀法 |
|---|---|---|---|
| RMST 差值 | 63.3 天 | 26.1 – 100.6 天 | 在前 365 天裡平均多活的天數 |
| RMST 比值 | 1.29 | 1.09 – 1.51 | 在前 365 天裡平均存活時間的倍數 |
差值講「多活幾天」,是能直接寫進衛教單張的量;比值講「久幾倍」,在跨研究比較時比較好用, 因為它不帶單位。兩個要一起報的實際理由是它們對 τ 的敏感度不同:同樣把 τ 從 180 天換到 650 天, 差值變成 5.0 倍(上一節那張表),兩組 RMST 的比值卻只從 1.14 走到 1.37, 因為分子分母同時變大。只給一個,讀者沒有辦法交叉檢核。
差值的 CI 不含 0、比值的 CI 不含 1,兩者指向同一個結論。這裡引的 p < 0.001 是差值的檢定; 比值的檢定建在 log 尺度上,p 值不會剛好等於它。
可以直接對病人說的一句話
RMST 最大的實用價值在這裡:它不需要翻譯。
在確診後的第一年裡(τ = 365 天,也就是 11.99 個月),Karnofsky ≥ 80 分的病人平均活了 9.36 個月,Karnofsky ≤ 70 分的平均活了 7.28 個月, 相差 2.08 個月(95% CI 0.86 到 3.30 個月)。
換句話說:體能狀態較好的那一組,在第一年裡平均多活了大約 2 個月。 這句話不必先解釋什麼是瞬時風險,也不必先解釋什麼叫「比例」。
也要注意 RMST 與中位存活期是兩個不同的東西,臨床討論裡常被混為一談: 中位數回答「一半的人撐到哪裡」,RMST 回答「這段期間內平均活了多久」。 中位數在追蹤不夠長、曲線還沒降到 0.5 時根本估不出來;RMST 在那種情況下照樣算得出來, 這也是它在早期報告裡受歡迎的原因之一。
怎麼讀報表
論文裡的 RMST 通常長這樣:一張 KM 圖,加一列或一小張表,列出兩組各自的 RMST(τ)、差值、 比值與各自的 CI。要檢查的是三件事:
- τ 是多少,而且它是怎麼決定的。 Methods 裡應該寫得出來。「τ 取自兩組追蹤時間較短者的最大值」 是可接受的事先規則;完全沒交代 τ 從哪來,那個差值就沒辦法評價。
- 差值與比值有沒有一起給、CI 有沒有給。 只給點估計的 RMST 跟只給點估計的 HR 一樣不可用。
- 有沒有 τ 的敏感度分析。 現代試驗的附錄裡常常就是本頁圖二那條曲線。 只報單一 τ、而那個 τ 剛好落在差距最大的地方,就要回頭看第 1 點。
還有一個容易被跳過的:RMST 換掉的是效果量,不是設限的假設。 KM 曲線本身仍然要求非資訊性設限,RMST 完整繼承這個前提—— 見設限與截切。
常見誤用
| 誤用 | 為什麼錯 |
|---|---|
| 看完曲線才決定 τ | 差值是 τ 的函數(本頁從 20.6 天到 102.3 天),挑 τ 等於連答案的大小一起挑 |
| 算了一排 τ,只報最好看的那個 | 選擇性呈現。要嘛事先指定一個,要嘛整條曲線都放進附錄 |
| τ 超過追蹤較短那組的最後觀察時間 | 多出來的面積是最後一階被水平延伸的產物,是外插不是估計 |
| 把 τ 訂在追蹤尾端 | 那一段的曲線由少數幾個人撐著,區間會寬到沒有結論 |
| 只報差值或只報比值 | 兩者對 τ 的敏感度不同,讀者無法交叉檢核 |
| 比值的 CI 直接在比值尺度上加減 | 比值右偏且下界為 0,要在 log 尺度做再指數回來 |
| 把 RMST 說成「平均餘命」 | 它只涵蓋 [0, τ],對 τ 之後完全不發言 |
| 把 RMST 差值拿去跟 HR 比大小 | 單位與問題都不同,一個是時間、一個是風險的比值 |
因為 cox.zph() 顯著才臨時改用 RMST,且未說明 | 換效果量是方法學決定,時機(事前或事後)必須寫進 Methods |
| 用了 RMST 就不檢查設限機制 | 非資訊性設限是 KM 的前提,RMST 建在 KM 上,前提照樣要成立 |
這一頁與其他頁的關係
- 底層是 KM——見 Kaplan-Meier 曲線與 log-rank 檢定。 KM 估得不對,RMST 就不會對;風險人數表、設限的處理方式全部沿用那一頁的規矩。
- HR 的對照組——見 Cox 比例風險模型。 兩者不是替代關係:多數論文兩個都報,HR 給主結論,RMST 給臨床解讀。
- PH 假設違反時的路四——見 比例風險假設與 Schoenfeld residuals。 前三條路(分層、時間分段、時間相依係數)都還在修 Cox 模型,這一條是換掉效果量本身。
- 另一條非 PH 的路——見 加權 log-rank 與 milestone survival。 分工是這樣:加權 log-rank 回答「兩條曲線有沒有差」,RMST 回答「差多少、單位是天」。 免疫治療那種延遲效果的場景,兩個常常一起出現。
- 設限的前提——見設限與截切。
重跑本頁的所有數字
/opt/homebrew/bin/Rscript figures/scripts/B3-07-rmst.R讀讀看這張圖
答案取自產生本頁圖表的同一份統計輸出,不是另外打上去的。
τ = 365 天時,RMST 分析報出好幾個數字。哪一個可以直接對病人說成「平均多活了這麼久」?
看答案與解析
正確答案: 63.3 天——兩組限制平均存活時間的差
63.3 天是兩組 RMST 的差,而它最值得注意的性質是單位:天。所以它可以直接被說成「在這一年裡平均多活了六十幾天」,而風險比說不出這句話——風險比是一個比值,沒有時間的單位,也回答不了病人「那我大概能多多少時間」。19.0 是這個差的標準誤,描述的是估計的精確度而不是效果大小;26.1 是它的 95% 信賴區間下界,把區間端點當成效果大小是報了一個不同的量,不是比較保守。
報表上有三個以天為單位的數字。哪一個不可能是任何一組的 RMST?
看答案與解析
正確答案: 365.0 天不可能——它等於 τ,而 RMST 是曲線在 0 到 τ 之間的面積
RMST 是存活曲線從 0 積到 τ 的面積,上限就是全程都存活,也就是 τ 本身。所以 365.0 這個數字只可能是 τ,不會是任何一組的 RMST——這是一個很快的自我檢查:讀到一個大於或等於 τ 的「RMST」,一定是讀錯欄了。284.9 與 221.6 分別是兩組的 RMST,兩者都合法;差得多不代表哪一個不可能,那正是這個分析要量的東西。
同一份資料、同一個對比,τ 從 365 天換成 90 天之後,RMST 差變小很多。這說明什麼?
看答案與解析
正確答案: 差變成 5.2 天——τ 是估計目標的一部分,所以必須事先指定
τ = 90 天時的差是 5.2 天,一年時是 63.3 天。兩個都不是錯的,它們是兩個不同的問題:「頭三個月平均多活多久」與「頭一年平均多活多久」。所以 τ 不是一個技術參數而是估計目標的一部分,必須在看資料之前就指定;事後挑一個讓結果好看的 τ,跟事後挑終點是同一件事。11.2 是 τ = 90 天那個差的信賴區間上界,不是點估計。
用到這個方法的章節
素材來源與授權
本頁為原創內容