專家已經雙重審閱,尚未人工抽查

自我對照設計

讓每個人當自己的對照,一次消掉所有不隨時間變的個人特徵——包括你沒測量、也不知道存在的那些。SCCS 與 case-crossover 的差別、三個假設各自在什麼時候破掉,以及為什麼「事件會不會影響後續暴露」是這個設計最致命的問題。

這一頁在解決什麼問題

工具變數那一頁處理的是同一個困境——未測量的干擾——但它的代價很高:三個假設裡有兩個無法驗證,而且信賴區間會寬上好幾倍。

自我對照設計(self-controlled design)走的是完全不同的一條路,而且它的想法簡單到近乎粗暴:

如果比較的兩邊是同一個人的兩段時間,那麼所有「不隨時間改變的個人特徵」在比較裡自動消失。

基因、體質、性別、族裔、童年環境、社經地位、慢性共病的底子、就醫習慣——這些東西你測不測得到都無所謂,因為它們在同一個人的兩段時間裡是同一個值,在人內的比較裡直接抵消掉。這不是統計校正,是設計上的消去。

代價換到了別的地方:它只回答「為什麼是現在」,不回答「為什麼是」;它只適合短暫的暴露配上時點明確的急性事件;而且它有三個假設,其中至少一個在藥物流行病學裡經常被破壞。

兩種錨點:SCCS 與 case-crossover

所有自我對照設計都由三個東西定義:一個錨點(anchor,時間軸上的定位點)、一個焦點窗(focal window,假設風險升高的那段時間)、以及一個或多個對照窗(referent window,用來估「平常的頻率」)。分析一律以個人為分層(條件式 Poisson 或條件式邏輯迴歸)——這一步才是把不隨時間變的特徵消掉的地方。

兩大家族的差別在於錨點釘在哪裡:

Case-crossover(CCO)Self-controlled case series(SCCS)
錨點結果事件暴露
比的是焦點窗與對照窗的暴露頻率焦點窗與對照窗的事件發生率
問句「事件發生在這一刻,前面剛好有暴露的機會有多大?」「暴露發生了,之後短時間內出事的機率有多高?」
對照窗放哪事件之前的一或多個時點觀察期內焦點窗以外的全部時間
方向單向(除非刻意做雙向)雙向——觀察期不會因為事件發生而結束
統計模型條件式邏輯迴歸或 Mantel-Haenszel,估 OR條件式 Poisson 迴歸,估 IRR
對長期用藥有已知的向上偏誤(persistent user bias)較不受影響,但時間相依干擾的風險上升
對「事件影響後續暴露」不敏感(對照窗在事件之前)敏感,是它的核心假設之一
對「事件影響觀察期長度」不敏感敏感(結果是死亡時直接破功)

最後兩列是實務上選哪一個的關鍵:結果的死亡率高、或出事之後醫師會停藥的情況,CCO 比較安全;暴露的使用率隨時間快速改變(例如疫苗剛開打)的情況,SCCS 比較安全。

這一頁的模擬

設定對應到藥物流行病學最典型的問題:一個十四天的療程,與一個急性事件(比方說心律不整住院),真實的發生率比是 3.0。模擬裡刻意放了兩件事:

  • 人與人之間的基線事件率差很多(frailty 的標準差是 1.0,也就是最高風險與最低風險的人差好幾倍),而且這個 frailty 完全沒有被測量
  • 病得比較重的人比較容易被開這個藥——模擬出來的暴露組與未暴露組,frailty 差了 0.83 個標準差

模擬了 4000 個人、各觀察 365 天,其中 1614 位在期間內用過藥,共發生 4084 次事件。

八位模擬病人的時間軸,每人一列。灰線是三百六十五天的觀察期,黃色方塊是十四天的風險窗,紅點是落在風險窗內的事件、藍點是落在風險窗外的事件。前四位有事件落在風險窗內,後四位沒有。
八位模擬病人。黃色方塊是十四天的風險窗(焦點窗),其餘的觀察期是對照窗。整個設計唯一做的比較是同一列之內黃色與非黃色的事件密度——不同列之間從來沒有互相比較過。產圖腳本 figures/scripts/B6-05-self-controlled.R

SCCS 的條件式概似,寫出來長這樣

SCCS 的核心是一條可以手寫的式子。對一位在觀察期內有 nin_i 次事件的病人,其中 ni,riskn_{i,\text{risk}} 次落在風險窗裡,他在風險窗待了 rir_i 天、在對照窗待了 cic_i 天,那麼在已知他一共出了幾次事的條件下,這些事件落在風險窗的機率給出:

