R语言的交流

Table of Contents

https://wp.me/p80aHo-2BN

引言

当你制作探索性图表时,甚至在查看之前,你就知道该图表会展示哪些变量。你制作每一张图表都有明确目的,能够快速看一眼,然后继续看下一张图表。在大多数分析过程中,你会生成几十甚至几百张图表,其中大多数会被立刻丢弃。

现在你已经理解了数据,接下来需要把你的理解传达给他人。你的受众很可能不会和你拥有相同的背景知识,也不会对这些数据投入很深的兴趣。为了帮助他人快速建立起对数据的良好认知,你需要花费相当多的精力,让你的图表尽可能做到自解释。在本章中,你将学习 ggplot2 提供的一些工具来实现这一点。

本章聚焦于创建优秀图形所需的工具。我们假设你已经知道自己想要什么,只是需要知道如何去实现。因此,我们非常建议将本章与一本优秀的通用可视化书籍搭配阅读。我们尤其喜欢 Albert Cairo 的《The Truthful Art》。这本书并不教授创建可视化的具体操作方法,而是侧重于如何思考,才能制作出有效的图形。

标签

将探索性图形转换为说明性图形时,最容易入手的地方就是添加良好的标签。你可以使用 labs() 函数来添加标签。

library(ggplot2)
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = class)) +
geom_smooth(se = FALSE) +
labs(
x = "Engine displacement (L)",
y = "Highway fuel economy (mpg)",
color = "Car type",
title = "Fuel efficiency generally decreases with engine size",
subtitle = "Two seaters (sports cars) are an exception because of their light weight",
caption = "Data from fueleconomy.gov"
)

图表标题的目的是概括主要发现。应避免那种只描述图表内容的标题,例如“发动机排量与燃油经济性的散点图”。

如果你需要添加更多文本,还有另外两个有用的标签:subtitle 会在标题下方以较小的字体添加更多细节,而 caption 会在图表右下角添加文本,通常用于说明数据来源。你也可以使用 labs() 来替换坐标轴和图例标题。通常,最好把简短的变量名替换为更详细的描述,并注明单位。

也可以使用数学公式来代替文本字符串。只需把 "" 换成 quote(),并查看 ?plotmath 中可用的选项。

library(dplyr)
df <- tibble(
x = 1:10,
y = cumsum(x^2)
)
ggplot(df, aes(x, y)) +
geom_point() +
labs(
x = quote(x[i]),
y = quote(sum(x[i] ^ 2, i == 1, n))
)

注释

除了为图表的主要组成部分添加标签之外,为单个观测值或观测组添加标签也常常很有用。你手头的第一个工具是 geom_text()geom_text() 与 geom_point() 类似,但它多了一个额外的美学映射:label。这使得你可以为图表添加文本标签。

标签有两个可能的来源。首先,你可能有一个提供标签的 tibble。在下面的图中,我们筛选出每种驱动类型中发动机排量最大的汽车,并将它们的信息保存为一个名为 label_info 的新数据框。

# 从 mpg 数据集中筛选出每种驱动类型(drv)里排量(displ)最大的汽车
# 并为这些记录创建更容易理解的驱动类型标签
label_info <- mpg |>
# 按驱动类型分组,确保后续操作在每个驱动类型内部独立进行
group_by(drv) |>
# 在每个组内按排量从大到小排序,让排量最大的记录排在最前面
arrange(desc(displ)) |>
# 取每个组的第一条记录,也就是该驱动类型中排量最大的那辆车
slice_head(n = 1) |>
# 新建一个更可读的变量 drive_type,把简写的 drv 转成完整英文描述
mutate(
drive_type = case_when(
drv == "f" ~ "front-wheel drive", # 前轮驱动
drv == "r" ~ "rear-wheel drive", # 后轮驱动
drv == "4" ~ "4-wheel drive" # 四轮驱动
)
) |>
# 只保留后续绘图或标注真正需要的变量
select(displ, hwy, drv, drive_type)
# 打印查看筛选和整理后的结果
label_info
# 使用 mpg 数据集创建一个散点图,查看发动机排量(displ)与高速油耗(hwy)的关系,
# 并根据驱动类型(drv)着色
ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +
# 绘制散点,alpha = 0.3 让点更透明,避免重叠过于拥挤
geom_point(alpha = 0.3) +
# 添加平滑趋势线,不显示标准误差带
geom_smooth(se = FALSE) +
# 添加文本标签,使用 label_info 数据框中的内容
# 这里单独指定数据源,是为了只给少数关键点加注释
geom_text(
data = label_info,
aes(x = displ, y = hwy, label = drive_type),
# fontface = "bold" 让标签加粗,size 控制字号
# hjust 和 vjust 用来微调标签相对点的位置
fontface = "bold", size = 5, hjust = "right", vjust = "bottom"
) +
# 隐藏图例,因为标签已经直接说明了驱动类型
theme(legend.position = "none")

