The V Lab
适用对象会运行基础 R 代码的学习者
内容规模15 章 · 20 幅实绘图表
学习方式按顺序学习或按主题查阅

1 阅读指南

这份教程面向已经会运行 R 代码、希望系统掌握 ggplot2 常用方法的读者。所有示例都使用 R 自带数据或 ggplot2 内置数据,不需要下载外部数据;代码块可以复制到 RStudio 中直接运行。

建议按下面的顺序思考一张图:

  1. 数据(data):一行代表什么观察?变量类型是否正确?
  2. 映射(mapping):哪些变量对应 x、y、颜色、填充、大小或分组?
  3. 几何对象(geom):用点、线、柱、箱线还是密度表达?
  4. 统计变换(stat):画原始值,还是计数、分箱、平滑或汇总值?
  5. 位置与坐标(position / coord):堆叠、并列、抖动或缩放?
  6. 标度(scale):颜色、刻度、标签和变换如何呈现?
  7. 分面与主题(facet / theme):如何组织小图并完善版式?

核心心智模型:ggplot(data, aes(…)) + geom_*() + scale_*() + facet_*() + coord_*() + theme_*()。每个 + 都是在图上增加或修改一层。

1.1 本教程使用的数据

数据 内容 常见用途
mpg 1999 与 2008 年车型的油耗和规格 散点图、柱状图、分面
diamonds 约 5.4 万颗钻石的规格与价格 大数据、分布、对数标度
economics 美国月度经济指标 日期与时间序列
mtcars 32 辆汽车的性能 小型演示与建模
dim(mpg)
#> [1] 234  11
names(mpg)
#>  [1] "manufacturer" "model"        "displ"        "year"         "cyl"          "trans"       
#>  [7] "drv"          "cty"          "hwy"          "fl"           "class"
head(mpg, 3)

2 图形语法基础

2.1 最小可用图形

ggplot() 创建画布,aes() 声明变量到视觉属性的映射,geom_point() 决定用点来画每一行数据。

ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) +
  geom_point() +
  labs(
    title = "排量越大,通常高速油耗表现越低",
    x = "发动机排量(升)",
    y = "高速油耗(英里/加仑)"
  )
散点图:发动机排量越大,高速每加仑英里数总体越低。

图 1:发动机排量与高速油耗的关系。

数据与映射也可分别放在图层里。全局 aes() 会被后续图层继承;局部 aes() 只影响所在图层。

2.2 映射与固定设置

  • 写在 aes() 里面:把变量映射到视觉属性,会产生数据驱动的变化和图例。
  • 写在 aes() 外面:把所有标记固定为同一个值,通常不产生图例。
ggplot(mpg, aes(displ, hwy)) +
  geom_point(aes(color = class), alpha = 0.72, size = 2.2) +
  labs(
    title = "变量放进 aes(),常量放在 aes() 外",
    color = "车型类别",
    x = "发动机排量(升)",
    y = "高速油耗(英里/加仑)"
  ) +
  guides(color = guide_legend(nrow = 2, byrow = TRUE,
                              override.aes = list(alpha = 1, size = 3)))
按车型类别着色的排量与高速油耗散点图。

图 2:颜色映射车型类别,透明度和点大小固定。

下面是最常见的错误之一:aes(color = "steelblue") 会把字符串当成一个类别并创建图例,而不是把点设为蓝色。

# 错误:把 "steelblue" 当作一个数据类别
ggplot(mpg, aes(displ, hwy)) + geom_point(aes(color = "steelblue"))

# 正确:所有点固定为蓝色
ggplot(mpg, aes(displ, hwy)) + geom_point(color = "steelblue")

2.3 全局映射、局部映射与分组

这里点的颜色映射到车型类别,而趋势线只按驱动方式 drv 分组。group 控制哪些观察属于同一条线;show.legend = FALSE 隐藏趋势线图例。