Li(β)=(eβ)ni,risk(ci+eβri)niL_i(\beta) = \frac{\left(e^{\beta}\right)^{n_{i,\text{risk}}}}{\left(c_i + e^{\beta} r_i\right)^{n_i}}

eβe^\beta 就是發生率比(IRR)。式子裡沒有那個人的基線風險——它在「已知他出了幾次事」這個條件裡被約掉了。這一行代數就是「所有不隨時間變的個人特徵被消掉」的全部內容。

同時它也解釋了兩件事:只有同時有事件又有暴露的人才對估計有貢獻(其他人的貢獻是常數,微分掉了),以及這個設計估不出絕對風險——分母被條件掉了,只剩比值。

library(survival)
set.seed(20260822)

OBS <- 365L; RISK <- 14L; IRR <- 3.0; BASE <- 0.0016

# ── 模擬:frailty 未測量,且同時決定「會不會被開藥」與「事件率」 ──────
simulate_people <- function(n) {
  frailty <- rnorm(n)
  exposed <- rbinom(n, 1, plogis(-0.5 + 1.0 * frailty))   # 適應症干擾
  start   <- ifelse(exposed == 1, sample(OBS - RISK, n, TRUE), NA)
  rate    <- BASE * exp(frailty)
  lapply(seq_len(n), function(i) {
    inrisk <- rep(FALSE, OBS)
    if (exposed[i] == 1) inrisk[start[i]:(start[i] + RISK - 1L)] <- TRUE
    days <- which(rbinom(OBS, 1, pmin(rate[i] * ifelse(inrisk, IRR, 1), 1)) == 1)
    list(exposed = exposed[i], inrisk = inrisk, eventDays = days,
         riskDays = sum(inrisk), controlDays = OBS - sum(inrisk),
         events = length(days),
         eventsInRisk = if (length(days)) sum(inrisk[days]) else 0L)
  })
}
rows <- simulate_people(4000)

# ── SCCS:條件式概似,直接最佳化 ─────────────────────────────────────
# 只有「有事件 且 有暴露」的人進得來——其餘的人在概似裡是常數
use   <- Filter(function(r) r$events > 0 && r$exposed == 1, rows)
r_day <- sapply(use, `[[`, "riskDays")
c_day <- sapply(use, `[[`, "controlDays")
n_rsk <- sapply(use, `[[`, "eventsInRisk")
n_all <- sapply(use, `[[`, "events")

nll <- function(b) -sum(n_rsk * b - n_all * log(c_day + exp(b) * r_day))
o   <- optim(0, nll, method = "BFGS", hessian = TRUE)
se  <- sqrt(1 / o$hessian[1, 1])
exp(c(IRR = o$par, lcl = o$par - 1.96 * se, ucl = o$par + 1.96 * se))

# ── Case-crossover:每一次事件一個分層,條件式邏輯迴歸 ───────────────
lags <- c(30, 60, 90)                     # 三個對照時點,都在事件之前
recs <- list()
for (k in seq_along(rows)) {
  r <- rows[[k]]
  for (ev in r$eventDays) {
    wins <- c(ev, ev - lags)
    if (any(wins < 1)) next
    recs[[length(recs) + 1]] <- data.frame(
      sid = paste0(k, "_", ev), case = c(1L, rep(0L, length(lags))),
      exposed = as.integer(r$inrisk[wins]))
  }
}
dd <- do.call(rbind, recs)
summary(clogit(case ~ exposed + strata(sid), data = dd))$conf.int

驗證環境:R 4.6.0 + survival 3.8.6(SCCS 的概似函數用 base R 的 optim 直接最佳化)

五種分析,同一個真值

森林圖,五列估計值與 95% 信賴區間,虛線標在真實發生率比 3。跨人比較的估計明顯高於真值;SCCS 與使用全部事件的 case-crossover 涵蓋真值;只用第一次事件的 case-crossover 與事件終止觀察期的 SCCS 都偏高。
同一份模擬資料的五種分析。虛線是真實的發生率比。跨人比較被適應症干擾推高;兩種正確做的人內比較涵蓋真值;最後兩列各自破壞了一個假設。產圖腳本 figures/scripts/B6-05-self-controlled.R
分析方式估計值95% CI相對真值
真實發生率比(模擬設定值)3.00
暴露期人時 vs 其餘人時(跨人比較)4.574.05–5.17被適應症干擾推高
SCCS(人內比較)3.012.66–3.41涵蓋真值
Case-crossover,每一次事件都用2.832.35–3.41涵蓋真值
Case-crossover,只用第一次事件4.623.24–6.60在這份模擬裡被高估
SCCS,但事件會終止觀察期3.873.05–4.91第三個假設被破壞

