Table 1 與標準化平均差
Table 1 在 RCT 與在觀察性研究裡問的不是同一個問題、SMD 為什麼不隨樣本數膨脹、|SMD| < 0.1 這個慣例哪來的,以及連續變項該報平均還是中位數、遺漏值該擺在哪裡。
這張表在回答什麼問題
論文的第一張表幾乎永遠是基線特徵表,但它在不同設計裡回答的不是同一個問題——這是本頁最重要的一件事,其他細節都是從它推出來的。
| 設計 | Table 1 回答的問題 | 讀者要拿它做什麼判斷 |
|---|---|---|
| RCT | 「這一次的隨機分配,實際抽出來的兩組長得像嗎?」 | 判斷有沒有大到需要在分析裡校正的偶發不平衡 |
| 世代 / 病例對照 / 真實世界資料 | 「這兩群人本來就有多不一樣?」 | 判斷混淆有多嚴重、後面的校正有沒有可能撐得住 |
| 傾向分數配對 / 加權後 | 「配對/加權有沒有把差異壓下去?」 | 判斷這個平衡程序有沒有成功,是它的驗收標準 |
| 任何研究 | 「這群人是誰?我的病人像他們嗎?」 | 判斷外部效度,這是 Table 1 最被低估的用途 |
差別很實際。在 RCT 裡,兩組不同只可能來自運氣,因為分組與病人特徵無關;所以「有沒有差」這件事你事先就知道答案,Table 1 只是讓你看差多大。在觀察性研究裡,兩組不同是真的不同——醫師本來就把某種藥開給某一類病人——所以 Table 1 是在告訴你混淆的規模。
這一頁用的例子
survival::pbc 是 Mayo Clinic 的原發性膽汁性膽管炎(primary biliary cholangitis, PBC)試驗,共 418 人。它剛好在同一個檔案裡裝了上表的前兩種情境:
- 312 人被隨機分配到 D-penicillamine 或安慰劑 → 這是 RCT 的 Table 1
- 106 人拒絕隨機分配、但同意被登錄追蹤 → 「隨機組 vs 登錄組」就是一個貨真價實的觀察性比較
同一份資料、同樣七八個變項,換一種分組方式,這張表的意義就整個變了。
標準化平均差怎麼算
標準化平均差(standardised mean difference, SMD)把「兩組差多少」換算成「差幾個標準差」。連續變項的公式是
分母是兩組標準差的平方平均(不是合併標準差 pooled SD,兩者在樣本數不等時會略有差異,Table 1 慣用前者)。
二元變項把比例當成 Bernoulli 變項的平均數,變異數由比例本身決定:
三個以上類別的變項需要 Austin 的多分類推廣:把 個類別的比例寫成 維向量 ,用兩組的平均共變異數矩陣 做馬氏距離
三個公式的共同點是分母都只跟離散程度有關,跟樣本數無關。這正是它取代 p 值的理由,下一節會用實驗把這件事演給你看。
動手跑一次
library(survival)
data(pbc, package = "survival")
pbc$arm <- factor(pbc$trt, levels = c(1, 2),
labels = c("D-penicillamine", "Placebo"))
# ── 手算 SMD:連續變項 ──────────────────────────────────
smd_cont <- function(x, g) {
ok <- !is.na(x) & !is.na(g); x <- x[ok]; g <- droplevels(factor(g[ok]))
m <- tapply(x, g, mean); s <- tapply(x, g, sd)
unname((m[1] - m[2]) / sqrt((s[1]^2 + s[2]^2) / 2))
}
# ── 手算 SMD:二元變項 ──────────────────────────────────
smd_bin <- function(x, g) { # x 為 0/1
ok <- !is.na(x) & !is.na(g); x <- as.numeric(x[ok]); g <- droplevels(factor(g[ok]))
p <- tapply(x, g, mean)
unname((p[1] - p[2]) / sqrt((p[1] * (1 - p[1]) + p[2] * (1 - p[2])) / 2))
}
smd_cont(pbc$age, pbc$arm) # 年齡
smd_bin(pbc$sex == "m", pbc$arm) # 性別
# ── 實務上的一行版本(本機 tableone 裝不起來,故本頁手算)──
# library(tableone)
# CreateTableOne(vars = c("age", "sex", "bili", "albumin", "stage"),
# strata = "arm", data = pbc) |> print(smd = TRUE, nonnormal = "bili")驗證環境:R 4.6.0 + survival 3.8.6。實務上這一切用 tableone::CreateTableOne() 一行就有,這裡手算是為了讓你看見公式。
import numpy as np
import statsmodels.api as sm
pbc = sm.datasets.get_rdataset("pbc", "survival").data
pbc["arm"] = pbc["trt"].map({1: "D-penicillamine", 2: "Placebo"})
rct = pbc.dropna(subset=["arm"])
def smd_cont(x, g):
a, b = [x[g == lv].dropna() for lv in sorted(g.dropna().unique())]
return (a.mean() - b.mean()) / np.sqrt((a.std(ddof=1) ** 2 + b.std(ddof=1) ** 2) / 2)
print(smd_cont(rct["age"], rct["arm"]))
# 一行版本:
# from tableone import TableOne
# TableOne(rct, columns=["age", "sex", "bili", "albumin", "stage"],
# groupby="arm", nonnormal=["bili"], smd=True)tableone 的 Python 版介面幾乎一樣,SMD 欄用 pval_test_name 之外的 smd=True 開啟。
兩張 Table 1,同一份資料
(一)RCT 的 Table 1:D-penicillamine vs 安慰劑
| 變項 | D-penicillamine (n = 158) | 安慰劑 (n = 154) | 遺漏(藥/安慰劑) | |SMD| | p(示範用,不該出現在論文裡) |
|---|---|---|---|---|---|
| 年齡(歲) mean ± SD / median (IQR) | 51.4 ± 11.0 51.9 (43.0–58.9) | 48.6 ± 10.0 48.1 (41.4–55.8) | 0 / 0 | 0.270 | 0.018 |
| 性別 | 女性 137 (86.7%);男性 21 (13.3%) | 女性 139 (90.3%);男性 15 (9.7%) | 0 / 0 | 0.111 | 0.421 |
| 血清膽紅素(mg/dL) mean ± SD / median (IQR) | 2.87 ± 3.63 1.40 (0.80–3.20) | 3.65 ± 5.28 1.30 (0.72–3.60) | 0 / 0 | 0.171 | 0.133 |
| 血清白蛋白(g/dL) mean ± SD / median (IQR) | 3.52 ± 0.44 3.56 (3.21–3.83) | 3.52 ± 0.40 3.54 (3.34–3.78) | 0 / 0 | 0.018 | 0.874 |
| 血清膽固醇(mg/dL) mean ± SD / median (IQR) | 365 ± 210 316 (248–417) | 374 ± 252 304 (254–377) | 18 / 10 | 0.038 | 0.747 |
| 血小板數(×10⁹/L) mean ± SD / median (IQR) | 259 ± 100 255 (190–322) | 265 ± 91 260 (207–322) | 2 / 2 | 0.067 | 0.554 |
| 水腫 | 無 132 (83.5%);未治療或已緩解 16 (10.1%);用利尿劑仍持續 10 (6.3%) | 無 131 (85.1%);未治療或已緩解 13 (8.4%);用利尿劑仍持續 10 (6.5%) | 0 / 0 | 0.058 | 0.877 |
| 組織學期別 | 第 1 期 12 (7.6%);第 2 期 35 (22.2%);第 3 期 56 (35.4%);第 4 期 55 (34.8%) | 第 1 期 4 (2.6%);第 2 期 32 (20.8%);第 3 期 64 (41.6%);第 4 期 54 (35.1%) | 0 / 0 | 0.246 | 0.201 |
(二)觀察性的 Table 1:接受隨機分配 vs 拒絕隨機分配
| 變項 | 隨機組 (n = 312) | 登錄組 (n = 106) | 遺漏(隨機/登錄) | |SMD| |
|---|---|---|---|---|
| 年齡(歲) | 50.0 ± 10.6 49.8 (42.2–56.7) | 52.9 ± 9.8 53.0 (46.0–61.0) | 0 / 0 | 0.280 |
| 性別 | 女性 276 (88.5%);男性 36 (11.5%) | 女性 98 (92.5%);男性 8 (7.5%) | 0 / 0 | 0.136 |
| 血清膽紅素(mg/dL) | 3.26 ± 4.53 1.35 (0.80–3.42) | 3.12 ± 4.04 1.40 (0.72–3.08) | 0 / 0 | 0.032 |
| 血清白蛋白(g/dL) | 3.52 ± 0.42 3.55 (3.31–3.80) | 3.43 ± 0.43 3.47 (3.12–3.72) | 0 / 0 | 0.208 |
| 血清膽固醇(mg/dL) | 370 ± 232 310 (250–400) | 未測量 未測量 | 28 / 全部 | — |
| 血小板數(×10⁹/L) | 262 ± 96 257 (200–322) | 242 ± 105 226 (166–286) | 4 / 7 | 0.201 |
| 水腫 | 無 263 (84.3%);未治療或已緩解 29 (9.3%);用利尿劑仍持續 20 (6.4%) | 無 91 (85.8%);未治療或已緩解 15 (14.2%);用利尿劑仍持續 0 (0%) | 0 / 0 | 0.393 |
| 組織學期別 | 第 1 期 16 (5.1%);第 2 期 67 (21.5%);第 3 期 120 (38.5%);第 4 期 109 (34.9%) | 第 1 期 5 (5%);第 2 期 25 (25%);第 3 期 35 (35%);第 4 期 35 (35%) | 0 / 6 | 0.093 |
把兩張表的 |SMD| 疊在同一個軸上看最清楚。這種圖叫 Love plot,在傾向分數研究裡是標準配備:
figures/scripts/B1-01-table1.R值得停下來看的是:隨機分配的兩組也有 4 個變項的 |SMD| 超過 0.1,年齡是 0.270。這不代表隨機化失敗,它代表 n = 312 的一次抽樣本來就會出現這種程度的偶發差異。反過來說,登錄組那條線上超過 0.1 的有 5 個變項,其中水腫(oedema)達到 0.393——這才是「兩群人本來就不一樣」。
為什麼是 SMD,不是 p 值
拿本頁的資料做一個實驗:把隨機分配的那 312 人原封不動複製十份,得到 3120 筆。兩組的平均數、標準差、分布形狀全部沒變,只有樣本數變成十倍。
| 原始(n = 312) | 複製十份(n = 3120) | |
|---|---|---|
| 年齡 |SMD| | 0.270 | 0.271 |
| 年齡 t 檢定 p | 0.018 | < 0.001 |
SMD 幾乎不動(差的那 0.001 只來自標準差的 分母),p 值卻掉了十幾個數量級。p 值同時編碼了「差多少」與「有多少人」,而 Table 1 只想問前者。 這就是為什麼樣本數幾萬人的健保資料庫研究,Table 1 全欄 p < 0.001 是常態,而那一欄什麼都沒告訴你。
RCT 還有第二個更根本的理由:虛無假設「兩組來自同一母體」在隨機分配下依定義為真。你在檢定一個你已經知道答案的假設,任何顯著結果依定義都是型一錯誤。本頁 RCT 那張表測了 8 個變項,最小的 p 值是 0.018,有 1 個落在 0.05 以下——完全在預期之內:若這些檢定彼此獨立,測 8 個變項至少出現一個「顯著」的機率約 33.7%,論文裡常見的 20 個變項則約 64.2%。基線變項彼此常相關(年齡與期別、膽紅素與白蛋白都不獨立),所以這兩個數字是量級上的參考值,不是這張表的確切機率——但方向不變:變項愈多,碰巧出現一個「顯著」愈不值得驚訝。
|SMD| < 0.1 這條線哪裡來的
這個門檻常被引用成鐵律,但它的來歷比想像中鬆:它源自傾向分數配對文獻(Normand 等人 2001 年的用法、Austin 一系列方法學論文的推廣),理由是模擬研究顯示殘餘 |SMD| 小於 0.1 時,配對後估計的偏誤通常已經小到可以忽略。
三件要記住的事:
- 它是慣例不是檢定,沒有 0.1 這一側顯著、另一側不顯著這種事。0.09 與 0.11 沒有實質差別。
- 重要的混淆因子即使 0.05 也可能不夠。 門檻該不該收緊,取決於那個變項對結果的影響有多強,不取決於它是哪一列。
- 不重要的變項超過 0.1 通常不必處理。 一個與結果無關的變項不平衡,不會造成混淆。
另有 0.25 的寬鬆版本流通(早期 propensity score 文獻),所以看到別人用 0.1 以外的門檻不必驚訝,但論文必須寫明用的是哪一條線。
平均數還是中位數:看分布,不是看習慣
上面兩張表故意把 mean ± SD 與 median (IQR) 並排列出來,是為了讓你看見兩者何時會分道揚鑣。血清膽紅素就是典型例子:
figures/scripts/B1-01-table1.R平均是 3.22、標準差 4.41,中位數只有 1.4(IQR 0.8–3.4)。要命的是 mean − SD = -1.19,是負數,而實際觀測到的最小值是 0.3。你寫下「3.22 ± 4.41」時,讀者腦中自動浮現的那個對稱區間根本不存在於這份資料裡。
判準很簡單:
- 分布大致對稱 → mean ± SD。它保留了更多資訊,也是後續 t 檢定、線性模型使用的量。
- 明顯偏態、有離群值、或本來就是有界的量(住院天數、住院費用、膽紅素、CRP、追蹤時間)→ median (IQR)。
- 兩者都報在 Table 1 是完全可以接受的,多一欄的成本遠低於誤導。
遺漏值:該單獨列一行,而且該列
答案是該,而且理由不只是誠實。看第二張表的 cholesterol 那一列:登錄組 106 人全部沒有這項數值——那 106 個拒絕隨機分配的病人根本沒有做試驗的抽血套組(chol、copper、alk.phos、ast、trig、ascites、hepato、spiders 全部同樣情形)。這不是零星遺漏,是整組沒有測量,它的 SMD 不存在,表上只能留白並註明。
如果 Table 1 只印百分比而不印遺漏數,這件事會完全消失在版面裡,讀者會以為兩組都測了。分母悄悄變小是基線表最常見的誤導來源。
三個實作要點:
- 遺漏數與分母都要寫出來。 「n = 106,cholesterol 遺漏 106」與「n = 106」看起來差很多。
- 百分比要說明分母是誰。 用有效值當分母(complete case)與用全組人數當分母,數字不一樣,兩種都有人用,但必須寫明。
- 遺漏本身可能就是資訊。 上面那個例子裡,「沒測」與「拒絕隨機分配」是同一件事的兩面。把遺漏當成一個類別去看,常常會看到選擇機制。
怎麼讀別人的 Table 1
由上而下四個動作:
- 先看兩欄的 n,再看每一列的遺漏數。分母有沒有一路縮水,決定了後面所有百分比的意義。
- 看有沒有 SMD 欄。沒有的話,把你自己關心的那兩三個變項心算一下:連續變項的差除以大約的標準差就是粗估的 SMD。
- 看連續變項報的是哪一種摘要,以及那個摘要合不合分布。mean ± SD 出現在住院天數上是警訊。
- 看這群人是誰。年齡中位數、性別比、疾病期別——你的病人落在這個分布裡嗎?這一步跟平衡無關,但它決定這篇論文對你有沒有用。
常見誤用
| 誤用 | 為什麼錯 |
|---|---|
| 在 RCT 的 Table 1 放組間 p 值 | 虛無假設依定義為真,顯著的結果只能是型一錯誤;CONSORT 明確不建議 |
| 大型資料庫研究用 p 值判斷平衡 | p 值隨 n 膨脹,幾萬人時全欄 p < 0.001,什麼資訊都沒有 |
| 用 Table 1 的 p 值挑多變量模型的共變項 | 該不該校正取決於因果結構(DAG),不是取決於基線碰巧的不平衡 |
| |SMD| 0.11 就宣告「配對失敗」 | 0.1 是慣例不是檢定門檻;該不該擔心取決於該變項與結果的關聯強度 |
| 偏態變項報 mean ± SD | 讀者會腦補一個對稱區間;膽紅素這種變項 mean − SD 甚至可能是負數 |
| 只印百分比、不印遺漏數 | 分母縮水完全看不出來,是基線表最常見的誤導 |
| Table 1 用插補後的數值卻沒說明 | Table 1 該描述實際觀察到的資料,插補屬於分析階段 |
| 拿觀察性研究的 Table 1 說「兩組沒有差異」 | 未達顯著只代表本研究未偵測到差異,不代表兩組相同;何況該用 SMD 而不是檢定 |
| 把 Table 1 平衡當作「已無混淆」 | 它只涵蓋你有測量到的變項;未測量的混淆因子不會出現在任何一列 |
重跑本頁的所有數字
/opt/homebrew/bin/Rscript figures/scripts/B1-01-table1.R讀讀看這張圖
答案取自產生本頁圖表的同一份統計輸出,不是另外打上去的。
把接受隨機分配的 312 人原封不動複製十份,得到 3120 筆。兩組的平均數、標準差、分布形狀全部沒變,只有樣本數變成十倍。年齡那一列會怎麼變?
看答案與解析
正確答案: SMD 的絕對值變成 0.271,與原本的 0.270 幾乎一樣——它的分母只跟離散程度有關,跟有多少人無關
SMD 的分母是兩組標準差的平方平均,只跟離散程度有關;複製十份不改變離散程度,所以 0.270 只動到 0.271,那一點點差距來自標準差的 n-1 分母。p 值不一樣:它同時編碼了「差多少」與「有多少人」,複製之後從 0.018 掉到小數點後十幾位才看得到。所以說 p 值不動是錯的,而說 SMD 被稀釋掉也是錯的——被稀釋的是 p 值能承載的資訊,不是差異本身。健保資料庫那種幾萬人的研究,Table 1 整欄都顯著,就是這個機制。
第二張表(隨機組對登錄組)的膽固醇那一列,登錄組那一欄是空白的,SMD 也沒有數字。發生了什麼事?
看答案與解析
正確答案: 登錄組 106 人全部沒有這項數值——整組沒有測量,那一格的 SMD 不存在
那 106 個拒絕隨機分配的病人根本沒有做試驗的抽血套組,所以登錄組這一列的分母是零,不是「小了一點」。28 是隨機組那一側真正的零星遺漏,134 是兩組加起來的總數;把 106 讀成零星遺漏,會讓人以為完整病例分析還救得回來。零星遺漏與整組未測量的差別在於後者沒有任何可比的東西。而且如果 Table 1 只印百分比不印遺漏數,這件事會完全消失在版面裡,讀者會以為兩組都測了——分母悄悄變小是基線表最常見的誤導來源。順帶一提,遺漏本身常常就是資訊:這裡「沒測」與「拒絕隨機分配」是同一件事的兩面。
血清膽紅素的偏態係數是 2.70,論文的 Table 1 卻寫成 3.22 ± 4.41 mg/dL。這個寫法的問題在哪裡?
看答案與解析
正確答案: 平均減一個標準差是 -1.19 mg/dL,而膽紅素不可能是負的
偏態係數 2.70 已經說明分布嚴重右偏,而 mean ± SD 這個寫法隱含一個對稱區間:往下減一個標準差就到 -1.19 mg/dL,而實際觀測到的最小值是正的。中位數 1.40 落在平均數 3.22 的左邊不是算錯,那正是右偏分布該有的樣子——長尾把平均數往右拉,而中位數不動。最大值 28.00 也不是該剔除的錯誤資料,膽紅素本來就可以到那麼高,剔除它只是把偏態藏起來。判準是分布長什麼樣子(畫直方圖、看偏態),不是先跑一次常態性檢定再決定:明顯偏態、有界、或有長尾的量(住院天數、住院費用、CRP、膽紅素)報中位數與四分位距,兩者都報在 Table 1 也完全可以接受。
RCT 那張 Table 1 一共測了 8 個變項,其中一列的 p 值落在慣用的顯著水準以下。該怎麼看這件事?
看答案與解析
正確答案: 若這些檢定彼此獨立,測 8 個變項至少出現一個顯著的機率約 0.337,所以出現一個完全在預期之內
0.337 是「測 8 個變項、至少出現一個顯著」的機率,0.642 則是變項更多時(論文常見的規模)的對應機率——把後者套到這張表上會高估碰巧顯著的可能性,雖然兩個數字都指向同一個方向:變項愈多,碰巧出現一個顯著愈不值得驚訝。基線變項彼此常相關(年齡與期別、膽紅素與白蛋白都不獨立),所以這兩個數字是量級上的參考值,不是這張表的確切機率。至於 0.018 那一列,它低於慣用水準也不代表隨機分配失敗:RCT 的虛無假設「兩組來自同一母體」在隨機分配下依定義為真,任何顯著結果依定義都是型一錯誤。要判斷不平衡該看 SMD,而且不要用 Table 1 的 p 值挑要不要放進模型的共變項——該不該校正取決於因果結構,不是基線碰巧的不平衡。
RCT 那張表裡,性別那一列的 SMD 絕對值剛好越過 0.1 這條線。該怎麼讀?
看答案與解析
正確答案: 0.111 與 0.1 的差距沒有實質意義——0.1 是傾向分數文獻的慣例,不是檢定門檻
0.1 這條線來自傾向分數配對文獻,理由是模擬顯示殘餘不平衡小於它時偏誤通常已可忽略——它是慣例不是檢定,0.111 與 0.1 之間沒有實質差別,文獻裡另有更寬鬆的版本在流通,所以論文必須寫明用的是哪一條線。0.246 確實比 0.1 大得多,但 RCT 裡兩組的差異只可能來自運氣,Table 1 要回答的是「差多大、需不需要在分析裡校正」,不是「隨機分配成不成功」。0.018 那一列的確很平衡,可是一張全部落在 0.1 以內的 Table 1 只涵蓋你有測量到的變項,未測量的混淆因子不會出現在任何一列——把 Table 1 平衡當成「已無混淆」是另一種誤用。
同一份資料換一種分組:把接受隨機分配的人與拒絕隨機分配、只同意被登錄的人拿來比。水腫那一列的 SMD 絕對值比 RCT 那張表的任何一列都大。這說明什麼?
看答案與解析
正確答案: 0.393 量的是真實存在的組間差異,不是抽樣運氣——這一格量的是混淆的規模
RCT 的兩組只可能因為運氣而不同,因為分組與病人特徵無關;所以 0.058 這種數字問的是「這一次抽出來差多少」。換成隨機組對登錄組,分組是病人自己決定的,0.393 量的就是真正的組間差異,也就是混淆的規模。0.014 這個 p 值在這裡不再是無意義的(虛無假設不再依定義為真),但它回答的是「差異存不存在」而不是「差多大」,而後者才決定後面的校正撐不撐得住。至於資料品質,登錄組的水腫是有紀錄的,缺的是抽血項目,那是另一列的事。同一份資料、同樣的變項,換一種分組,這張表問的問題就整個換了。
用到這個方法的章節
延伸觀看
用 AI 做臨床研究:做出論文第一張表 Table 1|該不該放 p 值?改用 SMD
AI 臨床研究實戰 EP6|Table 1 基線特徵表怎麼做:p 值的陷阱、常態性檢定、缺失值
[R tutorial EP1] How to make baseline characteristics table / Table1
Table 1 in R with gtsummary素材來源與授權
本頁為原創內容