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

敏感度分析

「請補做敏感度分析」是審稿意見裡出現頻率最高的一句,而它要求的東西比多數人以為的具體。本頁用同一份資料跑十六個各自都站得住的分析設定,讓估計值的散布變成看得到的東西,並用打散暴露的置換模擬量出「看到結果之後才挑一個報」要付多少代價。

「請補做敏感度分析」到底在要求什麼

敏感度分析是把一個本來就可以合理選擇的分析決定換掉,重跑,看結論會不會翻

它動的是分析決定。這句話裡的每一個字都在排除別的東西,而那三件別的東西經常被混進來:

它動的是那叫什麼在哪一頁
分析決定(模型設定、共變項、結果定義、納入條件)敏感度分析本頁
資料點(哪一個觀察值、哪一篇研究在推動結果)影響分析影響診斷
族群(這個效果在誰身上比較大)次族群分析多重比較
顯著性門檻(做了很多次檢定該怎麼判)多重比較調整多重比較

最後一列特別容易搞混,所以講清楚:敏感度分析不需要多重比較調整。它問的不是「這裡面有沒有一個是真的」, 而是「同一個結論在不同設定下站不站得住」。對它做調整等於把穩定性的問題當成顯著性的問題來回答。

這一頁的資料,以及它不能拿來說的話

本頁用 survival::rotterdam,2,982 筆乳癌登錄資料, 其中接受荷爾蒙治療的有 339 筆。整份資料沒有任何遺漏值 (0 格),所以「換一種遺漏值處理」這個很常見的敏感度分析軸在這裡不存在, 下面四個軸是別的東西。

十六個都站得住的分析

四個軸,每個軸兩個選項,全部組合:

選項 A選項 B為什麼兩個都站得住
結果定義無復發存活整體存活兩者在乳癌文獻都是標準終點
共變項集合年齡、腫瘤大小、分化度、淋巴結數再加停經狀態與荷爾蒙受體加不加這兩項都有人做
淋巴結數編碼原值截頂在十右尾很長,截頂是常見處理
追蹤長度全追蹤截尾在五年五年是乳癌的慣用時間窗

完全因子而不是隨手挑一串設定,是因為只有因子設計能把散布歸因到軸上。 挑一串設定只會得到一堆點,而「哪一個決定在主導結果」才是要問的問題。

上下兩層的規格曲線。上層:橫軸是依風險比排序的 16 個分析設定,縱軸是對數尺度的風險比,每一個設定畫成點估計加 95% 信賴區間。最左端的點是 0.695,最右端是 0.995,水平線標在無效值 1。第 12 個位置以淺色直帶標出,該處的點以紅色實心方塊繪製,代表事先指定的主分析,其區間跨過無效值。下層:八列的設定矩陣,每兩列對應一個軸的兩個選項,深色圓點代表該設定選了那一個選項;紅色圓點標出主分析在四個軸上各自的選擇。淋巴結數截頂那一列的深色圓點集中在左半邊。
同一個問題、同一份資料。風險比從 0.695 走到 0.995,7 個設定達到統計顯著、9 個沒有。下層讓每一根線可以被追回它是由哪四個決定組成的。產圖腳本 figures/scripts/B8-07-sensitivity-analysis.R
名次結果共變項淋巴結追蹤風險比95% CIp
1無復發存活核心加兩項截頂在十截尾五年0.6950.585–0.826< 0.001
2無復發存活核心截頂在十截尾五年0.7170.604–0.851< 0.001
3無復發存活核心加兩項截頂在十全追蹤0.7330.627–0.856< 0.001
4無復發存活核心截頂在十全追蹤0.7430.636–0.867< 0.001
5整體存活核心加兩項截頂在十截尾五年0.7570.613–0.934= 0.009
6整體存活核心加兩項截頂在十全追蹤0.7860.659–0.937= 0.007
7整體存活核心截頂在十全追蹤0.8090.680–0.964= 0.018
8整體存活核心截頂在十截尾五年0.8230.669–1.013= 0.066
9無復發存活核心加兩項原值截尾五年0.8470.715–1.002= 0.053
10無復發存活核心原值截尾五年0.8700.736–1.028= 0.103
11無復發存活核心加兩項原值全追蹤0.8740.751–1.018= 0.083
12(主分析)無復發存活核心原值全追蹤0.8840.760–1.028= 0.111
13整體存活核心加兩項原值截尾五年0.9250.753–1.138= 0.462
14整體存活核心加兩項原值全追蹤0.9320.784–1.108= 0.423
15整體存活核心原值全追蹤0.9540.803–1.133= 0.590
16整體存活核心原值截尾五年0.9950.811–1.220= 0.960

