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

干擾、DAG 與該校正什麼

干擾因子的三個條件、把因果假設畫成 DAG 之後「該放什麼進模型」變成一個可以檢查的問題、以及為什麼校正對撞因子會憑空製造出關聯——包括那個發生在暴露之前、看起來完全無害的變項。

這一頁在解決什麼問題

世代研究那一章談過觀察性研究的核心難題:兩組人本來就不一樣,所以觀察到的關聯不等於治療的效果。那一章的處理方式是「把已測量到的干擾因子放進 Cox 模型校正掉」,並且誠實地說了那句限定詞——統計校正只能處理你有測量到的變項

這一頁往前補一步,補的是更根本的問題:在你有測量的那些變項裡面,哪些該放進模型?

直覺的答案是「全部都放,反正校正越多越乾淨」。這個直覺在三種情況下是錯的,而且錯的方向不是「校正得不夠」,是校正本身製造出偏誤。分辨這三種情況不能靠 p 值、不能靠逐步迴歸、不能靠 AIC——它們全都只看資料裡的關聯,而該不該校正是一個關於因果結構的問題,資料本身不會告訴你答案。

有向無環圖(directed acyclic graph, DAG)就是把因果結構寫下來的工具。它不會幫你找出真相,它只做一件事:把你腦中的假設攤開,讓「該校正什麼」變成一個可以逐條檢查的問題,而不是一個憑感覺的決定。

干擾因子的三個條件

一個變項 CC 要算是暴露 AA 與結果 YY 之間的干擾因子(confounder),傳統上要同時滿足三個條件:

  1. CC 與暴露 AA 有關聯——用藥組與不用藥組在 CC 上不一樣
  2. CCYY 的獨立危險因子——就算不看 AACC 本身也會影響結果
  3. CC 不在 AYA \to Y 的因果路徑上——它不是暴露造成的、也不是暴露發揮作用的中間站

第三條是最常被跳過的,而它正是干擾因子與中介因子的分界線。前兩條在資料裡看得到(可以算相關、可以跑迴歸),第三條看不到——它是關於時序與機轉的知識,只能來自臨床、來自文獻、來自你對這個疾病的理解。

用 DAG 把問題畫出來

DAG 的規則少到可以一句話講完:每個變項畫一個節點,每個「我認為 X 直接造成 Y」畫一個箭頭,方向永遠指向後果,而且不准繞回自己(無環)。 沒有箭頭代表的是一個實質主張——「我認為這兩者之間沒有直接因果」——所以畫不出箭頭的地方跟畫得出來的地方一樣重要。

畫完之後,關聯(association)會沿著三種路徑在圖上流動:

路徑形狀名稱預設是通的嗎校正中間那個變項會怎樣
ACYA \leftarrow C \to Y分岔(fork)/共同原因關掉它,這正是我們要的
AMYA \to M \to Y鏈(chain)/中介關掉它,但關掉的是你想估的效果
ASYA \to S \leftarrow Y對撞(collider)/共同結果不通打開它,憑空製造出關聯

第三列是整個 DAG 語言最反直覺、也最有用的一條。對撞路徑預設是關著的,校正會把它打開。 這就是「校正越多越好」這個直覺最重要的反例。

四種結構,四種答案

把四種結構各模擬 500 次、每次 2000 個受試者,每一次都跑兩個模型:只放暴露的(Y ~ A),以及多放一個變項的(Y ~ A + Z)。四個結構的箭頭強度全部設成一樣,所以它們之間唯一的差別是形狀

四張小型 DAG:干擾因子 C 同時指向 A 與 Y;對撞因子 S 被 A 與 Y 同時指向;中介因子 M 位於 A 到 Y 的路徑上;M-bias 結構中 Z 被兩個未測量變項 U1 與 U2 同時指向。每張圖用紅色虛線框標出被校正的變項。
四種結構,紅色虛線框是被放進模型的那個變項。灰底節點代表未測量。A 到 Y 有真實效果的只有第一與第三張;第二、第四張的真實效果是 0。產圖腳本 figures/scripts/B6-01-dag.R
set.seed(20260822)
n <- 2000; g <- 0.8      # g = 每一條非因果箭頭的強度,四種結構共用