ggplot(mpg, aes(displ, hwy)) +
  geom_point(aes(color = class), alpha = 0.55) +
  geom_smooth(
    aes(group = drv),
    method = "lm", formula = y ~ x,
    se = FALSE, color = "grey25", linewidth = 0.8,
    show.legend = FALSE
  ) +
  labs(
    title = "每个图层可以拥有自己的映射",
    subtitle = "点按车型类别着色;趋势线按驱动方式分组",
    color = "车型类别",
    x = "发动机排量(升)",
    y = "高速油耗(英里/加仑)"
  )
按车型类别着色的散点图,叠加按驱动方式分组的线性趋势线。

图 3:局部映射允许不同图层使用不同分组。

当某层不应该继承全局映射时,可使用 inherit.aes = FALSE,并在该层完整指定自己的 data 和 aes()。

3 常用几何对象

3.1 散点图:过度重叠、透明度与抖动

离散或取值重复的数据容易重叠。常见对策是减小点、增加透明度,或使用 geom_jitter() 轻微扰动位置。

ggplot(mpg, aes(factor(cyl), cty, color = factor(cyl))) +
  geom_jitter(width = 0.16, height = 0, alpha = 0.5, size = 1.8,
              show.legend = FALSE) +
  labs(
    title = "离散 x 轴上用抖动减少遮挡",
    x = "汽缸数",
    y = "城市油耗(英里/加仑)"
  )
按汽缸数显示城市油耗的抖动散点图。

图 4:抖动显示原本重叠的离散观察。

geom_jitter() 适合展示个体观察,但抖动只是显示手段,不应被解释成真实测量误差。大数据还可考虑二维分箱、等高线或抽样。

3.2 平滑曲线与回归线

geom_smooth() 默认方法会随数据量变化。为保证分析含义明确,最好显式写出方法:

  • method = "lm":线性模型;
  • method = "loess":局部平滑,适合较小数据;
  • se = TRUE/FALSE:是否显示置信带;
  • formula = y ~ x:明确模型公式。
ggplot(mpg, aes(displ, hwy, color = drv)) +
  geom_point(alpha = 0.45) +
  geom_smooth(method = "lm", formula = y ~ x, se = TRUE, linewidth = 0.9) +
  scale_color_brewer(palette = "Dark2") +
  labs(
    title = "按组拟合线性趋势",
    subtitle = "颜色映射会同时让点和趋势线按 drv 分组",
    color = "驱动方式",
    x = "发动机排量(升)",
    y = "高速油耗(英里/加仑)"
  )
排量和高速油耗散点图,按驱动方式着色并分别拟合线性回归。

图 5:散点图叠加线性回归线及置信带。

注意:平滑线描述关联,不自动代表因果关系;外推到数据范围之外尤其危险。

3.3 线图与时间序列

geom_line() 会按 x 排序后连接点,适合时间序列;geom_path() 按数据行顺序连接,适合轨迹。多条线必须有正确的 group,而映射 color、linetype 通常会自动建立分组。

unemp_median <- median(economics$unemploy, na.rm = TRUE)

ggplot(economics, aes(date, unemploy)) +
  geom_line(color = "#2c7fb8", linewidth = 0.65) +
  geom_hline(yintercept = unemp_median, linetype = "dashed", color = "#d95f0e") +
  scale_x_date(date_breaks = "8 years", date_labels = "%Y") +
  scale_y_continuous(labels = scales::label_number(big.mark = ",")) +
  labs(
    title = "美国失业人数时间序列",
    subtitle = "虚线表示全时期中位数",
    x = NULL,
    y = "失业人数(千人)",
    caption = "数据:ggplot2::economics"
  )
1967年至2015年美国失业人数月度时间序列,并标出中位数参考线。

图 6:时间序列与参考线。

时间变量应是 Date 或日期时间类型,而不是普通字符串。多组时间序列还要检查每组内部是否按时间排序。

3.4 柱状图:geom_bar() 与 geom_col()

这两个函数外观相似,数据含义不同:

  • geom_bar():只有 x,默认 stat = "count",计算每类行数;
  • geom_col():同时提供 x 和 y,直接画已经汇总好的数值。
