← 返回 Tessera
误差棒2 张图模拟 9 项文献 + 1 项本研究2026-04-13

scatter_errorbar

我这一项结果,放在已有文献的估计里是什么位置?

需要的输入
每行一项研究:x 的估计和上下限 + y 的估计和上下限
示例数据
模拟 9 项文献 + 1 项本研究
依赖
ggplot2 · ggrepel · scales · dplyr
配色
未命名#FF0000 #1F77B4 #FF7F0E #2CA02C #9467BD
成图预览另有 1 张在配方里
scatter_errorbar

什么时候用它

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

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

怎么读

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

常见陷阱

  • 上下限被录反了。 从文献里抄数据时很常见,而且画出来不会报错,只会得到一条方向诡异的棒。用 pmin() / pmax() 兜底。
  • 误差棒含义混着来。 从不同文献抄来的可能有的是 95% CI、有的是 SD——混在一张图上不能比。抄的时候就要统一,统一不了就在图注里说明。
  • 给误差棒加帽子。 二维误差棒里 width = 0(不要帽子)更好:两个方向都有帽子的话,一个点周围会出现四个小横线,像一个十字架,很吵。
  • 点太多就没法看。 超过二三十项研究,误差棒会织成一张网。那时该换森林图(一维排列)。
  • 标签只给要强调的那个点。 每个点都贴名字的话,图例里已有的信息会被再写一遍,而真正要强调的那个点淹没在一堆同样大小的黑字里——强调靠的是对比,不是靠标注得够全
  • 别把它当因果证据。 各研究的人群、方法、时期都不同,横着比只能提示一致性。

配色

目标点用纯红 #FF0000,其余文献点用一套普通定性色。这是刻意的:红色在这一套里是唯一的"信号色",它不参与"区分不同地区"这件事,只承担一个职责——把读者的眼睛拉过去。

同时还映射了 shape(目标点三角、文献点圆)。这里用两个通道编码同一件事是值得的:图会被黑白打印,而且这一处的强调不能失效。这和上面 point_errorbar 那页"别重复编码"的建议不矛盾——那里重复的是已经由位置表达过的顺序,这里重复的是唯一一处需要冗余的强调。

配方

方法绘图系统什么时候选它
Ageom_segment() + geom_errorbar()ggplot2只想看整体分布,不需要指出某一个点
B+ 突出目标点ggplot2论文配图——把"本研究"放进文献背景里,这是它最常见的用途

数据准备

library(dplyr)
library(ggplot2)
library(ggrepel)
library(scales)

set.seed(20260910)

reference_data <- tibble(
  label  = paste("Reference", LETTERS[1:9]),
  group  = paste("Region", LETTERS[1:9]),
  source = "Literature",
  x_mean  = round(runif(9, 150, 820)),
  x_error = round(runif(9, 35, 180)),
  y_mean  = round(0.004 + x_mean * 0.000035 + rnorm(9, 0, 0.006), 4),
  y_error = round(runif(9, 0.002, 0.010), 4)
) |>
  mutate(
    y_mean  = pmax(y_mean, 0.003),
    x_lower = pmax(x_mean - x_error, 0),   # 截到 0:这些量不可能是负的
    x_upper = x_mean + x_error,
    y_lower = pmax(y_mean - y_error, 0),
    y_upper = y_mean + y_error
  )

target_data <- tibble(
  label = "This study", group = "Target", source = "This Study",
  x_mean = 380, x_lower = 340, x_upper = 420,
  y_mean = 0.0034, y_lower = 0.0028, y_upper = 0.0040
)

plot_data <- bind_rows(target_data, reference_data) |>
  mutate(
    # 兜底:从文献里抄数据时 lower/upper 录反是常事,而且画出来不报错
    x_min = pmin(x_lower, x_upper, na.rm = TRUE),
    x_max = pmax(x_lower, x_upper, na.rm = TRUE),
    y_min = pmin(y_lower, y_upper, na.rm = TRUE),
    y_max = pmax(y_lower, y_upper, na.rm = TRUE),
    source = factor(source, levels = c("This Study", "Literature"))
  )

方法 A · 基础二维误差棒

x 方向没有现成的 geom_errorbarh 那么顺手,用 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 = "#1f77b4") +   # 点最后画,压在两条棒上面
  labs(x = "Input measure", y = "Rate measure",
       title = "Scatter Plot with X and Y Error 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

方法 B · 突出目标点

#| fig: highlight
#| fig-width: 8
#| fig-height: 6
region_levels <- c(
  "This Study",
  sort(unique(plot_data$group[plot_data$source != "This Study"]))
)

region_colors <- c(
  "This Study" = "#FF0000",   # 唯一的信号色,不参与区分地区
  setNames(
    c("#1f77b4", "#ff7f0e", "#2ca02c", "#9467bd", "#8c564b",
      "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"),
    region_levels[region_levels != "This Study"]
  )
)

plot_data <- plot_data |>
  mutate(
    legend_group = if_else(source == "This Study", "This Study", group),
    # levels 顺序决定图例顺序 —— 把 This Study 放第一位
    legend_group = factor(legend_group, levels = region_levels)
  )

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 = legend_group),
    alpha = 0.6, linewidth = 0.5, na.rm = TRUE
  ) +
  geom_errorbar(
    aes(ymin = y_min, ymax = y_max, color = legend_group),
    width = 0, alpha = 0.6, linewidth = 0.5, na.rm = TRUE
  ) +
  geom_point(aes(color = legend_group, shape = source), size = 3, alpha = 0.95) +
  scale_color_manual(values = region_colors, breaks = region_levels, name = "Region") +
  # 17 是实心三角、16 是实心圆 —— 黑白打印时靠形状也能认出目标点
  scale_shape_manual(values = c("This Study" = 17, "Literature" = 16),
                     name = "Study Type") +
  scale_x_continuous(breaks = seq(0, 1000, by = 100), labels = label_number()) +
  # 只标目标点。九个文献点的身份图例里已经有了,再各贴一个名字就是第三次重复
  # (颜色一次、图例一次、标签一次),而且十个同样大小的黑标签会把"这张图到底
  # 在说哪个点"整个淹掉 —— 红色加粗只给这一个。
  geom_text_repel(
    data = filter(plot_data, source == "This Study"),
    aes(label = label),
    color = "#FF0000", fontface = "bold", size = 4,
    box.padding = 0.8, min.segment.length = 0
  ) +
  labs(x = "Input measure", y = "Rate measure",
       title = "This Study in the Context of Published Estimates") +
  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

两种方法怎么选

  1. 只是想看分布形态用 A。 一个颜色就够,图干净,趋势也看得更清楚。
  2. 论文配图基本都是 B。 那张图的目的就是"把我放进背景里",不突出目标点等于没画。
  3. B 的图例会很长。 九个地区 + 两种研究类型,图例可能占掉三分之一版面。研究项数多的时候,可以只给目标点上色、其余全灰——背景就该长得像背景
运行环境4 个包 · 2026-08-04

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

  • dplyr 1.2.1
  • ggplot2 4.0.3
  • ggrepel 0.9.6
  • scales 1.4.0