# 1. 干擾因子:C 同時造成 A 與 Y,真實效果 = 1
gen_confounder <- function(n) {
  C <- rnorm(n); A <- g * C + rnorm(n); Y <- 1 * A + g * C + rnorm(n)
  data.frame(A, Y, Z = C)
}
# 2. 對撞因子:A 與 Y 各自獨立,兩者共同造成 S,真實效果 = 0
gen_collider <- function(n) {
  A <- rnorm(n); Y <- 0 * A + rnorm(n); S <- g * A + g * Y + rnorm(n)
  data.frame(A, Y, Z = S)
}
# 3. 中介因子:A 透過 M 作用,總效果 = 0.2 + 0.8 * 0.8
gen_mediator <- function(n) {
  A <- rnorm(n); M <- g * A + rnorm(n); Y <- 0.2 * A + g * M + rnorm(n)
  data.frame(A, Y, Z = M)
}
# 4. M-bias:Z 發生在暴露之前,但它是兩個未測量變項的共同結果,真實效果 = 0
gen_mbias <- function(n) {
  U1 <- rnorm(n); U2 <- rnorm(n)
  Z <- g * U1 + g * U2 + rnorm(n)
  A <- g * U1 + rnorm(n)
  Y <- 0 * A + g * U2 + rnorm(n)
  data.frame(A, Y, Z)
}

d <- gen_collider(n)
coef(lm(Y ~ A, data = d))["A"]        # 未校正
coef(lm(Y ~ A + Z, data = d))["A"]    # 校正對撞因子之後

驗證環境:R 4.6.0,base R 即可,不需要任何套件

四個面板,各自畫出未校正與校正後估計值在 500 次模擬中的分布密度曲線,虛線標示真實效果。干擾因子的面板裡校正後的分布落在真實值上;另外三個面板裡校正後的分布明顯偏離真實值。
每個面板是同一種結構重複 500 次的估計值分布。藍色是未校正、紅色是多放了一個變項。黑色虛線是真實效果。只有第一個面板裡「多放一個變項」是對的。產圖腳本 figures/scripts/B6-01-dag.R
結構臨床上的讀法真實效果Y ~ AY ~ A + Z該怎麼做
干擾因子(confounder)疾病嚴重度同時決定了用不用藥與會不會死1.00+1.39+1.00校正
對撞因子(collider)住院與否同時被暴露與結果決定0.000.00−0.39不要校正
中介因子(mediator)藥物透過降血壓起作用0.84+0.84+0.20不要校正(除非你要的是直接效果)
M-bias一個發生在暴露之前、卻是兩個未測量因素共同結果的變項0.000.00−0.12不要校正

四列裡只有第一列的「多校正一個」是進步的。其餘三列,那個多放進去的變項把一個原本無偏的估計弄壞了。

對撞因子:校正會憑空製造關聯

第二列的數字值得停下來看:暴露與結果在模擬裡是完全獨立的(真實效果設成 0),未校正的估計是 +0.001,校正之後變成 −0.388。單看一次模擬,那個關聯的信賴區間是 -0.455 到 -0.372,完全不跨過 0,p 值小到報表印不出來。

兩個毫無關係的變項,因為多校正了一個共同結果,看起來變得高度相關。

直覺上為什麼會這樣:假設住院(SS)的原因只有兩個,暴露與疾病本身。現在你只看住院的病人——在這群人裡面,沒有暴露的那些人一定是因為疾病比較嚴重才住進來的。於是在住院病人裡,「沒暴露」與「病得重」綁在一起,而這個關聯在整體人群裡根本不存在。它是你選擇看誰的時候創造出來的。