ggplot(mpg, aes(y = class, fill = class)) +
  geom_bar(show.legend = FALSE, width = 0.72) +
  scale_fill_brewer(palette = "Blues") +
  labs(
    title = "geom_bar() 用于原始行的计数",
    x = "车型数量",
    y = NULL
  )
车型类别频数的水平柱状图。

图 7:geom_bar() 自动计算每个车型类别的数量。

class_mean <- aggregate(hwy ~ class, data = mpg, FUN = mean)
class_mean$class <- reorder(class_mean$class, class_mean$hwy)

ggplot(class_mean, aes(hwy, class, fill = hwy)) +
  geom_col(width = 0.72, show.legend = FALSE) +
  scale_fill_gradient(low = "#bdd7e7", high = "#08519c") +
  labs(
    title = "geom_col() 用于已经计算好的数值",
    x = "平均高速油耗(英里/加仑)",
    y = NULL
  )
各车型类别平均高速油耗柱状图,按数值从低到高排列。

图 8:geom_col() 绘制预先汇总的平均值。

3.5 堆叠、并列与比例

position 控制同一 x 类别中的多个组如何摆放:

position 作用 适合回答
"stack" 默认堆叠 总量和组成是多少?
"dodge" 并列 组间绝对数量如何比较?
"fill" 每柱归一到 100% 组成比例如何比较?
"identity" 原位置重叠 已有坐标或配合透明度
mpg_three <- subset(mpg, class %in% c("compact", "midsize", "suv"))

ggplot(mpg_three, aes(class, fill = drv)) +
  geom_bar(position = "fill", width = 0.72) +
  scale_y_continuous(labels = scales::label_percent()) +
  scale_fill_brewer(palette = "Set2") +
  labs(
    title = "100% 堆叠柱图比较组成,而不是数量",
    x = NULL,
    y = "车型占比",
    fill = "驱动方式"
  )
紧凑型、中型和SUV三类车型中不同驱动方式所占百分比的百分比堆叠柱状图。

图 9:position = ‘fill’ 比较每类车型的驱动方式构成。

百分比柱会隐藏样本量差异;若每组总数很重要,应同时标注 n 或另画数量图。

4 分布与统计变换

4.1 直方图与密度图

直方图的结论可能随箱宽变化。bins 指箱数,binwidth 指每箱宽度;分析时应根据测量精度与样本规模尝试多个合理值。下面把直方图转换为密度尺度,再叠加核密度曲线。

ggplot(mpg, aes(hwy)) +
  geom_histogram(
    aes(y = after_stat(density)),
    binwidth = 2, boundary = 0,
    fill = "#9ecae1", color = "white"
  ) +
  geom_density(color = "#d95f0e", linewidth = 1.05, adjust = 1) +
  labs(
    title = "明确选择箱宽,再解释分布",
    subtitle = "直方图 binwidth = 2;曲线为核密度估计",
    x = "高速油耗(英里/加仑)",
    y = "密度"
  )
高速油耗分布的直方图,叠加橙色核密度曲线。

图 10:高速油耗的密度直方图和核密度曲线。

after_stat(density) 引用统计变换产生的变量,是旧写法 ..density.. 的现代替代。

4.2 箱线图与小提琴图

箱线图紧凑地显示中位数、四分位距和异常值;小提琴图显示分布形状。叠加使用时,不要让元素互相遮挡。

mpg_four <- subset(mpg, class %in% c("compact", "midsize", "pickup", "suv"))

ggplot(mpg_four, aes(class, hwy, fill = class)) +
  geom_violin(trim = FALSE, alpha = 0.65, color = NA, show.legend = FALSE) +
  geom_boxplot(width = 0.14, outlier.shape = NA, fill = "white",
               linewidth = 0.45) +
  scale_fill_brewer(palette = "Set2") +
  labs(
    title = "分布形状与稳健摘要可以互补",
    x = NULL,
    y = "高速油耗(英里/加仑)"
  )
四类车型高速油耗的小提琴图和箱线图。

图 11:小提琴图展示形状,窄箱线图展示稳健摘要。

小样本的小提琴形状可能不稳定;必要时叠加抖动点并报告每组样本量。

4.3 stat_summary():边汇总边作图

