Menu

R ggplot2 入门教程:图形语法

ggplot2 的工作原理:数据 + aes() + geom 的思维模型,映射与设定美学属性的区别,labs()、facet_wrap()、主题,以及用 ggsave() 保存图形。

ggplot2 用图层构建图形

ggplot2 是 R 中使用最广泛的绘图包,人们对它推崇备至,靠的是一个设计理念:图形语法。base R 给每种图表一个函数:plot()、hist()、barplot(),每个函数都有自己的参数。ggplot2 则给你一小组可以组合的部件:一个数据集,一个从列到视觉属性的映射,以及绘图图层,用 + 组合成任何图表。语法学一次,每种新图表都只是把同一句话换一个词。另外,它的默认样式(灰色面板、合理的坐标轴、自动生成的图例)不用花任何功夫调整就足够体面。

它是一个附加包,所以需要安装一次,并在每次会话中加载:

install.packages("ggplot2")   # once, on your machine
library(ggplot2)              # at the top of every script that uses it

有一点要事先知道:ggplot2 需要的是数据框(而不是零散的向量),最好是整洁的长格式:每行一条观测,每列一个变量。如果你的数据是宽格式,先把它转换成长格式,看似是绘图的问题往往就迎刃而解了。

思维模型:数据、映射、几何

每张 ggplot 图都回答三个问题,每个部分回答一个:

  1. 数据:用哪个数据框?ggplot(mtcars, ...)
  2. 映射:哪些列决定哪些视觉属性?aes(x = wt, y = hp)
  3. 几何:观测值该怎么画?+ geom_point()

下面逐个拆解它的结构:

ggplot(mtcars, aes(x = wt, y = hp)) +
    geom_point()
  • ggplot(mtcars, ...) 开始一张图,默认数据是 mtcars。单独运行它只会画出一个空的灰色面板,因为还没有图层。
  • aes(x = wt, y = hp) 是美学映射:横向位置来自 wt 列,纵向位置来自 hp 列。注意你写的是裸列名,而不是 mtcars$wt,因为映射是在你提供的数据内部求值的。
  • + geom_point() 添加一个把每行画成一个点的图层。这里的 + 真的就是在叠加图层:图形是一个你逐步搭建的对象,你可以继续添加另一个 geom、标签、主题,每个都再用一个 +。

运行它,你会得到和散点图指南中 base R 版本一样的重量与马力散点图,只是画在带白色网格线的灰色面板上。把 geom_point() 换成 geom_line(),同样的映射就变成了折线图,这就是图形语法在发挥作用。

你真正会用到的 geom

十来个 geom 几乎能覆盖所有日常工作:

geom_point()       # scatter plot: two numeric variables
geom_line()        # line chart: trends over an ordered variable
geom_col()         # bar chart from pre-computed heights (needs y)
geom_bar()         # bar chart that counts raw rows for you (no y)
geom_histogram()   # distribution of one numeric variable (set binwidth)
geom_boxplot()     # distributions compared across groups
geom_smooth()      # fitted trend line; method = "lm" for straight

图层可以叠加,在表达力上 ggplot2 正是从这里开始胜过 base R:geom_point() + geom_smooth(method = "lm") 用一行可读的代码就画出了带回归线和置信带的散点图。因为图形是对象,你还可以先保存一个基础图形,再从它分出不同版本:

p <- ggplot(mtcars, aes(x = wt, y = hp)) + geom_point()

p + geom_smooth(method = "lm")   # one variant
p + geom_smooth()                # another (loess curve)

映射与设定:经典的 aes() 困惑

每个 ggplot2 初学者都会犯的一个错误,通常在第一周:把一个字面颜色写在 aes() 里面。规则很简单:写在 aes() 里表示“随数据变化”;写在外面表示“固定设定”,两种写法的效果完全不同:

# MAPPING: color varies by a column - each cylinder count gets its own
# color, and a legend appears automatically
ggplot(mtcars, aes(x = wt, y = hp, color = factor(cyl))) +
    geom_point(size = 2)

# SETTING: every point is literally steelblue - no legend, no mapping
ggplot(mtcars, aes(x = wt, y = hp)) +
    geom_point(color = "steelblue", size = 2)

再看这个陷阱本身:

# WRONG: "blue" inside aes() is treated as data, not as a color
ggplot(mtcars, aes(x = wt, y = hp, color = "blue")) +
    geom_point()