M-bias:發生在暴露之前,還是不能校正

上面那條判準會讓人得出一個很方便的簡化規則:「只校正暴露之前發生的事,就不會踩到對撞因子。」這個規則八成的時候是對的,而 M-bias 就是剩下的那兩成。

第四張 DAG 的結構是這樣:有兩個沒被測量到的東西——U1U_1(比方說健康行為傾向)與 U2U_2(比方說尚未診斷的潛在疾病)。U1U_1 影響病人會不會接受這個暴露,U2U_2 影響結果,而兩者都影響同一個變項 ZZ(比方說某個早年就測過的檢驗值)。

ZZ 完全符合「發生在暴露之前」,它跟暴露有關聯、跟結果也有關聯——照傳統三條件的檢查表,它看起來就是個標準的干擾因子。但它其實是 U1U_1U2U_2對撞因子。校正它,就打開了 AU1ZU2YA \leftarrow U_1 \to Z \leftarrow U_2 \to Y 這條原本關著的路:真實效果是 0,校正後的估計是 −0.124。

中介因子:校正掉的是你想估的東西

第三列的情況不一樣——它不是偏誤,是估錯了問題

模擬裡藥物有兩條作用路徑:直接的一條,以及透過中介變項(血壓)的一條。總效果是 0.84,未校正的模型估到 0.841,估得很準。把中介變項放進去之後,估計掉到 0.200——那正好是直接效果

所以這裡沒有人算錯,只是這兩個數字回答的是不同的問題:

  • 總效果(total effect):「給這個藥,結果會差多少?」——這是臨床決策要的數字
  • 直接效果(direct effect):「扣掉降血壓那條路之後,這個藥還剩多少作用?」——這是機轉研究要的數字

後門準則的白話版

有了上面三種路徑,「該校正什麼」就有了一個可以機械執行的準則。後門路徑(backdoor path)指的是任何一條從暴露出發、第一個箭頭是指向暴露的(也就是箭頭「倒著」離開暴露)、最後走到結果的路徑。這種路徑帶來的關聯不是因果,是雜訊。

後門準則(backdoor criterion)的白話版是三句話:

  1. 把所有後門路徑找出來
  2. 挑一組變項,讓每一條後門路徑都被擋住(路徑上有一個非對撞的節點被校正,或是路徑上有一個對撞節點且它沒有被校正)
  3. 這組變項裡不能包含暴露的後代(也就是暴露造成的任何東西,包括中介因子)

滿足這三條的那組變項,就是一組合法的校正集合(adjustment set)。注意這裡有兩件事跟直覺不合:

  • 合法的校正集合可能不只一組,而且不一定是變項最多的那組
  • 有時候把某個變項加進去,會讓原本合法的集合變成不合法——這正是 M-bias 的情況

實務上不必手算:dagitty 套件(也有網頁版)可以把圖畫進去、直接吐出所有最小的合法校正集合,下一節就示範怎麼做。真正需要人做的是把箭頭畫對,那部分沒有工具可以代勞。

把 DAG 交給機器:從圖到校正集合

上一節說「實務上不必手算」。這一節就是那句話的兌現——把同一張圖寫成程式碼,讓它自己吐出答案。

dagitty 的語法幾乎就是把箭頭打出來。下面這段是這一頁第一張圖左上角那個干擾因子結構,一字不差:

library(dagitty)

g <- dagitty('dag {
  A [exposure,pos="0,0"]
  Y [outcome,pos="1,0"]
  C [pos="0.5,-0.85"]
  A -> Y
  C -> A
  C -> Y
}')

adjustmentSets(g)                      # 所有最小充分校正集合
impliedConditionalIndependencies(g)    # 這張圖宣稱「應該看不到」的關聯

驗證環境:dagitty 0.3.4。網頁版在 dagitty.net,語法完全相同——下面的字串可以直接貼進去。