stat_summary() 可直接按 x 分组计算中心值与区间。下面的点表示均值,误差线表示“均值 ± 1 个标准差”;它不是置信区间,图注应写清定义。

mean_minus_sd <- function(z) mean(z, na.rm = TRUE) - sd(z, na.rm = TRUE)
mean_plus_sd  <- function(z) mean(z, na.rm = TRUE) + sd(z, na.rm = TRUE)

ggplot(mpg, aes(factor(cyl), cty)) +
  stat_summary(
    fun = mean,
    fun.min = mean_minus_sd,
    fun.max = mean_plus_sd,
    geom = "pointrange",
    color = "#2c7fb8",
    linewidth = 0.8
  ) +
  labs(
    title = "stat_summary() 在绘图时完成分组汇总",
    subtitle = "点 = 均值;线 = 均值 ± 1 个标准差",
    x = "汽缸数",
    y = "城市油耗(英里/加仑)"
  )
按汽缸数显示城市油耗均值和标准差误差线。

图 12:各汽缸数的城市油耗均值与一个标准差范围。

探索阶段这样很方便;正式分析中,若汇总逻辑复杂,建议先生成明确的汇总数据框,再用 geom_col()、geom_point() 或 geom_errorbar() 作图,便于审计。

5 分面:用小多图比较组别

5.1 facet_wrap() 与 facet_grid()

  • facet_wrap(~ var):一个分面变量,自动换行;
  • facet_grid(rows ~ cols):行列组合有明确语义;
  • scales = "free_y" 等:允许各面板使用不同标度,但会削弱跨面板的绝对比较。
ggplot(mpg, aes(displ, hwy)) +
  geom_point(aes(color = class), alpha = 0.6, show.legend = FALSE) +
  geom_smooth(method = "lm", formula = y ~ x, se = FALSE,
              color = "grey25", linewidth = 0.7) +
  facet_wrap(~ drv, nrow = 1) +
  labs(
    title = "facet_wrap() 保持相同坐标,便于直接比较",
    x = "发动机排量(升)",
    y = "高速油耗(英里/加仑)"
  )
三个分面分别显示前驱、四驱和后驱车型的排量与高速油耗。

图 13:按驱动方式分面的排量—油耗关系。

facet_data <- subset(mpg, cyl %in% c(4, 6, 8))

ggplot(facet_data, aes(class, fill = class)) +
  geom_bar(show.legend = FALSE) +
  facet_grid(drv ~ cyl, scales = "free_x", space = "free_x") +
  labs(
    title = "facet_grid() 适合有行列语义的比较",
    subtitle = "行 = 驱动方式;列 = 汽缸数",
    x = NULL,
    y = "车型数量"
  ) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))
以驱动方式为行、汽缸数为列的车型数量分面柱状图。

图 14:行列分面同时编码驱动方式和汽缸数。

空面板有时是重要信息,说明某些组合不存在;不要只为“好看”就删除它。

6 标度:控制数据如何映射到视觉属性

6.1 连续、离散与手动标度

标度函数通常采用 scale_<视觉属性>_<类型>() 命名,例如:

  • scale_x_continuous()、scale_y_log10():位置;
  • scale_color_brewer()、scale_fill_viridis_d():离散颜色;
  • scale_color_viridis_c()、scale_fill_gradient():连续颜色;
  • scale_color_manual():手动指定类别颜色;
  • scale_x_date():日期轴。

color 通常控制点、线和轮廓;fill 控制柱、面积与封闭形状的内部填充。

diamond_sample <- diamonds[sample.int(nrow(diamonds), 3000), ]

ggplot(diamond_sample, aes(carat, price, color = cut)) +
  geom_point(alpha = 0.42, size = 1.2) +
  scale_x_log10(
    breaks = c(0.3, 0.5, 1, 2, 3),
    labels = scales::label_number()
  ) +
  scale_y_log10(labels = scales::label_dollar()) +
  scale_color_viridis_d(option = "C", end = 0.9) +
  labs(
    title = "对数标度揭示乘法关系",
    subtitle = "坐标轴被变换,原始数据没有被改写",
    x = "重量(克拉,对数轴)",
    y = "价格(美元,对数轴)",
    color = "切工"
  )
