Hadley Wickham:一个改变了R的人

Hadley Wickham:一个改变了R的人

Graphics for communication(通信图)

Hadley Wickham:一个改变了 R 的人

本文是翻译及实践大神的这篇文章。如下链接是大神的文章,如下是自己的翻译和实践:

r4ds.had.co.nz/graphics

1.1简介

在探索性数据分析中,您学习了如何使用plots图像作为数据分析的展示工具。当你使用plots图像的时候,你清楚甚至在这之前使用哪些变量在plots上进行展示。你为了一个目的而创建一个plot图像,并且能快速的查看它,然后继续为了下一个目标而创建plot图像。在大多数的分析过程中,你会绘制出数十个或数百个plot图像,其中大部分图会立即被丢弃掉。

现在你了解了你的数据,并且需要把你的理解告诉别人。我们的读者可能不会了解您的背景知识,也不会在数据上投入大量资金。 为了帮助他人快速建立一个良好的数据知识模型,您需要投入相当大的精力,使自己的情节尽可能不言自明。 在本章中,您将学习ggplot2提供的一些工具来实现这个目的。

本章重点介绍创建良好图形所需的工具。假设你知道你想要的,只需要知道如何去做。出于这个原因,我强烈建议将本章与一本好的通用可视化书籍配对阅读,这本书就是由Albert Cairo编著的《 The Truthful Art 》。这本书没有教授创建可视化的机制,而是专注于为了创建有效的图形而需要思考的问题。

1.1.1 先决条件

在本章中在本章中,我们将再次关注ggplot2. 我们还将使用一个小dplyr进行数据处理,以及一些ggplot2扩展包,包括ggrepel和viridis。 我们不用在这里加载这些扩展,而是使用 notation来显式引用它们的函数。 这将有助于清楚哪些函数被内置到ggplot2 中,哪些来自其他包。 切记,如果你还没有install.packages(),你需要安装这些软件包。

library(tidyverse)

******************************************************************************************

1.2 标签

将探索性图形转化为说明性图形时最容易开始的地方是标签。你可以使用labs()函数添加标签。这个例子添加个标题:

> library(tidyverse)
> ggplot(mpg,aes(displ,hwy))+
+   geom_point(aes(color=class))+
+   geom_smooth(se = FALSE)+
+   labs(title = "Fuel efficiency generally decreases with engine size")
`geom_smooth()` using method = 'loess'

图形标题的目的是为了总结主要的发现。为了避免之描述标题的情节,例如:“发动机排量与燃油经济性的散点图”。

如果你需要添加更多的文本,你还可以使用ggplot2 2.2.0及更高版本中使用另外两个有用的标签(在阅读本书时可以使用这两个标签)

  • subtitle 在标题的下方以更小的字体添加更多的细节
  • caption 在图的右下方添加文本,通常用于描述数据的来源
