Folioevanzhou.org
← 返回 Tessera
R5 张图2026-08-11

grouped_scatter

One point per observation across two continuous variables, with an optional group carried by colour, shape, or both.

示例数据
iris
配色
walter_white2
语言
R
成图预览另有 4 张在配方里
grouped_scatter

Introduction

散点图是两个连续变量最直接的入口:一行数据一个点,横轴一个变量,纵轴另一个。先看点云有没有方向、弯曲、分团、离群和重叠,再决定要不要相关系数或拟合模型。

先画干净的散点,别一上来就加拟合线——线会替读者先把点云总结掉,弯曲、分团和离群反而容易被略过。要模型时再去 scatter_fit。

分组也不是散点图的必需部分。没有真实分组就画普通散点;有分组时,再从形状、颜色或两者共同编码里挑一种。

读的时候按这个顺序:

  1. 先看整体形状。 点云是上升、下降、弯曲,还是没有明显方向。
  2. 看组内聚集。 同一种颜色是否聚在相近区域,组间是分开还是重叠。
  3. 再看离散程度。 同一组内部的点散得多宽,有没有次级结构。
  4. 找离群点。 尤其是远离本组主体、可能改变后续相关或回归结果的那些。

Example Data

散点图要的输入是:2 个连续变量,同一批观测,一行一个观测;可选再加 1 个分组变量。两个变量必须来自同一行,否则连成对都谈不上。

iris 是 150 朵花分属三个物种。这里用花瓣长和花瓣宽——三个物种会形成清楚但并非完全无重叠的点群,正适合演示分组。

library(ggplot2)
library(ggpubr)
library(biopalette)

# 公开地址,和数据集页上「下载 CSV」给的是同一个 —— 不写仓库相对路径:
# 那个目录不进仓库,读者 clone 下来也没有这个文件,这段代码就跑不了
d <- read.csv("https://assets.evanzhou.org/tessera/csv/iris.csv")

# 分组变量必须是 factor。写成 1/2/3 那种数值编码时也要先转 —— 否则会得到
# 一根连续色条,图能画出来,只是图例把三个组说成了一个连续量
d$Species <- factor(
  d$Species,
  levels = c("setosa", "versicolor", "virginica")
)

Palettes

三个物种是三个无序类别,用定性色板 walter_white2 里区分较清楚的青、橄榄和橙三色。只有三个真实分组就只取三色,不为了展示完整五色增加虚假类别。

浅色点在白底上容易消失,所以颜色版统一用带深色细边的圆点。边线只负责保持轮廓,不编码第二个变量。

形状是另一套通道,但它只在和颜色映射同一个变量时才该出现。颜色和形状分别映射两个不同分组,读者要同时解两套图例,信息密度很快就失控。

walter_white2 <- get_palette("walter_white2", type = "qualitative")
species_colors <- setNames(
  walter_white2[c(1, 2, 4)],
  levels(d$Species)
)

# 21/22/24 是带边框的圆、方、三角:fill 上色、color 画边,两者能分开控制
species_shapes <- setNames(c(21, 22, 24), levels(d$Species))

# 五张图共用同一套坐标范围和版式,轴一致才横着比得了
scatter_scales <- list(
  scale_x_continuous(
    breaks = 1:7,
    limits = c(0.9, 7.1),
    expand = expansion(mult = c(0, 0))
  ),
  scale_y_continuous(
    breaks = seq(0, 2.5, 0.5),
    limits = c(-0.05, 2.55),
    expand = expansion(mult = c(0, 0))
  )
)

scatter_theme <- theme_pubr(base_size = 13, legend = "right") +
  theme(
    panel.grid.major = element_line(color = "#E5E3DC", linewidth = 0.35),
    panel.grid.minor = element_blank(),
    axis.line = element_line(color = "#333330", linewidth = 0.45),
    axis.ticks = element_line(color = "#333330", linewidth = 0.4),
    plot.title = element_text(face = "bold", size = 15, hjust = 0),
    legend.title = element_text(face = "bold"),
    legend.key.height = grid::unit(5, "mm"),
    strip.background = element_blank(),
    strip.text = element_text(face = "bold"),
    plot.margin = margin(14, 16, 12, 12)
  )

Recipe

No. Method Input Data Palettes
1 ggplot2 d —
2 ggplot2 d species_shapes
3 ggplot2 d species_colors
4 ggplot2 d species_colors / species_shapes
5 ggplot2 size_demo species_colors

1 · 普通散点

没有分组要表达时,只画点。不要为了"丰富"画面主动加颜色或形状。

#| fig: basic
#| fig-width: 7.4
#| fig-height: 5.1
ggplot(d, aes(Petal.Length, Petal.Width)) +
  # alpha 不是装饰:点一重叠,只有半透明才看得出那里叠了几个
  geom_point(size = 2.7, alpha = 0.72, color = "#333330") +
  scatter_scales +
  labs(
    title = "Iris petal measurements",
    x = "Petal length (cm)",
    y = "Petal width (cm)"
  ) +
  scatter_theme
grouped_scatter — basic
grouped_scatter-basic

2 · 用形状分组

形状适合黑白输出,也适合颜色不能承担信息的场景。三个物种的点云聚集位置不同,所以即使没有颜色也读得出分组结构。