pos= 只影響版面、不影響運算;寫上去是為了讓你貼進 dagitty.net 之後看到的圖跟這裡一樣。 (y 座標取負值,因為網頁版的 y 軸朝下。)

四張圖各自的答案

把這一頁的四個結構都跑一遍,adjustmentSets() 給出的是:

結構直覺上會校正的變項adjustmentSets()直覺對嗎
干擾因子C{ C }
對撞因子S{ }
中介因子M{ }
M-biasZ{ }

四個結構裡只有干擾因子那一個,直覺的答案與工具的答案一致。另外三個,工具都回 { }——什麼都不要校正

這正是這一頁前面用模擬證明過的事,只是換成另一種證據:前面回答「校正了會偏多少」, 這裡回答「這張圖從一開始就沒有要求你校正任何東西」。兩種證據指向同一個結論, 而後者不需要跑任何一筆資料。

DAG 可以被資料反駁

impliedConditionalIndependencies() 列的是這張圖宣稱「在資料裡應該看不到關聯」的地方。 對撞因子與 M-bias 兩個結構,它都吐出同一句 A _||_ Y—— 暴露與結果應該無關,因為這兩張圖裡本來就沒有從 A 到 Y 的因果箭頭; M-bias 那張圖裡唯一把兩者連起來的那條路徑要經過對撞因子 Z,只要你不去校正它,那條路就一直是關著的。

這件事的用處是:你可以拿資料去檢查它。如果資料裡 A 與 Y 明明有關聯,那不是資料有問題, 是你的圖畫錯了——漏了一條箭頭,或是某條箭頭的方向反了。

反過來,干擾因子與中介因子那兩張圖的清單是空的。原因是那兩張圖在觀察得到的三個節點上是完全圖 (每一對之間都有直接的箭頭),而完全圖禁不了任何東西,因此也無從被資料反駁。 這個不對稱值得記住:一張什麼都不禁止的 DAG,是一張無法被證明錯誤的 DAG。

那到底該校正什麼

把上面所有東西壓成一張可以帶進 meeting 的檢查表:

變項的角色怎麼認該不該進模型
干擾因子在暴露之前,同時影響暴露與結果,不在因果路徑上,這是校正存在的理由
結果的預測因子(與暴露無關)影響結果、但與暴露無關可以放,會提高精確度、不會製造偏誤
暴露的預測因子(與結果無關)影響暴露、但不影響結果通常不放,會降低精確度並放大殘餘偏誤
中介因子在暴露之後,是暴露發揮作用的中間站不放(除非你要的就是直接效果,且做了完整的中介分析)
對撞因子同時被暴露與結果影響絕對不放,也不能用它篩選樣本
暴露之前的對撞因子(M-bias)是兩個未測量因素的共同結果不放,但代價取決於你不知道的箭頭強度
工具變數影響暴露、但只透過暴露影響結果不放進迴歸,它有自己的用法,見工具變數

從 DAG 走到實際的分析

DAG 只告訴你「該擋住哪些路」,不告訴你「怎麼擋」。擋的方法有好幾種,各有各的代價,接下來的幾頁分別是:

  • 在迴歸模型裡校正——最直接,見Cox 模型線性迴歸
  • 傾向分數配對——把「誰接受暴露」這件事壓成一個分數,再配出兩組可比較的人
  • 逆機率加權——用同一個分數加權,造出一個假想的母體
  • 工具變數——當關鍵的干擾因子根本沒被測量時,唯一還有機會的路
  • 自我對照設計——讓每個人當自己的對照,一次消掉所有不隨時間變的個人特徵

前三種都有同一個前提:干擾因子有被測量到。 DAG 上那些灰色的、未測量的節點,配對與加權完全處理不了——它們只是換一種方式使用你手上已有的變項。這件事在世代研究那一章已經說過一次,值得再說一次。

常見誤用

