測量誤差與錯分
「非差別性錯分會讓估計值偏向虛無」是被教得最廣、也最常被過度延伸的一條規則。本頁用模擬證明它在二分暴露上成立、在三分類暴露上可以反過來,並把連續變項的迴歸稀釋、結果的錯分、以及干擾因子測不準造成的殘餘干擾一起攤開。
兩個問題要先分開
「這個變項測不準」在論文裡是一句話,在統計上是兩件不同的事:
| 二分或類別變項 | 連續變項 | |
|---|---|---|
| 叫什麼 | 錯分(misclassification) | 測量誤差(measurement error) |
| 用什麼描述 | 敏感度與特異度 | 誤差的標準差、信度(reliability) |
| 典型後果 | 效果量被壓向虛無,但有例外 | 迴歸係數被系統性縮小 |
兩件事都有第二個維度:錯得均勻,還是錯得有方向。 如果測錯的機率跟另一個變項無關,叫非差別性(non-differential); 如果病例比對照更容易回想起暴露、或暴露組被追蹤得更仔細,那就是差別性(differential)。 這個維度決定的不是嚴重程度,而是方向能不能預測。
本頁所有數字都來自模擬,真值是寫死在程式碼裡的:暴露盛行率 30%、 未暴露風險 10%、真實勝算比 2.0。 量得完美無誤時估出來是 1.975,這是模擬誤差的尺度, 下面每一個數字都要跟它比、不是跟 2.0 比。
非差別性錯分:偏向虛無,而特異度傷得比較重
| 敏感度 | 特異度 | 看起來的暴露率 | 觀察到的勝算比 |
|---|---|---|---|
| 1.00 | 1.00 | 29.9% | 1.975 |
| 0.95 | 1.00 | 28.4% | 1.935 |
| 0.90 | 1.00 | 26.9% | 1.911 |
| 0.80 | 1.00 | 23.9% | 1.850 |
| 1.00 | 0.98 | 31.3% | 1.924 |
| 1.00 | 0.95 | 33.4% | 1.871 |
| 1.00 | 0.90 | 37.0% | 1.775 |
| 0.90 | 0.90 | 34.0% | 1.682 |
| 0.80 | 0.80 | 38.0% | 1.472 |
每一列都比第一列小。方向是可預測的:往 1 靠。 這是這條規則之所以有用的原因——看到一個被非差別性錯分污染的正結果, 你至少知道真值只會更遠離虛無,不會更靠近。
兩個參數也不是等價的:掉一樣多,特異度傷得比較重。 敏感度降到 0.90 時勝算比是 1.911, 特異度降到同樣的 0.90 時已經是 1.775。
figures/scripts/B8-04-measurement-error.R差別性錯分:方向不再可預測
同樣一份資料,這次讓錯分的機率取決於結果——也就是回憶偏誤(recall bias)的結構: 病例被反覆追問,想起暴露的機率比對照高。
| 情境 | 病例的敏感度/特異度 | 對照的敏感度/特異度 | 觀察到的勝算比 |
|---|---|---|---|
| 病例回想得比較清楚 | 0.90 / 0.95 | 0.70 / 0.95 | 2.392 |
| 對照回想得比較清楚 | 0.70 / 0.95 | 0.90 / 0.95 | 1.227 |
| 病例過度回報 | 0.95 / 0.85 | 0.95 / 0.95 | 2.294 |
| 非差別性,同一組參數 | 0.80 / 0.95 | 0.80 / 0.95 | 1.695 |
第一列把真值 2.0 誇大到 2.392, 第二列把它壓到 1.227——同一個真值,同一個測量工具的平均品質,兩個相反方向。 第三列顯示光是病例的特異度掉一點(過度回報)就足以製造 2.294。
第四列是對照組:把敏感度固定在前兩列的未加權中間值、且兩組相同,得到 1.695, 乖乖地偏向虛無。(說「未加權」是因為病例只佔少數,以人數加權的平均敏感度並不等於這個值—— 這一列的用途是「同一組參數、拿掉差別性」的對照,不是嚴格的平均。) 差別性與非差別性的差異不在錯得多嚴重,在錯得均不均勻。
三個類別以上,「偏向虛無」不再保證
這條規則被教的時候幾乎都省略了它的前提:二分暴露。有序的三分類就不保證了。
三個暴露層的分布是 50% / 30% / 20%,真實勝算比依序是 1、 2、4。下面兩個混淆矩陣都是非差別性的 (病例與對照用同一組機率),而且整體正確分類率刻意調成一樣 (都是 77%),所以兩者唯一的差別就是錯的方向:
| 真實層級 | 對稱:被歸到(低/中/高) | 不對稱:被歸到(低/中/高) | ||||
|---|---|---|---|---|---|---|
| 低 | 80% | 20% | 0% | 90% | 10% | 0% |
| 中 | 15% | 70% | 15% | 15% | 70% | 15% |
| 高 | 0% | 20% | 80% | 0% | 45% | 55% |
對稱的版本讓每一層平均地漏到相鄰層;不對稱的版本把最高層的 45% 推進中間層, 於是中間層裡混進了大量真正的高暴露者。
| 比較 | 真值 | 量得準時 | 對稱錯分後 | 不對稱錯分後 |
|---|---|---|---|---|
| 中 vs 低 | 2.0 | 2.028 | 1.716 | 2.134 |
| 高 vs 低 | 4.0 | 4.025 | 3.200 | 3.105 |
對稱那一欄照規則走,兩列都被壓向 1。不對稱那一欄的第一列是 2.134, 比同一個比較在量得準時的 2.028 還高——非差別性錯分把中間層的估計值推得遠離虛無, 也超過了它的真值 2.0。
連續變項:迴歸稀釋,而且幅度算得出來
單次量測的血壓、一份問卷估的鹽攝取量、一次抽血的生物標記,都帶著測量誤差。 如果那個誤差是古典型的(與真值獨立、平均為零),迴歸係數會被乘上一個小於 1 的常數, 這個常數就是信度(reliability):真值的變異數除以觀察值的變異數。
這個量有名字,而且你在論文裡看得到它——它就是量測信度的組內相關係數 (intraclass correlation coefficient, ICC)。下面那個重複量測的子研究, 估的正是它。ICC 有六種型式,論文只寫一個數字而不說是哪一種時無法解讀; 怎麼分辨見量測信度與 ICC。
| 誤差標準差 | 信度 | 估到的斜率 | 信度 × 真斜率 |
|---|---|---|---|
| 0.00 | 1.000 | 1.500 | 1.500 |
| 0.25 | 0.941 | 1.412 | 1.412 |
| 0.50 | 0.800 | 1.201 | 1.200 |
| 0.75 | 0.640 | 0.961 | 0.960 |
| 1.00 | 0.500 | 0.754 | 0.750 |
| 1.50 | 0.308 | 0.461 | 0.462 |
| 2.00 | 0.200 | 0.300 | 0.300 |
最後兩欄幾乎一模一樣。這不是經驗規律,是代數。 誤差標準差等於真值的標準差時(第五列),斜率剛好剩一半。
結果測不準:特異度做了大部分的傷害
上面都在講暴露。結果測不準的結構不一樣,而且在罕見結果上有一個違反直覺的性質。 下表的真實相對風險是 2.0,未暴露風險只有 2%。
| 結果判定的敏感度 | 結果判定的特異度 | 觀察到的相對風險 |
|---|---|---|
| 1.00 | 1.00 | 1.979 |
| 0.90 | 1.00 | 1.999 |
| 0.70 | 1.00 | 1.985 |
| 1.00 | 0.99 | 1.654 |
| 1.00 | 0.98 | 1.494 |
| 0.90 | 0.98 | 1.466 |
前三列:特異度完美時,敏感度掉到 0.70 都不影響相對風險 (1.985)。漏掉的事件在兩組是等比例的,比值不動。
後三列:特異度只掉 1 個百分點, 相對風險就從 1.979 掉到 1.654; 掉 2 個百分點是 1.494。
原因是基準風險只有 2%: 2% 的假陽性率作用在 98% 的健康人身上, 產生的假事件數跟真事件數同一個量級,兩組都被稀釋成一樣的背景雜訊。
干擾因子測不準:殘餘干擾
最後一種,也是最容易被漏掉的一種。這次模擬裡暴露對結果完全沒有作用(真實勝算比是 1), 兩者之間看起來的關聯全部來自一個干擾因子。
| 校正時,干擾因子的測量誤差標準差 | 校正後的暴露勝算比 |
|---|---|
| 0.00 | 1.017 |
| 0.25 | 1.086 |
| 0.50 | 1.280 |
| 1.00 | 1.676 |
| 1.50 | 1.938 |
| 2.00 | 2.090 |
完全不校正的話,暴露的勝算比是 2.380。 用量得完美的干擾因子校正,回到 1.017——正確。 但用一個帶誤差的版本校正,就只校掉一部分:誤差標準差等於干擾因子本身的標準差時 (第四列),殘餘的勝算比還有 1.676。
讀論文時看哪裡
- 暴露是什麼時候量的、由誰量的。 結果發生之後才問的暴露,預設要討論差別性錯分。
- 有沒有驗證子研究。 沒有敏感度/特異度或信度的估計,任何「錯分只會讓我們低估」的辯護都只是宣稱。
- 結果的定義。 診斷碼、自陳、判定委員會三者的特異度差很多,而特異度在罕見結果上主導一切。
- 暴露是幾個類別。 三分類以上就不能套用「偏向虛無」。
- 干擾因子的測量粒度。 「有/無」記錄的連續型干擾因子等於宣告會有殘餘干擾。
- 有沒有任何量化偏誤分析。 一句「可能存在測量誤差」不是分析;把 sens/spec 代進去重算一次才是。
動手跑一次
set.seed(20260824)
n <- 200000
# Truth: 30% exposed, risk 0.10 unexposed, odds ratio 2
E <- rbinom(n, 1, 0.3)
Y <- rbinom(n, 1, ifelse(E == 1, 2 / 11, 0.10))
or <- function(e, y) {
t <- matrix(as.numeric(table(e, y)), 2, 2) # doubles: integers overflow here
(t[2, 2] * t[1, 1]) / (t[2, 1] * t[1, 2])
}
or(E, Y)
# Non-differential: same sensitivity and specificity in cases and non-cases
mis <- function(e, sens, spec) rbinom(length(e), 1, ifelse(e == 1, sens, 1 - spec))
or(mis(E, 0.90, 0.90), Y) # pulled toward 1
# Differential: cases recall the exposure better than non-cases
sens <- ifelse(Y == 1, 0.90, 0.70)
or(rbinom(n, 1, ifelse(E == 1, sens, 0.05)), Y) # can go either way
# Regression dilution for a continuous exposure
x <- rnorm(n); y <- 2 + 1.5 * x + rnorm(n, 0, 2)
xs <- x + rnorm(n, 0, 1) # reliability = 1 / (1 + 1^2) = 0.5
coef(lm(y ~ xs))[2] # about half of 1.5驗證環境:R 4.6.0。本頁所有數字都是模擬產生的,seed 固定為 20260824。下面這段是可以立刻重跑的示意碼。它抽樣的順序與產生本頁表格的模擬不同,所以數字會落在同一個量級但不會逐位相同。
import numpy as np
rng = np.random.default_rng(20260824)
n = 200_000
E = rng.binomial(1, 0.3, n)
Y = rng.binomial(1, np.where(E == 1, 2 / 11, 0.10))
def odds_ratio(e, y):
a = ((e == 1) & (y == 1)).sum(); b = ((e == 1) & (y == 0)).sum()
c = ((e == 0) & (y == 1)).sum(); d = ((e == 0) & (y == 0)).sum()
return (a * d) / (b * c)
print(odds_ratio(E, Y))
mis = rng.binomial(1, np.where(E == 1, 0.90, 0.10)) # sens .9, spec .9
print(odds_ratio(mis, Y)) # toward the null
x = rng.normal(size=n); y = 2 + 1.5 * x + rng.normal(0, 2, n)
xs = x + rng.normal(0, 1, n)
print(np.polyfit(xs, y, 1)[0]) # about 0.75numpy 加 statsmodels 就足夠;重點是先造出真值,才有東西可以比。同樣是示意碼,抽樣順序與本頁表格的模擬不同。
常見誤用
| 誤用 | 為什麼錯 |
|---|---|
| 用「非差別性錯分只會偏向虛無」為任何正結果辯護 | 前提是二分暴露、非差別性、暴露的錯分與結果的錯分互相獨立、敏感度加特異度大於 1,而且只對期望值成立;三分類以上可以反向 |
| 把回憶取得的暴露當成非差別性 | 結果已知之後蒐集的暴露資訊,預設是差別性的 |
| 只報結果判定的敏感度 | 罕見結果的相對風險主要被特異度壓垮,不是敏感度 |
| 說「已校正吸菸」而不說吸菸怎麼量的 | 粗略測量的干擾因子留下的殘餘干擾與真實效果無法區分 |
| 用單次量測的暴露做劑量反應分析而不提稀釋 | 斜率被乘上信度,趨勢的斜率被系統性低估 |
| 把稀釋後的未達顯著讀成「沒有關聯」 | 測量品質會系統性降低偵測力,這是設計問題不是生物學結論 |
| 在 limitation 寫「可能存在測量誤差」就結束 | 那是描述不是分析;把 sens/spec 代進去重算才是量化偏誤分析 |
| 用同一份資料同時定義暴露與結果卻不討論相關誤差 | 兩者的誤差若相關,連方向都無法預測 |
重跑本頁的所有數字
/opt/homebrew/bin/Rscript figures/scripts/B8-04-measurement-error.R讀讀看這張圖
答案取自產生本頁圖表的同一份統計輸出,不是另外打上去的。
非差別性錯分那張表裡,量得完美時勝算比是 1.975。敏感度單獨降到 0.90 是 1.911;特異度單獨降到 0.90 時是多少,為什麼?
看答案與解析
正確答案: 1.775。掉一樣多,特異度傷得比較重,因為未暴露的人佔多數,假陽性是從比較大的那一群製造出來的
1.775 比敏感度那一列的 1.911 更低,所以在這個模擬裡特異度掉一樣多確實傷得更重。理由在盛行率:暴露率只有三成,未暴露的人佔多數,特異度掉一個百分點是從比較大的那一群製造假陽性,敏感度掉一個百分點是從比較小的那一群漏掉真陽性。1.472 是敏感度與特異度同時掉到更低的那一列,而它附帶的「與盛行率無關」是錯的——暴露很常見時這個排序會反過來。1.904 來自另一張只變動敏感度的曲線,把兩個參數說成對稱的,正好與整節要展示的東西相反。不隨盛行率改變的是方向,不是哪一個參數傷得比較重,而方向本身也還帶三個前提:暴露是二分的、暴露與結果的錯分彼此獨立、敏感度加特異度大於 1。
回憶偏誤那張表的四列真值都是 2。第一列「病例回想得比較清楚」把它推到 2.392。第二列「對照回想得比較清楚」是多少,這說明什麼?
看答案與解析
正確答案: 1.227。同一個真值、同一個測量工具的平均品質,方向卻可以相反——差別性錯分的問題不是錯得多嚴重,是錯得均不均勻
1.227 把真值壓到不到一半,而第一列的 2.392 把同一個真值誇大——兩列用的是同一組敏感度與特異度,只是誰比較準對調了。所以差別性錯分沒有可預測的方向,這正是它比非差別性錯分難處理的地方:你連「真值只會更遠離虛無」這種保底的話都不能說。1.695 是第四列,那一列刻意把兩組的參數設成相同,也就是把差別性拿掉,它才乖乖偏向虛無;把它當成差別性錯分的通則會把整節的結論寫反。2.294 是第三列,那裡是病例的特異度掉一點(過度回報),確實誇大,但它只是四種可能之一,不是通則。實務上的判準是暴露資訊什麼時候蒐集的:結果發生之後才問的暴露,差別性錯分是預設要討論的問題。
三個類別的暴露,兩個混淆矩陣的整體正確分類率刻意調成一樣。對稱錯分之下「中 vs 低」的勝算比是 1.716,量得準時是 2.028。不對稱錯分之下是多少?
看答案與解析
正確答案: 2.134,比量得準時還高。非差別性錯分把中間層的估計值推得遠離虛無,「偏向虛無」這條規則對三個類別以上不成立
2.134 高於量得準時的 2.028,也高於這個比較的真值——一個非差別性的錯分把估計推到了虛無的另一邊。機制是不對稱矩陣把最高層的一大部分推進中間層,於是中間層裡混進了大量真正的高暴露者,它的風險被拉高。3.105 與 3.200 都是「高 vs 低」那一列的數字,不是題目問的那一列;而 3.200 附帶的「整體正確分類率一樣就會給出一樣的答案」正好被這張表否定——兩個矩陣的正確率是刻意調成相同的,差別只在錯的方向。1.716 那一欄則是對稱錯分,它照規則走。實務後果落在劑量反應關係上:一條漂亮的階梯可能有一部分來自最高暴露層被低估,所以「觀察到劑量反應趨勢因此支持因果」在暴露分類粗略或自陳時要打折扣。
連續暴露的迴歸稀釋表裡,真斜率是 1.5。誤差標準差等於真值標準差那一列(信度 0.5),估到的斜率是多少?
看答案與解析
正確答案: 0.754,大約是真斜率的一半。古典測量誤差把係數乘上信度,這個幅度不是經驗規律而是代數
0.754 幾乎正好是 1.5 的一半,而信度是 0.5——係數被乘上信度,這是封閉解不是經驗規律,表上「估到的斜率」與「信度乘上真斜率」兩欄幾乎一模一樣就是它的證據。1.201 是誤差標準差比較小的那一列,把稀釋說成與誤差大小成正比,會在誤差變大時嚴重低估傷害。0.300 是誤差標準差最大的那一列,那裡的信度只有兩成;把信度 0.5 對應到「掉剩五分之一」是把兩列讀混了。兩個實務推論:有重複量測的子研究就能估出信度、把係數除回去(迴歸校準),沒有那個子研究就沒得校正;而且稀釋同時縮小點估計與檢定統計量,所以測不準的暴露會系統性地讓真實關聯更難被偵測到,把那種結果讀成「沒有關聯」是把測量品質的問題當成生物學結論。
結果測不準那張表的真實相對風險是 2,未暴露風險只有 2%。結果判定的敏感度掉到 0.70、特異度完美時,觀察到的相對風險是 1.985。改成特異度 0.98、敏感度完美呢?
看答案與解析
正確答案: 1.494。基準風險只有 2%,兩個百分點的假陽性率作用在絕大多數健康的人身上,產生的假事件跟真事件同一個量級
1.494 是特異度 0.98 那一列,掉得比敏感度 0.70 那一列(1.985)嚴重得多。這是罕見結果特有的:真事件本來就少,假陽性率作用在絕大多數健康的人身上,製造出來的假事件與真事件同一個量級,於是兩組都被稀釋成一樣的背景雜訊。1.999 來自特異度完美、只有敏感度掉下來的那一列——前三列確實幾乎不動,但那說的是敏感度:漏掉的事件在兩組是等比例的,比值當然不動,把它套到特異度上正好把結論寫反。1.654 是特異度只掉一個百分點的那一列,它比 1.494 高,所以「掉兩個百分點傷得比較小」在表上直接被否定。實務推論落在用診斷碼定義結果的資料庫研究:大家通常擔心診斷碼漏掉病人,但對罕見結果而言壓垮估計值的是假陽性,所以驗證研究該報的是陽性預測值與特異度。
這次模擬裡暴露對結果完全沒有作用,關聯全部來自一個干擾因子。完全不校正時暴露的勝算比是 2.380,用量得完美的干擾因子校正回到 1.017。改用一個誤差標準差等於干擾因子本身標準差的版本呢?
看答案與解析
正確答案: 1.676。校正只校掉一部分,剩下的殘餘干擾看起來與真實效果無法區分
1.676 離真值還很遠,而它是「已經校正過」的數字——校正的力道被干擾因子的測量誤差吃掉了一大半。1.086 是誤差很小那一列,把它當成通則會得出「放進模型就沒事」,而這張表整列整列在說的正好相反。2.090 是誤差最大那一列,它確實接近未校正的 2.380 但沒有等於;這一欄是連續變化的,不是「有校正/沒校正」的開關。實務上的說法是:已校正吸菸不等於吸菸不再是干擾因子——用有無兩格記錄的吸菸沒有攜帶包年數的資訊,剩下的那一部分干擾與真實效果無法區分。所以 limitation 段落該寫的不是「我們校正了所有已知干擾因子」,而是那些干擾因子是怎麼量的、殘餘干擾大概還剩多少。
非差別性錯分那張表除了勝算比,還有一欄「看起來的暴露率」。表上以百分比顯示,這裡一律寫成比例:真值是 0.30,敏感度單獨掉到 0.90 那一列是 0.269。特異度單獨掉到 0.90 那一列是多少,這一欄能拿來做什麼?
看答案與解析
正確答案: 是 0.370,高於真值。敏感度掉會把暴露率壓低、特異度掉會把它推高,而且推高的幅度比壓低的大
0.370 高於真值的 0.30,而敏感度掉一樣多是把它壓到 0.269:同樣掉一成,往上推的幅度是往下壓的兩倍以上。原因與勝算比那一欄相同——暴露率只有三成,特異度製造的假陽性來自未暴露那一大群,敏感度漏掉的真陽性只能來自比較小的那一群。0.239 是只動敏感度、掉得更兇的那一列,它同樣低於真值,但拿它主張「錯分一律把暴露率往下拉」會漏掉假陽性這條路徑,而那條路徑正是同一張表下半部在走的。0.313 是特異度只掉兩個百分點那一列,把它讀成特異度掉一成的結果,就會得出兩種錯誤對稱的結論,而這一欄整欄都在否定它。實務上的用法很直接:一篇研究報的暴露盛行率與登記資料或全國調查對不上時,先問暴露是怎麼量的——偏高指向特異度,偏低指向敏感度。
用到這個方法的章節
素材來源與授權
本頁為原創內容