一句話讀完這張表:這份資料對「有沒有關聯」這個問題沒有給出單一答案, 給出答案的是分析設定。

主導這條曲線的,是一個沒有人會寫進方法段的決定

把每個軸的兩個選項各自平均起來相減,得到那個軸把估計值推動了多少(對數尺度):

擺幅
淋巴結數編碼0.184
結果定義0.093
共變項集合0.037
追蹤長度0.015

主導的是淋巴結數要不要截頂。在上圖的下層可以直接看出來:截頂那一列的深色圓點幾乎全部集中在左半邊。

這件事值得停一下。論文的方法段會寫終點是什麼、校正了哪些共變項——那是讀者看得到的兩個軸, 而它們在這裡分別排第二與第三。排第一的是一個連方法段都不會提的資料整理決定。

主分析落在中段,而那正是重點

事先指定的主分析是四個軸上各自最少加工的那個選項: 無復發存活、核心共變項、淋巴結數原值、全追蹤。

它的風險比是 0.884(95% CI 0.760–1.028,p = 0.111), 未達統計顯著,在十六個設定裡排第 12 名。同一條曲線上有 7 個設定是顯著的。

主分析既不是最保守的那一個,也不是最顯著的那一個。它的正當性不來自它在曲線上的位置, 來自它是在看到這條曲線之前就決定好的。 如果正當性來自位置,那麼:

  • 挑最顯著的那一個,理由可以寫成「這是效果最明確的設定」
  • 挑最保守的那一個,理由可以寫成「我們採取了最謹慎的做法」

兩句話都寫得出來,而且兩句都是在看到結果之後寫的。事後挑一個保守的來假裝謹慎, 與事後挑一個顯著的,是同一個動作。

「敏感度分析顯示結果穩健」是一句空話

這一句在論文裡出現的頻率極高,而它本身不帶任何資訊:沒說做了哪些擾動、 沒說每一個擾動後的估計是多少、沒說有沒有哪一個讓結論改變。

報告時該給的是三件東西:

  1. 做了哪些擾動,逐一列出,包括那些沒有改變結論的
  2. 每一個擾動後的效果估計與區間,不是只給 p 值、不是只給平衡表、更不是只給一句話
  3. 有沒有哪一個讓結論改變,如果有,說明是哪一個、以及為什麼主分析仍然是主分析

穩健不等於對

上面那條曲線是「不穩健」的樣子。但反過來的情形更需要小心:十六個設定全部顯著, 也不能證明結論正確。

理由在本頁的資料裡就看得見。適應症干擾存在於全部十六個設定中, 換結果定義、換共變項、換編碼、換時間窗,沒有任何一個動作碰得到它。 一組整齊一致的曲線,可以整組偏在同一個方向。

影響診斷那一頁用統合分析講過同一件事: 一份合併分析可以在納入研究全都有同一個系統性偏誤時,對逐篇排除完美穩健—— 因為那個偏誤不會因為少了一篇而消失。

敏感度分析能排除的,只有它實際擾動過的那些東西。 沒被擾動的假設,它一個字都沒說。

事後挑最顯著的代價,量出來

前面說「事後挑與事先指定是不同的東西」。那個差別有多大,可以直接量。

做法:把暴露欄位隨機打散,真實關聯因此正好是零,然後重跑同一組 16 個設定。 重複 4,000 次(種子 20260826), 每一次記兩個數字——最小的那個 p 值,以及事先指定那個設定的 p 值。

兩根水平長條比較假陽性比例。上方紅色長條是「只報十六個設定裡最顯著的那一個」,長度對應 17.9%;下方藍色長條是「只報一個事先指定的設定」,長度對應 6.5%。一條垂直虛線標在名目水準 5%,並在上方標注 nominal 5%。紅色長條遠遠超過虛線,藍色長條也略微超過。橫軸以百分比刻度標示。
4,000 次置換,暴露被打散所以真實關聯是零。兩組跑的是同一批分析,唯一的差別是哪一個會被報出來。產圖腳本 figures/scripts/B8-07-sensitivity-analysis.R
  • 只報最顯著的那一個:17.9% 的模擬會宣稱顯著
  • 只報事先指定的那一個:6.5%

差距是 11.4 個百分點。這不需要任何推論就能解讀——暴露是被打散的, 所以上面每一次「顯著」都是假陽性。

同一批設定、同一份資料、同一個真實關聯(零)。唯一改變的是哪一個結果會被寫進論文。 這就是為什麼「事先指定」不是行政手續。