三千颗钻石的克拉与价格双对数散点图,颜色表示切工。

图 15:钻石重量和价格同时使用对数标度。

对数轴不能显示零或负值,并会改变视觉距离的含义。轴标题或图注明确说明变换。

6.2 手动颜色与类别顺序

手动颜色应使用有名称的向量,使颜色与类别按名称匹配,而不是依赖偶然的因子顺序。

drive_colors <- c("4" = "#1b9e77", "f" = "#d95f02", "r" = "#7570b3")

ggplot(mpg, aes(displ, hwy, color = drv)) +
  geom_point(alpha = 0.65, size = 2) +
  scale_color_manual(
    values = drive_colors,
    breaks = c("f", "4", "r"),
    labels = c("前轮驱动", "四轮驱动", "后轮驱动")
  ) +
  labs(
    title = "手动标度同时控制颜色、顺序和标签",
    x = "发动机排量(升)",
    y = "高速油耗(英里/加仑)",
    color = "驱动方式"
  )
三种驱动方式的排量和高速油耗散点图,使用手动指定颜色。

图 16:使用有名称的向量稳定指定类别颜色。

类别顺序会影响轴、堆叠和图例。常用 base R 方法包括:

# 指定业务顺序
dat$level <- factor(dat$level, levels = c("低", "中", "高"))

# 按某个数值汇总结果重排
dat$group <- reorder(dat$group, dat$value, FUN = median)

6.3 limits 与视觉缩放的区别

这是一个影响统计结果的重要区别:

# 会在统计计算前删除范围外的数据;平滑线、箱线图等可能改变
p + scale_y_continuous(limits = c(20, 40))

# 只放大显示窗口,不删除数据;适合“镜头缩放”
p + coord_cartesian(ylim = c(20, 40))

若只想观察局部区域,优先使用 coord_cartesian()。如果确实需要过滤数据,最好在绘图前显式过滤,让分析意图清楚可见。

7 坐标系统、标签与注释

7.1 坐标系统

  • coord_cartesian():缩放窗口而不删除数据;
  • coord_fixed(ratio = 1):固定 x、y 单位的物理比例;
  • coord_flip():交换横纵轴,仍可读旧代码;新版 ggplot2 通常更推荐直接交换 aes(x, y);
  • coord_polar():极坐标,可制作饼图/环图,但角度和面积较难准确比较。

坐标系统作用在标度之后。对需要精确比较的图,位置和长度通常比角度、面积更易读。

7.2 标题、标签、参考线与注释

labs() 统一管理标题、副标题、坐标轴、图例标题和图注。annotate() 添加单个说明;geom_text() / geom_label() 则从数据框批量添加标签。

hwy_median <- median(mpg$hwy)

ggplot(mpg, aes(displ, hwy)) +
  geom_point(color = "#2c7fb8", alpha = 0.55) +
  geom_hline(yintercept = hwy_median, linetype = "dashed",
             color = "#d95f0e", linewidth = 0.8) +
  annotate(
    "label", x = 6.2, y = hwy_median + 1.5,
    label = paste0("中位数 = ", hwy_median),
    hjust = 1, size = 3.5,
    fill = "#fff5eb", color = "#a63603", family = plot_family
  ) +
  annotate(
    "curve", x = 3.7, y = 42, xend = 2.4, yend = 43,
    curvature = 0.2, arrow = arrow(length = grid::unit(0.12, "inches")),
    color = "grey35"
  ) +
  annotate("text", x = 3.8, y = 42, label = "高油耗表现车型",
           hjust = 0, color = "grey25", family = plot_family) +
  labs(
    title = "注释应帮助读者看到结论",
    subtitle = "避免给每个点都加标签造成拥挤",
    x = "发动机排量(升)",
    y = "高速油耗(英里/加仑)",
    caption = "虚线为样本中位数"
  ) +
  coord_cartesian(clip = "off")