第一列是這一頁的重點:把所有人的暴露期人時放在一起、跟所有人的非暴露期人時比,估出來是 4.57,而真值是 3.00。 偏誤的來源不是算錯,是那 22596 個暴露人天全部來自 frailty 較高的那群人——跨人比較沒有辦法把這件事修好,因為 frailty 不在資料裡。

SCCS 用同一份資料估出 3.01(2.66–3.41)。它只用到 1017 位同時有事件又有暴露的病人、2555 次事件,其中 274 次落在風險窗內。沒有校正任何共變項,也沒有用到任何未暴露的對照組。

SCCS 的三個假設

這個設計買到的東西很大,所以價錢也不便宜。三個假設,逐條看:

假設一:事件必須是「獨立重複發生」的,或是夠罕見

如果一次事件會改變後續事件的機率(例如第一次中風之後,第二次的機率永遠不一樣了),條件式概似的推導就不成立。慣例上的處理是只分析每個人的第一次事件,而這只有在事件夠罕見(研究期間的發生率低於 10%)時才安全。本頁的模擬用的是獨立的 Poisson 過程,所以重複事件全部可以用。

假設二:事件不能影響後續的暴露機率(event-independent exposure)

這是藥物流行病學裡最常被破壞的一條。 病人出事之後,醫師往往就不敢再開那個藥了——於是「事件之後」那段時間的暴露機率被人為壓低,對照窗看起來比實際上更「乾淨」,IRR 被高估。

歷史上的處理是在暴露之前挖一段過渡窗(pre-exposure window)排除掉。近年的研究指出這個修法的效果並不穩定,偏誤大小不只取決於延後多久,還取決於觀察期有多長、暴露多半發生在觀察期的哪一段。擔心這條假設的時候,正確的作法是用專門處理事件相依暴露的 SCCS 延伸版做敏感度分析,而不是加一段過渡窗就當作解決了。

假設三:事件不能影響觀察期的長度(event-independent observation period)

SCCS 之所以是雙向的,靠的就是「觀察期不會因為事件發生而結束」。結果是死亡的時候,這條假設直接破功——人死了,後面的觀察期就沒了。

模擬的最後一列示範了這件事:把資料改成「事件一發生就終止追蹤」,其他都不變,SCCS 估出 3.87(3.05–4.91),比真值 3.00 高了將近三成,而且信賴區間完全沒有涵蓋真值

Case-crossover 的核心假設

CCO 的假設只有一句話,但它管得很寬:在「暴露沒有效果」這個虛無假設之下,對照窗的暴露機率必須代表焦點窗的暴露機率。

這句話的兩個直接後果:

第一,暴露的使用率不能有明顯的時間趨勢。 如果這個藥的處方量逐年上升,那麼「事件當天」比「三十天前」更可能在用藥——與藥效完全無關,純粹因為時間。估計會被系統性推高。處理方式有兩種:雙向 CCO(對照窗放在事件前後兩側,但這需要多加一條「事件不影響後續暴露」的假設),或是 case-time-control,用一組沒發生事件的人估出「時間趨勢的 OR」再除掉。

第二,長期用藥會產生向上的偏誤。 CCO 只有暴露狀態不一致的人才提供訊息。如果一個藥是天天吃的,那麼唯一會出現「事件當天有吃、三十天前沒吃」的人,就是剛開始吃不久就出事的那些——反過來的組合(事件當天沒吃、三十天前有吃)幾乎不存在,因為沒出事的人不會停藥。這叫 persistent user bias,它是 CCO 在慢性用藥上系統性的問題,SCCS 因為是雙向的所以比較不受影響。

這句「只有暴露狀態不一致的人才提供訊息」在本頁的模擬裡可以直接數出來。上表第三列(每一次事件都用)一共有 3052 個配對組——一次事件一組——其中暴露狀態在焦點窗與對照窗之間真的不一致、因此進得了條件式概似的只有 445 組(14.6%)。第四列(只用第一次事件)是 127 / 1168 組(10.9%)。兩列真正撐起估計的配對組都不到表面組數的六分之一,而讀者只看上表是看不出來的——這正是下面「常見誤用」表裡「沒說明有多少人實際貢獻了訊息」那一條。

這個設計消不掉什麼