> ggplot(mpg,aes(displ,hwy))+
+ geom_point(aes(color=class))+
+ geom_smooth(se = FALSE)+
+ labs(
+  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 "
`geom_smooth()` using method = 'loess'

你也可以使用labs()函数来替换轴和图例标题。用更详细的描述替换简短的变量名通常是一个好主意,并且包含单位。

> ggplot(mpg, aes(displ, hwy)) +
+ geom_point(aes(colour = class)) +
+ geom_smooth(se = FALSE) +
+ labs(
+ x = "Engine displacement (L)",
+ y = "Highway fuel economy (mpg)",
+ colour = "Car type"
`geom_smooth()` using method = 'loess'

可以使用数学方程式来代替文本字符串变成了可能。只需要切换quote()中的""和阅读出函数方程式中的数学图形:

> df <- tibble(
+ x = runif(10),
+ y = runif(10)
> ggplot(df, aes(x, y)) +
+   geom_point() +
+   labs(
+     x = quote(sum(x[i] ^ 2, i == 1, n)),
+     y = quote(alpha + beta + frac(delta, theta))
+ )

1.2.1 练习

  1. 使用自定义标题、字幕、标题、x、y和颜色标签在燃油经济性数据上创建一个图表;
  2. 使用函数geom_smooth()绘图有点误导,因为大型发动机的高速公路上包含轻量级跑车,并且高速公路上是倾斜的。使用您的建模工具来拟合和显示更好的模型;
  3. 采取您在上个月创建的探索性图形,并添加信息性标题,使其他人更容易理解。

******************************************************************************************

1.3 注释

除了标记图形的主要组成部分之外,标记个别观察或观察组通常也很有用。您可以使用的第一个工具是geom_text()。geom_text()类似于geom_point(),但是它还有个额外的审美:标签。这使得可以添加文本到您的图形中。

有两种可能的标签来源。首先,你可能有提供标签的骰子。下面的图不是非常有用,但是它说明了一个有用的方法:用dplyr包在每个类中拉出效率最高的汽车,然后将其标记在图上:

> best_in_class <- mpg %>%
+ group_by(class) %>%
+ filter(row_number(desc(hwy)) == 1)
> ggplot(mpg, aes(displ, hwy)) +
+ geom_point(aes(colour = class)) +
+ geom_text(aes(label = model), data = best_in_class)

这个图形是很难让人理解的,因为标签和点相互重叠,通过geom_label切换到在文本后面绘制矩形框,我们可以使图形变得更好一些。我们还使用nudge_y参数将标签略微移到相应的点上:

> ggplot(mpg, aes(displ, hwy)) +
+ geom_point(aes(colour = class)) +
+ geom_label(aes(label = model), data = best_in_class, nudge_y = 2, alpha = 0.5

以上的函数能帮助我们实现需要的功能,但是我们仔细观察左上角,就会发现实际上有两个标签是彼此之上的,发生这种情况是因为紧凑型和紧凑型车型中最好的汽车的高速公路里程和排量完全相同。 我们无法通过对每个标签应用相同的转换来解决这些问题。 相反,我们可以使用Kamil Slowikowski的ggrepel包。 这个有用的包将自动调整标签,使它们不重叠:

> install.packages("ggrepel")
> ggplot(mpg, aes(displ, hwy)) +
+ geom_point(aes(colour = class)) +
+ geom_point(size = 3, shape = 1, data = best_in_class) +
+ ggrepel::geom_label_repel(aes(label = model), data = best_in_class)

注意这里使用的另一个方便的技术:我添加了第二层大的空心点,以突出显示我标记的点。

您有时可以使用相同的想法来将图例替换为直接放在图上的标签。如下这个图不算很好,但是也不是很差:

> class_avg <- mpg %>%
+ group_by(class) %>%
+ summarise(
+ displ = median(displ),
+ hwy = median(hwy)
> ggplot(mpg, aes(displ, hwy, colour = class)) +
+ ggrepel::geom_label_repel(aes(label = class),
+ data = class_avg,
+ size = 6,
+ label.size = 0,
+ segment.color = NA
+ geom_point() +
+ theme(legend.position = "none")

或者,您可能只想将一个标签添加到图中,但仍需要创建一个数据框。 通常情况下,您需要将标签放置在绘图的角落,因此使用summarize()计算x和y的最大值可以方便地创建新的数据框。

> label <- mpg %>%
+ summarise(
+ displ = max(displ),
+ hwy = max(hwy),
+ label = "Increasing engine size is \nrelated to decreasing fuel economy."
> ggplot(mpg, aes(displ, hwy)) +
+ geom_point() +
+ geom_text(aes(label = label), data = label, vjust = "top", hjust = "right")

如果要将文本完全放置在图的边界上,可以使用+ Inf和-Inf。 由于我们不再计算mpg的位置,我们可以使用tibble()来创建数据框:

> label <- tibble(
+ displ = Inf,
+ hwy = Inf,
+ label = "Increasing engine size is \nrelated to decreasing fuel economy."
> ggplot(mpg, aes(displ, hwy)) +
+ geom_point() +
+ geom_text(aes(label = label), data = label, vjust = "top", hjust = "right")

在这些例子中,我使用“\ n”手动将标签分解成行。 另一种方法是使用stringr :: str_wrap()自动添加换行符,给定每行所需的字符数:

注意使用hjust和vjust来控制标签的对齐。 图28.1显示了所有九种可能的组合。

请记住,除了geom_text()之外,ggplot2中还有许多其他的geom可用来帮助注释你的图形。 一些想法:

  • 使用geom_hline()和geom_vline()添加参考线。 我经常使它们变厚(size = 2)和白色(color = white),并将它们绘制在主数据层下面。 这使得他们很容易看到,而不会将注意力从数据中吸引过来。
  • 使用geom_rect()在感兴趣的点周围绘制一个矩形。 矩形的边界由xmin,xmax,ymin,ymax定义
  • 使用带有箭头参数的geom_segment(),用箭头引起对点的注意。 使用x和y来定义起始位置,以及xend和yend来定义结束位置。

唯一的限制就是你的想象力(以及你对定位注释的耐心美观)

1.3.1 练习

  1. 使用具有无限位置的geom_text()将文本放在图的四个角落。
  2. 阅读annotate()的文档。 你如何使用它来添加一个文本标签的图形,而不必创建一个tibble?
  3. geom_text()如何与faceting进行交互? 你怎么能把一个标签添加到单个方面? 你怎么能在每个方面都贴上不同的标签? (提示:考虑基础数据。)
  4. geom_label()有什么参数控制背景框的外观?
  5. arrow()的四个参数是什么? 他们如何工作? 创建一系列展示最重要选项的图形。

******************************************************************************************

1.4 刻度线

为了能更好的表达你所制作的图形,可以使用第三种方法就是调整图形的刻度线。刻度线能够从数据值上从你理解的事上控制图像。通常情况下,ggplot2 会自动为您添加刻度线。例如,当你输入:

> library(ggplot2)
> ggplot(mpg, aes(displ, hwy)) +
+ geom_point(aes(colour = class)

ggplot2会自动添加默认刻度线在图像后:

> ggplot(mpg, aes(displ, hwy)) +
+ geom_point(aes(colour = class)) +
+ scale_x_continuous() +
+ scale_y_continuous() +
+ scale_colour_discrete()

请注意对刻度线的命名方案,scale_后面跟着美学的名称,然后是_,然后是刻度线的名称。默认刻度线的命名是根据它们对齐的变量的类型命名:连续、离散、日期时间或日期。有很多非默认的刻度线,您将在下面了解到:

默认的刻度线进过精心挑选,为各种投入做好工作。不过,您可能有以下两个原因来覆盖默认的刻度线:

  • 您可能想调整默认刻度线的一些参数。这允许你做一些事情,比如改变坐标轴上的断点或者图例上的关键性标签。
  • 您可能想要完全替换规模,并使用完全不同的算法。 通常你可以做得比默认的更好,因为你对数据有了更多的了解。

******************************************************************************************

1.4.1 轴刻度和图例标示

有两个主要的参数影响轴上的刻度线以及图例上的键:中断和标签。中断控制所打钩的位置或者与键有关的相关值。标签控制与每个勾号/键相关联的文本标签。 中断最常见的用法是覆盖默认选项:

> ggplot(mpg, aes(displ, hwy)) +
+ geom_point() +
+ scale_y_continuous(breaks = seq(15, 40, by = 5))

您可以使用相同的方式使用标签(一个字符向量的长度与断点相同),但也可以将其设置为NULL以完全禁止标签。 这对于地图或发布不能共享绝对数字的图像非常有用。

> ggplot(mpg, aes(displ, hwy)) +
+ geom_point() +
+ scale_x_continuous(labels = NULL) +
+ scale_y_continuous(labels = NULL)

您也可以使用分割点和标签来控制图形的外观。全部的坐标轴和图例被称为指南。轴适用于X轴和Y轴,图例适用于其他的一切。

分隔点的另一个用途是当您的数据点相对较少,并且想要精确地显示观察点的位置时。例如:拿这个图像来说明每个美国总统何时开始和结束任期。

presidential %>%
  mutate(id = 33 + row_number()) %>%
  ggplot(aes(start, id)) +
    geom_point() +
    geom_segment(aes(xend = end, yend = id)) +
    scale_x_date(NULL, breaks = presidential$start, date_labels = "'%y")

请注意,日期和日期时间坐标轴的中断和标签的规格有点不同

  • date_labels 采用格式规范,格式与parse_datetime()相同;
  • date_breaks(这里没有显示),需要一个像2天或1个月的字符串。

******************************************************************************************

1.4.2 图像布局

您最经常使用分割点和标签来调整轴。虽然它们都是为了图像的展示,但还是有一些其他的技巧或者你会使用到的。

为了控制图像的整体位置,您需要使用theme()设置。我们将回到本章末尾的主题,简而言之,它们控制着图像的非数据部分。

> base <- ggplot(mpg, aes(displ, hwy)) +
+ geom_point(aes(colour = class))
> base + theme(legend.position = "left")
> base + theme(legend.position = "top")
> base + theme(legend.position = "bottom")
> base + theme(legend.position = "right") # the default

您也可以使用 legend.position = "none"来完全禁止显示图例。

*********************************************************************************

> ggplot(mpg, aes(displ, hwy)) +
+ geom_point(aes(colour = class)) +
+ geom_smooth(se = FALSE) +
+ theme(legend.position = "bottom") +
+ guides(colour = guide_legend(nrow = 1, override.aes = list(size = 4)))
`geom_smooth()` using method = 'loess'

******************************************************************************************

1.4.3 更换坐标轴

而不是只调整一些细节,你可以完全替换坐标轴。 有两种类型的坐标轴刻度线可能要切换:连续位置刻度线和颜色刻度线。 幸运的是,相同的原则适用于所有其他的著作,所以一旦你掌握了位置和颜色,你将能够迅速拿起其他的坐标轴刻度线替换。

绘制变量的变换非常有用。 例如,正如我们在钻石价格中看到的那样,如果我们记录下它们的变化,就很容易看出克拉和价格之间的确切关系:

> ggplot(diamonds, aes(carat, price)) +
+ geom_bin2d()
> ggplot(diamonds, aes(log10(carat), log10(price))) +
+ geom_bin2d()

然而,这种转换的缺点是,坐标轴现在标有转换后的轴,使得很难解释该图。 我们可以用尺度去做,而不是在审美制图上进行转型。 这在视觉上是相同的,除了在原始数据刻度上标记轴。

> ggplot(diamonds, aes(carat, price)) +
+ geom_bin2d() + 
+ scale_x_log10() + 
+ scale_y_log10()

另一个频繁定制的坐标轴刻度线是色彩。默认分类比例选择色轮周围均匀间隔的颜色。 有用的替代品是ColorBrewer量表,它们经过手动调节,可以更好地适应常见色盲类型的人群。 下面的两幅图看起来相似,但是红色和绿色的色调有足够的差别,即使是红绿色盲的人也可以区分右侧的点。

> ggplot(mpg, aes(displ, hwy)) +
+ geom_point(aes(color = drv))
> ggplot(mpg, aes(displ, hwy)) +
+ geom_point(aes(color = drv)) +
+ scale_colour_brewer(palette = "Set1")

不要忘记更简单的技术。 如果只有几种颜色,则可以添加冗余形状映射。 这也将有助于确保您的情节是可以在黑色和白色的解释。

> ggplot(mpg, aes(displ, hwy)) +
+ geom_point(aes(color = drv, shape = drv)) +
+ scale_colour_brewer(palette = "Set1")

ColorBrewer刻度盘在线记录在 colorbrewer2.org/ ,并通过Erich Neuwirth的RColorBrewer软件包在R中提供。 图28.2显示了所有调色板的完整列表。 顺序(顶部)和发散(底部)调色板特别有用,如果您的分类值是有序的,或有一个“中”。 如果您使用cut()将连续变量创建为分类变量,则通常会出现这种情况。

Figure 28.2: All ColourBrewer scales

1.5 放大

有三种方法来控制绘图限制:

  1. 调整绘制的数据
  2. 设置每个刻度线的限制
  3. 在coord_cartesian()中设置xlim和ylim

要放大图的一个区域,通常最好使用coord_cartesian()。比较以下两个图:

> ggplot(mpg, mapping = aes(displ, hwy)) +
+ geom_point(aes(color = class)) +
+ geom_smooth() +
+ coord_cartesian(xlim = c(5, 7), ylim = c(10, 30))
`geom_smooth()` using method = 'loess'
> library(dplyr)
> mpg %>%
+ filter(displ >= 5, displ <= 7, hwy >= 10, hwy <= 30) %>%
+ ggplot(aes(displ, hwy)) +
+ geom_point(aes(color = class)) +
+ geom_smooth()

您还可以设置个别刻度线的限制。 减少限制基本上相当于数据的子集化。 如果要扩大限制,例如,要在不同的图形上匹配比例,通常会更有用。 例如,如果我们提取两类汽车并分别绘制它们,则很难比较这些图,因为所有三个尺度(X轴,Y轴和颜色美学)都有不同的范围。

> suv <- mpg %>% filter(class == "suv")
> compact <- mpg %>% filter(class == "compact")
> ggplot(suv, aes(displ, hwy, colour = drv)) +
+ geom_point()
> ggplot(compact, aes(displ, hwy, colour = drv)) +
+ geom_point()

克服这个问题的一个方法就是在多个图形上分享比例尺,用全部数据的限制来训练比例尺。

> x_scale <- scale_x_continuous(limits = range(mpg$displ))
> y_scale <- scale_y_continuous(limits = range(mpg$hwy))
> col_scale <- scale_colour_discrete(limits = unique(mpg$drv))
> ggplot(suv, aes(displ, hwy, colour = drv)) +
+ geom_point() +
+ x_scale +
+ y_scale +
+ col_scale
> ggplot(compact, aes(displ, hwy, colour = drv)) +
+ geom_point() +
+ x_scale +