你也可以使用相同的思路,通过 ggrepel 包中的 geom_text_repel() 来突出图中的某些点。再注意这里用到的另一个实用技巧:我们额外添加了第二层较大的空心点,以进一步突出这些被标注的点。

# install.packages("ggrepel")
# 加载 ggrepel 包,用于绘制自动避让的文本标签,避免标签重叠
library(ggrepel)
# 筛选出潜在离群点:
# 1) 高速油耗 hwy 大于 40 的车辆;
# 2) 或者同时满足高速油耗大于 20 且发动机排量 displ 大于 5 的车辆
potential_outliers <- mpg |>
filter(hwy > 40 | (hwy > 20 & displ > 5))
# 绘制散点图:横轴为发动机排量(displ),纵轴为高速油耗(hwy)
ggplot(mpg, aes(x = displ, y = hwy)) +
# 绘制所有车辆的散点
geom_point() +
# 为潜在离群点添加文本标签,标签内容使用车型名称 model
# geom_text_repel() 会自动调整标签位置,尽量避免与其他点和标签重叠
geom_text_repel(data = potential_outliers, aes(label = model)) +
# 用红色点突出显示这些潜在离群点,便于观察
geom_point(data = potential_outliers, color = "red") +
# 再次用更醒目的红色空心圆圈标出这些点
# size 控制圈的大小,shape = "circle open" 表示空心圆
geom_point(
data = potential_outliers,
color = "red", size = 3, shape = "circle open"
)

请记住,除了 geom_text() 和 geom_label() 之外,ggplot2 中还有许多其他几何对象可用于帮助你为图表添加注释。下面是一些想法:

  • 使用 geom_hline() 和 geom_vline() 来添加参考线。我们通常会把它们画得较粗(linewidth = 2)并设为白色(color = "white"),同时将它们放在主要数据层的下方。这样可以让它们清晰可见,同时又不会把注意力从数据本身上移开。
  • 使用 geom_rect() 可以在感兴趣的点周围绘制一个矩形。矩形的边界由美学映射 xminxmaxyminymax 来定义。或者,你也可以查看 ggforce 包,特别是 geom_mark_hull(),它可以让你用凸包来标注点的子集。
  • 使用带有 arrow 参数的 geom_segment() 来通过箭头突出某个点。使用美学映射 x 和 y 来定义起始位置,使用 xend 和 yend 来定义结束位置。

另一个用于向图表添加注释的实用函数是 annotate()。一般来说,geom 通常适合突出显示数据的一个子集,而 annotate() 则适合向图表中添加一个或少数几个注释元素。

为了演示如何使用 annotate(),我们先创建一些要添加到图中的文本。由于这段文本有点长,我们会使用 stringr::str_wrap(),根据你希望每行显示的字符数自动给它添加换行:

library(stringr)
# 将一段说明文字按指定宽度自动换行,便于在图表中显示
trend_text <- "Larger engine sizes tend to have lower fuel economy." |>
str_wrap(width = 30)
# 输出处理后的文本
trend_text

然后,我们添加两层注释:一层使用标签几何对象,另一层使用线段几何对象。两者中的 x 和 y 美学映射定义注释应从何处开始,而线段注释中的 xend 和 yend 美学映射定义线段的结束位置。另请注意,这条线段被设置成了箭头样式。