誤用為什麼錯
「校正越多越好」對撞因子與中介因子會讓估計變差,不是變好
用單變項 p 值或逐步迴歸挑校正變項該不該校正是因果結構問題,資料裡的關聯回答不了
「兩組間有顯著差異所以要校正」只滿足干擾三條件的第一條
校正治療之後才測到的變項那多半是中介因子或對撞因子,兩種都會壞事
把中介因子校正掉還說估的是治療效果估到的是直接效果,不是總效果
只分析住院/只分析有回覆的人,卻不當成一回事用對撞因子篩選樣本與把它放進模型是同一個動作
「這個變項發生在暴露之前,所以校正一定安全」M-bias 是這條規則的反例
校正後就宣稱因果未測量的干擾因子不會出現在任何統計量裡
把工具變數當共變項校正進去會放大殘餘偏誤,它的正確用法是另一套估計式
DAG 畫完就當成已經證明了因果結構DAG 是把假設寫清楚,不是把假設驗證掉

重跑本頁的所有數字

/opt/homebrew/bin/Rscript figures/scripts/B6-01-dag.R

讀讀看這張圖

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

模擬把暴露與結果設定成完全獨立,真實效果是 0。500 次模擬裡,把兩者的共同結果放進模型之後,平均估計移到了別的地方。哪一句正確說明這個移動?

看答案與解析

正確答案: 校正後 500 次模擬的平均是 -0.388,這條關聯是校正共同結果打開的,資料生成的時候並沒有它

未校正的平均是 0.001,貼著模擬設定的真實效果;把共同結果放進模型之後,500 次模擬的平均掉到 -0.388。那不是隨機誤差——單次模擬校正後的區間上界 -0.372 已經整段落在零的左邊,既沒有涵蓋零,也確實改變了結論。對撞路徑預設是關著的,校正會把它打開,於是兩個毫無關係的變項看起來高度相關。同樣的事不只發生在「放進模型」:只收住院病人、只分析有回覆問卷的人,在 DAG 上都是同一個動作。

模擬裡藥物有兩條作用路徑,總效果是 0.84。把中介變項放進模型之後估計掉了下來。哪一句正確說明這個下降?

看答案與解析

正確答案: 校正後估到 0.200,那是扣掉中介那條路之後的直接效果,它回答的是機轉問題而不是總效果

未校正估到 0.841,貼著真實的總效果;放進中介變項之後估計是 0.200,那正是模擬裡設定的直接效果那一條路。這一列不是有人算錯,是估到了另一個問題的答案:總效果回答「給這個藥,結果會差多少」,直接效果回答「扣掉那條路之後還剩多少作用」。而 -0.640 是那一列的偏誤欄,也就是估計值與真實值的差,不是任何模型印出來的係數;把偏誤欄讀成估計欄會得到「這個藥有害」這種相反的結論。至於共線性,中介變項與暴露相關正是它一定會改變暴露係數的原因,那個改變就是被扣掉的那條路。

干擾因子那一列的未校正平均估計是 1.390,而模擬設定的真實效果是 1。哪一句正確說明未校正的估計高出來的那一段?

看答案與解析

正確答案: 那一段是 0.390,共同原因讓本來就容易發生結果的人比較容易被暴露,既有的差異被算到暴露頭上

1.390 是未校正的平均估計,1.000 是把共同原因放進模型之後的平均估計,兩者差 0.390,而那一欄在表上就叫偏誤欄。共同原因同時決定誰被暴露與誰會發生結果,於是兩組人本來就存在的差異被算到暴露的帳上;把那條分岔路關掉,估計就回到 1。0.021 是未校正那一列 500 次模擬之間的標準差,比 0.390 小一個數量級,所以那個落差不可能是模擬的隨機波動——這也是為什麼偏誤要看重複模擬的平均,不看單一次的估計。至於「校正把效果一起扣掉了」,校正後的平均並沒有低於真實效果,它就落在真實效果上。