#| fig: shape
#| fig-width: 7.4
#| fig-height: 5.1
ggplot(d, aes(Petal.Length, Petal.Width, shape = Species)) +
  # 只用边框、不填充,三种形状在同一深灰下靠轮廓区分
  geom_point(size = 3.1, alpha = 0.72, color = "#333330", stroke = 0.7) +
  scale_shape_manual(values = species_shapes) +
  scatter_scales +
  labs(
    title = "Grouped by shape",
    x = "Petal length (cm)",
    y = "Petal width (cm)",
    shape = "Species"
  ) +
  scatter_theme
grouped_scatter — shape
grouped_scatter-shape

3 · 用颜色分组

彩色输出下最直接的分组方案。所有点保持同一种圆形,读者只需要解一套图例。

#| fig: color
#| fig-width: 7.4
#| fig-height: 5.1
ggplot(d, aes(Petal.Length, Petal.Width, fill = Species)) +
  geom_point(
    shape = 21,          # 带边框的圆:fill 给分组色,color 给统一深灰边
    size = 3.2,
    alpha = 0.82,
    color = "#333330",
    stroke = 0.55
  ) +
  scale_fill_manual(values = species_colors) +
  scatter_scales +
  labs(
    title = "Grouped by color",
    x = "Petal length (cm)",
    y = "Petal width (cm)",
    fill = "Species"
  ) +
  scatter_theme
grouped_scatter — color
grouped_scatter-color

4 · 形状与颜色共同分组

两种通道映射的是同一个变量。这没有增加任何新信息,只是让分组在色觉差异、灰度打印或投影偏色时仍然可辨。

屏幕展示优先用 recipe 3;确实需要冗余编码时才用这一版。

#| fig: color-shape
#| fig-width: 7.4
#| fig-height: 5.1
# fill 和 shape 都指向 Species —— 两个通道说同一件事,图例才合得成一个
ggplot(d, aes(Petal.Length, Petal.Width, fill = Species, shape = Species)) +
  geom_point(
    size = 3.2,
    alpha = 0.82,
    color = "#333330",
    stroke = 0.6
  ) +
  scale_fill_manual(values = species_colors) +
  scale_shape_manual(values = species_shapes) +
  scatter_scales +
  labs(
    title = "Color + shape",
    x = "Petal length (cm)",
    y = "Petal width (cm)",
    fill = "Species",
    shape = "Species"
  ) +
  scatter_theme
grouped_scatter — color-shape
grouped_scatter-color-shape

5 · 调整点大小

size 控制的是点的直径感,不是装饰。下面复制同一份数据做三个分面,只改 geom_point() 的大小,颜色、透明度和坐标完全相同。

size = 3 是这份数据和当前画布的平衡点,不是通用默认值。样本量、重叠程度、输出尺寸和导出后的缩放比例一变,都得重新判断。

#| fig: point-size
#| fig-width: 11
#| fig-height: 4.2
size_settings <- data.frame(
  size_label = factor(
    c("size = 1.5", "size = 3", "size = 5"),
    levels = c("size = 1.5", "size = 3", "size = 5")
  ),
  point_size = c(1.5, 3, 5)
)

# 无键 merge = 笛卡尔积:同一份数据复制三遍,各带一个尺寸
size_demo <- merge(d, size_settings)

ggplot(
  size_demo,
  aes(Petal.Length, Petal.Width, fill = Species, size = point_size)
) +
  geom_point(shape = 21, alpha = 0.78, color = "#333330", stroke = 0.5) +
  facet_wrap(~size_label, nrow = 1) +
  scale_fill_manual(values = species_colors) +
  scale_size_identity() +   # point_size 存的就是毫米数,不要再映射一次
  scatter_scales +
  guides(fill = "none") +   # 三个面板重复同一套颜色,图例只会占版面
  labs(
    title = "Point size changes overlap and visual weight",
    x = "Petal length (cm)",
    y = "Petal width (cm)"
  ) +
  scatter_theme +
  theme(plot.title = element_text(size = 14))
grouped_scatter — point-size
grouped_scatter-point-size

Constraints

  • 点的大小没有通用默认值。 太大或不透明,重叠的观测会被完全遮住;太小,导出后缩进论文双栏或幻灯片就直接消失。按最终版面判断,不按预览窗口。
  • 重叠到一定程度它就不再是散点图了。 先减小点再降 alpha;点多到成片时该换 hexbin 或二维密度,而不是无限缩小点。
  • 形状能区分的组数有限。 人能稳定辨认的点形就那么几种,超过五六组时颜色更清楚。

五个 recipe 怎么比

  1. 没有分组 → recipe 1。
  2. 黑白输出,或颜色不能承担信息 → recipe 2。
  3. 常规彩色图 → recipe 3。
  4. 需要无障碍或灰度兼容 → recipe 4。别把颜色和形状分别映射到两个不同变量。
  5. 最后按点数和最终版面调 size → recipe 5。别把预览窗口里的默认值当成品参数。
运行环境4 个包 · 2026-08-11

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

  • biopalette 0.1.0
  • ggplot2 4.0.3
  • ggpubr 0.6.2
  • grid 4.5.1