# 绘制 mpg 数据集中发动机排量(displ)与高速油耗(hwy)的散点图
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point() +
# 添加标签注释:在图中指定位置放置一段说明文字,用来解释总体趋势
annotate(
geom = "label", # 使用标签几何对象,显示带边框的文本框
x = 3.5, # 标签的横坐标位置
y = 38, # 标签的纵坐标位置
label = trend_text, # 标签内容,使用前面整理好的趋势说明文字
hjust = "left", # 文本水平左对齐,使排版更自然
color = "red" # 文本颜色设置为红色,便于突出显示
) +
# 添加线段注释:用一条带箭头的线段,把文字和图中的目标位置联系起来
annotate(
geom = "segment", # 使用线段几何对象,绘制一条连接线
x = 3, # 线段起点的横坐标
y = 35, # 线段起点的纵坐标
xend = 5, # 线段终点的横坐标
yend = 25, # 线段终点的纵坐标
color = "red" # 线段颜色设置为红色,与标签保持一致
arrow = arrow(type = "closed") # 在线段末端添加闭合箭头,增强指向性
)

注释是传达可视化主要结论和有趣特征的强大工具。唯一的限制只是你的想象力(以及你为了让注释排版更美观而调整位置时的耐心)!

比例尺

改进图表以便更好地传达信息的第三种方法是调整比例尺。比例尺控制美学映射在视觉上的呈现方式。

默认比例尺

通常情况下,ggplot2 会自动为你添加比例尺。例如,当你输入:

ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = class))

ggplot2 会在后台自动添加默认比例尺:

ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = class)) +
scale_x_continuous() +
scale_y_continuous() +
scale_color_discrete()

请注意尺度(比例尺)的命名规则:scale_ 后接美学属性名称,然后是 _,最后是尺度名称。默认尺度的命名取决于它们所对应的变量类型:continuous、discrete、datetime 或 date。scale_x_continuous() 会把 displ 中的数值映射到 x 轴上的连续数轴上,scale_color_discrete() 会为每一类汽车选择颜色,等等。下面你将学习很多非默认尺度。

默认比例尺经过精心选择,能够很好地适用于各种输入。不过,出于两个原因,你可能希望覆盖这些默认设置:

  • 你可能会想调整默认尺度的一些参数。这样你就可以更改坐标轴上的刻度,或者图例中的键标签。
  • 你可能想完全替换该尺度,并使用一种完全不同的算法。通常,由于你对数据了解更多,因此可以做得比默认设置更好。

坐标轴刻度和图例键

统称起来,坐标轴和图例被称为导引。坐标轴用于 x 和 y 美学属性;图例用于其他所有美学属性。

有两个主要参数会影响坐标轴上刻度以及图例中的键的外观:breaks 和 labelsbreaks 控制刻度的位置,或与键相关联的值。labels 控制与每个刻度/键对应的文本标签。breaks 最常见的用途是覆盖默认选择:

ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +
geom_point() +
scale_y_continuous(breaks = seq(15, 40, by = 5))

你可以以同样的方式使用标签(一个长度与刻度相同的字符向量),但也可以将其设置为 NULL 以完全隐藏标签。这对于地图,或者用于发布那些你不能分享绝对数值的图表时会很有用。你也可以使用刻度和标签来控制图例的外观。对于分类变量的离散尺度,标签可以是一个命名列表,其中包含现有的级别名称及其对应的期望标签。

# 绘制 mpg 数据集中排量与高速油耗的散点图,并按驱动方式着色
ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +
# 添加散点层
geom_point() +
# 隐藏 x 轴刻度标签
scale_x_continuous(labels = NULL) +
# 隐藏 y 轴刻度标签
scale_y_continuous(labels = NULL) +
# 自定义图例标签:4=四驱,f=前驱,r=后驱
scale_color_discrete(labels = c("4" = "4-wheel", "f" = "front", "r" = "rear"))

labels 参数配合 scales 包中的标注函数,也可用于将数字格式化为货币、百分比等。左侧图展示了使用 label_dollar() 的默认标注方式,它会添加美元符号以及千位分隔符逗号。右侧图则通过将美元值除以 1,000,并添加后缀 “K”(表示“千”),再加上自定义的刻度,进一步实现了个性化设置。请注意,breaks 仍然采用数据的原始尺度。

library(scales)
# Left
ggplot(diamonds, aes(x = price, y = cut)) +
geom_boxplot(alpha = 0.05) +
scale_x_continuous(labels = label_dollar())
# Right
ggplot(diamonds, aes(x = price, y = cut)) +
geom_boxplot(alpha = 0.05) +
scale_x_continuous(
labels = label_dollar(scale = 1/1000, suffix = "K"),
breaks = seq(1000, 19000, by = 6000)
)

另一个很实用的标签函数是 label_percent()