讀論文時看哪裡

  1. 敏感度分析在哪一段。 在附錄一張表裡逐設定列出效果估計,跟在討論裡寫一句「結果穩健」, 是兩件完全不同的事。
  2. 有沒有效果估計,還是只有 p 值與平衡表。 只有 p 值的話,看不出結論會不會翻。
  3. 主分析與敏感度分析的位置有沒有寫死。 找「pre-specified」「according to the protocol」 這類字眼,以及有沒有預先註冊編號。
  4. 列出來的擾動是不是只有沒事的那些。 一組全部通過的敏感度分析, 跟一組被挑過的敏感度分析,在紙上長得一模一樣。
  5. 沒有被擾動的是什麼。 這通常比列出來的更重要——見上面「穩健不等於對」。

動手跑一次

library(survival)

d0 <- rotterdam
d0$rfs_time  <- pmin(d0$rtime, d0$dtime)
d0$rfs       <- as.integer(d0$recur == 1 | d0$death == 1)
d0$nodes_cap <- pmin(d0$nodes, 10)
d0$lpgr      <- log(d0$pgr + 1)

grid <- expand.grid(
  outcome  = c("rfs", "os"),
  covars   = c("core", "plus"),
  nodesf   = c("raw", "cap"),
  followup = c("full", "trunc5"),
  stringsAsFactors = FALSE
)

fit_one <- function(d, g) {
  if (g$followup == "trunc5") {
    tt <- 5 * 365.25
    d$rfs <- ifelse(d$rfs_time > tt, 0L, d$rfs); d$rfs_time <- pmin(d$rfs_time, tt)
    d$death <- ifelse(d$dtime > tt, 0L, d$death); d$dtime <- pmin(d$dtime, tt)
  }
  nod <- if (g$nodesf == "raw") "nodes" else "nodes_cap"
  cov <- if (g$covars == "core") c("age", "size", "grade", nod)
         else c("age", "size", "grade", nod, "meno", "lpgr")
  lhs <- if (g$outcome == "rfs") "Surv(rfs_time, rfs)" else "Surv(dtime, death)"
  f <- as.formula(paste(lhs, "~ hormon +", paste(cov, collapse = " + ")))
  summary(coxph(f, data = d))$coefficients["hormon", c("coef", "Pr(>|z|)")]
}

res <- t(sapply(seq_len(nrow(grid)), function(i) fit_one(d0, as.list(grid[i, ]))))
grid$hr <- exp(res[, 1]); grid$p <- res[, 2]
print(grid[order(grid$hr), ], row.names = FALSE)

驗證環境:R 4.6.0。下面只跑十六個設定的部分;置換模擬要跑 4,000 次,在完整腳本裡。

常見誤用

誤用為什麼錯
只寫「敏感度分析顯示結果穩健」沒說擾動了什麼、每個估計是多少、有沒有翻,這句話等於沒寫
敏感度分析只報 p 值看不出結論會不會翻,而那正是它要回答的
傾向分數的敏感度分析只報平衡表平衡表說的是配對做得好不好,不是估計值有沒有變
看到結果之後才決定跑哪些設定那是選擇性報告,與事先指定在紙上長得一樣
事後挑一個最保守的設定當主分析與事後挑最顯著的是同一個動作,只是方向相反
「敏感度分析全部通過」就寫成結論正確它只排除實際擾動過的東西;共同的偏誤不會因為換設定而消失
對敏感度分析做多重比較調整它問的是穩定性不是顯著性
把逐一排除觀察值當成敏感度分析的全部那是影響分析,動的是資料點不是分析決定
主分析與敏感度分析的位置在稿件裡沒有寫死讀者無從判斷哪一個是事先決定的
只擾動不會改變結論的那些軸一組被挑過的敏感度分析,看起來與一組完整的一模一樣

重跑本頁的所有數字

/opt/homebrew/bin/Rscript figures/scripts/B8-07-sensitivity-analysis.R

讀讀看這張圖

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

同一份資料、同一個問題,十六個各自都站得住的分析設定。最靠近虛無值的那一端給出的風險比是多少,這個數字該怎麼用?

看答案與解析

正確答案: 0.995,而另一端離虛無值明顯更遠。兩端都是站得住的分析,所以要讀的是這個寬度本身,不是從裡面挑一個

0.995 是十六個設定裡最靠近虛無值的那一個,而另一端是 0.695——同一份資料、同一個問題、每一個設定都講得出理由。這道題要練的是把整條曲線當成一個量來讀:寬度告訴你結論對分析決定有多敏感,而它跨過虛無值這件事告訴你「有沒有效果」這個問題在這份資料上不是由資料決定的,是由設定決定的。0.884 是事先指定的主分析,它落在中段而不是右端。0.695 是另一端,把它說成最靠近虛無值的那一個會讓整條曲線的方向反過來;它的 p 值確實最小,但那是因為它離虛無值最遠,不是因為它最靠近。

