分布圖:為什麼不要用長條圖加誤差線
mean 加一根誤差線的長條圖把樣本數、分布形狀、離群值全部藏起來,而那根誤差線可能是 SD、SE 或 95% CI 中的任何一種,長度差好幾倍。盒鬚圖、小提琴圖與 raincloud 各補上什麼,以及期刊為什麼開始要求畫出每一個資料點。
這一頁在解決什麼問題
論文與投影片上最常見的連續變項畫法是這樣的:每組一根長條,長條高度是平均數,頂上長出一根短短的誤差線。 這種圖有個綽號叫 dynamite plot(炸藥棒圖),因為它長得像插著引線的炸藥。
它的問題不是不好看,是每組只畫了兩個數字:一個柱高、一根鬍鬚長度。 你手上原本有幾十幾百個觀測值,畫完之後讀者拿到兩個摘要量,而且看不出來這兩個數字是從幾個人身上算來的。
本頁把同一份資料畫成四種圖,然後具體算出前一種畫法藏掉了什麼。
資料是 MASS::birthwt:189 位產婦,結果變項是新生兒出生體重(公克),
分組變項與 t 檢定與變異數分析那一頁的 ANOVA 例子完全相同,
所以兩頁講的是同一份資料的同一組分層。
同一份資料,四種畫法
figures/scripts/B1-05-distribution-plots.R四種畫法看得到與看不到的東西:
| 畫法 | 讀者拿得到 | 讀者拿不到 |
|---|---|---|
| 長條圖 + 誤差線 | 平均數、一根長度未定義的鬍鬚 | 樣本數、離散程度、形狀、離群值、個別觀測值 |
| 盒鬚圖 | 中位數、四分位距、鬍鬚範圍、被標記的離群點 | 樣本數、雙峰、箱內部的密度分布 |
| 小提琴圖 | 完整的密度形狀、雙峰、長尾 | 樣本數、個別觀測值(密度是估計出來的,不是資料本身) |
| raincloud | 密度形狀、四分位數、每一個觀測值、樣本數 | 沒有——它的成本是版面,不是資訊 |
注意 A 那一格的縱軸:長條圖的柱長要能被比較,起點就必須是 0, 於是整段資料被擠在圖的上緣。另外三種圖不受這個限制,縱軸可以貼著資料走,所以同樣的紙面上解析度高得多。
dynamite 在這份資料上藏了什麼
前一節的表格是原則。原則說服不了人,所以這一節把數字算出來。
一、三組的人數差了好幾倍,而長條圖上完全看不到
| 組別 | n | 平均數(g) | SD(g) | SE(g) | 中位數(g) | 四分位距(g) |
|---|---|---|---|---|---|---|
| 白人 | 96 | 3102.7 | 727.9 | 74.3 | 3062 | 1066.2 |
| 黑人 | 26 | 2719.7 | 638.7 | 125.3 | 2849 | 686.5 |
| 其他 | 67 | 2805.3 | 722.2 | 88.2 | 2835 | 961 |
最大組與最小組的人數比是 3.69,而三組的 SD 幾乎一樣 (最大與最小只差 1.14 倍)。可是三根 SE 誤差線的長度差了 1.69 倍。原因是 SE 等於 SD 除以 n 的平方根,所以誤差線的長度主要在講樣本數,不在講離散程度。
這件事在這份資料上有一個極乾淨的例子:黑人組的 SD 是三組裡最小的 (638.7 g),可是它的 SE 誤差線是三組裡最長的(125.3 g), 因為那一組只有 26 個人。
二、平均數差得看起來很多,個別的嬰兒重疊得很厲害
| 兩組比較 | 平均數差(g) | 隨機各抽一人,前者較重的機率 | 前者落在後者四分位距內的比例 | 兩組數值範圍的交集(g) |
|---|---|---|---|---|
| 白人 vs 黑人 | 383 | 0.654 | 30.2% | 1135 到 3860 |
| 白人 vs 其他 | 297.4 | 0.606 | 42.7% | 1021 到 4054 |
| 黑人 vs 其他 | -85.6 | 0.459 | 53.8% | 1135 到 3860 |
第一列是最有代表性的:白人組與 黑人組的平均數差 383 公克, 在長條圖上是肉眼可見的兩根不同高度的柱子。但如果從兩組各隨機抽一個嬰兒, 白人組那個比較重的機率只有 0.654——比丟硬幣好一些,遠不到「兩組不一樣」的程度。 而且有 30.2% 的 白人組嬰兒的體重,落在 黑人組的四分位距裡面。
「隨機各抽一人,前者較重的機率」這個量(機率優越性,probability of superiority)之所以好用, 是因為它跟原始單位無關,而且每個讀者都懂 0.5 是什麼意思。它不是檢定,也不取代 ANOVA 與事後比較那一頁在做的推論—— 它回答的是另一個問題:這個組間差距,放到一個病人身上,有多大。
三、形狀:這份資料其實相當乾淨,說實話比較有用
教科書講到這裡通常會說「而且長條圖藏住了偏態、雙峰與離群值」。在這份資料上,這句話大部分不成立, 照實報出來比硬套原則有用:
| 組別 | 偏度 | 1.5 IQR 規則下的離群值個數 | 密度局部極大值(預設頻寬) | 密度局部極大值(頻寬放寬一半) |
|---|---|---|---|---|
| 白人 | -0.13 | 0 | 3 | 1 |
| 黑人 | -0.31 | 1 | 3 | 1 |
| 其他 | -0.42 | 1 | 2 | 1 |
三組的偏度絕對值最大只有 0.42,屬於接近對稱;
全部 189 個嬰兒裡,被 1.5 IQR 規則標成離群值的只有 2 個。
至於雙峰:在 density() 的預設頻寬下,最多的一組有 3 個局部極大值,
看起來像有第二個群;但把頻寬放寬一半之後,三組全部只剩 1 個。
多出來的那幾個峰是平滑參數的產物,不是次族群的證據。
所以在這份資料上,dynamite plot 的傷害來自人數差距與重疊,不是來自被藏起來的偏態或雙峰。 形狀真的不同時會發生什麼,第五節用刻意構造的資料補上。
三種誤差線,外觀一樣,長度差好幾倍
上面一直說「那根誤差線」,因為在圖上根本看不出它是什麼。同一組資料可以畫出三種誤差線:
- SD(標準差) 描述的是個體之間有多分散。它不隨樣本數縮小——多收一百個人,SD 大致不變。
- SE(標準誤) 描述的是平均數這個估計有多精確。它等於 SD 除以 n 的平方根,所以人越多它越短。
- 95% CI 是 SE 乘上一個 t 分位數,大約兩倍的 SE。它回答的是「平均數合理落在哪個範圍」。
figures/scripts/B1-05-distribution-plots.R| 組別 | SD 半長(g) | SE 半長(g) | 95% CI 半長(g) | SD 是 SE 的幾倍 | CI 是 SE 的幾倍 |
|---|---|---|---|---|---|
| 白人 | 727.9 | 74.3 | 147.5 | 9.8 | 1.99 |
| 黑人 | 638.7 | 125.3 | 258 | 5.1 | 2.06 |
| 其他 | 722.2 | 88.2 | 176.2 | 8.19 | 2 |
在 白人組,SD 的棒子是 SE 棒子的 9.8 倍長。 換句話說,同一份資料、同一個平均數,作者選 SE 或選 SD,讀者看到的圖差了將近一個數量級。 而 SE 是三者中最短的,所以選 SE 會讓組間看起來最分開——這也是它最常被選的原因之一。
還有一個相關的誤讀:誤差線有沒有重疊,不能當成檢定的替代品。 兩組的 95% CI 稍微重疊時, 兩組平均數差的檢定仍然可能達到統計顯著;反過來,兩組的 SE 棒子沒有重疊,也不代表達到顯著。 要判斷組間差距,看的是差距本身的信賴區間,不是兩個各自的區間有沒有碰到。 這一點在 t 檢定與變異數分析那一頁講得更完整。
同一張長條圖,三份完全不同的資料
前面第三節已經照實說了:birthwt 的三組分布相當對稱、離群值很少。
那形狀真的不同的時候會怎樣?下面三份資料是刻意構造的,不是任何研究的結果:
三份都是 40 個觀測值,平均數都是 3000,標準差都是 700,
所以 SE 都是 110.7,95% CI 半長都是 223.9。它們的長條圖是同一張圖畫三次。
figures/scripts/B1-05-distribution-plots.R| 構造樣本 | 平均數 | SD | SE | 中位數 | 偏度 | 離群值個數 | 密度峰數 |
|---|---|---|---|---|---|---|---|
| 樣本一:對稱單峰 | 3000 | 700 | 110.7 | 3000 | 0 | 0 | 1 |
| 樣本二:兩個群 | 3000 | 700 | 110.7 | 3000 | 0 | 0 | 2 |
| 樣本三:四個極端值 | 3000 | 700 | 110.7 | 2831.8 | 1.95 | 4 | 3 |
前四欄一模一樣,後四欄完全不同。樣本二分成兩群,平均數落在兩群之間沒有人的地方—— 一個沒有任何觀測值靠近的位置,卻是柱子的高度。樣本三的偏度是 1.95, 4 個極端值把平均數從中位數 2831.8 拉到了 3000。
動手跑一次
library(MASS)
data(birthwt, package = "MASS")
bw <- birthwt
bw$race_f <- factor(bw$race, levels = 1:3, labels = c("White", "Black", "Other"))
sp <- split(bw$bwt, bw$race_f)
# 1. 長條圖加 SE。放在這裡是為了對照,不是建議。
m <- sapply(sp, mean)
se <- sapply(sp, function(x) sd(x) / sqrt(length(x)))
mids <- barplot(m, ylim = c(0, 3600), ylab = "Birth weight (g)")
arrows(mids, m, mids, m + se, angle = 90, code = 2, length = 0.05)
# 2. 盒鬚圖。一行。
boxplot(bwt ~ race_f, data = bw, ylab = "Birth weight (g)")
# 3. 小提琴圖:density() + polygon(),不需要額外套件。
violin <- function(x, at, width = 0.35, col = "#dfe6ee") {
d <- density(x, adjust = 1.2)
k <- d$x >= min(x) & d$x <= max(x) # 不要畫到資料範圍以外
dy <- d$y[k] / max(d$y) * width
polygon(c(at + dy, rev(at - dy)), c(d$x[k], rev(d$x[k])), col = col)
}
plot(NA, xlim = c(0.5, 3.5), ylim = range(bw$bwt), xaxt = "n",
xlab = "", ylab = "Birth weight (g)")
axis(1, at = 1:3, labels = levels(bw$race_f))
for (i in 1:3) violin(sp[[i]], i)
# 4. raincloud:半個小提琴 + 窄箱 + 每一個觀測值。
plot(NA, xlim = c(0.5, 3.5), ylim = range(bw$bwt), xaxt = "n",
xlab = "", ylab = "Birth weight (g)")
axis(1, at = 1:3, labels = levels(bw$race_f))
set.seed(1) # 固定 seed,否則每次重畫點位都不同
for (i in 1:3) {
x <- sp[[i]]
d <- density(x, adjust = 1.2)
k <- d$x >= min(x) & d$x <= max(x)
dy <- d$y[k] / max(d$y) * 0.34
polygon(c(rep(i, sum(k)), rev(i + dy)), c(d$x[k], rev(d$x[k])),
col = "#dfe6ee", border = "#4d6a8c")
boxplot(x, at = i - 0.02, add = TRUE, boxwex = 0.09,
axes = FALSE, outline = FALSE)
points(jitter(rep(i - 0.22, length(x)), amount = 0.09), x,
pch = 16, cex = 0.6, col = adjustcolor("#4d6a8c", 0.4))
text(i - 0.22, min(bw$bwt), paste0("n = ", length(x)), cex = 0.8)
}驗證環境:R 4.6.0 + MASS 7.3.65。四種圖全部只用 base R,不需要安裝任何東西——這一頁的立場是「畫得比長條圖好」根本不需要新工具。抖動點一定要固定 seed,否則同一份資料每次重畫的點位都不同。
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.api as sm
bw = sm.datasets.get_rdataset("birthwt", "MASS").data
bw["race_f"] = bw["race"].map({1: "White", 2: "Black", 3: "Other"})
# 盒鬚圖與小提琴圖各一行
sns.boxplot(data=bw, x="race_f", y="bwt")
sns.violinplot(data=bw, x="race_f", y="bwt", inner=None, cut=0)
# raincloud 要自己疊三層:半個小提琴、窄箱、抖動點
fig, ax = plt.subplots(figsize=(6, 4))
sns.violinplot(data=bw, x="race_f", y="bwt", inner=None, cut=0,
split=True, hue=1, legend=False, ax=ax)
sns.boxplot(data=bw, x="race_f", y="bwt", width=0.08, showfliers=False,
boxprops={"zorder": 3}, ax=ax)
sns.stripplot(data=bw, x="race_f", y="bwt", size=3, alpha=0.45,
jitter=0.18, dodge=False, ax=ax)
ax.set_ylabel("Birth weight (g)")
# 各組樣本數要自己標,seaborn 不會加
for i, (lab, grp) in enumerate(bw.groupby("race_f", sort=False)):
ax.text(i, bw["bwt"].min(), f"n = {len(grp)}", ha="center", fontsize=8)matplotlib 與 seaborn 都有 boxplot 與 violinplot,但沒有現成的 raincloud 函式;下面的做法是把 violinplot、boxplot、stripplot 三層疊起來自己組。
期刊已經開始要求畫出個別資料點
這件事不是風格偏好,已經進了投稿規範。
Weissgerber 等人在 PLOS Biology(2015)調查了頂尖生理學期刊的論文,發現絕大多數連續變項都畫成長條圖加誤差線, 而且相當多的樣本數小到那兩個摘要量幾乎沒有意義。他們的核心論點就是本頁第五節那張圖: 同一張長條圖對應到很多份長相完全不同的資料。
之後的變化:
- PLOS Biology 的投稿規範建議連續資料要用讓讀者能評估分布的畫法(散點、盒鬚、直方), 並且規定樣本數小的資料(每組 9 個以下的獨立觀測值)必須畫出完整分布,不能只畫摘要量。
- eLife 要求圖說寫明樣本數與誤差線的定義(連同統計方法與重複次數), 也就是把上一節那個「圖說沒寫就不能解讀」的問題直接寫進規範。
- 越來越多期刊在圖表規範裡不鼓勵用長條圖表達連續變項。
實務上的判準很簡單:
- 每組不到二十個觀測值 → 直接把每一個點畫出來(點圖或 raincloud),不要摘要。
- 每組數十到數百 → 盒鬚圖或小提琴圖,能疊上抽樣後的點更好。
- 每組上千 → 點會糊成一片,用小提琴圖或直方圖,並且把樣本數寫在圖上。
- 任何情況 → 圖說要寫明誤差線是什麼、樣本數是多少。
那長條圖什麼時候是對的
長條圖不是壞圖,是被用錯了地方。它的本業是計數與比例:類別變項各層有幾個人、幾成, 柱長從 0 起算而且真的代表「有多少」。這種時候柱長是可加的、可比的,長條圖是正確的選擇—— 細節見 卡方檢定與比例的比較。
至於連續變項,即使你只想報摘要量,也還有兩個選擇比長條圖好:
- 報平均數與其信賴區間的點區間圖(不畫柱子)。柱子本身沒有帶任何資訊,卻佔掉整個縱軸空間。
- 偏態變項改報中位數與四分位距。 該報平均數還是中位數,判準在 Table 1 與標準化平均差那一頁「平均數還是中位數」那一節。
常見誤用
| 誤用 | 為什麼錯 |
|---|---|
| 連續變項畫成長條圖加誤差線 | 每組只剩兩個數字,樣本數、形狀、離群值、個別觀測值全部消失 |
| 圖說只寫「error bars」不寫是哪一種 | SD、SE、95% CI 在圖上長得一樣,長度差好幾倍,讀者無法解讀 |
| 用 SE 當誤差線讓組間看起來分開 | SE 是三者中最短的;它描述的是平均數的精確度,不是個體的離散 |
| 把「鬍鬚比較長」讀成「那一組比較不一致」 | SE 的長度主要由樣本數決定,本頁的例子裡方向是反過來的 |
| 用兩組誤差線有沒有重疊代替檢定 | 要看的是差距本身的信賴區間,不是兩個各自區間有沒有碰到 |
| 各組人數差很多卻不標出來 | 讀者無法判斷哪一根柱子是穩定的估計 |
| 小提琴圖有兩個鼓包就宣稱有兩個亞群 | 峰數隨頻寬變動,本頁三組在放寬頻寬後全部只剩一個峰 |
| 看到平均數差得多就說個別病人差得多 | 本頁的例子裡隨機各抽一人的優越機率只有 0.654 |
| 只因為誤差線重疊就說「兩組沒有差異」 | 只能說本圖未顯示分離;要主張相當需要非劣性設計與事先設定的界限 |
| 類別變項的次數也改畫小提琴圖 | 計數與比例本來就該用長條圖,別把本頁的結論套過頭 |
重跑本頁的所有數字
/opt/homebrew/bin/Rscript figures/scripts/B1-05-distribution-plots.R讀讀看這張圖
答案取自產生本頁圖表的同一份統計輸出,不是另外打上去的。
dynamite plot 上,人數最少的那一組(26 人)的誤差線是三根裡最長的。這代表那一組的嬰兒體重比較不一致嗎?
看答案與解析
正確答案: 不是。那一組的標準差 638.7 公克其實是三組裡最小的——標準誤等於標準差除以人數的平方根
三組的標準差幾乎一樣,最大與最小差不到兩成,可是三根標準誤誤差線的長度差了 1.7 倍。最乾淨的例子就是這一組:標準差 638.7 公克是三組裡最小的,標準誤卻是三組裡最長的,因為它只有 26 個人。所以「哪一組的鬍鬚比較長」這個視覺印象,講的不是那一組的人比較不一致,而是那一組的人比較少——這是一個方向會反過來的錯覺,不是程度上的失真。258.0 是同一組的信賴區間半長,大約是標準誤的兩倍,換一種誤差線並不會改變上面那件事:三種誤差線在圖上長得一模一樣,只有圖說能區分。
白人組與黑人組的平均出生體重差 383 公克,在長條圖上是肉眼可見的兩根不同高度的柱子。把這個組間差距放到一個病人身上,有多大?
看答案與解析
正確答案: 從兩組各隨機抽一個嬰兒,前一組那個比較重的機率是 0.654——比丟硬幣好一些
機率優越性 0.654 跟原始單位無關,而且每個讀者都懂一半是什麼意思:它回答的正是「這個組間差距放到單一個病人身上有多大」,答案是比丟硬幣好一些。0.302 確實是前一組落在後一組四分位距裡的比例,但這個比例低不代表兩組不重疊——四分位距只涵蓋中間一半的人,落在它外面是常態,而兩組的數值範圍其實從一千一百多公克一路交集到三千八百多。0.462 是反方向的同一種比例,它描述的是重疊,不是差距的大小。組間平均數差得看起來很多、個別的人卻重疊得很厲害,正是長條圖最會誤導的地方;而這個量不是檢定,也不取代 ANOVA 與事後比較做的推論,它回答的是另一個問題。
同一組資料的三種誤差線半長分別是 727.9、74.3 與 147.5 公克,而圖說只寫了 error bars 四個字。這張圖能不能解讀?
看答案與解析
正確答案: 不能。標準差的棒子是標準誤棒子的 9.8 倍長,換一種定義讀者看到的圖就差了一個數量級
三種誤差線在圖上長得一模一樣,只有圖說能區分,而它們的長度差了將近一個數量級:標準差的棒子是標準誤棒子的 9.8 倍。所以圖說沒寫明是哪一種時,這一格資料就當作沒有讀到——不是「解讀得不夠精確」,是不能解讀。「看相對長度推回去」在只有一張圖的時候做不到,讀者手上沒有另外兩根可以比;信賴區間確實大約是標準誤的 2.0 倍,但那是在你已經知道兩者都畫出來的前提下。至於 74.3 是三者中最短的,而最短的那一種會讓組間看起來最分開,這也是它最常被選的原因之一。另外,誤差線有沒有重疊不能當成檢定的替代品:要判斷組間差距,看的是差距本身的信賴區間,不是兩個各自的區間有沒有碰到。
三份刻意構造的資料,樣本數、平均數與標準差都相同,所以它們的長條圖是同一張圖畫三次,柱子的高度都是 3000 公克。哪一份的柱子高度落在一個沒有任何觀測值靠近的位置?
看答案與解析
正確答案: 密度有 2 個峰的那一份——兩群人各自聚在柱子的左右兩側,中間那一段是空的,平均數就落在那段空隙裡
三份的樣本數、平均數、標準差、標準誤完全相同,長條圖畫出來是同一張圖,差別全在圖上看不到的地方。四個極端值那一份的中位數 2832 公克確實被拉離了 3000,但「平均數不等於中位數」不等於「平均數附近沒有人」——那一份的觀測值連成一片,離 3000 最近的那一個幾乎就站在柱子的高度上,它失真的方式是柱子代表不了典型的病人,不是柱子落在空地上。對稱那一份更直接,對稱不是平坦,它是單峰的,密度最高的位置正好就是柱子的高度,三份裡只有它的長條圖沒有說謊。真正落在空地上的是有兩個峰的那一份——兩群人各自聚在兩側,中間空了五百多公克,平均數就落在那段沒有人的空隙裡。遇到這種形狀該做的不是換一種圖,是回頭問這一組是不是其實不是一組(有反應與沒反應、兩種亞型、兩個中心的收案差異),圖只是讓你有機會問這個問題。
這份資料三組的密度曲線在預設頻寬下最多有 3 個局部極大值,看起來像有次族群;把頻寬放寬一半之後,三組全部只剩 1 個。該怎麼下結論?
看答案與解析
正確答案: 多出來的峰是平滑參數的產物。放寬之後只剩 1 個,而「密度有幾個峰」是你選的頻寬的性質,不是資料的性質
看到論文的小提琴圖有兩個鼓包,下結論之前要問的是作者用了什麼頻寬——而多數論文不會寫。這份資料就是例子:預設頻寬下最多的一組有三個局部極大值,放寬一半之後三組全部只剩一個。預設值是一個經驗公式,不是資料的結論,所以不能拿它當作次族群存在的證據;反過來說,放寬頻寬也不是在抹掉訊號,是在問這個峰撐不撐得住。被四分位距規則標成離群值的個數說明這份資料相當乾淨,但那件事跟峰數的判讀無關。這也是 raincloud 把個別點畫出來的另一個理由:點畫出來之後,讀者不必相信任何平滑參數。
審稿人說「多收一百個人,誤差線就會變短,圖看起來會更有說服力」。這句話對哪一種誤差線成立?
看答案與解析
正確答案: 只對標準誤與信賴區間成立。標準誤 74.3 公克等於標準差除以人數的平方根,人愈多它愈短
標準差描述的是個體之間有多分散,它是這群人的性質:多收一百個人,估計會更穩,但數值大致不變,這一組是 727.9 公克。標準誤描述的則是平均數這個估計有多精確,等於標準差除以人數的平方根,所以人愈多它愈短,這一組是 74.3 公克,而信賴區間大約是它的兩倍,會跟著一起縮。3.7 是最大組與最小組的人數比,它確實解釋了三根標準誤誤差線為什麼長度不同,但它不會讓標準差跟著變——三組的標準差幾乎一樣。至於「圖看起來更有說服力」這句話本身就值得警覺:讓組間看起來最分開的那一種誤差線,正好是最短的那一種。
用到這個方法的章節
素材來源與授權
- Beyond Bar and Line Graphs: Time for a New Data Presentation ParadigmCC BY「長條圖只畫兩個數字、同一張長條圖對應到很多份不同的資料」這個論點,以及期刊該要求畫出個別觀測值的主張,出自 Weissgerber 等人(PLOS Biology 2015)。本頁所有數字都是本站在 MASS::birthwt 上自行計算的。
- Raincloud plots: a multi-platform tool for robust data visualizationCC BYraincloud(半個小提琴 + 箱 + 個別點)這個組合與名稱出自 Allen 等人(Wellcome Open Research 2019)。本頁的實作是用 base R 的 density() 與 polygon() 重寫的,沒有使用該文的程式碼。