Introduction
点加误差棒展示每组的汇总统计:一个点表示均值,一根棒表示预先定义好的误差范围。手上只有均值和标准差时,它仍然能画;手上有原始观测时,也可以先按组汇总成同样的输入表。
它也常被拿来替代柱状图。替代是对的:柱子从 0 画起,会让人不自觉地按面积比较,而均值之间的差异跟"从零到均值那一段"没有关系。点估计就是一个点,误差棒就是范围,没有多余的墨水暗示别的东西。
读的时候按这个顺序:
- 先确认误差棒是什么。
±SD、±SE、95% CI是三件完全不同的事——图注里没写清楚,这张图就没法读。 - 看点的位置。 那是点估计。
- 看棒的长度。 短说明估计稳、组内齐,长说明不稳、离散大——取决于第 1 条是哪一种。
- 看重不重叠。 误差棒重叠通常意味着差异不明确,但不重叠也不等于显著。
- 看 y 轴从哪开始。 点图不必从 0 起,所以要留意轴的范围有没有把差异放大。
Example Data
这张图层最终要的输入是:已经汇总好的表——分类变量 + 点估计 + 误差范围,一行一个组。原始观测需要先汇总;而一旦汇总,分布形状和单个观测就不会出现在图里。
这里直接从 mtcars 衍生汇总表:按 4、6、8 缸分组,分别计算燃油效率 mpg 和车重 wt 的均值与 SD。两个指标的量纲差着一个数量级——这正是 recipe 2 要解决的问题。
library(dplyr)
library(ggplot2)
library(ggpubr)
library(biopalette)
# 公开地址,和数据集页上「下载 CSV」给的是同一个
cars <- read.csv("https://assets.evanzhou.org/tessera/csv/mtcars.csv")
summary_data <- cars |>
transmute(
cylinders = factor(cyl, levels = c(4, 6, 8), labels = c("4 cylinders", "6 cylinders", "8 cylinders")),
`Fuel economy (mpg)` = mpg,
`Weight (1000 lb)` = wt
) |>
tidyr::pivot_longer(
cols = -cylinders,
names_to = "metric",
values_to = "value"
) |>
summarise(
mean = mean(value),
sd = sd(value),
.by = c(metric, cylinders)
) |>
mutate(metric = factor(metric, levels = c("Fuel economy (mpg)", "Weight (1000 lb)")))
# 误差范围先算好,画的时候直接用 —— 也方便在这里一眼确认用的是 SD 不是 SE
plot_data <- summary_data |>
mutate(ymin = mean - sd, ymax = mean + sd)
Palettes
三个气缸组是有序的分类,这里从站内定性色板 bcell_atlas 选取橙、紫、绿三色。跳过与橙色过近的粉色,让细误差棒之间仍有足够区分;气缸数顺序则继续由 x 轴从左到右表达。
黑白打印的场合可以再映射一个 shape。但只在颜色真的不够用时加,平时多一个通道就是多一分噪音。
# 按组名绑定,不按位置:分面之后各面板的行序不一定一致
group_levels <- levels(summary_data$cylinders)
cols <- setNames(
get_palette("bcell_atlas", type = "qualitative")[c(1, 4, 3)],
group_levels
)
Recipe
| No. | Method | Input Data | Palettes |
|---|---|---|---|
| 1 | ggplot2 |
plot_data |
cols |
| 2 | ggplot2 |
plot_data |
cols |
1 · 单坐标系
两个指标共用一个 y 轴。同一个坐标系里能直接比高低,这是最强的比较方式。
#| fig: basic
#| fig-width: 6
#| fig-height: 4
ggplot(plot_data, aes(x = cylinders, y = mean, color = cylinders)) +
geom_errorbar(
aes(ymin = ymin, ymax = ymax),
width = 0.2, # 帽子宽度;再大就比数据点还抢眼
linewidth = 1
) +
geom_point(size = 3) + # 点画在误差棒之后,才会压在棒上面
labs(
x = "Cylinders", y = "Value",
color = "Cylinders" # 误差棒含义写在图注;图例只负责识别组别
) +
expand_limits(y = 0) + # 把 0 纳入范围,避免轴范围过窄放大差异
scale_color_manual(values = cols) +
theme_pubr(base_size = 12)