# 使用 diamonds 数据集,按 cut 作为 x 轴,并按 clarity 填充颜色
ggplot(diamonds, aes(x = cut, fill = clarity)) +
# 绘制堆叠比例柱状图,按总量归一化为 100%
geom_bar(position = "fill") +
# 设置 y 轴名称为 Percentage,并将刻度标签显示为百分比格式
scale_y_continuous(name = "Percentage", labels = label_percent())

breaks 的另一个用途是:当你只有相对较少的数据点,并希望准确突出观测值出现的位置时。比如,下面这张图展示了每位美国总统开始和结束任期的时间。

# 对 presidential 数据进行处理,生成按顺序递增的 id
presidential |>
mutate(id = 33 + row_number()) |>
# 绘制开始时间与 id 的点线图
ggplot(aes(x = start, y = id)) +
# 添加每位总统任期开始时间的点
geom_point() +
# 添加从 start 到 end 的线段,表示任期区间
geom_segment(aes(xend = end, yend = id)) +
# 设置 x 轴为日期轴,去掉名称,并以总统任期开始日期作为刻度,标签显示为两位年份
scale_x_date(name = NULL, breaks = presidential$start, date_labels = "'%y")

请注意,对于 breaks 参数,我们使用 presidential$start 将 start 变量提取为一个向量,因为这个参数不能进行美学映射。另请注意,日期和日期时间尺度中 breaks 与 labels 的指定方式略有不同:

  • breaks 不能像 aes() 里的变量那样“按数据行自动映射”,所以这里要直接传入一个向量presidential$start
  • presidential$start 的意思是把数据框里 start 这一列取出来,作为 breaks 的具体位置。
  • date_labels 用来控制刻度标签怎么显示,它后面接的是一种格式字符串,比如年份、月份、日期的显示格式。
  • date_breaks 用来控制刻度间隔,比如每 2 天、每 1 个月一个刻度,所以它接收像 "2 days""1 month" 这样的字符串。

图例布局

你最常会使用 breaks 和 labels 来调整坐标轴。虽然它们也可以用于图例,但还有一些其他技巧你更可能会用到。

要控制图例的整体位置,你需要使用 theme() 设置。我们会在本章结尾回到主题(theme)部分,但简单来说,它们用于控制图形中非数据的部分。theme 设置中的 legend.position 控制图例的绘制位置:


# 创建一个基础散点图:x 轴为发动机排量 displ,y 轴为高速油耗 hwy
# 并根据 class 给点上色
base <- ggplot(mpg, aes(x = displ, y = hwy)) +
  geom_point(aes(color = class))

# 将图例放在右侧(默认位置)
base + theme(legend.position = "right") # the default

# 将图例放在左侧
base + theme(legend.position = "left")

# 将图例放在上方,并将颜色图例分成 3 行显示
base + 
  theme(legend.position = "top") +
  guides(color = guide_legend(nrow = 3))

# 将图例放在下方,并将颜色图例分成 3 行显示
base + 
  theme(legend.position = "bottom") +
  guides(color = guide_legend(nrow = 3))

如果你的绘图区域偏矮宽,就将图例放置在顶部或底部;若绘图区域偏高窄,则把图例放在左侧或右侧。你也可以通过设置 legend.position = “none” 来彻底隐藏图例。

若要单独控制各个图例的展示效果,可搭配guides()函数与guide_legend()函数或guide_colorbar()函数使用。下方示例展示了两项重要设置:通过nrow参数设置图例所占行数,以及修改某项美学参数来放大数据点。倘若你在绘图时设置了较低透明度以呈现大量数据点,该用法会格外实用。

# 创建一个散点图:x 轴为发动机排量 displ,y 轴为高速油耗 hwy
# 点的颜色按 class 分类
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = class)) +
# 添加平滑曲线,不显示置信区间
geom_smooth(se = FALSE) +
# 将图例放在底部
theme(legend.position = "bottom") +
# 调整颜色图例:
# - 分成 2 行显示
# - override.aes 用于修改图例中的点样式
# 这里把图例点的大小设为 4
guides(color = guide_legend(nrow = 2, override.aes = list(size = 4)))

注意,guides() 中参数的名称与该美学的名称一致,就像在 labs() 中一样。

替换比例尺

与其只是稍微调整细节,不如直接完全替换比例尺。你最可能想要替换的比例尺主要有两类:连续位置比例尺和颜色比例尺。幸运的是,同样的原则也适用于其他所有美学,因此一旦你掌握了位置和颜色,就能很快学会其他比例尺的替换。

