中斷時間序列與差異中的差異(ITS 與 DiD)
某個政策在某個月上路,之後指標變了——那有多少該記在政策頭上?這一頁用同一份資料把兩種答法並排:ITS 把介入前的趨勢延伸出去當反事實,DiD 向一條政策打不到的對照序列借反事實。也講階躍與斜率的分別、季節性沒先扣掉會誤判成什麼樣、平行趨勢的事前檢查為什麼只能否定不能證明,以及一批真值已知為零的負對照怎麼把殘餘偏誤量出來。
一個政策上路了,然後呢
台灣讀者最容易撞到這個問題形狀的地方有三個:健保把某個藥或某項檢查從自費改成給付、醫院導入一套感染管制 bundle、以及 COVID 期間就醫行為整個位移。三件事都長成同一個樣子——一個時點、一個介入、一條逐月的指標,然後有人問「所以降了多少」。
最省事的答法是前後比:介入前平均這麼多,介入後平均那麼多,相除。這份資料上做出來是每月 873 對 571,比值 0.654(95% CI 0.642–0.665),看起來乾淨俐落。
它的問題不是精度不夠,是它把本來就在走的趨勢整段記在政策頭上。指標如果已經在下降,前後比會把那段下降算成政策的功勞;如果指標有季節性、而介入剛好落在旺季與淡季的交界,前後比連方向都可能講反。
這一頁講的兩個方法,差別只在一句話:反事實從哪裡來。
- 中斷時間序列(interrupted time series, ITS)把介入前的趨勢延伸出去,當作「如果政策沒上路,指標會走到哪裡」。反事實是外插來的。
- 差異中的差異(difference-in-differences, DiD)找一條政策打不到的對照序列,用它在同一段時間裡的變化當反事實。反事實是借來的。
兩者都不是憑空變出對照組。ITS 賭的是「趨勢會延續」,DiD 賭的是「兩條線本來會平行地走下去」。這一頁把兩個賭注放在同一份資料上,讓你看到多一條對照序列到底買到了什麼、又要付什麼。
這一頁用的資料
datasets::Seatbelts 是 base R 內建的,data(Seatbelts) 一行就有:英國 192 個月的道路傷亡月資料,從 1969-01 到 1984-12。它剛好同時提供介入序列與天然對照序列:
front——前座傷亡。1983 年的強制繫安全帶法規範的就是前座。rear——後座傷亡。同一批車、同一條路、同一個經濟環境,但法律管不到後座。
還有一件事必須先處理:介入前後的行駛里程從每月 14463 變成 18890,多了 30.6%。如果不把它放進模型,「開得比較多所以撞得比較多」會被折進「法律的效果」裡。所以本頁的每個模型都帶 offset(log(kms)),把結果從「每月幾個人」換成「每單位里程幾個人」——這就是 Poisson 迴歸那一頁講的 offset。
本頁最後一節的 empirical calibration 用的是模擬資料,不是 Seatbelts;那一節開頭會再說明一次。
中斷時間序列:把介入前的趨勢延伸出去當反事實
ITS 就是一個帶了兩個特別項的迴歸。用 Poisson 寫成:
- 是月份序號, 是介入前的長期趨勢。這一項就是前後比缺的東西。
- 是介入後為 1 的指示變項, 是階躍(level change):介入當下那一跳。
- 是介入後累積的月數(介入前為 0), 是斜率變化(slope change):介入之後每個月比介入前多走多少。本頁程式碼裡的
tsl在介入當月已經是 1,所以 是把不連續點外推到 的那個值,介入後第一個月實際觀察到的比值是 。把 報成階躍是慣例,但編碼方式會在階躍與斜率之間搬動一小塊,Methods 要寫清楚用的是哪一種。 - 是隨時間變動的共變項,本頁放的是油價。
階躍與斜率回答的是兩個不同的臨床問題。階躍問的是「這件事一發生就改變了什麼」——法規、給付、開關型的介入通常長這樣。斜率問的是「這件事改變了後續的走向」——衛教、訓練、逐步擴散的介入通常長這樣。一個介入可以只有其中一種,也可以兩種都有。
figures/scripts/B6-10-its-did.R配適出來的階躍是 RR(rate ratio,發生率比;本頁每一個取過指數的係數都是這個量)0.680(95% CI 0.654–0.707,p < 0.0001),也就是法律上路當下,前座傷亡相對於反事實下降了約 32.0%。斜率變化是每月 RR 1.0123(95% CI 1.010–1.015)——大於 1,代表法律之後的月趨勢比法律之前往上翹。
前後比不是中斷時間序列
把上面的模型跟本頁開頭那個前後比並排:
| 做法 | 估計 | 95% CI | 它分得開什麼 |
|---|---|---|---|
| 純前後比(沒有時間項) | 0.654 | 0.642–0.665 | 什麼都分不開,只有一個數字 |
| ITS(時間 + 階躍 + 斜率) | 0.680 | 0.654–0.707 | 長期趨勢、階躍、斜率變化各一個係數 |
兩個數字沒有差很多,這其實是這份資料的運氣——介入前的趨勢本來就相對平緩。真正的差別在最右邊那一欄:前後比連「這是一跳還是一個轉向」都答不出來,而這正是臨床上最想知道的那件事。政策如果只是把指標往下推一格、之後照舊往上爬,跟政策把整條走勢扳彎,後續的決策完全不同。
還有一個看不見的代價:前後比沒有辦法被自己的圖抓錯。ITS 的圖上有一條反事實虛線,讀者可以自己判斷那條線延伸得合不合理;前後比只給兩個平均數,沒有任何東西可以被質疑。
沒先扣掉的東西,會被記在政策頭上
這一節是這一頁最實際的一段。同一個階躍,把校正項一項一項拿掉:
| 模型 | 階躍 RR | 95% CI | 每月斜率 RR | AIC |
|---|---|---|---|---|
| 完整校正:油價 + 里程 offset | 0.680 | 0.654–0.707 | 1.0123 | 5204 |
| 拿掉油價,保留里程 offset | 0.665 | 0.640–0.691 | 1.0129 | 5330 |
| 拿掉里程 offset,保留油價 | 0.682 | 0.657–0.709 | 1.0136 | 4719 |
| 兩個都不放,只有計數 | 0.660 | 0.635–0.685 | 1.0144 | 4988 |
| 完整校正 + 日曆月 | 0.739 | 0.711–0.768 | 1.0062 | 2941 |
| (對照)純前後比,沒有時間項 | 0.654 | 0.642–0.665 | — | — |
第一件值得注意的事跟直覺不合:把油價與里程 offset 拿掉,估計只在 0.660 到 0.682 之間動了 3.4%,信賴區間大幅重疊。 也就是說,在這份資料上,「你有沒有校正油價」根本改不了結論。這件事本來被寫成本頁的主要教學點,是實際跑過之後被自己的數字推翻的。
真正會改變結論的是季節性。加進日曆月之後,階躍從 0.680 變成 0.739(95% CI 0.711–0.768)——與完整校正模型的區間 0.654–0.707 不重疊。AIC 同時從 5204 掉到 2941,跌了 2263,代表季節性是這份資料裡被忽略掉的最大一塊結構。
原因不神秘:道路傷亡有明顯的年內週期(冬季日照短、天候差,傷亡高),而法律在 1983-02 上路,介入後那 23 個月覆蓋的月份組合跟介入前 169 個月的不一樣。沒有把日曆月扣掉,那塊季節差就被記在法律頭上。
安慰劑序列:法律管不到的那條線
ITS 最大的弱點是它沒有對照組——它的對照是同一群人的過去。所以只要有另一個原因剛好在同一個月改變了指標(另一項政策、一次油價衝擊、統計口徑換了),ITS 分不出來。
Seatbelts 給了一個很乾淨的檢查:把完全一樣的模型跑在 rear 上。後座傷亡受同樣的季節、同樣的油價、同樣的里程影響,但法律不管後座。所以它是一個負對照結果(negative control outcome):如果模型是對的,這裡不應該看到階躍。
| 序列 | 模型 | 階躍 RR | 95% CI | p |
|---|---|---|---|---|
| 前座(法律適用) | 完整校正 | 0.680 | 0.654–0.707 | < 0.0001 |
| 後座(安慰劑) | 完整校正 | 0.963 | 0.919–1.008 | 0.1072 |
| 後座(安慰劑) | 不校正,只有計數 | 0.953 | 0.910–0.998 | 0.0400 |
完整校正的安慰劑序列給出階躍 RR 0.963,95% CI 0.919–1.008 跨過 1,未達統計顯著。這是這一頁最有價值的一列:法律真的只打到前座,而這件事現在是讀者自己在表上看得到的證據,不是作者的斷言。
措辭要準確:這一列說的是「在這份資料上未偵測到後座的階躍」,不是「後座完全沒有變化」。CI 的下界到 0.919,也就是一個約 8.1% 的真實下降仍然與資料相容。要主張「後座沒有受影響」,需要的是事先設定的等效界線,不是一個跨過 1 的區間。
第三列是附帶的教訓:同一個安慰劑序列,不校正就「顯著」了(RR 0.953,95% CI 0.910–0.998,p = 0.0400)。安慰劑檢定本身也吃模型設定——它不是一個免費的保險,一個設定錯誤的安慰劑檢定會給你假的安心感,也會給你假的警報。
負對照的完整分類(負對照暴露與負對照結果各能反駁什麼、一個負對照不為零時怎麼解讀)在 工具變數那一頁;本頁最後一節談的是同一個想法擴大到幾十個結果之後能做什麼。
差異中的差異:反事實用借的
有了 rear 這條線,可以做的就不只是安慰劑檢定。把兩條序列疊成 long format,加一個組別指示變項,反事實就從「延伸出去」換成「借過來」:
——那個交互作用項——就是 DiD 估計。它的意思是:治療組在介入前後的變化,減掉對照組在同一段時間裡的變化。任何同時打到兩組的東西(油價、天氣、經濟、統計口徑)在相減時被消掉,這就是多一條對照序列買到的東西。
figures/scripts/B6-10-its-did.R三個係數各自的意思值得逐一拆開,因為論文表格常常只印交互作用那一列,讀者不知道另外兩列被拿去做了什麼:
| 係數 | RR | 95% CI | 它在講什麼 |
|---|---|---|---|
| 組別主效果 | 2.182 | 2.162–2.202 | 介入前,前座傷亡是後座的幾倍——兩組本來就不同,DiD 不要求它們相同 |
| 時期主效果 | 1.190 | 1.162–1.218 | 對照序列自己在介入後的變化,也就是借來的反事實 |
| 交互作用(DiD 估計) | 0.642 | 0.624–0.660 | 前座相對於後座那個變化,額外多走的部分 |
組別主效果 2.182 說的是介入前前座傷亡本來就是後座的兩倍多。DiD 不要求兩組的水準相同,它只要求兩組的變化趨勢平行——這是 DiD 與配對、加權那一類方法最不一樣的地方,也是它常被誤解的地方。
平行趨勢:DiD 唯一的識別假設
DiD 用對照序列換掉了外插,代價是換來一個新的假設:平行趨勢(parallel trends)——如果政策沒有上路,兩條序列的差距會維持不變。
這個假設沒有辦法被驗證,因為它講的是一個沒有發生的世界。能做的是事前檢查:把介入前那段切成若干期,各期估一個「治療組相對於對照組」的值,看它們是不是都貼在 1 附近。這就是 event-study 圖。
figures/scripts/B6-10-its-did.R| 年 | 前座對後座 RR | 95% CI | 區間 |
|---|---|---|---|
| 1969 | 1.152 | 1.098–1.209 | 不含 1 |
| 1970 | 1.174 | 1.120–1.232 | 不含 1 |
| 1971 | 1.143 | 1.090–1.199 | 不含 1 |
| 1972 | 1.168 | 1.114–1.225 | 不含 1 |
| 1973 | 1.079 | 1.029–1.131 | 不含 1 |
| 1974 | 1.057 | 1.007–1.110 | 不含 1 |
| 1975 | 1.003 | 0.954–1.054 | 含 1 |
| 1976 | 1.038 | 0.988–1.092 | 含 1 |
| 1977 | 1.097 | 1.044–1.154 | 不含 1 |
| 1978 | 1.089 | 1.036–1.144 | 不含 1 |
| 1979 | 1.089 | 1.036–1.144 | 不含 1 |
| 1980 | 1.056 | 1.005–1.110 | 不含 1 |
| 1981 | 1.018 | 0.968–1.070 | 含 1 |
| 1982 | 基準年(固定為 1) | — | — |
結果很明確,而且對這一頁是好消息:13 個非基準年裡有 10 個的區間不含 1,全部為零的聯合檢定是卡方 129.6(13 自由度,p < 0.0001)。1969 到 1972 那四年,前座對後座的比值是基準年的 1.143 到 1.174 倍;要到 1975 之後才大致趨平。
所以本頁的 DiD 估計 0.642 不能被寫成比 ITS 的 0.680 更可信的數字。 誠實的寫法是:事前檢查否定了全期的平行趨勢,只有介入前的後段勉強站得住;如果要用 DiD,應該把樣本限縮到後段、或改用允許趨勢差異的設定,並且把這個決定寫進 Methods。
怎麼算
四個模型全部用 base R 的 glm(),沒有任何額外套件。
data(Seatbelts)
d <- as.data.frame(Seatbelts)
d$time <- seq_len(nrow(d))
d$mon <- (d$time - 1) %% 12 + 1
d$month <- factor(d$mon)
# 介入 index 從資料推導,不要寫死月份。公文上的生效日與資料庫裡
# 真正開始變化的月份常常差一到兩個月。
law_idx <- which(d$law == 1)[1]
# 介入後累積的月數,介入前為 0。這個編碼讓 law 的係數是「介入當下
# 那一跳」、tsl 的係數是「之後每個月的斜率變化」。原點挪一個月會
# 在兩個係數之間搬動數值,所以要固定寫法。
d$tsl <- pmax(0, d$time - law_idx + 1)
# --- 1. ITS:前座 ---------------------------------------------------
its <- glm(front ~ time + law + tsl + PetrolPrice + offset(log(kms)),
data = d, family = poisson())
exp(cbind(RR = coef(its), confint.default(its)))["law", ]
# --- 2. 季節性:加日曆月。本頁真正會改變結論的那一項 -----------------
its_season <- glm(front ~ time + law + tsl + PetrolPrice + month +
offset(log(kms)), data = d, family = poisson())
exp(coef(its_season)["law"])
# --- 3. 安慰劑序列:一模一樣的模型跑在 rear 上 -----------------------
# 法律不管後座。這裡出現階躍就代表模型抓到的不只是法律。
placebo <- glm(rear ~ time + law + tsl + PetrolPrice + offset(log(kms)),
data = d, family = poisson())
exp(cbind(RR = coef(placebo), confint.default(placebo)))["law", ]
# --- 4. DiD:兩條序列疊成 long format -------------------------------
long <- rbind(
data.frame(y = d$front, grp = 1L, d[, c("time", "law", "kms", "PetrolPrice")]),
data.frame(y = d$rear, grp = 0L, d[, c("time", "law", "kms", "PetrolPrice")])
)
did <- glm(y ~ grp * law + time + PetrolPrice + offset(log(kms)),
data = long, family = poisson())
exp(cbind(RR = coef(did), confint.default(did)))["grp:law", ]
# --- 5. 平行趨勢的事前檢查(event study)----------------------------
# 只用介入前的資料,逐年估一個 grp x year 交互作用。
pre <- subset(long, law == 0 & time < law_idx)
pre$year <- 1969 + (pre$time - 1) %/% 12
pre$yf <- relevel(factor(pre$year), ref = "1982") # 最後一個完整的介入前年
es <- glm(y ~ grp + yf + grp:yf + PetrolPrice + offset(log(kms)),
data = pre, family = poisson())
# 聯合檢定:所有介入前交互作用同時為零。拒絕就是否定平行趨勢;
# 不拒絕「不」等於平行趨勢成立。
es_null <- glm(y ~ grp + yf + PetrolPrice + offset(log(kms)),
data = pre, family = poisson())
anova(es_null, es, test = "LRT")驗證環境:R 4.6.0 + jsonlite 2.0.0。Seatbelts 是 base R 內建資料集,data(Seatbelts) 一行即得。
import numpy as np
import pandas as pd
import statsmodels.api as sm
import statsmodels.formula.api as smf
d = sm.datasets.get_rdataset("Seatbelts", "datasets").data.copy()
d["time"] = np.arange(1, len(d) + 1)
d["mon"] = (d["time"] - 1) % 12 + 1
law_idx = int(d.loc[d["law"] == 1, "time"].iloc[0])
d["tsl"] = np.maximum(0, d["time"] - law_idx + 1)
# offset 在 statsmodels 是 fit 的參數,不是公式的一部分。
its = smf.glm("front ~ time + law + tsl + PetrolPrice",
data=d, family=sm.families.Poisson(),
offset=np.log(d["kms"])).fit()
print(np.exp(its.params["law"]), np.exp(its.conf_int().loc["law"]))
# 季節性:C(mon) 是 patsy 的因子寫法,等同 R 的 factor()
its_season = smf.glm("front ~ time + law + tsl + PetrolPrice + C(mon)",
data=d, family=sm.families.Poisson(),
offset=np.log(d["kms"])).fit()
# DiD:long format,grp * law 在 patsy 裡與 R 的意思相同
long = pd.concat([
d.assign(y=d["front"], grp=1),
d.assign(y=d["rear"], grp=0),
], ignore_index=True)
did = smf.glm("y ~ grp * law + time + PetrolPrice",
data=long, family=sm.families.Poisson(),
offset=np.log(long["kms"])).fit()
print(np.exp(did.params["grp:law"]))statsmodels 的 GLM 與 R 的 glm() 在 Poisson family 下係數逐位相同(Seatbelts 可用 statsmodels.api.datasets.get_rdataset 取得)。差別在 offset 的傳法與交互作用的公式語法,下面的程式碼標了出來。本頁報的數字全部來自 R。
ITS 與 DiD 並排:多一條對照序列買到什麼
| ITS | DiD | |
|---|---|---|
| 反事實從哪來 | 介入前的趨勢外插出去 | 向對照序列借 |
| 核心假設 | 沒有介入的話,介入前的趨勢會延續 | 沒有介入的話,兩組的差距會維持不變 |
| 本頁的估計 | 0.680(0.654–0.707) | 0.642(0.624–0.660) |
| 假設可以事前檢查嗎 | 不能直接檢查;靠安慰劑序列與多個介入時點旁證 | 可以做 event study,但只能否定不能證明 |
| 本頁的檢查結果 | 安慰劑序列未偵測到階躍,與「只打到前座」相容 | 事前檢查否定了全期的平行趨勢 |
| 什麼情況下會壞掉 | 同時有另一個介入、指標定義改版、趨勢本來就要轉折 | 對照組被政策間接影響(溢出)、兩組面對的衝擊不同,或各單位在不同時點上路(見上面的 staggered adoption 警告) |
兩個估計差了 0.038,DiD 那個離 1 更遠,也就是降幅更大(0.642 對 0.680)。差距的來源看得到:DiD 的時期主效果 1.190 大於 1,也就是借來的反事實說「介入後這段期間本來應該上升」,於是前座相對於它就要多降一點。
這不代表 DiD 比較好。 它代表兩個方法對「本來會怎樣」給了不同的答案,而在這份資料上,DiD 那個答案背後的假設沒有通過事前檢查。多一條對照序列買到的是「共同衝擊被消掉」,付出的是「兩條線的相對走勢必須穩定」——而後者在這裡站不住。
實務上常見的做法是兩個都報:ITS 當主分析、DiD 當敏感度分析(或反過來),並且說明兩者的假設不同,一致才是支持,不一致就要說明差在哪。只報其中一個而不提另一個,讀者沒有辦法判斷結論對方法的選擇有多敏感。
負對照與 empirical calibration
單一個負對照結果(像上面的 rear)能回答「有沒有偏誤」。一批負對照可以回答更進一步的問題:偏誤有多大、往哪個方向。
figures/scripts/B6-10-its-did.R左圖那團點是這一節的全部論點。39 個結果的真值都是 1,如果分析是無偏的,它們應該散布在 1 的兩側;實際上整團偏到右邊,配適出來的虛無分布中心是 RR 1.316,偏離 1 達 31.6%,離散程度(log 尺度的 tau)是 0.117。
更刺眼的是這一個數字:39 個負對照裡有 34 個的 95% CI 不含 1——換句話說,如果照常規讀 p 值,這 34 個「顯著關聯」你會全部相信,而它們每一個的真值都是 1。名目上 95% 的區間,實際涵蓋率只有 12.8%。
| 未校正 | 校正後 | 名目值 / 真值 | |
|---|---|---|---|
| 負對照的 95% CI 涵蓋率 | 12.8% | 94.9% | 95.0% |
| 目標結果的 RR | 2.99 | 2.27 | 2.0 |
| 目標結果的 95% CI | 2.72–3.29 | 1.77–2.91 | 要蓋住 2.0 才算對 |
校正的做法本身很簡單:把每個估計在 log 尺度上減掉那個偏移量 mu,把標準誤改成原本的 se 與虛無分布離散度 tau 的平方和開根號。結果是三件事同時發生:
- 負對照的涵蓋率從 12.8% 回到 94.9%,也就是名目值該有的樣子。
- 目標結果從 2.99(95% CI 2.72–3.29,不含真值 2.0)變成 2.27(95% CI 1.77–2.91,蓋住真值)。
- 代價是區間寬了 2.6 倍。校正不會讓你更有把握,它讓你的把握變誠實。
讀一篇政策評估論文要問的問題
看到「某政策上路後 X 下降了多少」這種標題時,四個問題可以問掉大部分的疑慮:
- 對照組是誰? ITS 的對照是同一群人的過去,DiD 的對照是另一群人的同期。沒有講清楚是哪一種,那個數字就沒有辦法評價。用了 DiD 的話,對照組憑什麼被政策打不到,要寫得出理由。
- 平行趨勢查了沒? 有沒有 event-study 圖,還是只有一句「我們檢查過平行趨勢且未達顯著」。後者是把檢定力不足讀成證據。
- 季節性與長期趨勢扣掉了沒? 本頁的例子裡,只差一個日曆月項就讓階躍從 0.680 走到 0.739,兩個區間不重疊。
- 有沒有負對照或安慰劑序列? 一個「法律管不到」的結果或族群,是最便宜也最有說服力的檢查。
另外兩個常被跳過的細節:介入時點是怎麼定的(公文日期與資料庫裡的轉折常常差一到兩個月),以及有沒有處理自相關(沒有的話標準誤偏窄,區間比實際窄)。
常見誤用
| 誤用 | 為什麼錯 |
|---|---|
| 用前後平均值相比就宣稱政策效果 | 把介入前本來就在走的趨勢整段記在政策頭上 |
| 只報階躍不報斜率 | 讀者會以為那個降幅一直維持,實際上可能在觀察窗內就回升 |
| 介入後只有幾個月就報斜率變化 | 斜率是這個模型裡最脆弱的估計,短觀察窗撐不住 |
| 不校正季節性 | 介入後的觀察窗只要不是整數個年,季節差就會被記在政策頭上 |
| 介入月份照公文日期寫死 | 資料庫裡真正的轉折常差一到兩個月,過渡月會被塞到錯的一邊 |
| 把 DiD 的「兩組水準不同」當成問題 | DiD 要求的是趨勢平行,不是水準相同 |
| event-study 圖平坦就宣稱平行趨勢成立 | 那張圖能否定假設,不能證明假設;不顯著也可能只是檢定力不足 |
| 事前檢查失敗仍照原樣報 DiD | 至少要限縮期間或改設定,並把決定寫進 Methods |
| 對照序列其實會被政策間接影響 | 溢出效應會把真實效果稀釋掉,DiD 的估計會偏向虛無 |
| 各單位上路時間不同時仍套本頁的 2×2 DiD | 錯開處理時點下,two-way fixed effects 會拿「已經被處理過的單位」當對照;效果隨時間變化時可能產生負權重、甚至方向相反的估計 |
| 沒有檢查殘差自相關 | 標準誤偏樂觀,區間比它該有的窄 |
| 把兩條序列疊成長表就直接讀 GLM 的標準誤 | 同一個月的那兩列不是獨立觀察,區間會偏窄 |
| 只報 ITS 或只報 DiD,不提另一個 | 兩者假設不同,讀者無法判斷結論對方法選擇有多敏感 |
| 負對照序列未達顯著就說「完全沒有影響」 | 未偵測到不等於不存在;要主張相當需要事先設定的等效界線 |
| 資料庫研究不放任何負對照 | 殘餘偏誤在這類資料裡是常態,沒有負對照就沒有辦法量它 |
這一頁與其他頁的關係
- 臨床上會用到它的地方——見資料庫與健保申報資料研究。跨年度的趨勢分析必須查給付政策沿革,而政策變更本身就是一個可以用本頁方法估計的介入。
- 底層的模型——見 Poisson 迴歸與發生率。本頁的每一個模型都是帶 offset 的 Poisson GLM,係數的指數就是 RR。
- 另一種「自己當自己對照」的設計——見自我對照設計。它把對照放在同一個人的其他時間窗內,與 ITS 用整條序列的過去當對照是同一個家族的兩種切法。
- 反事實與識別假設的語言——見因果圖與後門準則與目標試驗模擬。平行趨勢是一個關於反事實的假設,跟可交換性一樣不可驗證。
- 負對照的完整分類——見工具變數。
- 時間相關的陷阱——見時間相依共變項。政策評估的資料常常同時有時間相依的暴露與時間相依的干擾。
重跑本頁的所有數字
/opt/homebrew/bin/Rscript figures/scripts/B6-10-its-did.R讀讀看這張圖
答案取自產生本頁圖表的同一份統計輸出,不是另外打上去的。
同一個階躍,把油價與里程 offset 拿掉,估計只在 0.660 到 0.682 之間動;把日曆月加進去,階躍卻從 0.680 變成 0.739。這說了什麼?
看答案與解析
正確答案: 季節性才是被忽略掉的最大一塊:加進日曆月之後的 0.739,區間與完整校正模型不重疊
道路傷亡有明顯的年內週期,而法律在二月上路,介入後那二十三個月覆蓋的月份組合跟介入前不一樣——沒有把日曆月扣掉,那塊季節差就被記在法律頭上。所以階躍從 0.680 移到 0.739,而且兩個區間不重疊。反過來,0.660 到 0.682 這一整段是油價與里程三個變體的全部影響,區間大幅重疊,在這份資料上「有沒有校正油價」根本改不了結論。這一段的教訓不是「多校正比較安全」,而是「要校正對的東西」——哪一項重要由資料的性質決定,而任何以月為單位的健康指標幾乎都有季節性。
把完全一樣的模型跑在後座傷亡(法律管不到的那條序列)上,階躍是 0.963,95% CI 從 0.919 到 1.008。這一列該怎麼寫?
看答案與解析
正確答案: 「在這份資料上未偵測到後座的階躍」,並且注意下界到 0.919,一個約 8% 的真實下降仍然與資料相容
這一列是這一頁最有價值的一列,但它說的是「未偵測到」而不是「沒有」。0.963 的區間跨過虛無值,下界到 0.919,也就是一個接近一成的真實下降仍然與資料相容;要主張後座沒有受影響,需要的是事先設定的等效界線。0.953 是同一條安慰劑序列在不校正的模型下的結果,它的區間不含虛無值,也就是「顯著」了——安慰劑檢定本身也吃模型設定,一個設定錯誤的安慰劑檢定會給你假的安心感,也會給你假的警報,而不是法律溢出的證據。
介入前逐年的前座對後座比值,13 個非基準年裡有 10 個的區間不含虛無值。這對 DiD 的估計說了什麼?
看答案與解析
正確答案: 事前檢查否定了全期的平行趨勢——最早那一年已經是基準年的 1.152 倍——DiD 不能寫成比 ITS 可信
event study 檢查的是「介入前兩條線的相對走勢穩不穩定」,而 13 個非基準年裡有 10 個的區間不含虛無值,最早那一年的比值高到 1.152 倍,要到後段才大致趨平。這否定了全期的平行趨勢,所以 0.642 不能被寫成比 ITS 的 0.680 更可信的數字。但「否定」不等於「完全失效」:誠實的作法是把樣本限縮到趨勢站得住的後段、或改用允許趨勢差異的設定,並且把這個決定寫進 Methods。反過來,介入前的形狀也不是與 DiD 無關的背景——DiD 的反事實整個是向對照序列借來的,借得成不成立就靠這張圖。
ITS 估出階躍 0.680,DiD 估出 0.642。這個差距是從哪裡來的?
看答案與解析
正確答案: 從借來的反事實:DiD 的時期主效果是 1.190,也就是對照序列說「介入後這段期間本來應該上升」,於是前座相對於它就要多降一點
兩個方法對「本來會怎樣」給了不同的答案。ITS 把介入前的趨勢外插出去,DiD 向對照序列借,而對照序列說介入後這段期間本來應該上升——時期主效果 1.190 大於虛無值——於是前座相對於它就要多降一點,0.642 比 0.680 離虛無值更遠。2.182 是組別主效果,也就是前座與後座那個固定的差距,那正是 DiD 在相減時消掉的東西,不會進到政策效果裡。1.012 是 ITS 的每月斜率變化——介入後的月趨勢相對於介入前多走多少——而 DiD 的模型裡也有時間項,兩者不是「有沒有建模」的差別。差距本身不代表哪一個比較好,它代表兩個方法的假設不同,而在這份資料上 DiD 那個假設沒有通過事前檢查。
39 個真值都等於虛無值的負對照結果,配適出來的虛無分布中心是 1.316。這說了什麼?
看答案與解析
正確答案: 名目 95% 的區間實際只涵蓋了 0.128 的負對照——照常規讀 p 值會把大批虛無結果當成顯著
如果分析是無偏的,39 個真值等於虛無值的估計應該散布在虛無值的兩側;實際上整團偏到同一邊,配適出來的中心是 1.316。這正是系統性偏誤的樣子——0.117 是那團點的離散程度,它比偏移量小,反而說明這團點是一起偏的,不是各自亂跳。後果寫在涵蓋率上:名目 95% 的區間實際只涵蓋 0.128 的負對照,也就是絕大多數真值等於虛無值的結果會被讀成顯著關聯。這正是資料庫研究的處境:一個未測量的干擾因子同時影響暴露與每一個結果,而分析者看不到它。
把偏移量扣回去之後,目標結果從 2.99 變成 2.27,而它的真值是 2。校正買到了什麼、付出了什麼?
看答案與解析
正確答案: 買到的是誠實的把握:校正後的下界降到 1.77,區間終於蓋住真值,代價是區間寬了兩倍多
校正做的是兩件事:把每個估計在 log 尺度上減掉那個偏移量,並把標準誤改成原本的標準誤與虛無分布離散度的平方和開根號。結果是未校正的 2.99(下界 2.72,整段落在真值的右側)變成 2.27(下界 1.77),區間蓋住真值,而且負對照的涵蓋率回到名目值。代價是區間寬了 2.64 倍——這不是把訊號稀釋掉,而是把原本被低估的不確定性還原。校正不會讓你更有把握,它讓你的把握變誠實;一個看起來很窄卻蓋不住真值的區間,窄是假的。
純前後比給出 0.654,區間 0.642 到 0.665;ITS 的階躍是 0.680,區間 0.654 到 0.707。兩個數字很接近,可以說前後比夠用了嗎?
看答案與解析
正確答案: 不行。前後比只給一個數字,答不出這是一跳還是一個轉向;ITS 另外估了每月 1.012 的斜率變化
兩個數字接近是這份資料的運氣——介入前的趨勢本來就相對平緩。真正的差別不在數值而在「分得開什麼」:前後比只有兩個平均數,它答不出政策是把指標往下推一格、還是把整條走勢扳彎,而這兩件事對後續決策的意義完全不同。ITS 除了階躍 0.680 之外,另外估了 1.012 這個斜率變化——介入後的月趨勢相對於介入前多走多少;介入後本身的月斜率要把時間項與這一項合起來看——那是前後比在結構上就看不到的東西。0.009 是前後比的標準誤,它小既不是優點也不是可以事後修的東西——把區間放寬並不會生出一個斜率係數。還有一個看不見的代價:前後比沒有辦法被自己的圖抓錯,而 ITS 的圖上有一條反事實虛線,讀者可以自己判斷它延伸得合不合理。
用到這個方法的章節
素材來源與授權
本頁為原創內容