排量与高速油耗散点图,显示高速油耗中位数虚线,并注释高油耗区域。

图 17:用参考线和注释突出关键区域。

unit() 与 arrow() 来自 R 的 grid 包;它们随 R 一起安装,无需额外依赖。

8 主题、图例与可复用样式

8.1 主题控制什么

主题只控制非数据元素,不会改变数据映射。常用完整主题有 theme_minimal()、theme_classic()、theme_bw();theme() 用于局部调整:

  • element_text():文字;
  • element_line():网格线和轴线;
  • element_rect():背景和边框;
  • element_blank():移除元素。

本教程在 setup 代码块中定义了 theme_guide()。项目中把统一样式封装成函数,比在每张图后复制一长串 theme() 更可靠。

ggplot(mpg, aes(displ, hwy, color = class)) +
  geom_point(alpha = 0.65, size = 2) +
  scale_color_brewer(palette = "Dark2") +
  labs(
    title = "主题负责版式,标度负责映射",
    subtitle = "图例位于底部,次网格线已移除",
    x = "发动机排量(升)",
    y = "高速油耗(英里/加仑)",
    color = "车型类别"
  ) +
  guides(color = guide_legend(nrow = 2, byrow = TRUE,
                              override.aes = list(alpha = 1, size = 3))) +
  theme_guide(base_size = 13)
按车型类别着色的排量与高速油耗散点图,展示统一的自定义主题。

图 18:可复用主题统一字体、网格、标题和图例位置。

若多个图层映射到同一变量,并且标度名称一致,ggplot2 通常会合并图例。可用 guides() 调整顺序、行数和图例中的示例标记,或用 theme(legend.position = "none") 删除不必要的图例。

9 二维汇总:热图

当两个轴都是类别、单元格表示数量或数值时,geom_tile() 很实用。

heat <- as.data.frame(table(cyl = mpg$cyl, drv = mpg$drv))

ggplot(heat, aes(cyl, drv, fill = Freq)) +
  geom_tile(color = "white", linewidth = 0.8) +
  geom_text(aes(label = Freq), color = "white", fontface = "bold",
            family = plot_family) +
  scale_fill_viridis_c(option = "B", begin = 0.2, end = 0.9) +
  labs(
    title = "geom_tile() 将二维组合映射为颜色",
    x = "汽缸数",
    y = "驱动方式",
    fill = "车型数量"
  ) +
  coord_fixed()
热图显示不同汽缸数与驱动方式组合中的车型数量,并在单元格内标数值。

图 19:汽缸数和驱动方式组合的车型数量热图。

连续色板应具有感知上相对均匀的亮度变化。viridis 系列通常对色觉差异和灰度打印更友好。若低值单元格文字不清楚,可根据填充值动态切换文字颜色。

10 在函数中使用 ggplot2

不要再使用已弃用的 aes_string()。在函数中按列名编程,可使用 .data[[...]]:

make_scatter <- function(data, x, y, color = NULL) {
  if (is.null(color)) {
    mapping <- aes(x = .data[[x]], y = .data[[y]])
  } else {
    mapping <- aes(
      x = .data[[x]],
      y = .data[[y]],
      color = .data[[color]]
    )
  }

  ggplot(data, mapping) +
    geom_point(alpha = 0.65, size = 2) +
    labs(x = x, y = y, color = color) +
    theme_guide()
}

make_scatter(mpg, "displ", "hwy", "drv")

这个函数返回的是 ggplot 对象,所以仍可继续叠加:

make_scatter(mpg, "displ", "hwy", "drv") +
  geom_smooth(method = "lm", formula = y ~ x) +
  labs(title = "函数生成的图仍可继续扩展")

11 导出高质量图表

ggsave() 默认保存最后显示的图;在脚本和函数中,显式传入 plot 更安全。宽高与 dpi 共同决定栅格图像像素尺寸。

p <- ggplot(mpg, aes(displ, hwy, color = drv)) +
  geom_point(alpha = 0.7) +
  labs(x = "Engine displacement (L)", y = "Highway MPG") +
  theme_guide()