绘制变量的变换通常非常有用。例如,如果对克拉和价格进行对数变换,就更容易看清它们之间的精确关系:

# Left
ggplot(diamonds, aes(x = carat, y = price)) +
geom_bin2d()
#> `stat_bin2d()` using `bins = 30`. Pick better value `binwidth`.
# Right
ggplot(diamonds, aes(x = log10(carat), y = log10(price))) +
geom_bin2d()
#> `stat_bin2d()` using `bins = 30`. Pick better value `binwidth`.

然而,这种转换的缺点在于,轴线现在是用转换后的值来标记的,这使得解读图表变得困难。我们可以在美学映射中不进行转换,而是用刻度来进行转换。这在视觉上是一样的,只是轴线是按照原始数据刻度来标记的。

ggplot(diamonds, aes(x = carat, y = price)) +
geom_bin2d() +
scale_x_log10() +
scale_y_log10()

另一个经常需要自定义的比例尺是颜色。默认的分类比例尺会选择在色轮上均匀分布的颜色。一个有用的替代方案是 ColorBrewer 比例尺,它们经过人工调校,对常见色盲人群更友好。下面这两幅图看起来相似,但红色和绿色的色调差异足够明显,因此右图中的点即使对红绿色盲的人也能区分出来。

ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = drv))
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = drv)) +
scale_color_brewer(palette = "Set1")

不要忘记一些更简单的可访问性改进技巧。如果颜色数量不多,你可以添加一个冗余的形状映射。这也有助于确保你的图在黑白情况下仍然易于解读。

ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = drv, shape = drv)) +
scale_color_brewer(palette = "Set1")

ColorBrewer 比例尺的文档可在 https://colorbrewer2.org/ 在线查看,并可通过 Erich Neuwirth 开发的 RColorBrewer 包在 R 中使用。图 11.1 展示了所有调色板的完整列表。如果你的分类值是有序的,或者有一个“中间值”,那么顺序型(上方)和发散型(下方)调色板尤其有用。这种情况通常会在你使用 cut() 将连续变量转换为分类变量时出现。

当你已经预先定义了数值与颜色之间的映射时,可以使用 scale_color_manual()。例如,如果我们把总统所属政党映射为颜色,就希望采用标准对应关系:共和党用红色,民主党用蓝色。分配这些颜色的一种方法是使用十六进制颜色代码:

presidential |>
mutate(id = 33 + row_number()) |>
ggplot(aes(x = start, y = id, color = party)) +
geom_point() +
geom_segment(aes(xend = end, yend = id)) +
scale_color_manual(values = c(Republican = "#E81B23", Democratic = "#00AEF3"))

对于连续颜色,可以使用内置的 scale_color_gradient() 或 scale_fill_gradient()。如果你使用的是发散型比例尺,可以使用 scale_color_gradient2()。这样可以让你为正值和负值指定不同的颜色。例如,当你想区分均值以上和均值以下的点时,这也很有用。

另一种选择是使用 viridis 颜色比例尺。设计者 Nathaniel Smith 和 Stéfan van der Walt 精心设计了连续色彩方案,使其不仅能被各种色盲人群感知,而且在颜色和黑白显示中都具有知觉上的一致性。这些比例尺在 ggplot2 中提供连续型(c)、离散型(d)和分箱型(b)调色板。

# 创建包含 10000 个随机正态分布样本的二维数据框
df <- tibble(
x = rnorm(10000),
y = rnorm(10000)
)
# 默认连续色标的六边形分箱图
ggplot(df, aes(x, y)) +
geom_hex() +
coord_fixed() +
labs(title = "Default, continuous", x = NULL, y = NULL)
# 使用 Viridis 连续色标的六边形分箱图
ggplot(df, aes(x, y)) +
geom_hex() +
coord_fixed() +
scale_fill_viridis_c() +
labs(title = "Viridis, continuous", x = NULL, y = NULL)
# 使用 Viridis 分箱色标的六边形分箱图
ggplot(df, aes(x, y)) +
geom_hex() +
coord_fixed() +
scale_fill_viridis_b() +
labs(title = "Viridis, binned", x = NULL, y = NULL)

请注意,所有颜色比例尺都有两种形式:分别对应 color 和 fill 美学映射的 scale_color_*() 和 scale_fill_*()(其中 color 比例尺同时支持英式和美式拼写)。