它画出的是橙红色的点,还带一个只有一项、标签为 “blue” 的图例。原因是:aes() 把列映射到属性,所以 color = "blue" 创建了一个只有一个类别的虚假列,每个值都是单词 “blue”,然后 ggplot 给这个类别分配了默认的第一种颜色,而它碰巧是一种偏红的橙粉色。如果你的意思是“把点变成蓝色”,颜色就要写在映射外面:geom_point(color = "blue")。同样的规则也适用于 size、shape、fill 和 alpha。(另外注意映射示例中的 factor(cyl):cyl 是数值,用 factor() 包起来,就是要求每组用不同的颜色,而不是连续的颜色渐变。)

标签、分面和主题

labs() 在一个地方给所有东西命名,一张图有了真正的标签才算完成:

ggplot(mtcars, aes(x = wt, y = hp, color = factor(cyl))) +
    geom_point(size = 2) +
    labs(title = "Horsepower vs. weight",
         x     = "Weight (1000 lbs)",
         y     = "Horsepower",
         color = "Cylinders")

分面是 ggplot2 的招牌功能:把一张图拆成一组小图,每组一个面板,共享坐标轴,让面板之间的比较公平可信:

ggplot(mtcars, aes(x = wt, y = hp)) +
    geom_point() +
    facet_wrap(~ cyl)

会出现三个面板,分别对应 4 缸、6 缸和 8 缸的汽车,排列成网格,每个面板上方标注着组的值。在 base R 中,这需要费力地摆弄 par(mfrow) 并手动统一坐标范围;在这里只需一行。分面偏爱整洁的数据,所以 ggplot2 和 dplyr 流水线搭配起来非常自然:先筛选和汇总,再把结果直接用管道传给 ggplot()。

主题一次调用就能重新设计所有非数据元素的样式:+ theme_minimal()(干净的白色背景)、+ theme_bw()、+ theme_classic()(只有坐标轴,没有网格),或者默认的灰色主题。每个项目选定一个并保持一致:每张图都使用同一主题的报告,读起来像一份完整的文档,而不是一本剪贴簿。

用 ggsave() 保存,以及什么时候 base R 依然够用

ggsave() 把最近一张图写入磁盘,并根据文件扩展名推断格式:

ggsave("hp-vs-weight.png", width = 8, height = 5)          # last plot shown
ggsave("hp-vs-weight.pdf", plot = p, width = 8, height = 5) # a saved object

width 和 height 默认以英寸为单位;要显式设置它们,因为字体大小会随输出尺寸缩放,而默认值很少正好适合图形的最终用途。

至于什么时候值得用它:在分析过程中花五秒钟看一眼某个变量,base R 的 plot() 等函数打字更少,也没有任何依赖,处处都用 ggplot2 并不会有奖励。一旦图形涉及分组、图例、分面或者要给别人看,ggplot2 就物有所值了:它的语法能从容应对复杂的图形,而 base R 绘图要费很大力气才能做到。

要点总结

  • ggplot2 用数据 + aes() 映射 + geom 图层组合出每张图表,用 + 连接;语法学一次,每种图表都能复用。
  • aes() 把列映射到视觉属性:写裸列名,在你的数据框中求值。
  • 写在 aes() 里 = 随数据变化(并生成图例);写在外面 = 固定设定。aes(color = "blue") 是经典 bug。
  • 用 labs() 加标题,用 facet_wrap(~ group) 画小多图,用 theme_minimal() 等主题设置样式,用 ggsave() 并显式指定尺寸来导出。
  • 自己快速看一眼:用 base R。有分组、分面或者要给别人看:用 ggplot2。

绘图这一章到此结束。接下来自然的一步是描述性统计,用数字描述这些图形所揭示的形状。

常见问题

R 中的 ggplot2 是什么?

ggplot2 是 R 中最流行的绘图包。它不是每种图表一个函数,而是用同样的三个部分组合出每一张图:一个数据框,一个把列映射到视觉属性的 aes(),以及一个或多个 geom_*() 图层,用 + 连接起来。它是 tidyverse 的一部分。

怎么安装 ggplot2?

运行一次 install.packages("ggplot2"),然后在每个用到它的脚本开头写 library(ggplot2)。安装是把包放到你的电脑上;library() 是把它加载到当前会话中。

ggplot2 中的 aes() 有什么作用?

aes() 把数据中的列映射到视觉属性:aes(x = wt, y = hp, color = factor(cyl)) 表示横向位置来自 wt,纵向位置来自 hp,点的颜色来自气缸数。写在 aes() 里的东西随数据变化;写在外面的是固定设定。

为什么我的 ggplot 点不是我指定的颜色?

因为你把颜色写在了 aes() 里面:aes(color = "blue") 并不是设定颜色,而是映射了一个名叫 "blue" 的虚构类别,于是 ggplot 选了它自己的默认颜色,还加了一个图例。要设定一个字面颜色,就把它写在映射外面:geom_point(color = "blue")。

Coddy 编程语言插图

和 Coddy 一起学编程

开始