自我對照設計消掉的是不隨時間變的東西。所以下面這些它一個也處理不了:

  • 時間相依的干擾因子——年齡、季節(流感、氣溫)、疾病本身的自然進程。觀察期越長,這個問題越嚴重。標準作法是把年齡與日曆時間切成區段一起放進條件式模型。
  • 暴露與結果的時點記錄不準——焦點窗與對照窗會被劃錯,事件被歸到錯的時段。所以這個設計要求事件的時間點明確可考(急性事件、有精確日期);起病時間模糊的疾病(子宮內膜異位症)或「有需要才吃」的藥(偏頭痛止痛藥)不適合。
  • 選擇偏誤——如果落在焦點窗的事件比落在對照窗的更容易被記錄下來(例如打完疫苗後的不適比較容易被通報),關聯會被高估。
  • 絕對風險——這個設計本質上估不出來。要換算成可歸因分數或每萬人多幾例,需要外部的分母資訊,而那要非常小心。

什麼時候該想到自我對照設計

四個條件同時成立的時候,它往往是最好的選擇:

  1. 暴露是短暫的(或者暴露雖然是永久的,但它對結果的影響是短暫的——疫苗就是這樣)
  2. 結果是急性的、時點明確的
  3. 未測量的、不隨時間變的干擾是主要威脅——這正是DAG 上那些灰色節點
  4. 找不到合適的對照組,或者暴露很快就普及到全人群(疫苗接種活動)

反過來,暴露是慢性的、結果的起病時間模糊、或者主要的威脅是時間相依的干擾時,回去用世代研究加上加權比較實際。

為什麼這一頁沒有推薦影片

本站的影片清單經過實際驗證才收錄。這個主題在全部語言裡都找不到教學等級的影片——中文(繁體與簡體)完全沒有,英文只有三支研討會或講座的側錄(觀看數在數百之譜,長度三十到六十分鐘,簡報翻頁為主)。

與其硬塞,這裡直接說明現況。真的想聽人講的話,目前最接近的三支是 Leicester RSS Hub 的 Self-controlled case series methodology(32 分鐘,方法學導向)、case-crossover 原創者 Malcolm Maclure 本人的 Self controlled Design(60 分鐘),以及 OHDSI 的 Vaccine safety evaluation using SCCS(58 分鐘,應用導向)。它們都是給已經懂的人聽的,不是入門教材。

比較有效率的路徑是先讀本頁,再直接讀頁尾那篇 CC BY 的回顧文——它有完整的術語表與註解式書目。

常見誤用

誤用為什麼錯
用 SCCS 研究以死亡為結果的問題事件終止了觀察期,第三個假設直接破功
出事後醫師會停藥的情境仍用標準 SCCS事件影響了後續暴露機率,IRR 會被高估
用 CCO 研究天天吃的慢性用藥Persistent user bias,只有剛開始吃就出事的人提供訊息
暴露的處方量有明顯時間趨勢卻用單向 CCO對照窗不再代表「平常」,估計被系統性推高
用自我對照設計研究起病時間模糊的疾病焦點窗與對照窗會劃錯,事件被歸到錯的時段
觀察期長達數年卻不校正年齡與季節這個設計只消掉不隨時間變的東西
從自我對照分析報「每萬人多幾例」分母被條件掉了,這個設計估不出絕對風險
沒說明有多少人實際貢獻了訊息只有同時有事件又有暴露的人進得了概似
把 IRR 說成「跨人」的相對風險它估的是同一個人不同時期的比值,問的是「為什麼是現在」
只做自我對照分析就宣稱排除了所有干擾時間相依的干擾原封不動
未達顯著寫成「該藥不會引起這個事件」只能說本分析未偵測到風險升高

重跑本頁的所有數字

/opt/homebrew/bin/Rscript figures/scripts/B6-05-self-controlled.R

讀讀看這張圖

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

同一份模擬資料,把所有人的暴露期人時跟所有人的非暴露期人時放在一起比,估出發生率比 4.57,而模擬設定的真值是 3。多出來的那一段是什麼?

看答案與解析

正確答案: 暴露組與未暴露組在一個沒被測量到的體質變項上差了 0.83 個標準差,跨人比較把這段差距算進了藥效裡

跨人比較的分母是每一個沒用過藥的人的人時,而那些人的體質風險本來就比較低——暴露組與未暴露組的未測量體質差了 0.83 個標準差,這段差距整個被記在藥的頭上。這是系統性的偏誤,不是抽樣波動:把樣本數加大,4.57 只會估得更精準,不會靠近真值。1.00 是模擬裡體質變異本身的標準差,它是偏誤的原料而不是偏誤的大小。跨人比較沒有辦法修好這件事,因為體質不在資料裡——這正是自我對照設計把比較搬進同一個人身上的理由。

這份模擬有 4000 個人、一共發生 4084 次事件。SCCS 估出 3.01,它是用多少資料算出來的?

看答案與解析

正確答案: 只有同時有事件又有暴露的那 1017 位病人的 2555 次事件進得了概似,其他人在概似裡是常數