四個軸各自把估計值推動多少(對數尺度上的擺幅)。哪一個軸主導了整條曲線?

看答案與解析

正確答案: 0.184,淋巴結數的編碼。原值或截頂在十,是一個幾乎沒有論文會在方法段交代的決定

淋巴結數編碼的擺幅是 0.184,比第二名大了一倍左右。機制講得出來:淋巴結數是這份資料裡最強的預後因子,而它同時與是否接受治療有關(預後差的人比較可能拿到),所以「怎麼把它放進模型」直接決定了有多少干擾被移掉——截頂等於換掉一個強干擾因子的函數形式。結果定義的 0.093 排第二,而「換終點是最大的改變」這個推論在這裡不成立:無復發存活與整體存活在乳癌高度相關,換掉它動的是事件集合,共變項結構沒有變。共變項集合的 0.037 排第三,「最能改變估計值」正好相反——多加兩個與治療關聯較弱的變項,能移掉的干擾有限。真正的教訓是:論文看得到的是終點與共變項清單,主導散布的卻是那個沒有人覺得需要交代的資料整理決定。

事先指定的主分析,在依風險比排序的十六個設定裡落在第幾名?這個位置代表什麼?

看答案與解析

正確答案: 第 12 名,落在中段。它既不是最保守也不是最顯著的一個,它的正當性完全來自事先指定

主分析落在第 12 名,中段偏右。它未達統計顯著,而同一條曲線上有七個設定顯著——如果主分析的正當性來自它的位置,這一頁就沒有立場拒絕那七個。第 16 名是最靠近虛無值的那一端,把主分析等同於最保守的設定,等於容許作者在事後挑一個最保守的來假裝謹慎,那與挑最顯著的是同一個動作。第 1 名是最偏離虛無值的那一端,「主分析應該是效果最明確的」正是選擇性報告的標準說法。事先指定之所以有力量,就是因為它在看到這條曲線之前就決定好了。

把暴露隨機打散,真實關聯因此是零,再跑同一組十六個設定。只報最顯著那一個的話,宣稱顯著的比例是多少?

看答案與解析

正確答案: 0.179。暴露已經被打散,所以這個比例裡的每一次顯著都是假陽性

0.179 是只報最顯著那一個設定時的假陽性比例。這個數字不需要任何推論就能解讀,因為暴露是被打散的,關聯為零是設計出來的而不是假設的。0.065 是同一批模擬裡事先指定單一設定的比例,把它說成挑最顯著的那一個會讓兩者的差距整個消失,而那個差距正是這張圖唯一要說的事。0.114 是兩者相減,它是差距不是比例。

置換模擬跑了四千次。它報出來的假陽性比例,蒙地卡羅標準誤是多少,這個數字限制了什麼?

看答案與解析

正確答案: 0.0034。比這個量級更細的差異,這個模擬讀不出來——所以它能支持的是兩組之間的大差距,不是任何一根長條與名目水準之間的小數點

0.0034 是名目水準附近的蒙地卡羅標準誤,也就是這個模擬的解析度。它決定了哪些差異可以讀:兩組相差 0.1140,是它的三十倍以上,穩固得很;而任何一根長條與名目水準之間幾個千分點的差距,就要小心得多。0.0653 是事先指定那一組的實測比例,把它說成標準誤會讓解析度看起來與訊號一樣大,那樣的話這張圖什麼都證明不了。0.1140 是兩組的差距,把它說成標準誤則相反——會把這張圖唯一穩固的發現判成雜訊。跑得夠不夠多不是靠感覺,是靠這個數字。

十六個設定裡有幾個達到統計顯著,而這個數字能支持與不能支持什麼?

看答案與解析

正確答案: 7 個。結論對「換一個分析決定」並不穩健;但反過來就算十六個全部顯著,也不能證明結論正確

七個設定顯著、九個不顯著,所以這個結論對換一個分析決定並不穩健。真正要記住的是反方向那一半:穩健不等於對。這份資料裡的治療是依適應症給的,這個干擾存在於全部十六個設定中,換設定不會讓它消失——一組全部顯著的曲線,仍然可以整組偏在同一個方向。把「全部顯著」讀成通過檢驗,正是這一頁要擋的誤讀。12 是主分析在曲線上的名次,不是顯著的個數;就算它是,「主分析低估了效果所以改報顯著的那些」也正好是選擇性報告的定義。

素材來源與授權

本頁為原創內容

回報內容問題

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

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

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

一併送出的資訊

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