# 适合文档或网页的 PNG:7 × 4.5 英寸,300 dpi
ggsave(
  filename = "mpg_scatter.png",
  plot = p,
  width = 7, height = 4.5, units = "in",
  dpi = 300, bg = "white"
)

# 矢量 PDF 适合论文与后期排版
ggsave("mpg_scatter.pdf", p, width = 7, height = 4.5, units = "in")

常见建议:

  • 网页:PNG,通常 120–180 dpi 已够用;需要高清打印可用 300 dpi;
  • 线条、文字为主:PDF 或 SVG 矢量格式;
  • 大量半透明点:PNG 往往比矢量文件更小;
  • 保持明确的 width、height、units 和 bg,避免不同机器输出不一致;
  • 先按最终展示尺寸检查字号,而不是只看 RStudio 的缩放预览。

12 常见问题与排错清单

现象 常见原因 处理方法
出现一个写着颜色名的奇怪图例 常量写进了 aes() 把 color = "red" 放到 aes() 外
柱子全是灰色或颜色没变化 混淆 color 与 fill 柱/面积内部通常用 fill
geom_bar() 报 y 相关错误 已汇总数据仍用了计数柱 改用 geom_col()
多条线被错误连在一起 缺少分组 映射 group、color 或 linetype
平滑线或箱线图缩放后改变 用 scale_* (limits=...) 删除了数据 仅缩放时用 coord_cartesian()
点太黑、看不出密度 过度重叠 使用较小点、alpha、jitter、分箱或抽样
直方图结论不稳定 依赖默认箱数 根据领域意义设置并比较 binwidth
类别顺序不合逻辑 字符/因子使用默认顺序 显式 factor(levels=...) 或 reorder()
图例重复或无法合并 不同图层的映射名/标度不同 统一映射变量和 labs(color/fill=...)
警告 “Removed … rows” 缺失值、越界值或标度限制 查 is.na()、数据范围和 limits

不要为了让警告消失就随手加 na.rm = TRUE。先确认缺失值为何存在、是否应删除,以及删除是否会改变结论。

13 快速选择指南

分析目标 首选图层 常用补充
两个连续变量的关系 geom_point() alpha、geom_smooth()
连续变量随时间变化 geom_line() geom_point()、参考线
类别频数 geom_bar() position、coord_flip()
已汇总的类别数值 geom_col() 排序、误差线
单变量分布 geom_histogram() / geom_density() 明确 binwidth / adjust
多组分布比较 geom_boxplot() / geom_violin() 抖动点、样本量
两个类别的组合数值 geom_tile() 连续填充标度、文字标签
多组重复比较 facet_wrap() / facet_grid() 保持共享标度

14 一套可靠的作图工作流

  1. 先确认分析单位、缺失值、变量类型与合理范围。
  2. 用最简单的几何对象画原始数据,先看异常与重叠。
  3. 再加入分组、统计汇总或分面;避免一次映射过多视觉属性。
  4. 明确选择标度、类别顺序、轴范围与颜色方案。
  5. 写出结论型标题、完整轴标签、单位、图例标题和数据来源。
  6. 在最终展示尺寸检查可读性、色觉友好性与黑白打印效果。
  7. 用 ggsave() 固定尺寸和格式,并保留生成图表的代码与会话信息。

15 版本与复现信息

本文档使用现代 ggplot2 语法:线宽使用 linewidth,统计变量使用 after_stat(),编程接口使用 .data[[...]]。

cat("R:", R.version.string, "\n")
#> R: R version 4.6.1 (2026-06-24)
cat("ggplot2:", as.character(packageVersion("ggplot2")), "\n")
#> ggplot2: 4.0.3
cat("knitr:", as.character(packageVersion("knitr")), "\n")
#> knitr: 1.51
cat("rmarkdown:", as.character(packageVersion("rmarkdown")), "\n")
#> rmarkdown: 2.31

至此,你已经覆盖了 ggplot2 最常用的图形语法、几何对象、统计变换、位置、分面、标度、坐标、主题、注释、编程与导出方法。实际项目中,优先保证数据含义和比较任务清晰,再追求视觉装饰。