缩放

控制图形范围有三种方式:

  1. 调整要绘制的数据。
  2. 在每个比例尺中设置范围。
  3. 在 coord_cartesian() 中设置 xlim 和 ylim

我们将通过一系列图形来演示这些选项。左侧的图展示了发动机排量与燃油效率之间的关系,并按驱动类型着色。右侧的图展示了相同的变量,但只绘制了数据的一个子集。对数据进行子集化不仅影响了 x 和 y 比例尺,也影响了平滑曲线。

# Left
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = drv)) +
geom_smooth()
# Right
mpg |>
filter(displ >= 5 & displ <= 6 & hwy >= 10 & hwy <= 25) |>
ggplot(aes(x = displ, y = hwy)) +
geom_point(aes(color = drv)) +
geom_smooth()

让我们把这些与下面的两个图进行比较:左图是在各个比例尺中设置范围,右图是在 coord_cartesian() 中设置范围。我们可以看到,缩小范围等同于对数据进行子集筛选。因此,如果要放大图中的某个区域,通常最好使用 coord_cartesian()

# Left
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = drv)) +
geom_smooth() +
scale_x_continuous(limits = c(5, 6)) +
scale_y_continuous(limits = c(10, 25))
# Right
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = drv)) +
geom_smooth() +
coord_cartesian(xlim = c(5, 6), ylim = c(10, 25))

另一方面,如果你想扩展范围,例如使不同图形之间的比例尺一致,那么通常在单个比例尺上设置范围会更有用。比如,如果我们提取两类汽车并分别绘图,由于三个比例尺(x 轴、y 轴和颜色美学映射)都具有不同的范围,因此很难比较这些图形。

suv <- mpg |> filter(class == "suv")
compact <- mpg |> filter(class == "compact")
# Left
ggplot(suv, aes(x = displ, y = hwy, color = drv)) +
geom_point()
# Right
ggplot(compact, aes(x = displ, y = hwy, color = drv)) +
geom_point()

克服这一问题的一种方法是在多个图形之间共享比例尺,并使用完整数据的范围来训练这些比例尺。

x_scale <- scale_x_continuous(limits = range(mpg$displ))
y_scale <- scale_y_continuous(limits = range(mpg$hwy))
col_scale <- scale_color_discrete(limits = unique(mpg$drv))
# Left
ggplot(suv, aes(x = displ, y = hwy, color = drv)) +
geom_point() +
x_scale +
y_scale +
col_scale
# Right
ggplot(compact, aes(x = displ, y = hwy, color = drv)) +
geom_point() +
x_scale +
y_scale +
col_scale

在这个特定情况下,你本可以直接使用分面,但这种技术在更一般的场景下也很有用,比如当你想把图形分布到报告的多页中时。

主题

最后,你可以使用主题来自定义图形中的非数据元素:

# 使用 mpg 数据集创建散点图,x 轴为发动机排量 displ,y 轴为高速油耗 hwy
ggplot(mpg, aes(x = displ, y = hwy)) +
# 绘制散点图,并按车辆类别 class 着色
geom_point(aes(color = class)) +
# 添加平滑曲线,不显示标准误差带
geom_smooth(se = FALSE) +
# 使用黑白主题
theme_bw()

ggplot2 包含图 11.2 所示的八种主题,默认主题是 theme_gray()

也可以控制每个主题的单独组成部分,比如 y 轴所用字体的大小和颜色。我们已经看到,legend.position 控制图例的绘制位置。使用 theme() 还可以自定义图例的许多其他方面。例如,在下面的图中,我们改变了图例的方向,并为其添加了黑色边框。注意,图例框和图形标题元素的自定义是通过 element_*() 函数完成的。这些函数用于指定非数据组件的样式,例如,标题文本的粗体是在 element_text() 的 face 参数中设置的,而图例边框的颜色则在 element_rect() 的 color 参数中定义。控制标题和副标题位置的主题元素分别是 plot.title.position 和 plot.caption.position。在下面的图中,这些值被设置为 "plot",表示这些元素与整个绘图区对齐,而不是与图面板对齐(默认设置)。另外还使用了几个有用的 theme() 组件来调整标题和副标题文本的排列和格式。

