8 注释
在构建数据可视化时,通常需要对显示的数据进行注释。从概念上讲,注释为图提供元数据:也就是说,它提供关于所显示数据的附加信息。然而,从实际的角度来看,元数据只是数据的另一种形式。因此,ggplot2中的注释工具重用了用于创建其他绘图的相同geoms。然而,为了满足用户在注释图时通常具有的特定需求,ggplot2本身中有一些辅助函数,以ggplot2方式扩展的许多其他包以可能对您有帮助。
8.1 绘图和坐标轴标题
自定义绘图时,修改图形、轴和图例相关的标题通常是有用的。为了帮助完成这项任务,ggplot2 提供了labs()辅助函数,它允许您使用名称-值对来设置各种标题,例如title = My plot title"
,x = "X axis"
或fill = "fill legend"
:
ggplot(mpg, aes(displ, hwy)) +
geom_point(aes(colour = factor(cyl))) +
labs(
x = "Engine displacement (litres)",
y = "Highway miles per gallon",
colour = "Number of cylinders",
title = "Mileage by engine size and cylinders",
subtitle = "Source: http://fueleconomy.gov"
)
提供给labs()
的值通常是文本字符串,其中\n
用于指定换行符,但您也可以用quote()
包装数学表达式。可以通过键入?plotmath找到这些表达式的使用规则。
values <- seq(from = -2, to = 2, by = .01)
df <- data.frame(x = values, y = values ^ 3)
ggplot(df, aes(x, y)) +
geom_path() +
labs(y = quote(f(x) == x^3))
在 ggtext 包和 ggplot2 主题系统(参见polishing)的帮助下,还可以在axis
和legend
标题中包含(一些)降价。要启用markdown,需要将相关主题元素设置为ggtext::element_markdown()
,如下所示:
df <- data.frame(x = 1:3, y = 1:3)
base <- ggplot(df, aes(x, y)) +
geom_point() +
labs(x = "Axis title with *italics* and **boldface**")
base
base + theme(axis.title.x = ggtext::element_markdown())
有两种方法可以删除轴标签:
- 设置
labs(x = "")
省略标签但仍分配空间; - 设置
labs(x = NULL)
删除标签及其空间。
8.2 文本标签
向图中添加文本是最常见的注释形式之一。大多数图不会从图中的每个观察中添加文本,但标记异常值和其他关键点非常有用。但是,由于 R 处理字体的方式,文本注释可能很棘手。ggplot2 包并没有提供所有的答案,但它确实提供了一些工具来让您的生活更轻松一些。标记图的主要工具是geom_text(),它在指定的x
和y
位置添加label
文本。geom_text()
拥有所有几何图形中最美观的,因为有很多方法可以控制文本的外观:
-
family
属性提供了字体的名称。这种图形属性允许您使用系统字体的名称,但需要小心。只有三种字体可以保证在任何地方都能使用:“sans”(默认字体)、“serif”或“mono”。如下所示:df <- data.frame(x = 1, y = 3:1, family = c("sans", "serif", "mono")) ggplot(df, aes(x, y)) + geom_text(aes(label = family, family = family))
image在绘图中使用系统字体可能很棘手,原因是每个图形设备 (GD) 对文本绘制的处理方式不同。有两组GDs:屏幕设备,如
windows()
(适用于windows)、quartz()
(适用于mac)、x11()
(主要适用于Linux)和RStudioGD()
(在RStudio中)将图形绘制到屏幕上,而文件设备,如png()
和pdf()
将图形写入文件。不幸的是,这些设备不会以相同的方式指定字体,所以如果你想让字体在任何地方都可以工作,你需要以不同的方式配置设备。以下两个包简化了这一问题:showtext, https://github.com/yixuan/showtext,通过将所有文本渲染为多边形,来生成与 GDs 独立的图。
extrafont,https://github.com/wch/extrafont,将字体转换为所有设备都可以使用的标准格式。
这两种方法各有利弊,因此您需要尝试这两种方法,看看哪种方法最适合您的需求。
-
fontface
属性指定样式,有3个值:“plain”(默认),“bold”或“italic”。例如:df <- data.frame(x = 1, y = 3:1, face = c("plain", "bold", "italic")) ggplot(df, aes(x, y)) + geom_text(aes(label = face, fontface = face))
image -
您可以使用
hjust
(“left”, “center”, “right”, “inward”, “outward”) 和vjust
(“bottom”, “middle”, “top”, “inward”, “向外”)属性来调整文本的对齐方式。默认情况下,对齐是居中的,但通常有更好的方式来代替。最有用的对齐方式之一是“向内”。它将文本向绘图中间对齐,以确保标签保持在绘图限制内:df <- data.frame( x = c(1, 1, 2, 2, 1.5), y = c(1, 2, 1, 2, 1.5), text = c( "bottom-left", "bottom-right", "top-left", "top-right", "center" ) ) ggplot(df, aes(x, y)) + geom_text(aes(label = text)) ggplot(df, aes(x, y)) + geom_text(aes(label = text), vjust = "inward", hjust = "inward")
imageimage size
属性控制字体大小。与大多数工具不同,ggplot2 以毫米 (mm) 为单位指定大小,而不是通常的点 (pts)。这种选择的原因是为了让它的单位与ggplot2保持一致。(每英寸有72.27pts,所以要将点转换为毫米,只需乘以72.27 / 25.4)。angle
指定文本的旋转角度。
ggplot2 包确实允许您将数据值映射到geom_text()
所使用的图形属性,但您应该克制使用:很难理解映射到这些图形属性的变量之间的关系,而且这样做很少有用。
除了各种图形属性之外,geom_text()
还有三个参数可以指定。与图形属性不同,它们只接受单个值,所以它们必须对所有标签都是相同的:
-
通常你想在图形上标注现有的点,但你不希望文本与点(或条形图等)重叠。在这种情况下,稍微偏移文本是有用的,你可以使用
nudge_x
和nudge_y
参数:df <- data.frame(trt = c("a", "b", "c"), resp = c(1.2, 3.4, 2.5)) ggplot(df, aes(resp, trt)) + geom_point() + geom_text(aes(label = paste0("(", resp, ")")), nudge_y = -0.25) + xlim(1, 3.6)
image(请注意,我手动调整了 x 轴限制以确保所有文本都适合绘图。)
-
第三个参数是
check_overlap
。如果check_overlap = TRUE
,重叠标签将自动从图中删除。算法很简单:标签按照它们在数据框中出现的顺序绘制;如果标签与现有点重叠,则将其省略。ggplot(mpg, aes(displ, hwy)) + geom_text(aes(label = model)) + xlim(1, 8) ggplot(mpg, aes(displ, hwy)) + geom_text(aes(label = model), check_overlap = TRUE) + xlim(1, 8)
image
乍一看,这个功能似乎不是很有用,但算法的简单性却派上了用场。如果您按优先级对输入数据进行排序,则结果是带有强调重要数据点标签的图。
geom_text()
的一个变体是geom_label()
:它在文本后面绘制一个圆角矩形。这对于向背景繁忙的绘图添加标签非常有用:
label <- data.frame(
waiting = c(55, 80),
eruptions = c(2, 4.3),
label = c("peak one", "peak two")
)
ggplot(faithfuld, aes(waiting, eruptions)) +
geom_tile(aes(fill = density)) +
geom_label(data = label, aes(label = label))
要想很好地标记数据仍然有一些挑战:
文本不影响绘图的限制。不幸的是,由于标签具有绝对大小(例如 3 厘米),而不管绘图的大小,因此无法进行此操作。这意味着图的限制需要根据图的大小而不同——ggplot2 无法做到这一点。相反,你需要根据您的数据和绘图尺寸调整
xlim()
和ylim()
。-
如果要标注很多点,就很难避免重叠。
check_overlap = TRUE
很有用,但几乎无法控制删除哪些标签。解决此问题的一种常用的方法是使用Kamil Slowikowski的 ggrepel 包https://github.com/slowkow/ggrepel。该包提供geom_text_repel()
,它优化标签定位以避免重叠。只要标签数量不多,它的效果就很好:mini_mpg <- mpg[sample(nrow(mpg), 20),] ggplot(mpg, aes(displ, hwy)) + geom_point(colour = "red") + ggrepel::geom_text_repel(data = mini_mpg, aes(label = class)) #> Warning: ggrepel: 8 unlabeled data points (too many overlaps). Consider #> increasing max.overlaps
image 有时很难确保文本标签适合您想要的空间。Claus Wilke的 ggfittext 包https://github.com/wilkox/ggfittext包含了一些有用工具,包括允许您在条形图中的列内放置文本标签函数。
8.3 构建自定义注释
用文本标记单个点是一种重要的注释,但它不是唯一有用的方法。ggplot2 包提供了其他几个工具来使用与显示数据相同的几何图形来注释绘图。例如,您可以使用:
geom_text()和geom_label()添加文本,如上面所示。
geom_rect()突出显示绘图中感兴趣的矩形区域。
geom_rect()
具有xmin
,xmax
,ymin
和ymax
图形属性。geom_line(),geom_path()和geom_segment()添加行。所有这些几何图形都有一个
arrow
参数,它允许您在线条上放置一个箭头。创建箭头arrow()的有参数angle
,length
,ends
和type
。geom_vline(),geom_hline()和geom_abline()允许您添加跨越整个绘图范围的参考线(有时称为规则)。
通常,您可以将注释放在前面(alpha
在需要时使用,以便您仍然可以看到数据)或在后面。在默认背景下,一条粗白线是一个有用的参考:它很容易看到,但不会跳到你身上。为了说明如何使用 ggplot2 工具来注释图表,我们将绘制美国失业率随时间变化的时间序列来理解:
ggplot(economics, aes(date, unemploy)) +
geom_line()
注释此图形的一种有用方法是使用阴影来指示当时哪位总统在位。为此,我们使用geom_rect()
引入阴影、geom_vline()
引入分隔符、geom_text()
添加标签,然后使用geom_line()
将数据覆盖在这些背景元素之上:
presidential <- subset(presidential, start > economics$date[1])
ggplot(economics) +
geom_rect(
aes(xmin = start, xmax = end, fill = party),
ymin = -Inf, ymax = Inf, alpha = 0.2,
data = presidential
) +
geom_vline(
aes(xintercept = as.numeric(start)),
data = presidential,
colour = "grey50", alpha = 0.5
) +
geom_text(
aes(x = start, y = 2500, label = name),
data = presidential,
size = 3, vjust = 0, hjust = 0, nudge_x = 50
) +
geom_line(aes(date, unemploy)) +
scale_fill_manual(values = c("blue", "red")) +
xlab("date") +
ylab("unemployment")
请注意,这里没有什么新东西:在大多数情况下,注释图ggplot2
是对现有几何图形的直接操作。也就是说,在这段代码中有一件特别的事情需要注意:使用-Inf
和Inf
作为位置。这些是指图的顶部和底部(或左侧和右侧)限制。
这种方法也可以以其他方式使用。例如,您可以用它向绘图添加单个注释,但这有点繁琐,因为您必须创建一个单行数据框:
yrng <- range(economics$unemploy)
xrng <- range(economics$date)
caption <- paste(strwrap("Unemployment rates in the US have
varied a lot over the years", 40), collapse = "\n")
ggplot(economics, aes(date, unemploy)) +
geom_line() +
geom_text(
aes(x, y, label = caption),
data = data.frame(x = xrng[1], y = yrng[2], caption = caption),
hjust = 0, vjust = 1, size = 4
)
此代码有效,并生成所需的图,但非常麻烦。每次要添加单个注释时都必须这样做,过程会很烦人,因此 ggplot2 包的annotate()为您创建数据框的辅助函数:
ggplot(economics, aes(date, unemploy)) +
geom_line() +
annotate(
geom = "text", x = xrng[1], y = yrng[2],
label = caption, hjust = 0, vjust = 1, size = 4
)
该annotate()
函数的便利性在其他情况下会派上用场。例如,一种常见的注释形式是通过在主数据集下方以不同颜色绘制较大的点来突出显示点的子集。要突出显示"subaru"制造的车辆,您可以使用它来创建基本图:
p <- ggplot(mpg, aes(displ, hwy)) +
geom_point(
data = filter(mpg, manufacturer == "subaru"),
colour = "orange",
size = 3
) +
geom_point()
这样做的问题是突出显示的类别不会被标记。使用annotate()
很容易纠正。
p +
annotate(geom = "point", x = 5.5, y = 40, colour = "orange", size = 3) +
annotate(geom = "point", x = 5.5, y = 40) +
annotate(geom = "text", x = 5.6, y = 40, label = "subaru", hjust = "left")
这种方法的优点是在绘图区域内创建标签,但缺点是标签与它挑选的点相距较远(否则与标签相邻的橙色和黑色点可能会与真实数据混淆)。另一种方法是使用不同的 geom 来完成这项工作。geom_curve()和geom_segment()可以用来绘制曲线和线连接点与标签,可以结合使用annotate()
如下图所示:
p +
annotate(
geom = "curve", x = 4, y = 35, xend = 2.65, yend = 27,
curvature = .3, arrow = arrow(length = unit(2, "mm"))
) +
annotate(geom = "text", x = 4.1, y = 35, label = "subaru", hjust = "left")
8.4 直接标记
上面的 "subaru"图提供了“直接标记”的示例,其中绘图区域本身包含点的标签,而不是使用图例。这通常会使绘图更容易阅读,因为它使标签更接近数据。更广泛的 ggplot2 生态系统包含各种其他工具,可以以更自动化的方式完成此任务。由托比·迪伦·霍金 (Toby Dylan Hocking)提供的directlabels包有许多工具来简化这个过程:
ggplot(mpg, aes(displ, hwy, colour = class)) +
geom_point()
ggplot(mpg, aes(displ, hwy, colour = class)) +
geom_point(show.legend = FALSE) +
directlabels::geom_dl(aes(label = class), method = "smart.grid")
Directlabels 提供了多种定位方法。smart.grid
是散点图的合理起点,但还有其他方法对频率多边形和线图更有用。有关其他技术,请参阅 directlabels 网站http://directlabels.r-forge.r-project.org。
另一个想法来自 Thomas Lin Pedersen https://github.com/thomasp85/ggforce的 ggforce 包。ggforce 包包含许多有用的工具来扩展 ggplot2 功能,包括geom_mark_ellipse()
使用圆形“突出显示”标记覆盖绘图等功能。例如:
ggplot(mpg, aes(displ, hwy)) +
geom_point() +
ggforce::geom_mark_ellipse(aes(label = cyl, group = cyl))
由 Hiroaki Yutani https://github.com/yutannihilation/gghighlight在 gghighlight 包中提供了第三种直接标记方法。在许多情况下,对于突出图中的点或线(或实际上是各种不同的几何图形)很有用,特别是对于纵向数据:
data(Oxboys, package = "nlme")
ggplot(Oxboys, aes(age, height, group = Subject)) +
geom_line() +
geom_point() +
gghighlight::gghighlight(Subject %in% 1:3)
#> Warning: Tried to calculate with group_by(), but the calculation failed.
#> Falling back to ungrouped filter operation...
#> Warning: Tried to calculate with group_by(), but the calculation failed.
#> Falling back to ungrouped filter operation...
#> label_key: Subject
8.5 跨分面注释
如果使用得当,注释可以成为帮助读者理解数据的强大工具。一个例子是当您希望读者跨分面比较组时。例如,在下面的图中,很容易看到每个分面内的关系,但分面之间的细微差异不会突然出现:
ggplot(diamonds, aes(log10(carat), log10(price))) +
geom_bin2d() +
facet_wrap(vars(cut), nrow = 1)
如果我们添加一条参考线,就更容易看到这些细微的差异:
mod_coef <- coef(lm(log10(price) ~ log10(carat), data = diamonds))
ggplot(diamonds, aes(log10(carat), log10(price))) +
geom_bin2d() +
geom_abline(intercept = mod_coef[1], slope = mod_coef[2],
colour = "white", size = 1) +
facet_wrap(vars(cut), nrow = 1)
在此图中,每个分面都使用同一条回归线显示一个类别的数据。由于有共享参考线来辅助比较,因此可以更轻松地将各个分面相互比较。
当您希望绘图的每个分面都显示来自单个组的数据时,会出现此主题的变化,并在每个面板中不显眼地绘制完整的数据集以帮助进行视觉比较。gghighlight 包在这种情况下特别有用:
ggplot(mpg, aes(displ, hwy, colour = factor(cyl))) +
geom_point() +
gghighlight::gghighlight() +
facet_wrap(vars(cyl))