把中介那張圖交給 dagitty 的 adjustmentSets(),它回傳的是什麼?

看答案與解析

正確答案: 它回傳 { },這張圖不要求校正任何東西,直覺想放的中介變項不在任何合法的集合裡

後門準則的第三條說得很明白:合法的校正集合不能包含暴露的後代,而中介變項正是暴露造成的東西。所以這張圖的答案是空集合,工具與模擬指向同一個結論——前者說「這張圖從一開始就沒有要求你校正任何東西」,後者說「校正了會偏多少」。含變項的那一組是干擾因子那張圖的答案,把它套到這張圖上等於把兩張圖的結論交換;而「必須校正的變項」是你打算輸入的東西,不是工具算出來的輸出,工具從來不會把暴露的後代放進來。要注意工具忠實回答的是你畫的那張圖,圖畫錯,答案就錯,而且錯得很有自信。

M-bias 那一列的偏誤比對撞因子那一列小得多。有人據此主張「遇到不確定的變項寧可校正」。哪一句最能說明這個主張的問題?

看答案與解析

正確答案: 校正 Z 之後的平均是 -0.124,而這個量級由四條箭頭決定,其中兩條連到你估不出來的變項

真實效果是零,不校正 Z 時平均估到 -0.001,校正之後掉到 -0.124。它確實比對撞因子那一列的 -0.388 小,這與「實務上寧可校正」那一派的說法一致,但兩件事要一起記得:這個量級由四條箭頭的強度決定,而其中兩條連到未測量的變項,資料估不出它們有多強。所以實務上的結論不是「要不要校正」,而是你必須先寫下你認為的結構,才知道自己在賭什麼。至於「實務上不會發生」,Z 發生在暴露之前、跟暴露有關聯、跟結果也有關聯——照傳統三條件的檢查表它看起來就是干擾因子,這正是「只校正暴露之前的事」這條簡化規則會失手的地方。

對撞因子那一列,單次模擬校正後的估計是 -0.413,95% 信賴區間不跨過零,p 值小到報表印不出來。哪一句正確說明這個 p 值?

看答案與解析

正確答案: 模擬設定的真實效果是 0.000,p 值只說這個關聯不容易由抽樣誤差產生,不說它是怎麼來的

真實效果被設定成 0.000,而校正對撞因子之後那個關聯不但存在、還高度顯著。p 值檢驗的是「這麼大的關聯有多容易由抽樣誤差產生」,不是「這個關聯是不是因果」——偏誤被穩定地複製出來的時候,p 值只會更小、區間只會更窄,所以「離零很遠」與「穩固的因果證據」之間沒有橋,區間下界 -0.455 說的是同一件事。未校正模型的 p 值 0.797 才是該信的那一個,而它之所以該信,不是因為數值比較大,是因為那個模型沒有打開對撞路徑。哪一個模型該信,是圖的形狀決定的,不是 p 值決定的。

延伸觀看

Clearing Up Confounding
ENACER Consulting· 4 min四分鐘把「什麼是干擾」講完,讀本頁第二節之前先看這支。
因果圖 DAG 是什麼?一張圖看穿因果的陷阱
繁中Colon & Code· 6 min繁中唯一正面講 DAG 的短片,正好對應本頁第三節。
Introduction to Causal Graphs
ENLeslie Myint· 6 min六分鐘的圖形語言入門,先看短的再決定要不要吃長講座。
Directed Acyclic Graphs (DAGs)
ENEpidemiology Stuff· 21 min補完 backdoor criterion 與 collider,本頁第五到第八節的完整版。
Principles of Epidemiology 05. Causal Inferences, Bias, Confounding, and Interaction
繁中臺大開放式課程 NTU OCW· 63 min繁中完整課程。想把因果推論當一門課上的話,這一集是本頁的教科書版。

素材來源與授權

本頁為原創內容

回報內容問題

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

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

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

一併送出的資訊

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