Folioevanzhou.org
← 返回 Tessera
R2 张图2026-04-13

scatter_errorbar

One point per study carrying error bars in both directions, with the study of interest picked out of the cloud.

示例数据
mtcars
配色
bcell_atlas
语言
R
成图预览另有 1 张在配方里
scatter_errorbar

Introduction

普通散点图的每个点是一次观测,位置就是它的值。这张图的每个点是一组观测的汇总估计,两个方向各有一条误差棒——因为 x 和 y 都是估出来的,都带不确定性。

它最典型的用途是论文里那张"本研究 vs 已有文献"的图:把自己的结果和别人的画在同一个平面上,让读者自己判断这个结果算不算离谱。

读的时候按这个顺序:

  1. 看点的位置。 两个方向的中心估计。
  2. 看两条棒的长度。 横棒是 x 的不确定性,竖棒是 y 的。它们不一定同量级——常见的情况是一个方向很准、另一个方向很虚。
  3. 看整体有没有趋势。 点云沿一条线排开就提示两个量有关联,但到此为止,别顺手拟合。
  4. 看目标点落在哪。 在文献点云中间说明一致,在边缘就要解释为什么。
  5. 看棒有没有重叠。 重叠多说明各研究之间本来就没分开,"我的结果和别人不同"这句话就说不出口。

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)
  )
scatter_errorbar — basic
scatter_errorbar-basic

2 · 突出目标点

论文配图基本都是这一版:图的目的就是"把我放进背景里",不突出目标点等于没画。

#| fig: highlight
#| fig-width: 8
#| fig-height: 6
ggplot(plot_data, aes(x = x_mean, y = y_mean)) +
  geom_segment(
    aes(x = x_min, xend = x_max, y = y_mean, yend = y_mean, color = label),
    alpha = 0.6, linewidth = 0.5, na.rm = TRUE
  ) +
  geom_errorbar(
    aes(ymin = y_min, ymax = y_max, color = label),
    width = 0, alpha = 0.6, linewidth = 0.5, na.rm = TRUE
  ) +
  geom_point(aes(color = label, shape = source), size = 3, alpha = 0.95) +
  scale_color_manual(values = group_cols, breaks = group_levels,
                     name = "Vehicle group") +
  # 17 是实心三角、16 是实心圆 —— 黑白打印时靠形状也能认出目标点
  scale_shape_manual(values = c("Target group" = 17, "Other groups" = 16),
                     name = NULL) +
  # 只标目标点。其它组已经共同构成背景,再各贴一个名字会削弱重点
  # (颜色一次、图例一次、标签一次),而且十个同样大小的黑标签会把"这张图到底
  # 在说哪个点"整个淹掉 —— 强调靠对比,不靠标注得够全。
  geom_text_repel(
    data = filter(plot_data, source == "Target group"),
    aes(label = label),
    color = group_cols[["6 cylinders - Manual"]], fontface = "bold", size = 4,
    box.padding = 0.8, min.segment.length = 0
  ) +
  labs(x = "Mean weight (1000 lbs)", y = "Mean miles per gallon",
       title = "Six-cylinder Manual Cars in Context") +
  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)
  )
scatter_errorbar — highlight
scatter_errorbar-highlight

Constraints

  • 两条棒的含义必须统一。 从不同文献抄来的可能有的是 95% CI、有的是 SD,混在一张图上不能比,而图上完全看不出哪根是哪种。抄的时候就统一,统一不了就在图注里说明。
  • 别拿带误差棒的点做回归。 普通最小二乘假设 x 是准确测量的,而这里每个 x 都是估出来的;要拟合得换考虑测量误差的模型。
  • 点多了会织成一张网。 超过二三十项研究,误差棒互相穿插到读不出来,那时该换森林图——一维排列,一项一行。
  • 横着比只能提示一致性,不是因果证据。 各研究的人群、方法、时期都不同。

两个 recipe 怎么比

  1. 只想看分布形态 → recipe 1。一个颜色就够,图干净,趋势也看得更清楚。
  2. 论文配图 → recipe 2。
  3. 研究项数多的时候,recipe 2 的图例会占掉三分之一版面。 那种情况只给目标点上色、其余全灰——背景就该长得像背景。
运行环境4 个包 · 2026-09-02T12:53:13.300+0800

R version 4.5.1 (2025-06-13 ucrt) · x86_64-w64-mingw32

  • biopalette 0.2.2
  • dplyr 1.2.1
  • ggplot2 4.0.3
  • ggrepel 0.9.6