條件式概似只吃「同時有事件又有暴露」的人:沒有事件的人與沒有暴露的人,他們對概似的貢獻是一個不含參數的常數,微分掉了。所以 4084 次事件裡只有 2555 次來自那 1017 位病人。274 是這 2555 次裡落在風險窗內的那些,它是分子那一側,不是全部的資料;把風險窗外的事件丟掉,就沒有東西可以估「平常的頻率」,比值也就無從算起。這也是報告 SCCS 時一定要寫出實際貢獻人數的理由——4000 個人裡真正撐起這個估計的只有 1017 位。

Case-crossover 那一列一共有 3052 個配對組,一次事件一組。條件式邏輯迴歸真正用到的是幾組?

看答案與解析

正確答案: 只有 445 組——焦點窗與對照窗的暴露狀態不一致的那些,其餘的組在條件式概似裡是常數

條件式邏輯迴歸只在分層內部比較,所以一個分層裡若事件當天與三個對照時點的暴露狀態全都一樣,它對概似就沒有貢獻。3052 組裡只有 445 組是不一致的,不到七分之一。1168 是「只用第一次事件」那一版的分層數,它回答的是另一個問題,而且那一版有訊息的組數更少。這件事讀者只看估計表是看不出來的:表面上有幾千組,實際撐起估計的是幾百組,而區間的寬窄由後者決定。所以報告 case-crossover 時要寫出有訊息的分層數。

Case-crossover 從「每一次事件都用」改成「只用第一次事件」,其他都不變,估計從 2.83 跳到 4.62。這個跳動說的是什麼?

看答案與解析

正確答案: 2.83 比較可信:被選為「第一次」的那些事件,對照窗系統性地比較不可能處在暴露狀態,只用第一次會把估計推高

「第一次」不是一次隨機抽出的事件。如果某個人的對照時點剛好落在用藥期間,他在那段時間的事件率是平常的三倍,很可能就在那時候出了第一次事,於是那一次才是「第一次」,現在這一次就不會被選中。結果是被選中的事件,它們的對照窗比平常更不可能處在暴露狀態,分母被壓小,估計被推高,2.83 因此變成 4.62。2.89 是把三個對照窗改成一個、但仍然使用全部事件的估計,它跟 2.83 幾乎一樣,所以高估的來源是樣本限制而不是對照窗的個數。教訓是:對照窗代表不代表「平常」,是這個設計唯一要保護的東西,而任何看起來無害的樣本限制都可能破壞它。

把資料改成「事件一發生就終止追蹤」,其他都不變,SCCS 估出 3.87,95% CI 是 3.05 到 4.91。這代表什麼?

看答案與解析

正確答案: 區間下界 3.05 已經在真值之上:第三個假設(事件不能影響觀察期長度)被破壞,整個估計被推高

SCCS 是雙向的,它靠的就是「觀察期不會因為事件發生而結束」。一旦事件終止追蹤,事件之後的那段對照時間整段消失,對照窗被系統性地縮短,比值就被推高:3.87 比真值高了將近三成,而且下界 3.05 完全沒有涵蓋真值。這不是變得保守,是偏了。3.41 是完整資料那一版的區間上界,兩個版本的區間幾乎不重疊,差別不在精確度而在偏誤。實務上的意思是:結果是死亡或高致死率時不宜直接用標準 SCCS,改用 case-crossover、專門處理事件相依觀察期的延伸版,或把追蹤起點改成第一次暴露那一刻。

落在風險窗內的事件一共 274 次。跨人比較用它們估出 4.57,SCCS 用同一批事件估出 3.01。同一批事件為什麼給出不同的答案?

看答案與解析

正確答案: 因為跨人比較的分母是全部 3810 次風險窗以外的事件,其中包含從來沒用過藥的人,而那些人的基線風險本來就比較低

分子那一側是同一批 274 次事件,不同的是分母。跨人比較拿它們去比全部 3810 次風險窗以外的事件,而那些事件大半來自從來沒用過藥的人;他們的體質風險比較低,分母被稀釋,比值就被推高。SCCS 只拿同一個人在風險窗外的時間當分母,體質在相減時抵消掉了。第三個選項有一半是真的:SCCS 的條件式概似確實只吃「同時有事件又有暴露」的人,1945 位有事件的人裡有一部分進不去。但人數不是這個落差的機制——分子那一側本來就是同一批事件,而差距的方向是可以事先推出來的(適應症干擾把跨人比較往上推),「樣本不同所以估計不一樣」則說不出任何方向。

素材來源與授權

回報內容問題

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

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

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

一併送出的資訊

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