# 使用 mpg 数据集绘制散点图
# x 轴表示发动机排量 displ,y 轴表示高速油耗 hwy
# 并根据驱动方式 drv 对点进行着色
ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +
# 绘制每辆车对应的散点
geom_point() +
# 添加标题和数据来源说明
labs(
title = "发动机越大,燃油经济性通常越低",
caption = "数据来源:https://fueleconomy.gov。"
) +
# 使用 theme() 自定义图形外观:
# - 调整图例位置
# - 设置图例横向排列
# - 给图例外框加黑色边框
# - 将标题设为加粗
# - 让标题和注释相对于整个图形区域对齐
# - 将注释左对齐
theme(
legend.position = c(0.6, 0.7),
legend.direction = "horizontal",
legend.box.background = element_rect(color = "black"),
plot.title = element_text(face = "bold"),
plot.title.position = "plot",
plot.caption.position = "plot",
plot.caption = element_text(hjust = 0)
)

要查看所有 theme() 组件的概览,请参阅 ?theme 的帮助文档。ggplot2 书籍也是了解主题设置完整细节的绝佳去处。

到目前为止,我们讨论的是如何创建和修改单个图形。那么,如果你有多个图形,并且想以某种方式将它们排列在一起,该怎么办呢?patchwork 包允许你把多个独立的图形组合到同一张图中。我们在本章前面已经加载过这个包。

要将两个图并排放置,你只需把它们相加即可。注意,你首先需要创建这些图并将它们保存为对象(在下面的示例中,它们被命名为 p1 和 p2)。然后,使用 + 将它们并排放置。

install.packages("patchwork")
library(patchwork)
p1 <- ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point() +
labs(title = "Plot 1")
p2 <- ggplot(mpg, aes(x = drv, y = hwy)) +
geom_boxplot() +
labs(title = "Plot 2")
p1 + p2

需要注意的是,在上面的代码块中,我们并没有使用 patchwork 包中的新函数。相反,这个包为 + 运算符添加了新的功能。

你也可以使用 patchwork 创建复杂的图布局。下面,| 将 p1 和 p3 并排放置,/ 则将 p2 移到下一行。

p3 <- ggplot(mpg, aes(x = cty, y = hwy)) +
geom_point() +
labs(title = "Plot 3")
(p1 | p3) / p2

此外,patchwork 还允许你将多个图中的图例收集到一个公共图例中,自定义图例的位置以及各个图的尺寸,并为你的图添加共同的标题、副标题、说明等。下面我们创建 5 个图。我们已经关闭了箱线图和散点图的图例,并使用 & theme(legend.position = "top") 将密度图的图例收集到图的顶部。请注意这里使用的是 & 运算符,而不是通常的 +。这是因为我们修改的是 patchwork 图的主题,而不是单个 ggplot。图例被放置在顶部的 guide_area() 内。最后,我们还自定义了 patchwork 各个组成部分的高度——guide 的高度为 1,箱线图为 3,密度图为 2,分面散点图为 4。Patchwork 会根据这个比例分配你为图预留的区域,并相应地放置各个组件。

p1 <- ggplot(mpg, aes(x = drv, y = cty, color = drv)) +
geom_boxplot(show.legend = FALSE) +
labs(title = "Plot 1")
p2 <- ggplot(mpg, aes(x = drv, y = hwy, color = drv)) +
geom_boxplot(show.legend = FALSE) +
labs(title = "Plot 2")
p3 <- ggplot(mpg, aes(x = cty, color = drv, fill = drv)) +
geom_density(alpha = 0.5) +
labs(title = "Plot 3")
p4 <- ggplot(mpg, aes(x = hwy, color = drv, fill = drv)) +
geom_density(alpha = 0.5) +
labs(title = "Plot 4")
p5 <- ggplot(mpg, aes(x = cty, y = hwy, color = drv)) +
geom_point(show.legend = FALSE) +
facet_wrap(~drv) +
labs(title = "Plot 5")
(guide_area() / (p1 + p2) / (p3 + p4) / p5) +
plot_annotation(
title = "City and highway mileage for cars with different drive trains",
caption = "Source: https://fueleconomy.gov."
) +
plot_layout(
guides = "collect",
heights = c(1, 3, 2, 4)
) &
theme(legend.position = "top")

如果你想进一步了解如何使用 patchwork 组合和排布多个图,我们建议查看该包官网上的指南:https://patchwork.data-imaginist.com。

评论

发表评论

了解 数据控|突破是我们的每一步 的更多信息

立即订阅以继续阅读并访问完整档案。

继续阅读