Introduction
普通散点图的每个点是一次观测,位置就是它的值。这张图的每个点是一组观测的汇总估计,两个方向各有一条误差棒——因为 x 和 y 都是估出来的,都带不确定性。
它最典型的用途是论文里那张"本研究 vs 已有文献"的图:把自己的结果和别人的画在同一个平面上,让读者自己判断这个结果算不算离谱。
读的时候按这个顺序:
- 看点的位置。 两个方向的中心估计。
- 看两条棒的长度。 横棒是 x 的不确定性,竖棒是 y 的。它们不一定同量级——常见的情况是一个方向很准、另一个方向很虚。
- 看整体有没有趋势。 点云沿一条线排开就提示两个量有关联,但到此为止,别顺手拟合。
- 看目标点落在哪。 在文献点云中间说明一致,在边缘就要解释为什么。
- 看棒有没有重叠。 重叠多说明各研究之间本来就没分开,"我的结果和别人不同"这句话就说不出口。
Example Data
这张图最终要的输入是:每行一组,x 的估计和上下限、y 的估计和上下限。可以直接读汇总结果,也可以像下面这样从个体级数据分组计算。
这里从 mtcars 衍生:按气缸数和变速箱类型组成 6 组,横轴是平均车重,纵轴是平均油耗,两个方向都画均值 ± SD。第二张图把 6 缸手动车组作为目标组。
library(dplyr)
library(ggplot2)
library(ggrepel)
library(biopalette)
plot_data <- mtcars |>
mutate(
cylinders = factor(cyl),
transmission = if_else(am == 1, "Manual", "Automatic")
) |>
group_by(cylinders, transmission) |>
summarise(
n = n(),
x_mean = mean(wt), x_sd = sd(wt),
y_mean = mean(mpg), y_sd = sd(mpg),
.groups = "drop"
) |>
mutate(
label = paste(cylinders, "cylinders -", transmission),
source = if_else(cylinders == "6" & transmission == "Manual",
"Target group", "Other groups"),
source = factor(source, levels = c("Target group", "Other groups")),
x_min = x_mean - x_sd, x_max = x_mean + x_sd,
y_min = y_mean - y_sd, y_max = y_mean + y_sd
)
Palettes
两张图都使用 bcell_atlas。基础图取其中一个蓝色;突出图让六个车型组各占一种颜色,使每个点和两条误差棒都能对应到自己的组。色板的珊瑚色和橙色较接近,因此六组只取橙、绿、紫、石板蓝、赭石和蓝灰。
同时还映射了 shape(目标点三角、文献点圆)。这里用两个通道编码同一件事是值得的:图会被黑白打印,而这一处的强调不能失效。这和 point_errorbar 那页"别重复编码"不矛盾——那里重复的是已经由位置表达过的顺序,这里重复的是唯一一处需要冗余的强调。
# 不分组那张图只取一个颜色;分组图则让每个点有自己的颜色
atlas_cols <- get_palette("bcell_atlas", type = "qualitative")
PLAIN <- atlas_cols[[5]]
group_levels <- c(
"6 cylinders - Manual", "4 cylinders - Automatic",
"4 cylinders - Manual", "6 cylinders - Automatic",
"8 cylinders - Automatic", "8 cylinders - Manual"
)
plot_data$label <- factor(plot_data$label, levels = group_levels)
group_cols <- setNames(atlas_cols[c(1, 3, 4, 5, 6, 7)], group_levels)
Recipe
| No. | Method | Input Data | Palettes |
|---|---|---|---|
| 1 | ggplot2 |
plot_data |
PLAIN |
| 2 | ggplot2 + ggrepel |
plot_data |
group_cols |
1 · 基础二维误差棒
只看整体分布,不指出任何一个点。
x 方向没有 geom_errorbar() 那么顺手的东西,用 geom_segment() 画一条水平线更直接。
#| fig: basic
#| fig-width: 7
#| fig-height: 5
ggplot(plot_data, aes(x = x_mean, y = y_mean)) +
# 横棒:从 x_min 拉到 x_max,y 固定在中心估计上
geom_segment(
aes(x = x_min, xend = x_max, y = y_mean, yend = y_mean),
color = "grey50", alpha = 0.7, linewidth = 0.5, na.rm = TRUE
) +
geom_errorbar(
aes(ymin = y_min, ymax = y_max),
width = 0, # 关键:不要帽子。两个方向都带帽会在每个点周围画出一个十字架
color = "grey50", alpha = 0.7, linewidth = 0.5, na.rm = TRUE
) +
geom_point(size = 2.8, color = PLAIN) + # 点最后画,压在两条棒上面
labs(x = "Mean weight (1000 lbs)", y = "Mean miles per gallon",
title = "Vehicle-group Means with X and Y SD Bars") +
theme_bw(base_size = 12) +
theme(
panel.grid.minor = element_blank(),
panel.grid.major = element_line(color = "grey90", linewidth = 0.3),
plot.title = element_text(face = "bold", hjust = 0)
)
