写给大家的-R-语言指南-全-
写给大家的 R 语言指南(全)
原文:
zh.annas-archive.org/md5/59066a75c7cc3e91cc40a73497de92f2译者:飞龙
前言

在 2020 年初,全球努力遏制 COVID-19 的传播时,有一个国家在其他国家未能做到的地方取得了成功:新西兰。新西兰能够应对 COVID-19 的原因有很多,其中之一就是 R 编程语言(没错,真的)。
这个谦逊的数据分析工具帮助新西兰应对 COVID-19,支持卫生部团队生成全国范围内的每日病例报告。基于这些报告中的信息,政府官员能够制定政策,保持该国基本免于 COVID-19 的影响。然而,团队规模很小,因此如果使用像 Excel 这样的工具每天生成报告是不可能的。正如团队领导 Chris Knox 告诉我:“试图在一个点选式环境中做我们所做的事是不可能的。”
相反,一些工作人员编写了 R 代码,可以每天运行以生成更新的报告。这些报告并不涉及任何复杂的统计数据;它们只是 COVID-19 病例的统计数字。它们的价值来自于 R 能够做的其他事情:数据分析和可视化、报告创建以及工作流自动化。
本书探讨了人们使用 R 来沟通和自动化任务的多种方式。你将学习如何做到以下几点:
-
制作专业质量的数据可视化、地图和表格
-
使用 R Markdown 替代繁琐的多工具工作流来创建报告
-
使用参数化报告一次生成多个报告
-
使用 R Markdown 制作幻灯片演示文稿和网站
-
自动化从 Google Sheets 和美国人口普查局导入在线数据的过程
-
创建自己的函数来自动化你反复执行的任务
-
将你的函数打包成一个可以与他人分享的包
最棒的是,你可以在不进行比计算平均值更复杂的统计分析的情况下完成这一切
难道 R 仅仅是用来做统计分析的吗?
很多人认为 R 只是一个用于硬核统计分析的工具,但它能做的不仅仅是处理数值数据。毕竟,每个 R 用户都必须以某种方式展示他们的发现并传达他们的结果,无论是通过数据可视化、报告、网站还是演示文稿。而且,使用 R 越多,你会发现自己越希望自动化那些目前手动完成的任务。
作为一名接受定性训练的社会学人类学家,且没有定量背景,我曾经对使用 R 进行可视化和沟通任务感到羞愧。但事实是,R 在这些工作中表现得非常出色。ggplot2 包是许多顶级信息设计师的首选工具。全球的用户已经利用 R 的自动化报告功能使他们的工作变得更加高效。R 不仅仅是替代其他工具,它能够更好地执行你现在可能已经在做的任务,比如生成报告和表格。
本书适合谁阅读
无论你的背景如何,使用 R 都能改变你的工作方式。如果你是一个当前正在使用 R 的用户,渴望探索其在可视化和沟通方面的应用,或者你是一个尚未使用 R 的用户,正在考虑 R 是否适合你,那么这本书适合你。我写《R for the Rest of Us》这本书时,力求无论你是否写过 R 代码,都能轻松理解。但即使你已经编写过完整的 R 程序,本书也能帮助你学习许多新技巧,让你的技能更上一层楼。
R 是任何从事数据工作的人都能受益的强大工具。也许你是一名研究人员,正在寻找一种新的方式来分享你的研究成果。也许你是一名记者,想更高效地分析公共数据。或者你是一名数据分析师,厌倦了在昂贵的专有工具中工作。如果你需要处理数据,R 将为你提供价值。
本书介绍
每一章都专注于 R 语言的一个应用,并包括了实际 R 项目的示例,这些项目采用了本书所涉及的技术。我将深入分析项目代码,逐步讲解程序的工作原理,并建议如何在示例基础上进一步拓展。本书分为三部分,如下所示。
在第一部分中,你将学习如何使用 R 进行数据可视化。
第一章: R 编程速成课程 介绍了 RStudio 编程环境和理解本书其余内容所需的 R 基础语法。
第二章: 数据可视化原则 本章分析了《Scientific American》为美国干旱状况制作的可视化内容。在这个过程中,本章介绍了 ggplot2 数据可视化包,并阐述了帮助你制作高质量图形的重要原则。
第三章: 自定义数据可视化主题 本章介绍了 BBC 的记者如何为 ggplot2 数据可视化包制作一个自定义主题。随着本章内容的展开,你将学习如何制作自己的主题。
第四章: 地图与地理空间数据 探索如何使用简单特征数据在 R 中制作地图的过程。你将学习如何编写制图代码、查找地理空间数据、选择适当的投影,并应用数据可视化原则使你的地图更具吸引力。
第五章: 设计有效的表格 本章将展示如何使用 gt 包在 R 中制作高质量的表格。在 R 表格专家 Tom Mock 的指导下,你将学习如何遵循设计原则,呈现你的表格数据。
第二部分聚焦于如何使用 R Markdown 高效地进行沟通。你将学习如何将第一部分中讨论的可视化内容整合到报告、幻灯片演示和完全通过 R 代码生成的静态网站中。
第六章:R Markdown 报告 介绍了 R Markdown,一种可以生成专业报告的 R 工具。本章涵盖了 R Markdown 文档的结构,展示了如何使用内联代码在数据值发生变化时自动更新报告文本,并讨论了该工具的多种导出选项。
第七章:参数化报告 介绍了使用 R Markdown 的一个优势:使用名为参数化报告的技术,同时生成多个报告。你将看到城市研究所的工作人员如何利用 R 为美国 50 个州生成财政简报。在这个过程中,你将了解参数化报告的工作原理,以及如何使用它。
第八章:幻灯片演示 解释了如何使用 R Markdown 和 xaringan 包制作幻灯片。你将学习如何制作自己的演示文稿,调整内容以适应幻灯片,并为幻灯片添加特效。
第九章:网站 展示了如何使用 R Markdown 和 distill 包创建你自己的网站。通过研究关于纽约州韦斯特切斯特县 COVID-19 病例率的网站,你将了解如何创建网站页面,如何通过 R 包添加互动性,以及如何通过多种方式部署你的网站。
第十章:Quarto 解释了如何使用 Quarto,R Markdown 的下一代版本。你将学习如何使用 Quarto 来完成以前使用 R Markdown 进行的所有项目(报告、参数化报告、幻灯片演示和网站)。
第三部分专注于如何使用 R 自动化工作并与他人共享。
第十一章:自动访问在线数据 探讨了两个 R 包,允许你自动从互联网导入数据:googlesheets4 用于处理 Google Sheets,tidycensus 用于处理美国人口普查局数据。你将学习这些包如何工作,以及如何使用它们自动化数据访问过程。
第十二章:创建函数和包 展示了如何创建自己的函数和包并与他人共享,这是 R 的一个主要优势。将自定义函数打包成一个包,可以让其他 R 用户简化工作,就像你在阅读 R 开发者为莫菲特癌症中心的研究人员构建的包时所了解到的那样。
本书结束时,你应该能够使用 R 完成各种非统计任务。你将知道如何有效地可视化数据,并通过地图和表格传达你的发现。你将能够将结果整合到报告中,使用 R Markdown 高效地生成幻灯片演示和网站。你还将理解如何使用其他人开发的包或你自己开发的包来自动化许多繁琐的任务。让我们开始吧!
第一部分 可视化
第一章:1 R 编程速成课程

R 因学习难度大而声名显赫,尤其是对于那些没有编程经验的人。本章旨在帮助从未使用过 R 的人。你将通过 RStudio 设置一个 R 编程环境,并学习如何使用函数、对象、包和项目来处理数据。你还将了解 tidyverse 包,它包含了本书中使用的核心数据分析和操作功能。
本章不会提供 R 编程的完整介绍;它将集中介绍你跟随本书其余部分所需的知识。如果你有 R 的先前经验,可以跳到 第二章。
设置
要有效使用 R,你需要两个软件。第一个是 R 本身,它提供了支撑语言运作的计算工具。第二个是像 RStudio 这样的 集成开发环境(IDE)。这个编码平台简化了与 R 的工作。理解 R 和 RStudio 之间关系的最佳方式是参考 Chester Ismay 和 Albert Kim 在其著作 《通过数据科学进行统计推断:R 和 Tidyverse 的现代探究》 中的类比:R 就像是驱动数据的引擎,而 RStudio 就像是提供用户友好界面的仪表盘。
安装 R 和 RStudio
要下载 R,请访问 cloud.r-project.org 并选择与你操作系统相对应的链接。安装完成后,打开该文件。这将打开一个界面,就像 图 1-1 中所示的那样,允许你在操作系统的命令行中使用 R。例如,输入 2 + 2,你应该会看到 4。

图 1-1:R 控制台
一些勇敢的灵魂仅使用命令行与 R 工作,但大多数人选择使用 RStudio,它提供了一种查看文件、代码输出等的方式。你可以在 posit.co/download/rstudio-desktop/ 下载 RStudio。像安装其他应用一样安装 RStudio 并打开它。
探索 RStudio 界面
当你第一次打开 RStudio 时,你应该会看到 图 1-2 中显示的三个窗格。

图 1-2:RStudio 编辑器
左侧窗格应该看起来很熟悉。它类似于你在命令行中使用 R 时看到的屏幕。这被称为 控制台。你将使用它来输入代码并查看结果。这个窗格有几个标签页,例如 Terminal 和 Background Jobs,用于更高级的功能。现在,你将只使用默认的标签页。
在右下角,文件面板显示了你计算机上的所有文件。你可以点击任何一个文件,在 RStudio 中打开它。最后,在右上角是环境面板,它显示了在 RStudio 中工作时可用的对象。有关对象的讨论,请参见“将数据保存为对象”部分,见第 11 页。
还有一个你在 RStudio 中工作时通常会使用的面板,但要查看它,你首先需要创建一个 R 脚本文件。
R 脚本文件
如果你所有的代码都写在控制台中,你将没有任何记录。假设你今天坐下来导入数据、进行分析,然后制作一些图表。如果你在控制台中运行这些操作,明天你必须从头开始重新编写这些代码。但如果你将代码写入文件中,你就可以多次运行它。
R 脚本文件,使用.R扩展名,可以保存你的代码,以便以后运行。要创建一个 R 脚本文件,进入文件新建文件R 脚本,然后脚本文件面板应该出现在 RStudio 的左上角,如图 1-3 所示。将此文件保存到你的文档文件夹中,命名为sample-code.R。

图 1-3:脚本文件面板(左上角)
现在,你可以在新的面板中输入 R 代码,将其添加到你的脚本文件中。例如,尝试在脚本文件面板中输入 2 + 2,执行一个简单的加法运算。
要运行脚本文件,点击运行或使用快捷键:在 macOS 上是 COMMAND-ENTER,在 Windows 上是 CTRL-ENTER。结果(在此案例中是 4)应该会显示在控制台面板中。
现在,你已经有了一个可用的编程环境。接下来,你将使用它编写一些简单的 R 代码。
基本的 R 语法
如果你正在学习 R,可能希望执行比 2 + 2 更复杂的操作,但理解基础知识将为你后续更严肃的数据分析任务做好准备。让我们先了解一些基础内容。
算术运算符
除了+,R 还支持常见的算术运算符:- 用于减法,* 用于乘法,/ 用于除法。尝试在控制台中输入以下内容:
> **2 - 1**
1
> **3 * 3**
9
> **16 / 4**
4
如你所见,R 会返回你输入的每个计算结果。你不必像这里显示的那样在运算符周围加上空格,但这样做会使你的代码更具可读性。
你还可以使用括号一次执行多个运算并查看其结果。括号指定了 R 评估表达式的顺序。试试运行以下代码:
> **2 * (2 + 1)**
6
这段代码首先计算括号内的表达式 2 + 1,然后将结果乘以 2,得到 6。
R 还具有更高级的算术运算符,如**用于计算指数:
> **2 ** 3**
8
这相当于 2³,结果是 8。
要获取除法操作的余数,你可以使用%%运算符:
> **10 %% 3**
1
将 10 除以 3 会产生余数 1,这是 R 返回的值。
对于本书中的活动,你不需要使用这些高级算术运算符,但了解它们总是有益的。
比较运算符
R 还使用比较运算符,它让你测试一个值与另一个值的比较结果。R 会返回 TRUE 或 FALSE。例如,在控制台中输入 2 > 1:
> **2 > 1**
TRUE
R 应该返回 TRUE,因为 2 大于 1。
其他常见的比较运算符包括小于(<)、大于或等于(>=)、小于或等于(<=)、等于(==)和不等于(!=)。以下是一些示例:
> **498 == 498**
TRUE
> **2 != 2**
FALSE
当你在控制台输入 498 == 498 时,R 应该返回 TRUE,因为两个值是相等的。如果你在控制台运行 2 != 2,R 应该返回 FALSE,因为 2 并不不等于 2。
你很少会使用比较运算符直接测试一个值与另一个值的比较;相反,你会用它们来执行像仅保留大于某个阈值的数据这样的任务。你将在“tidyverse 函数”部分看到比较运算符是如何使用的,见 第 15 页。
函数
你可以通过利用 R 的许多函数来执行更多有用的操作,函数是预定义的代码段,可以让你高效地完成特定任务。函数有一个名称和一对括号,其中包含参数,这些参数是影响函数行为的值。
考虑一下 print() 函数,它用于显示信息:
> **print(x = 1.1)**
1.1
print() 函数的名称是 print。在函数的括号内,你需要指定参数名称——在这个例子中是 x,然后是等号(=)和一个值,表示要显示的内容。这段代码会打印数字 1.1。
要分隔多个参数,你可以使用逗号。例如,你可以使用 print() 函数的 digits 参数来指定显示数字的小数位数:
> **print(x = 1.1, digits = 1)**
1
这段代码将只显示一个数字(换句话说,显示整数)。
使用这两个参数让你可以做特定的事情(显示结果),同时又提供了灵活性来改变函数的行为。
注意
要查看所有内置 R 函数的列表,请参见 stat.ethz.ch/R-manual/R-devel/library/base/html/00Index.xhtml。
一个常见的 R 语言模式是将函数嵌套使用。例如,如果你想计算值 10、20 和 30 的均值或平均数,你可以使用 mean() 函数对 c() 函数的结果进行操作,如下所示:
> **mean(x = c(10, 20, 30))**
20
c() 函数将多个值组合成一个,这非常必要,因为 mean() 函数只接受一个参数。这就是为什么代码中有两个匹配的开括号和闭括号:一个用于 mean(),另一个是嵌套的,用于 c()。
在这个例子中,等号后面的值 c(10, 20, 30) 告诉 R 使用值 10、20 和 30 来计算均值。在控制台运行这段代码会返回值 20。
median() 和 mode() 函数与 c() 的使用方式相同。要了解如何使用一个函数以及它接受哪些参数,可以在控制台输入 ? 后跟函数名称,查看该函数的帮助文件。
接下来,让我们看看如何导入 R 程序需要处理的数据。
数据处理
R 可以让你完成类似于 Excel 中可能执行的所有数据操作任务,比如计算平均值或总计。然而,从概念上讲,R 中的数据处理与在 Excel 中的工作非常不同,因为在 Excel 中,数据和分析代码存在于同一个地方:电子表格中。虽然你在 R 中处理的数据可能看起来与你在 Excel 中处理的数据类似,但通常来自某个外部文件,因此你必须运行代码来导入它。
导入数据
你将从一个逗号分隔值(CSV)文件中导入数据,这是一个保存一系列相关值的文本文件,以逗号分隔。你可以使用大多数电子表格应用程序打开 CSV 文件,它们使用列而不是逗号作为分隔符。例如,图 1-4 展示了 Excel 中的population-by-state.csv文件。

图 1-4:Excel 中的 population-by-state.csv 文件
要在 R 中处理此文件,请从data.rfortherestofus.com/population-by-state.csv下载它。将它保存到你计算机上的一个位置,比如你的文档文件夹。
接下来,要将文件导入 R 中,在本章前面创建的sample-code.R文件中添加以下类似的行,将我的文件路径替换为你系统上文件位置的路径:
read.csv(file = "`/Users/davidkeyes/Documents`/population-by-state.csv")
read.csv()函数中的 file 参数指定要打开的文件的路径。
read.csv()函数可以接受额外的可选参数,用逗号分隔。例如,以下行除了文件外还使用了 skip 参数来导入相同的文件但跳过第一行:
read.csv(file = "`/Users/davidkeyes/Documents`/population-by-state.csv", skip = 1)
要了解此函数的其他参数,请在控制台中输入?read.csv()以查看其帮助文件。
此时,你可以运行代码来导入你的数据(不带 skip 参数)。在 RStudio 中的脚本文件窗格中高亮显示要运行的行,并点击Run。你应该在控制台窗格中看到以下输出:
#> rank State Pop Growth Pop2018
#> 1 1 California 39613493 0.0038 39461588
#> 2 2 Texas 29730311 0.0385 28628666
#> 3 3 Florida 21944577 0.0330 21244317
#> 4 4 New York 19299981 -0.0118 19530351
#> 5 5 Pennsylvania 12804123 0.0003 12800922
#> 6 6 Illinois 12569321 -0.0121 12723071
#> 7 7 Ohio 11714618 0.0033 11676341
#> 8 8 Georgia 10830007 0.0303 10511131
#> 9 9 North Carolina 10701022 0.0308 10381615
#> 10 10 Michigan 9992427 0.0008 9984072
`--snip--`
这是 R 确认它导入了 CSV 文件并理解其中数据的方式。四个变量显示了每个州的排名(按人口大小计算),名称,当前人口,Pop 和 Pop2018 变量之间的人口增长(以百分比表示),以及 2018 年人口。输出中隐藏了几个其他变量,但如果你自己导入这个 CSV 文件,你会看到它们。
你可能认为现在你已经准备好处理数据了,但实际上你所做的只是显示了导入数据的代码的结果。要真正使用数据,你需要将它保存到一个对象中。
将数据保存为对象
要保存你的数据以便重用,你需要创建一个对象。在本讨论中,对象是一个数据结构,用于以后使用。要创建一个对象,请更新你的数据导入语法,使其看起来像这样:
population_data <- read.csv(file = "`/Users/davidkeyes/Documents`/population-by-state.csv")
现在,这行代码包含了<- 赋值操作符,它将后面的内容赋值给左边的项目。赋值操作符左侧是 population_data 对象。结合起来,这整行代码将 CSV 文件导入并赋值给名为 population_data 的对象。
当你运行这段代码时,你应该能在你的环境窗格中看到 population_data,如图 1-5 所示。

图 1-5:环境窗格中的 population_data 对象
这条信息确认了你的数据导入成功,并且 population_data 对象已准备好用于未来使用。现在,你无需重新运行代码来导入数据,只需在 R 脚本文件或控制台中输入 population_data,就可以输出数据。
以这种方式导入到对象中的数据被称为数据框。你可以看到,population_data 数据框有 52 个观测值和 9 个变量。变量是数据框的列,每个列代表一个值(例如,每个州的人口)。正如你将在全书中看到的,你可以使用 R 代码添加新变量或修改现有变量。52 个观测值来自 50 个州,以及哥伦比亚特区和波多黎各。
安装包
你一直在使用的 read.csv()函数,以及之前看到的 mean()和 c()函数,来自base R,即内置的 R 函数集。要使用 base R 函数,你只需输入它们的名称。然而,R 作为开源语言的一个好处是,任何人都可以创建自己的代码并与他人分享。全球的 R 用户会制作 R 包,提供自定义函数以完成特定目标。
理解包的最佳类比也来自《通过数据科学的统计推断》一书。base R 中的功能就像智能手机内置的功能。智能手机本身可以做很多事情,但你通常需要安装额外的应用程序来完成特定任务。包就像应用程序,提供超出 base R 内置功能的功能。在第十二章中,你将创建自己的 R 包。
你可以使用 install.packages()函数安装包。你将使用 tidyverse 包,它提供了一系列用于数据导入、清理、分析、可视化等的函数。要安装它,输入 install.packages("tidyverse")。通常,你会在控制台中输入包安装代码,而不是在脚本文件中,因为你只需要在电脑上安装一次包,未来就可以访问其代码。
要确认 tidyverse 包是否已正确安装,在 RStudio 的右下角窗格中点击Packages标签。搜索 tidyverse,你应该会看到它弹出。
现在您已经安装了 tidyverse,接下来将开始使用它。虽然您只需要在每台计算机上安装一次包,但每次重新启动 RStudio 时,您需要加载它们。返回到sample-code.R文件,并使用 tidyverse 包中的函数重新导入数据(您的文件路径会略有不同):
library(tidyverse)
population_data_2 <- read_csv(file = "/`Users/davidkeyes/Documents/`population-by-state.csv")
脚本顶部的library(tidyverse)语句加载了 tidyverse 包。然后,包中的 read_csv()函数用于导入数据。请注意,函数名称中的下划线(_)替代了句点(.);这与您之前使用的基础 R 函数不同。使用 read_csv()导入 CSV 文件实现了相同的目标——创建一个对象,在本例中,称为 population_data_2。在控制台中输入 population_data_2,您应该会看到以下输出:
#> # A tibble: 52 × 9
#> rank State Pop Growth Pop2018 Pop2010
#> <dbl> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 1 California 39613493 0.0038 39461588 37319502
#> 2 2 Texas 29730311 0.0385 28628666 25241971
#> 3 3 Florida 21944577 0.0330 21244317 18845537
#> 4 4 New York 19299981 -0.0118 19530351 19399878
#> 5 5 Pennsylvania 12804123 0.0003 12800922 12711160
#> 6 6 Illinois 12569321 -0.0121 12723071 12840503
#> 7 7 Ohio 11714618 0.0033 11676341 11539336
#> 8 8 Georgia 10830007 0.0303 10511131 9711881
#> 9 9 North Carolina 10701022 0.0308 10381615 9574323
#> 10 10 Michigan 9992427 0.0008 9984072 9877510
#> # 42 more rows
#> # 3 more variables: growthSince2010 <dbl>, Percent <dbl>,
#> # density <dbl>
这些数据与您使用 read.csv()函数生成的数据略有不同。例如,R 只显示前 10 行。之所以会有这种差异,是因为 read_csv()导入的数据不是数据框,而是称为tibble的数据类型。数据框和 tibble 都用于描述矩形数据,就像电子表格中的数据一样。数据框和 tibble 之间有一些小的差异,其中最重要的是,tibble 默认只打印前 10 行,而数据框则打印所有行。对于本书的目的,两个术语可以互换使用。
RStudio 项目
到目前为止,您已经从Documents文件夹导入了一个 CSV 文件。但因为其他人计算机上的文件路径可能不同,所以他们尝试运行您的代码时将无法正常工作。解决这个问题的一个方法是使用 RStudio 项目。
通过在项目中工作,您可以使用相对路径来引用文件,而不必在调用导入数据的函数时写出完整的文件路径。然后,如果您将 CSV 文件放入项目中,任何人都可以通过使用文件名来打开它,像这样:read_csv(file = "population-by-state.csv")。这使得路径更容易编写,并使其他人能够使用您的代码。
要创建一个新的 RStudio 项目,请前往文件新建项目。选择新建目录或现有目录,然后选择项目的存储位置。如果选择新建目录,您需要指定想要创建一个新项目。接下来,为新目录选择一个名称并指定它的位置。(不要勾选创建 Git 仓库和使用 renv 的选项框,它们用于更高级的用途。)
创建项目后,你应该能看到 RStudio 外观上的两个主要变化。首先,文件面板不再显示你计算机上的所有文件,而是仅显示 example-project 目录中的文件。现在,它只显示 example-project.Rproj 文件,这表明该文件夹包含一个项目。其次,在 RStudio 右上角,你可以看到 example-project 的名称。之前这里显示的是 Project: (None)。如果你想确认自己正在一个项目中工作,请在这里检查其名称。图 1-6 显示了这些变化。

图 1-6:RStudio 中的活动项目
现在你已经创建了项目,请将 population-by-state.csv 文件复制到 example-project 目录中。完成后,你应该能在 RStudio 的文件面板中看到它。
现在将此 CSV 文件添加到项目中,你可以更轻松地导入它。像之前一样,首先加载 tidyverse 包。然后,移除对 Documents 文件夹的引用,直接使用文件名导入数据:
library(tidyverse)
population_data_2 <- read_csv(file = "population-by-state.csv")
你能够以这种方式导入 population-by-state.csv 文件的原因是 RStudio 项目将工作目录设置为项目的根目录。通过这种方式设置工作目录后,所有对文件的引用都是相对于项目根目录下的 .Rproj 文件的。因此,任何人都可以运行这段代码,因为它从一个在他们计算机上保证存在的位置导入数据。
使用 tidyverse 进行数据分析
现在你已经导入了人口数据,可以开始对其进行一些分析了。尽管我一直把 tidyverse 称为一个单一的包,但它实际上是一个包的集合。我们将在本书中探索其中的几个函数,但本节将介绍其基本工作流程。
tidyverse 函数
由于你已经加载了 tidyverse 包,现在可以访问它的函数。例如,该包的 summarize() 函数接受一个数据框或 tibble,并计算该数据集中的一个或多个变量的信息。以下代码使用 summarize() 计算所有州的平均人口:
summarize(.data = population_data_2, mean_population = mean(Pop))
首先,代码将 population_data_2 传递给 summarize() 函数的 .data 参数,告诉 R 使用该数据框进行计算。接下来,它创建了一个名为 mean_population 的新变量,并将其赋值为之前介绍的 mean() 函数的输出。mean() 函数运行在 Pop 上,这是 population_data_2 数据框中的一个变量。
你可能会想,为什么在 mean() 函数中不需要像本章前面所示的那样使用 c() 函数。原因是你这里只传递了一个参数给函数:Pop,它包含了你计算均值所需的人口数据集。在这种情况下,不需要使用 c() 来将多个值合并为一个。
运行此代码应该返回一个包含单个变量(mean_population)的 tibble,如下所示:
#> # A tibble: 1 × 1
#> mean_population
#> <dbl>
#> 1 6433422
该变量是双精度类型(dbl),用于存储一般的数字数据。其他常见的数据类型有整数(用于存储整数值,如 4、82、915)、字符(用于存储文本值)和逻辑(用于存储 TRUE/FALSE 值,通常来源于比较操作)。mean_population 变量的值为 6433422,这是所有州的平均人口。
还要注意,summarize()函数从原始的 population_data_2 数据框中创建了一个全新的 tibble。这就是为什么在输出中不再出现来自 population_data_2 的变量。
这是一个基本的数据分析示例,但你可以使用 tidyverse 做更多的事情。
tidyverse 管道
使用 tidyverse 的一个优势是,它使用管道进行多步骤操作。tidyverse 管道写作%>%,允许你将步骤拆分成多行。例如,你可以像这样重写你的代码:
population_data_2 %>%
summarize(mean_population = mean(Pop))
这段代码的意思是:“从 population_data_2 数据框开始,然后在它上面运行 summarize()函数,通过计算 Pop 变量的均值来创建一个名为 mean_population 的变量。”
注意,管道后的行有缩进。为了使代码更易于阅读,RStudio 会自动在管道后的行首添加两个空格。
当你在数据分析中使用多个步骤时,管道就显得更加有用。举个例子,假设你想计算五个最大州的平均人口。以下代码添加了一行,使用 filter()函数(同样来自 tidyverse 包)仅包括 rank 变量小于或等于(<=)5 的州。然后,使用 summarize()函数计算这些州的平均值:
population_data_2 %>%
filter(rank <= 5) %>%
summarize(mean_population = mean(Pop))
运行这段代码会返回五个最大州的平均人口:
#> # A tibble: 1 × 1
#> mean_population
#> <dbl>
#> 1 24678497
使用管道将多个函数组合在一起,让你能够以多种方式精炼数据,同时保持代码的可读性和易理解性。缩进也可以使代码更易于阅读。到目前为止,你只看到了几个用于分析的函数,但 tidyverse 还有许多其他函数,能够让你几乎做任何你想做的事情。由于 tidyverse 的强大,它将出现在本书中你写的每一段 R 代码里。
注意
《R for Data Science》第二版,由 Hadley Wickham、Mine Çetinkaya-Rundel 和 Garrett Grolemund 编写,是 tidyverse 编程的圣经,值得阅读以了解更多关于包中众多函数如何工作的细节。
注释
除了代码,R 脚本文件通常还包含注释——这些是以井号(#)开头的行,R 会忽略这些行,它们只是供阅读脚本的人做备注。例如,你可以在前一节的代码中添加一个注释,像这样:
# Calculate the mean population of the five largest states
population_data_2 %>%
filter(rank <= 5) %>%
summarize(mean_population = mean(Pop))
这个注释将帮助他人理解代码中发生的事情,如果你有一段时间没有处理这段代码,它还可以作为一个有用的提醒。R 会忽略任何以井号开头的行,而不是尝试运行它们。
如何获得帮助
现在你已经学会了 R 的基础,可能已经准备好深入编码了。不过,在编写代码时,你会遇到错误。当你遇到问题时,能够获得帮助是成功使用 R 的重要部分。你可以使用两种主要策略来解决问题。
第一个方法是阅读你使用的函数的文档。记住,要访问任何函数的文档,只需在控制台输入 ? 然后输入函数名。例如,在图 1-7 的右下角面板中,你可以看到运行 ?read.csv 的结果。

图 1-7:read.csv()函数的文档
帮助文件可能有点难以解读,但本质上它们描述了函数来源的包、函数的功能、它接受的参数,以及如何使用它的一些示例。
注意
有关阅读文档的额外指导,我推荐 Kieran Healy 的《数据可视化:实用入门》一书的附录。该书的在线免费版可在 socviz.co/appendix.xhtml.
第二种方法是阅读与许多 R 包相关的文档网站。这些网站通常比 RStudio 的帮助文件更容易阅读。此外,它们通常包含较长的文章,称为小插曲,提供了有关如何使用特定包的概述。阅读这些可以帮助你了解如何在更大的项目背景下组合各个函数。本书中讨论的每个包都有一个很好的文档网站。
总结
在本章中,你学习了 R 编程的基础。你了解了如何下载和设置 R 及 RStudio,RStudio 的各个面板的作用,以及 R 脚本文件的工作原理。你还学会了如何导入 CSV 文件并在 R 中进行探索,如何将数据保存为对象,以及如何安装包以访问额外的功能。然后,为了使代码中使用的文件更易于访问,你创建了一个 RStudio 项目。最后,你尝试了 tidyverse 函数和 tidyverse 管道,并学会了如何在这些函数没有按预期工作时获得帮助。
现在你已经掌握了基础,准备开始使用 R 处理数据了。我们在第二章见!
额外资源
-
Kieran Healy, 数据可视化:实用入门(普林斯顿,新泽西州:普林斯顿大学出版社,2018),
socviz.co。 -
Chester Ismay 和 Albert Y. Kim, 统计推断与数据科学:R 和 Tidyverse 的现代深入探索(佛罗里达州博卡拉顿:CRC 出版社,2020),
moderndive.com。 -
David Keyes, “R 入门”,在线课程,访问日期:2023 年 11 月 10 日,
rfortherestofus.com/courses/getting-started。 -
哈德利·威克姆(Hadley Wickham)、米娜·切廷卡亚-伦德尔(Mine Çetinkaya-Rundel)和加勒特·格罗莱蒙德(Garrett Grolemund),R 数据科学,第二版。(加利福尼亚州塞巴斯托波尔:O'Reilly Media,2023 年)。
第二章:2 数据可视化的原则

2021 年春季,几乎整个美国西部都处于干旱状态。南加州官员在 4 月宣布进入水源紧急状态,称面临前所未有的状况。对于加利福尼亚和其他西部州的居民来说,这可能并不算新闻。像 2021 年西部的干旱情况一样,干旱越来越常见,但如何准确而有说服力地传达这一问题的程度仍然非常困难。如何以既准确又引人注目的方式呈现这些数据,促使人们关注?
数据可视化设计师 Cédric Scherer 和 Georgios Karamanis 在 2021 年秋季承担了这个挑战,为《科学美国人》杂志制作了过去二十年美国干旱状况的图表。他们转向了 ggplot2 包,将来自国家干旱中心的干巴巴的数据(不好意思,打了个小小的双关)转化为视觉上引人注目且富有冲击力的可视化。
本章深入探讨了 Scherer 和 Karamanis 创建的干旱数据可视化为何有效,并向你介绍了图形语法,这是一种用于理解图表的理论,支持 ggplot2 包的基础。随后,你将通过一步一步地重现干旱图表,学习如何使用 ggplot2。在此过程中,我将重点介绍一些高质量数据可视化的关键原则,帮助你提升自己的工作。
干旱可视化
其他新闻机构也在他们的报道中使用了相同的国家干旱中心数据,但 Scherer 和 Karamanis 将其可视化,使其既能吸引注意力,又能传达现象的规模。图 2-1 展示了最终可视化的一部分(由于空间限制,我只能包含四个区域)。该图表清晰地显示了过去二十年干旱状况的增加,尤其是在加利福尼亚和西南地区。
为了理解为什么这种可视化有效,我们来逐步分析。最广泛层面上,这种数据可视化以其极简的美学而著称。例如,图表没有网格线,文本标签也很少,轴线上的文字也非常简约。Scherer 和 Karamanis 去除了统计学家 Edward Tufte 在其 1983 年出版的《定量信息的视觉呈现》(Graphics Press)一书中所称的图表垃圾。Tufte 写道,冗余的元素往往会阻碍我们对图表的理解,而不是帮助我们理解(研究人员和数据可视化设计师普遍对此认同)。
需要证明 Scherer 和 Karamanis 的简化版图表比其他版本更好吗?图 2-2 展示了通过调整代码,添加了网格线和轴线上的文本标签的版本。

图 2-1:最终干旱可视化的一部分,经过一些调整以适应本书内容

图 2-2:干旱可视化的杂乱版本
这不仅仅是因为这个杂乱的版本看起来更糟;这种杂乱实际上会妨碍理解。我们的大脑没有集中在整体的干旱模式(图表的重点),而是被重复和不必要的轴文本困住了。
减少杂乱的最佳方法之一是将单个图表拆分成一组组件图表,正如 Scherer 和 Karamanis 所做的那样(这种方法叫做分面,将在“图表的分面”章节中进一步讨论,第 36 页)。每个矩形代表某一年中的一个地区。将更大的图表筛选出显示 2003 年西南地区的情况,便得到了图 2-3,其中 x 轴表示周,y 轴表示该地区在不同干旱水平下的百分比。

图 2-3:2003 年西南地区的干旱可视化
放大单一区域的单一年份也使颜色选择更为显眼。最浅的橙色条(这里打印的最浅灰色)显示该地区异常干旱的百分比,最深的紫色条(这里打印的最深灰色)显示经历极端干旱条件的百分比。正如你稍后会看到的,这一颜色范围的选择是有意为之,目的是让所有读者都能清晰地看到干旱水平的差异。
尽管图表复杂,Scherer 和 Karamanis 为生成它所编写的 R 代码相对简单,这在很大程度上归功于一种叫做图形语法的理论。
图形语法
在使用 Excel 时,你首先需要选择你想制作的图表类型。需要柱状图吗?点击柱状图图标。需要折线图吗?点击折线图图标。如果你只在 Excel 中制作过图表,这第一步可能看起来太显而易见,你甚至没有深入思考数据可视化过程,但实际上有许多方式可以思考图表。例如,我们可以不将图表类型视为独立的,而是从它们的共性出发,作为制作图表的起点。
这种思考图表的方式源于已故统计学家利兰·威尔金森。多年来,威尔金森深入思考数据可视化是什么,以及我们如何描述它。1999 年,他出版了一本名为《图形语法》(Springer)的书,试图开发一种一致的方式来描述所有图表。在书中,威尔金森认为我们不应将图表视为像 Excel 中那样的独立类型,而应将其视为遵循一种语法,我们可以用来描述任何图表。就像英语语法告诉我们名词后通常跟动词(这就是为什么“he goes”是对的,而“goes he”不对),图形语法帮助我们理解为什么某些图表类型“有效”。
从图形语法的角度思考数据可视化有助于突出显示,例如图表通常有一些数据被绘制在 x 轴上,另一些数据则绘制在 y 轴上。无论图表是条形图还是折线图,情况都是如此,正如图 2-4 所示。

图 2-4:展示相同数据的条形图和折线图
尽管这些图表看起来不同(对于 Excel 用户来说,它们属于不同类型的图表),威尔金森的图形语法强调它们的相似性。(顺便提一下,威尔金森对像 Excel 这样的图表工具的看法在他写到“多数图表软件将用户的请求导向一系列固定的图表类型”时变得清晰可见。)
当威尔金森写这本书时,还没有任何数据可视化工具能够实现他的图形语法。直到 2010 年,哈德利·威克汉姆(Hadley Wickham)在《计算与图形统计学杂志》上发表了文章《层次化图形语法》,并宣布推出了 ggplot2 包,这一局面才发生了变化。通过提供实现威尔金森思想的工具,ggplot2 彻底改变了数据可视化的世界。
使用 ggplot
ggplot2 R 包(我和几乎所有数据可视化领域的人一样,简称为ggplot)依赖于图表具有多个层次的理念。本节将带你了解一些最重要的层次。你将首先选择映射到美学属性的变量。然后,你将选择一个几何对象来表示你的数据。接下来,你将使用 scale_ 函数来改变图表的美学属性(例如颜色方案)。最后,你将使用 theme_ 函数来设置图表的整体外观和风格。
将数据映射到美学属性
要用 ggplot 创建图表,你首先需要将数据映射到美学属性。其实这只是意味着你使用 x 轴或 y 轴、颜色、大小等元素(即所谓的美学属性)来表示变量。你将使用在图 2-4 中介绍的阿富汗人均寿命数据来生成一个图表。要访问这些数据,可以输入以下代码:
**library(tidyverse)**
**gapminder_10_rows <- read_csv("https://data.rfortherestofus.com/data/gapminder_10_rows.csv")**
这段代码首先加载了在第一章中介绍的 tidyverse 包,然后使用 read_csv()函数从书籍网站获取数据,并将其赋值给 gapminder_10_rows 对象。
结果得到的 gapminder_10_rows tibble 如下所示:
#> # A tibble: 10 × 6
#> country continent year lifeExp pop gdpPercap
#> <fct> <fct> <int> <dbl> <int> <dbl>
#> 1 Afghanistan Asia 1952 28.8 8425333 779.
#> 2 Afghanistan Asia 1957 30.3 9240934 821.
#> 3 Afghanistan Asia 1962 32.0 10267083 853.
#> 4 Afghanistan Asia 1967 34.0 11537966 836.
#> 5 Afghanistan Asia 1972 36.1 13079460 740.
#> 6 Afghanistan Asia 1977 38.4 14880372 786.
#> 7 Afghanistan Asia 1982 39.9 12881816 978.
#> 8 Afghanistan Asia 1987 40.8 13867957 852.
#> 9 Afghanistan Asia 1992 41.7 16317921 649.
#> 10 Afghanistan Asia 1997 41.8 22227415 635.
这个输出是完整版 gapminder 数据框的简短版本,包含超过 1700 行数据。
在使用 ggplot 制作图表之前,你需要决定哪个变量放在 x 轴,哪个放在 y 轴。对于显示时间变化的数据,通常将日期(在此例中是年份)放在 x 轴,将变化的数值(在此例中是 lifeExp)放在 y 轴。为此,定义 ggplot()函数如下:
ggplot(
data = gapminder_10_rows,
mapping = aes(
x = year,
y = lifeExp
)
)
这个函数包含许多参数。每个参数单独占一行,以提高可读性,参数之间用逗号分隔。data 参数告诉 R 使用数据框 gapminder_10_rows,mapping 参数将 year 映射到 x 轴,lifeExp 映射到 y 轴。
运行此代码生成了图 2-5 中的图表,虽然看起来还不怎么特别。

图 2-5:一个空白图表,将年份值映射到 x 轴,将预期寿命值映射到 y 轴
请注意,x 轴对应年份,y 轴对应预期寿命,并且两个轴上的数值与数据的范围相匹配。在 gapminder_10_rows 数据框中,第一年是 1952 年,最后一年是 1997 年。x 轴的范围是根据这些数据创建的。同样,预期寿命(lifeExp)的数值从约 28 上升到约 42,能够很好地适配到 y 轴上。
选择几何对象
坐标轴很好,但图表缺乏任何类型的数据可视化表示。要获得这些,你需要添加下一个 ggplot 层:几何对象(geoms)。几何对象(geoms)是提供不同数据表示方式的函数。例如,要向图表中添加点,可以使用 geom_point():
ggplot(
data = gapminder_10_rows,
mapping = aes(
x = year,
y = lifeExp
)
) +
**geom_point()**
现在图表显示,1952 年的人均预期寿命约为 28 岁,并且数据集中的每一年这个值都有所上升(见图 2-6)。

图 2-6:添加了点的预期寿命图表
假设你改变主意,想改为绘制线图。只需将 geom_point() 替换为 geom_line(),如下所示:
ggplot(
data = gapminder_10_rows,
mapping = aes(
x = year,
y = lifeExp
)
) +
**geom_line()**
图 2-7 显示了结果。

图 2-7:相同数据的线图
如果你想让图表更加精美,可以同时添加 geom_point() 和 geom_line(),如下所示:
ggplot(
data = gapminder_10_rows,
mapping = aes(
x = year,
y = lifeExp
)
) +
**geom_point() +**
**geom_line()**
这段代码生成了带点的线图,如图 2-8 所示。

图 2-8:包含点和线的相同数据
你可以用 geom_col() 来创建一个条形图:
ggplot(
data = gapminder_10_rows,
mapping = aes(
x = year,
y = lifeExp
)
) +
**geom_col()**
请注意,在图 2-9 中,y 轴的范围已自动更新,从 0 到 40,以适应不同的几何对象(geom)。

图 2-9:以条形图表示的预期寿命数据
如你所见,线图和条形图之间的差异并不像 Excel 图表类型选择器所展示的那样大。两者都可以具有相同的基本属性(即,x 轴为年份,y 轴为预期寿命)。它们只是使用不同的几何对象来以可视化的方式展示数据。
许多几何对象已内置于 ggplot 中。除了 geom_bar()、geom_point() 和 geom_line(),geom_histogram()、geom_boxplot() 和 geom_area() 是最常用的几何对象。要查看所有几何对象,请访问 ggplot 文档网站:ggplot2.tidyverse.org/reference/index.xhtml#geoms。
修改美学属性
在我们回到旱灾数据可视化之前,先看一些额外的层,看看如何改变条形图的外观。假设你想改变条形的颜色。在图形语法方法中,这意味着将某个变量映射到填充的美学属性。(对于条形图,美学属性颜色只会改变条形的轮廓。)就像你将年份映射到 x 轴,生命预期(lifeExp)映射到 y 轴一样,你也可以将填充映射到一个变量,例如年份:
ggplot(
data = gapminder_10_rows,
mapping = aes(
x = year,
y = lifeExp,
**fill = year**
)
) +
geom_col()
图 2-10 显示了结果。现在,较早的年份的填充颜色更深,较晚的年份的填充颜色更浅(这也在图表右侧的图例中有所指示)。

图 2-10:相同的图表,现在添加了颜色
要更改填充颜色,使用一个新的 scale 层,调用 scale_fill_viridis_c() 函数(函数名末尾的 c 表示数据是连续的,意味着它可以取任何数字值):
ggplot(
data = gapminder_10_rows,
mapping = aes(
x = year,
y = lifeExp,
fill = year
)
) +
geom_col() +
**scale_fill_viridis_c()**
这个函数将默认调色板更改为对色盲友好的颜色,并且在灰度打印时效果良好。scale_fill_viridis_c() 函数只是许多以 scale_ 开头并可以改变填充尺度的函数之一。《ggplot2:数据分析的优雅图形》第三版的第十一章讨论了各种颜色和填充尺度。你可以在线阅读它,网址是 ggplot2-book.org/scales-colour.xhtml。
设置主题
我们将要看的最后一个层是主题层,它允许你改变图表的整体外观和感觉(包括背景和网格线)。与 scale_ 函数类似,许多函数也以 theme_ 开头。你可以如下添加 theme_minimal():
ggplot(
data = gapminder_10_rows,
mapping = aes(
x = year,
y = lifeExp,
fill = year
)
) +
geom_col() +
scale_fill_viridis_c() +
**theme_minimal()**
这个主题开始清理图表中的杂乱,正如你在图 2-11 中看到的那样。

图 2-11:添加 theme_minimal() 后的相同图表
到目前为止,你应该明白为什么 Hadley Wickham 将 ggplot2 包描述为使用图形的分层语法。它通过创建多个层次来实现 Wilkinson 的理论:首先,映射到美学属性的变量;其次,表示数据的几何图形(geoms);第三,调整美学属性的 scale_ 函数;最后,设置图表整体外观和感觉的 theme_ 函数。
你仍然可以在许多方面改进这个图表,但我们不如回到 Scherer 和 Karamanis 的旱灾数据可视化。通过逐步分析他们的代码,你将学到如何使用 ggplot 和 R 创建高质量的数据可视化。
重新创建旱灾可视化
旱灾可视化代码依赖于 ggplot 基础知识和一些鲜为人知的调整,使其非常出色。为了理解 Scherer 和 Karamanis 是如何制作他们的数据可视化的,我们将从一个简化版本的代码开始,然后逐层构建,逐步添加元素。
首先,您将导入数据。Scherer 和 Karamanis 对原始数据进行了大量的数据整理,但我为您保存了简化的输出。由于它是 JavaScript 对象表示法(JSON)格式,Scherer 和 Karamanis 使用 rio 包中的 import() 函数,这简化了导入 JSON 数据的过程:
**library(rio)**
**dm_perc_cat_hubs <- import("https://data.rfortherestofus.com/dm_perc_cat_hubs.json"))**
JSON 是用于 Web 应用程序的数据的常见格式,尽管在 R 中使用得较少,且处理起来可能比较复杂。幸运的是,rio 包简化了它的导入。
绘制一个区域和年份
Scherer 和 Karamanis 的最终图表包含多个年份和区域。为了了解他们是如何创建的,我们将首先查看 2003 年的西南地区。
首先,您需要创建一个数据框。您将使用 filter() 函数两次:第一次仅保留西南地区的数据,第二次仅保留 2003 年的数据。在这两种情况下,您使用以下语法:
filter(`variable_name == value`)
这告诉 R 仅保留变量名称等于某个值的观察。代码从 dm_perc_cat_hubs_raw 数据框开始,然后对其进行过滤,并将其保存为一个名为 southwest_2003 的新对象:
southwest_2003 <- dm_perc_cat_hubs %>%
filter(hub == "Southwest") %>%
filter(year == 2003)
要查看这个对象并查看您可以使用的变量,请在控制台中输入 southwest_2003,这应该会返回以下输出:
#> # A tibble: 255 × 7
#> date hub category percentage year week max_week
#> <date> <fct> <fct> <dbl> <dbl> <dbl> <dbl>
#> 1 2003-12-30 Sout... D0 0.0718 2003 52 52
#> 2 2003-12-30 Sout... D1 0.0828 2003 52 52
#> 3 2003-12-30 Sout... D2 0.2693 2003 52 52
#> 4 2003-12-30 Sout... D3 0.3108 2003 52 52
#> 5 2003-12-30 Sout... D4 0.0796 2003 52 52
#> 6 2003-12-23 Sout... D0 0.0823 2003 51 52
#> 7 2003-12-23 Sout... D1 0.1312 2003 51 52
#> 8 2003-12-23 Sout... D2 0.1886 2003 51 52
#> 9 2003-12-23 Sout... D3 0.3822 2003 51 52
#> 10 2003-12-23 Sout... D4 0.0828 2003 51 52
#> # 245 more rows
日期变量表示观察发生的周的开始日期。hub 变量是区域,category 是干旱水平:D0 表示最低的干旱水平,而 D5 表示最高的干旱水平。percentage 变量是该区域在该干旱类别中的百分比,范围从 0 到 1。year 和 week 变量分别是观察的年份和周数(从第 1 周开始)。max_week 变量是给定年份的最大周数。
现在您可以使用这个 southwest_2003 对象进行绘图:
ggplot(
data = **southwest_2003**,
aes(
x = week,
y = percentage,
fill = category
)
) +
geom_col()
ggplot() 函数告诉 R 将周数放在 x 轴上,将百分比放在 y 轴上,并使用类别变量作为填充颜色。geom_col() 函数创建一个条形图,其中每个条形的填充颜色表示该特定周该区域在每个干旱水平下的百分比,如 图 2-12 所示。

图 2-12:干旱可视化的一个年份(2003)和区域(西南)
这些颜色,包括明亮的粉色、蓝色、绿色和红色(在这里以灰度显示),与图表的最终版本不匹配,但您可以开始看到 Scherer 和 Karamanis 数据可视化的轮廓。
更改美学属性
接下来,Scherer 和 Karamanis 为他们的条形图选择了不同的填充颜色。为此,他们使用了 scale_fill_viridis_d() 函数。这里的 d 表示填充比例应用于具有离散类别的数据(D0、D1、D2、D3、D4 和 D5):
ggplot(
data = southwest_2003,
aes(
x = week,
y = percentage,
fill = category
)
) +
geom_col() +
**scale_fill_viridis_d(**
**option = "rocket",**
**direction = -1**
**)**
他们使用了参数 option = "rocket" 来选择 rocket 配色方案,该方案的颜色范围从奶油色到接近黑色。你还可以在 scale_fill_viridis_d() 函数中使用其他多个配色方案;可以在 sjmgarnier.github.io/viridisLite/reference/viridis.xhtml 查看它们。
然后,他们使用 direction = -1 参数反转了填充颜色的顺序,使得颜色越深表示干旱条件越严重。
Scherer 和 Karamanis 还调整了 x 轴和 y 轴的外观:
ggplot(
data = southwest_2003,
aes(
x = week,
y = percentage,
fill = category
)) +
geom_col() +
scale_fill_viridis_d(
option = "rocket",
direction = -1
) +
**scale_x_continuous(**
**name = NULL,**
**guide = "none"**
**) +**
**scale_y_continuous(**
**name = NULL,**
**labels = NULL,**
**position = "right"**
**)**
在 x 轴上,他们通过使用 name = NULL 移除了轴标题(“week”)和通过 guide = "none" 移除了轴标签(编号为 0 到 50 的周数)。在 y 轴上,他们通过 labels = NULL 移除了标题和显示百分比的文本,实际上这与 guide = "none" 起到了相同的作用。他们还通过 position = "right" 将轴线本身移到了右侧。此时这些轴线仅作为刻度标记出现,但稍后会变得更加明显。图 2-13 展示了这些调整的结果。

图 2-13:带有 x 轴和 y 轴调整的 2003 年西南部干旱数据
到目前为止,我们已经关注了组成较大数据可视化的单个图表。但 Scherer 和 Karamanis 实际上制作了 176 个图表,展示了 22 年和 8 个区域的数据。接下来,我们将讨论他们用来创建所有这些图表的 ggplot 特性。
对图表进行分面
ggplot 最有用的功能之一就是 分面(或在数据可视化领域更常见的术语 小型多图)。分面使用一个变量将单个图表拆分为多个图表。例如,想象一下一个显示各国预期寿命随时间变化的折线图;与其在一个图表中绘制多条线,分面会创建多个图表,每个图表显示一条线。为了指定要放在分面图表的行和列中的变量,使用 facet_grid() 函数,就像 Scherer 和 Karamanis 在他们的代码中做的那样:
dm_perc_cat_hubs %>%
filter(hub %in% c(
"Northwest",
"California",
"Southwest",
"Northern Plains"
)) %>%
ggplot(aes(
x = week,
y = percentage,
fill = category
)) +
geom_col() +
scale_fill_viridis_d(
option = "rocket",
direction = -1
) +
scale_x_continuous(
name = NULL,
guide = "none"
) +
scale_y_continuous(
name = NULL,
labels = NULL,
position = "right"
) +
**facet_grid(**
**rows = vars(year),**
**cols = vars(hub),**
**switch = "y"**
**)**
Scherer 和 Karamanis 将年份放在行中,将区域(hub)放在列中。switch = "y" 参数将年份标签从右侧(默认显示的位置)移动到左侧。将此代码应用后,你可以在图 2-14 中看到最终的图表效果。

图 2-14:干旱可视化的分面版本
难以置信的是,图表的轮廓只用了 10 行代码就创建出来了。其余的代码属于小幅修饰的范畴。这并不是说小幅修饰不重要(非常重要),或者它们的创建过程不费时(其实非常费时)。然而,这也显示出,即便是少量的 ggplot 代码,也能起到很大的作用。
添加最终修饰
现在让我们来看一下 Scherer 和 Karamanis 做的一些小的改进。第一个是应用主题。他们使用了 theme_light(),它去除了默认的灰色背景,并通过 base_family 参数将字体更改为 Roboto。
theme_light() 函数被称为完整主题,它会改变图表的整体外观和感觉。ggplot 包有多个完整的主题可供使用(它们在 ggplot2.tidyverse.org/reference/index.xhtml#themes 中列出)。个人和组织也会制作自己的主题,就像你在 第三章 中要做的那样。关于你可能考虑使用哪些主题,参考我在 rfortherestofus.com/2019/08/themes-to-improve-your-ggplot-figures 上的博客文章。
Scherer 和 Karamanis 不仅仅停留在应用 theme_light() 上。他们还使用了 theme() 函数对图表设计进行了进一步调整:
dm_perc_cat_hubs %>%
filter(hub %in% c(
"Northwest",
"California",
"Southwest",
"Northern Plains"
)) %>%
ggplot(aes(
x = week,
y = percentage,
fill = category
)) +
geom_rect(
aes(
xmin = .5,
xmax = max_week + .5,
ymin = -0.005,
ymax = 1
),
fill = "#f4f4f9",
color = NA,
size = 0.4
) +
geom_col() +
scale_fill_viridis_d(
option = "rocket",
direction = -1
) +
scale_x_continuous(
name = NULL,
guide = "none"
) +
scale_y_continuous(
name = NULL,
labels = NULL,
position = "right"
) +
facet_grid(
rows = vars(year),
cols = vars(hub),
switch = "y"
) +
theme_light(base_family = "Roboto") +
theme(
axis.title = element_text(
size = 14,
color = "black"
),
axis.text = element_text(
family = "Roboto Mono",
size = 11
),
❶ axis.line.x = element_blank(),
axis.line.y = element_line(
color = "black",
size = .2
),
axis.ticks.y = element_line(
color = "black",
size = .2
),
axis.ticks.length.y = unit(2, "mm"),
❷ legend.position = "top",
legend.title = element_text(
color = "#2DAADA",
face = "bold"
),
legend.text = element_text(color = "#2DAADA"),
strip.text.x = element_text(
hjust = .5,
face = "plain",
color = "black",
margin = margin(t = 20, b = 5)
),
strip.text.y.left = element_text(
❸ angle = 0,
vjust = .5,
face = "plain",
color = "black"
),
strip.background = element_rect(
fill = "transparent",
color = "transparent"
),
❹ panel.grid.minor = element_blank(),
panel.grid.major = element_blank(),
panel.spacing.x = unit(0.3, "lines"),
panel.spacing.y = unit(0.25, "lines"),
❺ panel.background = element_rect(
fill = "transparent",
color = "transparent"
),
panel.border = element_rect(
color = "transparent",
size = 0
),
plot.background = element_rect(
fill = "transparent",
color = "transparent",
size = .4
),
plot.margin = margin(rep(18, 4))
)
)
theme() 函数中的代码做了许多不同的事情,但我们来看几个最重要的。首先,它将图例从右侧(默认位置)移到了图表的顶部 ❷。然后,angle = 0 参数将列中的年份文字从垂直方向旋转为水平方向 ❸。如果没有这个参数,年份会难以辨认。
theme() 函数还在最终图表的右侧生成了独特的坐标轴线和刻度 ❶。调用 element_blank() 会去除所有网格线 ❹。最后,这段代码去除了边框,并给每个子图设置了透明背景 ❺。
你可能在想,等一下,单独的图表后面不是有灰色背景吗? 是的,亲爱的读者,的确有。Scherer 和 Karamanis 使用了一个单独的 geom,geom_rect() 来实现这一点:
geom_rect(
aes(
xmin = .5,
xmax = max_week + .5,
ymin = -0.005,
ymax = 1
),
fill = "#f4f4f9",
color = NA,
size = 0.4
)
他们还设置了一些特定于这个 geom 的额外美学属性——xmin、xmax、ymin 和 ymax——这些属性决定了它所生成的矩形的边界。结果是在每个小的子图后面加上了一个灰色背景,如 图 2-15 所示。

图 2-15:带有灰色背景的干旱可视化,每个小图都有一个灰色背景
最后,Scherer 和 Karamanis 对图例做了一些调整。之前你看到的是 scale_fill_viridis_d() 函数的简化版本。这里是一个更完整的版本:
scale_fill_viridis_d(
option = "rocket",
direction = -1,
name = "Category:",
labels = c(
"Abnormally Dry",
"Moderate Drought",
"Severe Drought",
"Extreme Drought",
"Exceptional Drought"
)
)
name 参数设置图例标题,labels 参数指定图例中显示的标签。图 2-16 展示了这些更改的结果。

图 2-16:更改图例文本的干旱可视化
现在,图例中的文本从 D0、D1、D2、D3 和 D4 改成了“不正常干旱”、“中度干旱”、“严重干旱”、“极端干旱”和“异常干旱”——这些类别更加用户友好。
完整的可视化代码
虽然我已经向你展示了 Scherer 和 Karamanis 编写的几乎完整的代码版本,但我做了一些小的修改以使其更易于理解。如果你感兴趣,完整代码可以在这里找到:
ggplot(dm_perc_cat_hubs, aes(week, percentage)) +
geom_rect(
aes(
xmin = .5,
xmax = max_week + .5,
ymin = -0.005,
ymax = 1
),
fill = "#f4f4f9",
color = NA,
size = 0.4,
show.legend = FALSE
) +
geom_col(
aes(
fill = category,
fill = after_scale(addmix(
darken(
fill,
.05,
space = "HLS"
),
"#d8005a",
.15
)),
color = after_scale(darken(
fill,
.2,
space = "HLS"
))
),
width = .9,
size = 0.12
) +
facet_grid(
rows = vars(year),
cols = vars(hub),
switch = "y"
) +
coord_cartesian(clip = "off") +
scale_x_continuous(
expand = c(.02, .02),
guide = "none",
name = NULL
) +
scale_y_continuous(
expand = c(0, 0),
position = "right",
labels = NULL,
name = NULL
) +
scale_fill_viridis_d(
option = "rocket",
name = "Category:",
direction = -1,
begin = .17,
end = .97,
labels = c(
"Abnormally Dry",
"Moderate Drought",
"Severe Drought",
"Extreme Drought",
"Exceptional Drought"
)
) +
guides(fill = guide_legend(
nrow = 2,
override.aes = list(size = 1)
)) +
theme_light(
base_size = 18,
base_family = "Roboto"
) +
theme(
axis.title = element_text(
size = 14,
color = "black"
),
axis.text = element_text(
family = "Roboto Mono",
size = 11
),
axis.line.x = element_blank(),
axis.line.y = element_line(
color = "black",
size = .2
),
axis.ticks.y = element_line(
color = "black",
size = .2
),
axis.ticks.length.y = unit(2, "mm"),
legend.position = "top",
legend.title = element_text(
color = "#2DAADA",
size = 18,
face = "bold"
),
legend.text = element_text(
color = "#2DAADA",
size = 16
),
strip.text.x = element_text(
size = 16,
hjust = .5,
face = "plain",
color = "black",
margin = margin(t = 20, b = 5)
),
strip.text.y.left = element_text(
size = 18,
angle = 0,
vjust = .5,
face = "plain",
color = "black"
),
strip.background = element_rect(
fill = "transparent",
color = "transparent"
),
panel.grid.minor = element_blank(),
panel.grid.major = element_blank(),
panel.spacing.x = unit(0.3, "lines"),
panel.spacing.y = unit(0.25, "lines"),
panel.background = element_rect(
fill = "transparent",
color = "transparent"
),
panel.border = element_rect(
color = "transparent",
size = 0
),
plot.background = element_rect(
fill = "transparent",
color = "transparent",
size = .4
),
plot.margin = margin(rep(18, 4))
)
还有一些关于颜色和间距的小调整,但大部分代码和你到目前为止看到的相差无几。
总结
你可能会认为 ggplot 是解决所有数据可视化问题的答案。是的,你手中有了一把新的锤子,但并不是所有问题都是钉子。如果你查看 2021 年 11 月《科学美国人》上出现的数据可视化版本,你会发现其中一些注释在我们的重现中并不可见。这是因为它们是在后期制作中添加的。虽然你可以在 ggplot 中找到方法来创建这些注释,但这通常不是最有效的时间利用方式。用 ggplot 完成 90% 的工作,然后使用 Illustrator、Figma 或类似的工具来完善你的作品。
即便如此,ggplot 仍然是一个非常强大的工具,用来制作你在纽约时报、FiveThirtyEight、BBC 以及其他知名新闻媒体中见过的图表。虽然它不是唯一能够生成高质量数据可视化的工具,但它使得这一过程变得简单直接。Scherer 和 Karamanis 的图表在多个方面展示了这一点:
-
它去除了多余的元素,比如网格线,保持对数据本身的关注。像 theme_light() 和 theme() 函数这样的完整主题帮助 Scherer 和 Karamanis 创建了一个简洁、有效传达信息的可视化。
-
它使用了精心挑选的颜色。scale_fill_viridis_d() 函数使他们能够创建一种色彩方案,能够展示不同组之间的差异,且对色盲友好,在打印成灰度时也表现良好。
-
它使用分面将来自二十年和八个地区的数据拆解成一组图表,并将这些图表结合起来创建一个单一的图。通过一次调用 facet_grid() 函数,Scherer 和 Karamanis 创建了超过 100 个小型子图,工具会自动将它们合并为一个单一图表。
学习在 ggplot 中创建数据可视化需要投入相当的时间。但长期来看,这种投入的回报更为丰厚。一旦你了解了 ggplot 的工作原理,你就能查看他人的代码并学会如何改进自己的代码。相比之下,当你在 Excel 中制作数据可视化时,一系列点击操作会消失在虚空中。要重新创建你上周做过的可视化,你需要记得你当时使用的确切步骤;而要制作别人做的数据可视化,你则需要他们为你写出自己的过程。
由于基于代码的数据可视化工具允许你记录每一个步骤,你不必是最有才华的设计师就能用 ggplot 制作高质量的数据可视化。你可以研究他人的代码,将其调整为自己的需求,并创造出既美观又能有效传达信息的数据可视化。
额外资源
-
Will Chase,“图形的魅力”,在线课程,访问时间:2023 年 11 月 6 日,
rfortherestofus.com/courses/glamour/。 -
Kieran Healy,数据可视化:实用导论(新泽西州普林斯顿:普林斯顿大学出版社,2018 年),
socviz.co。 -
Cédric Scherer,使用 ggplot2 的图形设计(佛罗里达州博卡拉顿:CRC 出版社,敬请期待)。
-
Hadley Wickham、Danielle Navarro 和 Thomas Lin Pedersen,ggplot2: 优雅的数据分析图形,第三版(纽约:Springer,敬请期待),
ggplot2-book.org。 -
Claus Wilke,数据可视化基础(加利福尼亚州塞巴斯托波尔:O'Reilly Media,2019 年),
clauswilke.com/dataviz/。
第三章:3 个自定义数据可视化主题

自定义主题不过是一段代码,它对所有图表应用一系列小的调整。在制作专业图表时,许多工作都涉及这些类型的调整。你应该使用什么字体?图例应该放在哪里?坐标轴应该有标题吗?图表需要网格线吗?这些问题看起来微不足道,但对最终结果有着重大影响。
2018 年,BBC 的数据记者 Nassos Stylianou 和 Clara Guibourg 与他们的团队共同开发了一个符合 BBC 风格的定制 ggplot 主题。通过推出这个 bbplot 包供他人使用,他们改变了组织文化,消除了瓶颈,使 BBC 能够更具创意地可视化数据。
与其强迫每个人都复制冗长的代码来调整他们制作的每个图表,不如让定制主题让所有使用它的人遵循风格指南,确保所有数据可视化符合品牌的标准。例如,为了理解 BBC 引入的自定义主题的意义,了解 bbplot 出现之前的工作方式是很有帮助的。在 2010 年代中期,想要制作数据可视化的记者有两种选择:
-
使用一个内部工具来创建数据可视化,但仅限于它设计生成的预定义图表。
-
使用 Excel 创建框架图,然后与平面设计师合作,最终确定图表。这种方法带来了更好的效果,并且灵活性更高,但也需要与设计师进行大量的、耗时的来回沟通。
这两种选择都不是理想的,BBC 的数据可视化输出有限。R 解放了记者们不必依赖设计师。并不是说设计师不好(他们并不差),而是 ggplot 让记者们能够自己探索不同的可视化方式。随着团队提升 ggplot 技能,他们意识到可能不仅仅能生成探索性数据可视化,还能够在 R 中创建生产就绪的图表,直接发布到 BBC 网站上。
本章讨论了自定义 ggplot 主题的强大功能,并逐步介绍 bbplot 包中的代码,演示自定义主题是如何工作的。你将学习如何将样式代码整合成一个可重用的函数,并且如何一致地修改图表的文本、坐标轴、网格线、背景及其他元素。
使用自定义主题样式化图表
bbplot 包有两个函数:bbc_style()和 finalise_plot()。后者处理像添加 BBC 标志和保存图表为正确尺寸之类的任务。现在,我们先来看 bbc_style()函数,它应用一个自定义的 ggplot 主题,使所有图表看起来一致,并遵循 BBC 的风格指南。
示例图
要查看这个函数的工作原理,你将创建一个显示几种企鹅物种人口数据的图表。你将使用 palmerpenguins 包,该包包含有关生活在南极洲三个岛屿上的企鹅的数据。为了了解这些数据的样子,加载 palmerpenguins 和 tidyverse 包:
**library(palmerpenguins)**
**library(tidyverse)**
现在你有了一个名为 penguins 的可用数据对象。以下是前 10 行的数据样式:
#> # A tibble: 344 × 8
#> species island bill_le... bill_... flipp... body_... sex
#> <fct> <fct> <dbl> <dbl> <int> <int> <fct>
#> 1 Adelie Torgersen 39.1 18.7 181 3750 male
#> 2 Adelie Torgersen 39.5 17.4 186 3800 fema...
#> 3 Adelie Torgersen 40.3 18 195 3250 fema...
#> 4 Adelie Torgersen NA NA NA NA <NA>
#> 5 Adelie Torgersen 36.7 19.3 193 3450 fema...
#> 6 Adelie Torgersen 39.3 20.6 190 3650 male
#> 7 Adelie Torgersen 38.9 17.8 181 3625 fema...
#> 8 Adelie Torgersen 39.2 19.6 195 4675 male
#> 9 Adelie Torgersen 34.1 18.1 193 3475 <NA>
#> 10 Adelie Torgersen 42 20.2 190 4250 <NA>
`--snip--`
为了将数据转换为更可用的格式,你将使用 dplyr 包中的 count()函数(tidyverse 包中的几个包之一)来计算每个岛上居住的企鹅数量:
**penguins %>%**
**count(island)**
这为你提供了一些可以用于绘图的简单数据:
#> # A tibble: 3 × 2
#> island n
#> <fct> <int>
#> 1 Biscoe 168
#> 2 Dream 124
#> 3 Torgersen 52
你将在本章中多次使用这些数据,因此将其保存为名为 penguins_summary 的对象:
**penguins_summary <- penguins %>%**
**count(island)**
现在你可以开始创建一个图表。在看 bbplot 的效果之前,先用 ggplot 的默认设置创建一个图表:
penguins_plot <- ggplot(
data = penguins_summary,
aes(
x = island,
y = n,
fill = island
)
) +
geom_col() +
labs(
title = "Number of Penguins",
subtitle = "Islands are in Antarctica",
caption = "Data from palmerpenguins package"
)
这段代码告诉 R 使用 penguins_summary 数据框,将岛屿放在 x 轴上,并将企鹅数量(n)放在 y 轴上,并使用填充美学属性使每个条形图呈现不同颜色。由于你将多次修改这个图表,将其保存为名为 penguins_plot 的对象会简化此过程。图 3-1 显示了结果图表。

图 3-1:默认主题的图表
这不是最美观的图表。灰色背景不好看,y 轴标题因为倾斜难以阅读,整体文本大小相当小。但别担心,很快你将对其进行改进。
BBC 的自定义主题
现在你已经有了一个基本的图表可供使用,你将开始使其看起来像 BBC 的图表。为此,你需要安装 bbplot 包。首先使用 install.packages("remotes")安装 remotes 包,以便能够从远程源访问包。然后,运行以下代码从 GitHub 仓库 github.com/bbc/bbplot 安装 bbplot:
**library(remotes)**
**install_github("bbc/bbplot")**
安装 bbplot 包后,加载它并将 bbc_style()函数应用于 penguins_plot,如下所示:
**library(bbplot)**
**penguins_plot +**
**bbc_style()**
图 3-2 展示了结果。

图 3-2:应用 BBC 样式后的相同图表
看起来差别很大,对吧?字体大小更大了,图例在顶部,没有轴标题,网格线简化了,背景是白色的。我们逐一看看这些变化。
BBC 主题组件
你刚刚看到 bbc_style()函数对基本图表的影响有多大。本节将带你深入了解函数的代码,稍作修改以提高可读性。有关函数的进一步讨论,请参阅第十二章。
函数定义
第一行为函数命名,并指示其后的内容实际上是函数定义:
bbc_style <- function() {
font <- "Helvetica"
ggplot2::theme(
`--snip--`
然后,代码定义了一个名为 font 的变量,并将其值设置为 Helvetica。这使得后续的部分可以简单地使用 font,而不需要多次重复 Helvetica。如果 BBC 团队想要使用不同的字体,他们可以在此处将 Helvetica 更改为例如 Comic Sans,这样就会更新所有 BBC 图形的字体(尽管我怀疑 BBC 高层可能不会同意这个选择)。
历史上,使用自定义字体在 R 中一直颇具挑战,但最近的变化使得这一过程变得简单了很多。为了确保像 Helvetica 这样的自定义字体能在 ggplot 中正常工作,请首先通过在控制台运行以下代码安装 systemfonts 和 ragg 包:
**install.packages(c("systemfonts", "ragg"))**
systemfonts 包使得 R 可以直接访问你在计算机上安装的字体,而 ragg 则允许 ggplot 在生成图形时使用这些字体。
接下来,从 RStudio 的主菜单栏中选择工具全局选项。点击界面顶部的图形菜单,在后端选项下选择AGG。这个更改应该确保 RStudio 在预览任何图形时使用 ragg 包。设置完成后,你应该能够像 bbc_style()函数使用 Helvetica 一样使用你想要的任何字体(前提是你已安装这些字体)。
在指定要使用的字体后,代码调用了 ggplot 的 theme()函数。与先加载 ggplot 并调用其 theme()函数不同,ggplot2::theme()语法一步到位地表明 theme()函数来自 ggplot2 包。在第十二章中创建 R 包时,你会以这种方式编写代码。
bbc_style()中的几乎所有代码都存在于 theme()函数内。记得在第二章中,theme()对现有主题进行额外的调整;它不像 theme_light()那样是一个完整的主题,后者会改变整个图形的外观和感觉。换句话说,通过直接进入 theme()函数,bbc_style()对 ggplot 的默认设置进行调整。正如你将看到的,bbc_style()函数进行了很多微调。
文本
theme()函数中的第一部分代码格式化文本:
plot.title = ggplot2::element_text(
family = font,
size = 28,
face = "bold",
color = "#222222"
),
plot.subtitle = ggplot2::element_text(
family = font,
size = 22,
margin = ggplot2::margin(9, 0, 9, 0)
),
plot.caption = ggplot2::element_blank(),
`--snip--`
要更改标题、副标题和注释,请遵循以下模式:
`area_of_chart` = `element_type`(
`property` = `value`
)
对于每个区域,此代码指定了元素类型:element_text()、element_line()、element_rect()或 element_blank()。在元素类型内,你为属性分配值——例如,将字体系列(属性)设置为 Helvetica(值)。如你将在本章后面看到的,bbc_style()函数使用各种 element_ 函数进行调整。
注意
有关自定义图形各部分的其他方法,请参见 ggplot2 包文档(ggplot2.tidyverse.org/reference/element.xhtml),它提供了一个全面的列表。
bbc_style() 函数做出的主要调整之一是增大了字体大小,以帮助提高可读性,特别是当使用 bbplot 包制作的图表在较小的移动设备上查看时。代码首先使用 Helvetica 28 点粗体字和接近黑色的颜色(十六进制代码 #222222)格式化标题(plot.title)。副标题(plot.subtitle)则为 22 点 Helvetica 字体。
bbc_style() 代码还通过 margin() 函数在标题和副标题之间添加了一些间距,指定了顶部(9)、右侧(0)、底部(9)和左侧(0)的间距值。最后,element_blank() 函数移除了默认的标题(通过 labs() 函数中的 caption 参数设置)“来自 palmer penguins 包的数据。”(如前所述,bbplot 包中的 finalise_plot() 函数会向图表底部添加元素,包括更新后的标题和 BBC 标志。)图 3-3 显示了这些更改。

图 3-3:带有文本和边距格式化更改的企鹅图表
有了这些更改,你就朝着 BBC 风格迈进了。
图例
接下来是格式化图例,将其定位于图表上方,并左对齐文本:
legend.position = "top",
legend.text.align = 0,
legend.background = ggplot2::element_blank(),
legend.title = ggplot2::element_blank(),
legend.key = ggplot2::element_blank(),
legend.text = ggplot2::element_text(
family = font,
size = 18,
color = "#222222"
),
这段代码移除了图例背景(如果整个图表的背景颜色不是白色,背景会显示),标题和图例键(显示岛屿名称的框的边框,仅在图 3-3 中隐约可见)。最后,代码将图例文本设置为 18 点的 Helvetica 字体,颜色接近黑色。图 3-4 显示了结果。

图 3-4:带有图例更改的企鹅图表
图例看起来更好了,但现在是时候格式化图表的其余部分,使其一致。
坐标轴
这段代码首先移除了坐标轴标题,因为它们通常占用大量的图表空间,而你可以使用标题和副标题来阐明坐标轴显示的内容:
axis.title = ggplot2::element_blank(),
axis.text = ggplot2::element_text(
family = font,
size = 18,
color = "#222222"
),
axis.text.x = ggplot2::element_text(margin = ggplot2::margin(5, b = 10)),
axis.ticks = ggplot2::element_blank(),
axis.line = ggplot2::element_blank(),
所有坐标轴上的文本都变成了 18 点的 Helvetica 字体,颜色接近黑色。x 轴上的文本(Biscoe、Dream 和 Torgersen)周围有些间距。最后,两个坐标轴的刻度和线条都被移除。图 3-5 显示了这些更改,尽管坐标轴线条的移除对此处的显示没有影响。

图 3-5:带有坐标轴格式化更改的企鹅图表
坐标轴文本与图例文本匹配,坐标轴的刻度线和线条被移除。
网格线
接下来是网格线:
panel.grid.minor = ggplot2::element_blank(),
panel.grid.major.y = ggplot2::element_line(color = "#cbcbcb"),
panel.grid.major.x = ggplot2::element_blank(),
`--snip--`
这里的方法相当简单:这段代码移除了两个坐标轴的次要网格线,移除了 x 轴的主要网格线,并保持 y 轴的主要网格线,但将其颜色改为浅灰色(#cbcbcb 十六进制颜色)。图 3-6 显示了结果。

图 3-6:带有网格线调整的企鹅图表
请注意,x 轴上的网格线已消失。
背景
上一版本的图表仍然有灰色背景。bbc_style()函数通过以下代码移除了它:
panel.background = ggplot2::element_blank(),
图 3-7 展示了生成的图。

图 3-7:去掉灰色背景后的图表
你几乎已经用bbc_style()函数重建了企鹅图。
小多图
bbc_style()函数包含了更多的代码来修改strip.background和strip.text。在 ggplot 中,strip指的是像第二章中讨论的那种在分面图上方的文字。接下来,你将把企鹅图转换为一个分面图,以便查看 BBC 主题中的这些组件。我使用了bbc_style()函数中的代码,去除了处理小多图的部分,制作了图 3-8。

图 3-8:没有改变条带文字格式的小多图
使用facet_wrap()函数制作小多图会为每个岛屿生成一张图表,但默认情况下,每个小多图上方的文字明显比其他部分的小。更重要的是,文字背后的灰色背景显得尤为突出,因为你已经去掉了图表其他部分的灰色背景。你努力实现的一致性此时被破坏了,文字小得与其他图表文字不成比例,灰色背景也像一个显眼的眼中钉。
以下代码更改了每个小多图上方的条带文字:
strip.background = ggplot2::element_rect(fill = "white"),
strip.text = ggplot2::element_text(size = 22, hjust = 0)
)
`--snip--`
这段代码移除了背景(更准确地说,给它上了白色背景)。接着,它使文字变大,加粗,并通过hjust = 0使其左对齐。请注意,我确实需要把文字大小调整得比实际图表中的稍微小一些,以适应书本的排版,并且我添加了代码来使其加粗。图 3-9 展示了结果。

图 3-9:BBC 风格的小多图
如果你查看 BBC 网站上的任何图表,你会发现它看起来和你自己的图表非常相似。bbc_style()函数对文字格式、图例、坐标轴、网格线和背景的调整会出现在全球数百万观众看到的图表中。
颜色
你可能在想,等一下,条形图的颜色呢?难道主题没有改变它们吗? 这是一个常见的困惑,但答案是没有改变。theme()函数的文档解释了为什么会这样:“主题是自定义图表非数据部分的强大方式:例如标题、标签、字体、背景、网格线和图例。”换句话说,ggplot 主题仅改变与数据无关的图表元素。
另一方面,图表使用颜色来传达有关数据的信息。例如,在分面图中,填充属性映射到岛屿(Biscoe 是鲑鱼色,Dream 是绿色,Torgersen 是蓝色)。正如你在第二章中看到的,你可以使用各种 scale_fill_ 函数来更改填充。在 ggplot 的世界里,这些 scale_ 函数控制颜色,而自定义主题控制图表的整体外观和感觉。
总结
当 Stylianou 和 Guibourg 开始为 BBC 开发自定义主题时,他们有一个问题:他们能否在 R 中创建可以直接放到 BBC 网站上的图表?使用 ggplot,他们成功了。bbplot 包使他们能够制作具有一致外观和感觉的图表,这些图表符合 BBC 标准,最重要的是,不需要设计师的帮助。
你可以在这个自定义主题中看到许多在第二章中讨论的高质量数据可视化原则。特别是,去除多余元素(例如坐标轴标题和网格线)有助于将注意力集中在数据本身。由于应用主题只需要用户在 ggplot 代码中添加一行代码,所以很容易让其他人参与进来。他们只需在代码中附加 bbc_style() 即可生成 BBC 风格的图表。
随着时间的推移,BBC 其他部门的人注意到了数据新闻团队生产的可直接使用的图表,并希望自己也能制作。团队成员为同事们设置了 R 培训,并开发了一本“食谱”(bbc.github.io/rcookbook/),展示了如何制作各种类型的图表。很快,BBC 的数据可视化的质量和数量都大幅提升。Stylianou 告诉我:“我不认为 BBC 有哪一天没有人使用这个包来制作图形。”
现在你已经了解了自定义 ggplot 主题的工作原理,试着做一个属于你自己的主题吧。毕竟,一旦你写好代码,就只需要一行代码即可应用它。
额外资源
-
BBC 视觉与数据新闻团队,“BBC 视觉与数据新闻 R 图形食谱,”GitHub,2019 年 1 月 24 日,
bbc.github.io/rcookbook/。 -
BBC 视觉与数据新闻团队,“BBC 视觉与数据新闻团队如何使用 R 制作图形,”Medium,2019 年 2 月 1 日,
medium.com/bbc-visual-and-data-journalism/how-the-bbc-visual-and-data-journalism-team-works-with-graphics-in-r-ed0b35693535。
第四章:4 地图和地理空间数据

当我刚开始学习 R 时,我认为它是一个用于处理数字的工具,而不是处理形状的工具,因此当我看到人们用它来制作地图时,我感到很惊讶。例如,开发者 Abdoul Madjid 使用 R 制作了一张 2021 年美国 COVID-19 疫情传播率的地图。
你可能认为制作地图需要像 ArcGIS 这样的专业地图制作软件,但它是一个昂贵的工具。虽然 Excel 近年来增加了地图制作支持,但它的功能有限(例如,你不能用它根据街道地址制作地图)。即使是类似于 ArcGIS 的开源工具 QGIS,仍然需要学习新技能。
使用 R 制作地图比使用 Excel 更灵活,比使用 ArcGIS 更便宜,而且基于你已经知道的语法。它还允许你使用一个工具完成所有数据操作任务,并应用第二章中讨论的高质量数据可视化原则。在本章中,你将处理地理空间数据的简单特征,并检查 Madjid 的代码,以了解他是如何制作这张地图的。你还将学习在哪里找到地理空间数据以及如何使用这些数据制作自己的地图。
地理空间数据简明入门
你不需要成为 GIS 专家就能制作地图,但你需要了解一些关于地理空间数据的工作原理,首先是它的两种主要类型:矢量数据和栅格数据。矢量数据使用点、线和多边形来表示世界。栅格数据通常来自数字照片,它将图像中的每个像素与特定的地理位置关联。矢量数据通常更容易处理,你将在本章中专门使用它。
过去,处理地理空间数据意味着要掌握不同的标准,每个标准都需要学习不同的方法。然而今天,大多数人使用简单特征模型(通常缩写为sf)来处理矢量地理空间数据,这种模型更易于理解。例如,要导入关于怀俄明州的简单特征数据,只需输入以下内容:
**library(sf)**
**wyoming <- read_sf("https://data.rfortherestofus.com/wyoming.geojson")**
然后你可以像这样查看数据:
> **wyoming**
#> Simple feature collection with 1 feature and 1 field
#> Geometry type: POLYGON
#> Dimension: XY
#> Bounding box: xmin: -111.0546 ymin: 40.99477 xmax: -104.0522 ymax: 45.00582
#> Geodetic CRS: WGS 84
#> NAME geometry
#> 1 Wyoming POLYGON ((-111.0449 43.3157...
输出包含两列,一列是州名(NAME),另一列叫做 geometry。除了两个主要区别外,这些数据看起来就像你之前见过的数据框。
首先,数据框上方有五行元数据。最上面的一行说明数据包含一个特征和一个字段。特征是一行数据,字段是包含非空间数据的任何列。其次,简单特征数据包含一个名为 geometry 的地理数据变量。因为 geometry 列必须存在,才能将数据框视为地理空间数据,因此它不算作一个字段。让我们逐一看看这些简单特征数据的各个部分。
几何类型
几何类型表示你正在处理的地理空间数据的形状,通常用大写字母表示。在这种情况下,相对简单的 POLYGON 类型表示一个单一的多边形。你可以通过调用 geom_sf() 来使用 ggplot 显示这些数据,geom_sf() 是专为处理简单要素数据而设计的特殊几何:
library(tidyverse)
wyoming %>%
ggplot() +
**geom_sf()**
图 4-1 显示了怀俄明州的结果地图。它看起来可能不太复杂,但并不是我让怀俄明州几乎变成了一个完美的矩形!

图 4-1:使用 POLYGON 简单要素数据生成的怀俄明州地图
简单要素数据中使用的其他几何类型包括 POINT,用于显示例如地图上的一个标记,表示单一位置。例如,图 4-2 中的地图使用 POINT 数据来显示怀俄明州的一个电动汽车充电站。

图 4-2:包含 POINT 简单要素数据的怀俄明州地图
LINESTRING 几何类型用于一组可以用线连接的点,通常用于表示道路。图 4-3 显示了一张地图,使用 LINESTRING 数据表示穿过怀俄明州的美国 30 号公路的一段。

图 4-3:使用 LINESTRING 简单要素数据表示的道路
每种几何类型都有一个 MULTI 变体(MULTIPOINT、MULTI LINESTRING 和 MULTIPOLYGON),它将该类型的多个实例组合在一行数据中。例如,图 4-4 使用 MULTIPOINT 数据来显示怀俄明州的所有电动汽车充电站。

图 4-4:使用 MULTIPOINT 数据表示多个电动汽车充电站
同样,你可以使用 MULTILINESTRING 数据不仅显示一条道路,而是显示怀俄明州的所有主要道路(图 4-5)。

图 4-5:使用 MULTILINESTRING 数据表示几条道路
最后,你可以使用 MULTIPOLYGON 数据,例如,表示一个由多个多边形组成的州。以下数据表示怀俄明州的 23 个县:
#> Simple feature collection with 23 features and 1 field
#> Geometry type: MULTIPOLYGON
#> Dimension: XY
#> Bounding box: xmin: -111.0546 ymin: 40.99477 xmax: -104.0522 ymax: 45.00582
#> Geodetic CRS: WGS 84
#> First 10 features:
#> NAME geometry
#> 34 Lincoln MULTIPOLYGON (((-111.0472 4...
#> 104 Fremont MULTIPOLYGON (((-109.4582 4...
#> 121 Uinta MULTIPOLYGON (((-110.6068 4...
#> 527 Big Horn MULTIPOLYGON (((-108.5923 4...
#> 551 Hot Springs MULTIPOLYGON (((-109.1714 4...
#> 601 Washakie MULTIPOLYGON (((-107.6335 4...
#> 769 Converse MULTIPOLYGON (((-105.6985 4...
#> 970 Sweetwater MULTIPOLYGON (((-110.0489 4...
#> 977 Crook MULTIPOLYGON (((-105.0856 4...
#> 1097 Carbon MULTIPOLYGON (((-106.9129 4...
如第二行所示,这些数据的几何类型是 MULTIPOLYGON。此外,几何列中重复出现的 MULTIPOLYGON 文本表明每行数据都包含一个 MULTIPOLYGON 类型的形状。图 4-6 展示了使用这些数据制作的地图。

图 4-6:怀俄明州各县的地图
注意,地图完全由多边形组成。
维度
接下来,地理空间数据框包含数据的维度,即你正在处理的地理空间数据类型。在怀俄明州的例子中,它看起来像是维度:XY,意味着数据是二维的,就像本章中使用的所有地理空间数据一样。还有另外两个维度(Z 和 M),你会更少见到它们。我将留给你进一步探索。
边界框
元数据倒数第二个元素是边界框,它表示能够容纳所有地理空间数据的最小区域。对于 wyoming 对象,它如下所示:
Bounding box: xmin: -111.0569 ymin: 40.99475 xmax: -104.0522 ymax: 45.0059
ymin 值 40.99475 和 ymax 值 45.0059 分别代表该州多边形可以适配的最低和最高纬度。x 值则用于表示经度的相应范围。边界框是自动计算的,通常你不需要担心修改它们。
坐标参考系统
最后一项元数据指定了坐标参考系统,该系统用于在绘制数据时对其进行投影。表示任何地理空间数据的挑战在于,你需要在二维地图上展示关于三维地球的信息。这样做需要选择一个坐标参考系统,它决定了在绘制地图时使用哪种类型的对应关系或投影。
怀俄明州县地图的数据包括行 Geodetic CRS: WGS 84,表示使用了名为WGS84的坐标参考系统。要查看不同的投影,可以查看使用Albers 等面积圆锥投影的相同地图。虽然在 图 4-6 中,怀俄明州看起来是完全水平的,但在 图 4-7 中,它似乎倾斜了。

图 4-7:使用 Albers 等面积圆锥投影制作的怀俄明州县地图
如果你想知道如何在制作自己的地图时更改投影,请不要担心:当我们在下一节查看 Madjid 的地图时,你将看到如何操作。如果你想了解如何为地图选择合适的投影,可以查看“使用合适的投影”章节中的 第 81 页。
几何列
除了元数据,简单要素数据在另一个方面与传统数据框有所不同:它的几何列。如你所猜测的那样,这一列保存了绘制地图所需的数据。
为了理解这一点,想象一下你小时候可能完成过的连点成图的绘画活动。当你将线条连接一个点到下一个点时,你的画作主题变得更加清晰。几何列也类似。它包含一组数字,每个数字对应一个点。如果你使用的是 LINESTRING/MULTILINESTRING 或 POLYGON/MULTIPOLYGON 简单要素数据,ggplot 会使用几何列中的数字绘制每个点,并添加线条连接这些点。如果你使用的是 POINT/MULTIPOINT 数据,它会绘制这些点,但不会连接它们。
再次感谢 R,你不需要担心这些细节或深入查看几何列。
重建 COVID-19 地图
现在你理解了地理空间数据的基础知识,让我们一起来看看 Madjid 用来制作 COVID-19 地图的代码。图 4-8 中展示了该地图,利用了几何类型、维度、边界框、投影和刚才讨论过的几何列。

图 4-8:Abdoul Madjid 的 2021 年美国 COVID-19 地图
我对代码做了一些小修改,使得最终的地图能够适应页面。你将从加载一些包开始:
**library(tidyverse)**
**library(albersusa)**
**library(sf)**
**library(zoo)**
**library(colorspace)**
albersusa 包将为你提供地理空间数据的访问权限。按以下步骤安装它:
**remotes::install_github("hrbrmstr/albersusa")**
你可以使用标准的 install.packages()代码安装所有其他包。你将使用 tidyverse 导入数据、操作数据并通过 ggplot 绘制图表。sf 包将帮助你更改坐标参考系统,并使用适合数据的投影。zoo 包有计算滚动平均数的函数,而 colorspace 包则提供了一个能够突出显示数据的颜色比例。
导入数据
接下来,你将导入所需的数据:按州的 COVID-19 病例率、州人口以及地理空间信息。Madjid 将这些数据分别导入,然后将它们合并,你也将按此方式操作。
COVID-19 数据直接来自纽约时报,该报通过其 GitHub 账户每天发布按州分类的病例率 CSV 文件。要导入该数据,请输入以下内容:
**covid_data <- read_csv("https://data.rfortherestofus.com/covid-us-states.csv") %>%**
**select(-fips)**
联邦信息处理标准(FIPS)是用于表示州的数字代码,但你将通过州的名称来引用州,因此这一行选择(-fips)会去掉 fips 变量。
查看这些数据,你可以看到 2020 年 1 月美国首次出现 COVID-19 病例:
#> # A tibble: 56,006 × 4
#> date state cases deaths
#> <date> <chr> <dbl> <dbl>
#> 1 2020-01-21 Washington 1 0
#> 2 2020-01-22 Washington 1 0
#> 3 2020-01-23 Washington 1 0
#> 4 2020-01-24 Illinois 1 0
#> 5 2020-01-24 Washington 1 0
#> 6 2020-01-25 California 1 0
#> 7 2020-01-25 Illinois 1 0
#> 8 2020-01-25 Washington 1 0
#> 9 2020-01-26 Arizona 1 0
#> 10 2020-01-26 California 2 0
`--snip--`
Madjid 的地图显示的是人均率(每 10 万人口的比率),而不是绝对率(没有考虑州人口的比率)。因此,要重建他的地图,你还需要获得每个州的人口数据。按照以下步骤将数据下载为 CSV 格式:
**usa_states <- read_csv("https://data.rfortherestofus.com/population-by-state.csv") %>%**
**select(State, Pop)**
这段代码导入数据,保留州和人口(Pop)变量,并将数据保存为名为 usa_states 的对象。usa_states 的样子如下:
#> # A tibble: 52 × 2
#> State Pop
#> <chr> <dbl>
#> 1 California 39613493
#> 2 Texas 29730311
#> 3 Florida 21944577
#> 4 New York 19299981
#> 5 Pennsylvania 12804123
#> 6 Illinois 12569321
#> 7 Ohio 11714618
#> 8 Georgia 10830007
#> 9 North Carolina 10701022
#> 10 Michigan 9992427
`--snip--`
最后,导入地理空间数据并将其保存为名为 usa_states_geom 的对象,如下所示:
**usa_states_geom <- usa_sf() %>%**
**select(name) %>%**
**st_transform(us_laea_proj)**
来自 albersusa 包的 usa_sf()函数为你提供所有美国州的简单要素数据。方便的是,它将阿拉斯加和夏威夷放置在易于查看的位置和比例。这些数据包含多个变量,但由于你只需要州名,代码仅保留了州名变量。
来自 sf 包的 st_transform()函数用于更改坐标参考系统。这里使用的参考系统来自 albersusa 包中的 us_laea_proj 对象。这是你之前用来改变怀俄明州县地图外观的阿尔伯斯等面积圆锥投影。
计算每日 COVID-19 病例
covid_data数据框列出了按州统计的累计 COVID-19 病例数,但没有按日统计病例数,所以下一步是计算每日病例数:
covid_cases <- covid_data %>%
group_by(state) %>%
mutate(
❶ pd_cases = lag(cases)
) %>%
❷ replace_na(list(pd_cases = 0)) %>%
mutate(
❸ daily_cases = case_when(
cases > pd_cases ~ cases - pd_cases,
TRUE ~ 0
)
) %>%
❹ ungroup() %>%
arrange(state, date)
group_by()函数按州计算总数,然后创建一个名为pd_cases的新变量,表示前一天的病例数(使用lag()函数将数据分配给此变量)❶。某些天没有前一天的病例数,因此使用replace_na()函数将该值设为 0❷。
接下来,这段代码创建了一个名为daily_cases的新变量❸。要设置这个变量的值,使用case_when()函数创建一个条件:如果cases变量(表示当天的病例数)大于pd_cases变量(表示前一天的病例数),那么daily_cases等于cases减去pd_cases。否则,daily_cases设置为 0。
最后,因为你在代码开始时按州对数据进行了分组,现在需要使用ungroup()函数去除这个分组,然后再按州和日期对数据进行排序❹。
这是结果数据框covid_cases:
#> # A tibble: 56,006 × 6
#> date state cases deaths pd_cases daily_cases
#> <date> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 2020-03-13 Alabama 6 0 0 6
#> 2 2020-03-14 Alabama 12 0 6 6
#> 3 2020-03-15 Alabama 23 0 12 11
#> 4 2020-03-16 Alabama 29 0 23 6
#> 5 2020-03-17 Alabama 39 0 29 10
#> 6 2020-03-18 Alabama 51 0 39 12
#> 7 2020-03-19 Alabama 78 0 51 27
#> 8 2020-03-20 Alabama 106 0 78 28
#> 9 2020-03-21 Alabama 131 0 106 25
#> 10 2020-03-22 Alabama 157 0 131 26
`--snip--`
在下一步中,你将利用新的daily_cases变量。
计算发病率
你还没有完全计算出所有的值。Madjid 用于制作地图的数据没有包括每日新增病例数。相反,它包含的是每 10 万人中五天滚动平均的病例数。滚动平均是某一时间段内的平均病例率。报告的特殊情况(例如,周末没有报告,而是将周六和周日的病例合并到周一)可能会导致某一天的数值不太可靠。使用滚动平均可以平滑这些特殊情况。按如下方式生成这些数据:
**covid_cases %>%**
**mutate(roll_cases = rollmean(**
**daily_cases,**
**k = 5,**
**fill = NA**
**))**
这段代码创建了一个名为covid_cases_rm的新数据框(其中rm代表滚动均值)。它创建的第一步是使用zoo包中的rollmean()函数来创建一个roll_cases变量,表示围绕单一日期的五天期间的平均病例数。k参数是你希望计算滚动平均的天数(在这里是 5 天),fill参数确定在像第一天这样没有先前几天的数据时会发生什么(Madjid 将这些值设为 NA)。
计算了roll_cases之后,你需要计算人均病例率。为此,你需要人口数据,因此将usa_states数据框中的人口数据与covid_cases数据进行连接,如下所示:
**covid_cases_rm <- covid_cases %>%**
**mutate(roll_cases = rollmean(**
**daily_cases,**
**k = 5,**
**fill = NA**
**)**
**) %>%**
**left_join(usa_states,**
**by = c("state" = "State")) %>%**
**drop_na(Pop)**
要删除缺失人口数据的行,你需要调用drop_na()函数,并将 Pop 变量作为参数。在实际操作中,这会删除几个美国领土(美属萨摩亚、关岛、北马里亚纳群岛和美属维尔京群岛)。
接下来,你通过将roll_cases变量乘以 100,000 并除以每个州的人口来创建一个名为incidence_rate的按人均病例率变量:
**covid_cases_rm <- covid_cases_rm %>%**
**mutate(incidence_rate = 10⁵ * roll_cases / Pop) %>%**
**mutate(**
**incidence_rate = cut(**
**incidence_rate,**
**breaks = c(seq(0, 50, 5), Inf),**
**include.lowest = TRUE**
**) %>%**
**factor(labels = paste0(">", seq(0, 50, 5)))**
**)**
与其保留原始值(例如,2021 年 6 月 29 日,佛罗里达州每 10 万人有 57.77737 例病例),不如使用 cut()函数将数值转换为类别:大于 0(大于零)的值,大于 5(大于五)的值,以及大于 50(大于五十)的值。
最后一步是过滤数据,只保留 2021 年的数据(这是 Madjid 地图中展示的唯一年份),然后选择创建地图所需的变量(州、省、日期和发病率):
**covid_cases_rm %>%**
**filter(date >= as.Date("2021-01-01")) %>%**
**select(state, date, incidence_rate)**
这是最终的 covid_cases_rm 数据框:
#> # A tibble: 18,980 × 3
#> state date incidence_rate
#> <chr> <date> <fct>
#> 1 Alabama 2021-01-01 >50
#> 2 Alabama 2021-01-02 >50
#> 3 Alabama 2021-01-03 >50
#> 4 Alabama 2021-01-04 >50
#> 5 Alabama 2021-01-05 >50
#> 6 Alabama 2021-01-06 >50
#> 7 Alabama 2021-01-07 >50
#> 8 Alabama 2021-01-08 >50
#> 9 Alabama 2021-01-09 >50
#> 10 Alabama 2021-01-10 >50
`--snip--`
你现在有一个可以与地理空间数据合并的数据框。
添加地理空间数据
你已经使用了三个数据源中的两个(COVID-19 病例数据和州人口数据),创建了 covid_cases_rm 数据框,接下来需要使用第三个数据源:你保存的地理空间数据 usa_states_geom。简单特征数据允许你将常规数据框与地理空间数据合并(这也是它的优势之一):
**usa_states_geom %>%**
**left_join(covid_cases_rm, by = c("name" = "state"))**
这段代码将 covid_cases_rm 数据框与地理空间数据合并,匹配 usa_states_geom 中的 name 变量与 covid_cases_rm 中的 state 变量。
接下来,创建一个新变量 fancy_date,用于美化日期格式(例如,使用“Jan. 01”而不是“2021-01-01”):
**usa_states_geom_covid <- usa_states_geom %>%**
left_join(covid_cases_rm, by = c("name" = "state")) %>%
**mutate(fancy_date = fct_inorder(format(date, "%b. %d"))) %>%**
**relocate(fancy_date, .before = incidence_rate)**
format()函数负责格式化,而 fct_inorder()函数使得 fancy_date 变量按照日期排序数据(而不是按字母顺序排列,这样 8 月就不会排在 1 月之前)。最后,relocate()函数将 fancy_date 列移到日期列旁边。
将此数据框保存为 usa_states_geom_covid,并查看结果:
#> Simple feature collection with 18615 features and 4 fields
#> Geometry type: MULTIPOLYGON
#> Dimension: XY
#> Bounding box: xmin: -2100000 ymin: -2500000 xmax: 2516374 ymax: 732103.3
#> CRS: +proj=laea +lat_0=45 +lon_0=-100 +x_0=0 +y_0=0 +a=6370997
+b=6370997 +units=m +no_defs
#> First 10 features:
#> name date fancy_date incidence_rate
#> 1 Arizona 2021-01-01 Jan. 01 >50
#> 2 Arizona 2021-01-02 Jan. 02 >50
#> 3 Arizona 2021-01-03 Jan. 03 >50
#> 4 Arizona 2021-01-04 Jan. 04 >50
#> 5 Arizona 2021-01-05 Jan. 05 >50
#> 6 Arizona 2021-01-06 Jan. 06 >50
#> 7 Arizona 2021-01-07 Jan. 07 >50
#> 8 Arizona 2021-01-08 Jan. 08 >50
#> 9 Arizona 2021-01-09 Jan. 09 >50
#> 10 Arizona 2021-01-10 Jan. 10 >50
#> geometry
#> 1 MULTIPOLYGON (((-1111066 -8...
#> 2 MULTIPOLYGON (((-1111066 -8...
#> 3 MULTIPOLYGON (((-1111066 -8...
#> 4 MULTIPOLYGON (((-1111066 -8...
#> 5 MULTIPOLYGON (((-1111066 -8...
#> 6 MULTIPOLYGON (((-1111066 -8...
#> 7 MULTIPOLYGON (((-1111066 -8...
#> 8 MULTIPOLYGON (((-1111066 -8...
#> 9 MULTIPOLYGON (((-1111066 -8...
#> 10 MULTIPOLYGON (((-1111066 -8...
你可以看到之前章节中提到的元数据和几何列。
制作地图
花了很多工夫才得到看似简单的 usa_states_geom_covid 数据框。虽然数据看起来简单,但 Madjid 用来制作地图的代码相当复杂。本节将分步骤向你讲解这些代码。
最终地图实际上是多个地图,每个地图代表 2021 年中的一天。将 365 天的数据合并会产生一个庞大的最终产品,因此,代码不会展示每一天,而是通过过滤 usa_states_geom_covid 来只显示 1 月的前六天:
**usa_states_geom_covid_six_days <- usa_states_geom_covid %>%**
**filter(date <= as.Date("2021-01-06"))**
将结果保存为一个名为 usa_states_geom_covid_six_days 的数据框。以下是该数据的样子:
#> Simple feature collection with 306 features and 4 fields
#> Geometry type: MULTIPOLYGON
#> Dimension: XY
#> Bounding box: xmin: -2100000 ymin: -2500000 xmax: 2516374 ymax: 732103.3
#> CRS: +proj=laea +lat_0=45 +lon_0=-100 +x_0=0 +y_0=0 +a=6370997 +b=6370997 +unit...
#> First 10 features:
#> name date fancy_date incidence_rate
#> 1 Arizona 2021-01-01 Jan. 01 >50
#> 2 Arizona 2021-01-02 Jan. 02 >50
#> 3 Arizona 2021-01-03 Jan. 03 >50
#> 4 Arizona 2021-01-04 Jan. 04 >50
#> 5 Arizona 2021-01-05 Jan. 05 >50
#> 6 Arizona 2021-01-06 Jan. 06 >50
#> 7 Arkansas 2021-01-01 Jan. 01 >50
#> 8 Arkansas 2021-01-02 Jan. 02 >50
#> 9 Arkansas 2021-01-03 Jan. 03 >50
#> 10 Arkansas 2021-01-04 Jan. 04 >50
#> geometry
#> 1 MULTIPOLYGON (((-1111066 -8...
#> 2 MULTIPOLYGON (((-1111066 -8...
#> 3 MULTIPOLYGON (((-1111066 -8...
#> 4 MULTIPOLYGON (((-1111066 -8...
#> 5 MULTIPOLYGON (((-1111066 -8...
#> 6 MULTIPOLYGON (((-1111066 -8...
#> 7 MULTIPOLYGON (((557903.1 -1...
#> 8 MULTIPOLYGON (((557903.1 -1...
#> 9 MULTIPOLYGON (((557903.1 -1...
#> 10 MULTIPOLYGON (((557903.1 -1...
Madjid 的地图非常庞大,因为它包含了所有 365 天的数据。为了适应本书的排版,某些元素的大小有所调整。
生成基本地图
有了六天的数据,你已经准备好制作地图。Madjid 的地图制作代码分为两大部分:生成基本地图,然后调整其外观。首先,你将回顾用于制作怀俄明州地图的三行代码,并进行一些装饰,以提高可视化效果:
**usa_states_geom_covid_six_days %>%**
**ggplot() +**
**geom_sf(**
**aes(fill = incidence_rate),**
**size = .05,**
**color = "grey55"**
**) +**
**facet_wrap(**
**vars(fancy_date),**
**strip.position = "bottom"**
**)**
geom_sf()函数绘制地理空间数据,修改了几个参数:size = .05 使州边界不那么突出,color = "grey55"将它们设置为中灰色。然后,facet_wrap()函数用于分面(即为每一天制作一张地图)。vars(fancy_date)代码指定使用 fancy_date 变量作为分面地图的依据,strip.position = "bottom"将标签 Jan. 01、Jan. 02 等移到地图底部。图 4-9 展示了结果。

图 4-9:展示 2021 年前六天 COVID-19 发病率的地图
生成了基本地图后,接下来你将让它看起来更加美观。
应用数据可视化原则
从现在开始,Madjid 使用的所有代码都用于改善地图的外观。如果你阅读过第二章,那么这些调整应该会让你感到熟悉,这突显了使用 ggplot 制作地图的一个好处:你可以应用制作图表时学到的数据可视化原则。
usa_states_geom_covid_six_days %>%
ggplot() +
geom_sf(
aes(fill = incidence_rate),
size = .05,
color = "transparent"
) +
facet_wrap(
vars(fancy_date),
strip.position = "bottom"
) +
scale_fill_discrete_sequential(
palette = "Rocket",
name = "COVID-19 INCIDENCE RATE",
guide = guide_legend(
title.position = "top",
title.hjust = .5,
title.theme = element_text(
family = "Times New Roman",
size = rel(9),
margin = margin(
b = .1,
unit = "cm"
)
),
nrow = 1,
keyheight = unit(.3, "cm"),
keywidth = unit(.3, "cm"),
label.theme = element_text(
family = "Times New Roman",
size = rel(6),
margin = margin(
r = 5,
unit = "pt"
)
)
)
) +
labs(
title = "2021 · A pandemic year",
caption = "Incidence rates are calculated for 100,000 people in each state.
Inspired from a graphic in the DIE ZEIT newspaper of November 18, 2021.
Data from NY Times · Tidytuesday Week-1 2022 · Abdoul ISSA BIDA."
) +
theme_minimal() +
theme(
text = element_text(
family = "Times New Roman",
color = "#111111"
),
plot.title = element_text(
size = rel(2.5),
face = "bold",
hjust = 0.5,
margin = margin(
t = .25,
b = .25,
unit = "cm"
)
),
plot.caption = element_text(
hjust = .5,
face = "bold",
margin = margin(
t = .25,
b = .25,
unit = "cm"
)
),
strip.text = element_text(
size = rel(0.75),
face = "bold"
),
legend.position = "top",
legend.box.spacing = unit(.25, "cm"),
panel.grid = element_blank(),
axis.text = element_blank(),
plot.margin = margin(
t = .25,
r = .25,
b = .25,
l = .25,
unit = "cm"
),
plot.background = element_rect(
fill = "#e5e4e2",
color = NA
)
)
来自 colorspace 包的 scale_fill_discrete_sequential()函数设置颜色比例。该代码使用火箭调色板(与 Cédric Scherer 和 Georgios Karamanis 在第二章中使用的调色板相同),并将图例标题更改为“COVID-19 发病率”。guide_legend()函数调整标题的位置、对齐方式和文本属性。然后,代码将彩色方块放在一行中,调整它们的高度和宽度,并调整标签(>0、>5 等)的文本属性。
接下来,labs()函数添加标题和说明。遵循主题 _minimal(),theme()函数进行一些设计调整,包括设置字体和文本颜色;使标题和说明加粗;并调整它们的大小、对齐方式和边距。然后,代码调整条形文本(如 Jan. 01、Jan. 02 等)的大小并加粗,图例被放置在地图顶部,并增加了一些间距。网格线、经纬度线被移除,接着整个可视化区域增加了一些内边距,并设置了浅灰色背景。
就是这样!图 4-10 展示了他重建的 COVID-19 地图。

图 4-10:Abdoul Madjid 地图的重建
从数据导入、数据清理到分析和可视化,你已经看到 Madjid 是如何在 R 中制作出一张漂亮的地图的。
制作自己的地图
你现在可能在想,好吧,太好了,但我到底该如何制作自己的地图呢? 在这一节中,你将学习如何找到地理空间数据,如何选择投影,并且如何准备数据进行绘制。
有两种方式可以访问简单特征的地理空间数据。第一种是导入原始数据,第二种是通过 R 函数访问它。
导入原始数据
地理空间数据可以有多种格式。尽管 ESRI shapefile(带 .shp 扩展名)是最常见的格式,但你也可能遇到 GeoJSON 文件(.geojson),如我们在本章开头的怀俄明州示例中使用的,KML 文件(.kml)等。Robin Lovelace、Jakub Nowosad 和 Jannes Muenchow 所著的 Geocomputation with R 第八章(第八章)讨论了这些格式。
好消息是,一个函数几乎可以读取任何类型的地理空间数据:sf 包中的 read_sf()。假设你从 geojson.xyz 网站下载了关于美国州边界的 GeoJSON 格式的地理空间数据,并将其保存在 data 文件夹中,命名为 states.geojson。要导入这些数据,可以使用 read_sf() 函数,像这样:
**us_states <- read_sf(dsn = "https://data.rfortherestofus.com/states.geojson")**
dsn 参数(即 数据源名称)告诉 read_sf() 从哪里查找文件。你将数据保存为对象 us_states。
使用 R 函数访问地理空间数据
有时你可能需要以这种方式处理原始数据,但并非总是如此。这是因为某些 R 包提供了访问地理空间数据的函数。Madjid 使用了 albersusa 包中的 usa_sf() 函数来获取数据。另一个访问与美国相关的地理空间数据的包 tigris,拥有许多为不同数据类型命名的函数。例如,加载 tigris 包并运行 states() 函数,如下所示:
**library(tigris)**
**states_tigris <- states(**
**cb = TRUE,**
**resolution = "20m",**
**progress_bar = FALSE**
**)**
cb = TRUE 参数让你放弃使用最详细的 shapefile,并将分辨率设置为更易管理的 20m(1:2000 万)。如果不做这些修改,生成的 shapefile 会很大,且处理起来很慢。设置 progress_bar = FALSE 可以隐藏 tigris 在加载数据时生成的消息。结果会保存为 states_tigris。tigris 包有函数可以获取关于县、普查区、道路等的地理空间数据。
如果你需要寻找美国以外的数据,rnaturalearth 包提供了从全球导入地理空间数据的函数。例如,使用 ne_countries() 来获取关于各个国家的地理空间数据:
**library(rnaturalearth)**
**africa_countries <- ne_countries(**
**returnclass = "sf",**
**continent = "Africa"**
**)**
这段代码使用了两个参数:returnclass = "sf" 用于获取简单特征格式的数据,continent = "Africa" 用于只获取非洲大陆上的国家。如果你将结果保存为名为 africa_countries 的对象,你可以像下面这样在地图上绘制数据:
**africa_countries %>%**
**ggplot() +**
**geom_sf()**
图 4-11 显示了生成的地图。

图 4-11:使用 rnaturalearth 包的数据制作的非洲地图
如果你找不到合适的包,始终可以退而求其次,使用 sf 包中的 read_sf()。
使用适当的投影
一旦你访问了地理空间数据,你需要决定使用哪种投影。如果你期望有一个简单的答案,可能会失望。正如 Geocomputation with R 所说:“哪个 CRS [使用什么坐标参考系统] 是个棘手的问题,通常没有一个‘正确’的答案。”
如果你被选择投影的任务压倒,可以使用 Kyle Walker 的 crsuggest 包来获得灵感。它的suggest_top_crs()函数返回一个非常适合你的数据的坐标参考系统。加载 crsuggest 并尝试在你的 africa_countries 数据上使用它:
**library(crsuggest)**
**africa_countries %>%**
**suggest_top_crs()**
suggest_top_crs()函数应该返回投影编号 28232。将该值传递给st_transform()函数,在绘图之前更改投影:
africa_countries %>%
**st_transform(28232) %>%**
**ggplot() +**
**geom_sf()**
运行时,此代码生成了图 4-12 中的地图。

图 4-12:使用投影编号 28232 制作的非洲地图
如你所见,你已成功使用不同的投影绘制了非洲地图。
地理空间数据整理
将传统数据框与地理空间数据合并的能力是处理简单特征数据的一个巨大优势。记住,在制作 COVID-19 地图时,Madjid 在将传统数据框与地理空间数据合并之前,先对传统数据框进行了分析。但因为简单特征数据就像传统数据框一样工作,所以你可以直接将 tidyverse 的数据整理和分析函数应用到简单特征对象上。为了了解这一点,请重新访问 africa_countries 简单特征数据,并选择两个变量(name 和 pop_est),查看国家的名称和人口:
**africa_countries %>%**
**select(name, pop_est)**
输出结果如下所示:
#> Simple feature collection with 51 features and 2 fields
#> Geometry type: MULTIPOLYGON
#> Dimension: XY
#> Bounding box: xmin: -17.62504 ymin: -34.81917 xmax: 51.13387 ymax: 37.34999
#> CRS: +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0
#> First 10 features:
#> name pop_est
#> 1 Angola 12799293
#> 11 Burundi 8988091
#> 13 Benin 8791832
#> 14 Burkina Faso 15746232
#> 25 Botswana 1990876
#> 26 Central African Rep. 4511488
#> 31 Côte d'Ivoire 20617068
#> 32 Cameroon 18879301
#> 33 Dem. Rep. Congo 68692542
#> 34 Congo 4012809
#> geometry
#> 1 MULTIPOLYGON (((16.32653 -5...
#> 11 MULTIPOLYGON (((29.34 -4.49...
#> 13 MULTIPOLYGON (((2.691702 6....
#> 14 MULTIPOLYGON (((-2.827496 9...
#> 25 MULTIPOLYGON (((25.64916 -1...
#> 26 MULTIPOLYGON (((15.27946 7....
#> 31 MULTIPOLYGON (((-2.856125 4...
#> 32 MULTIPOLYGON (((13.07582 2....
#> 33 MULTIPOLYGON (((30.83386 3....
#> 34 MULTIPOLYGON (((12.99552 -4...
假设你想制作一张显示哪些非洲国家人口超过 2000 万的地图。首先,你需要为每个国家计算这个值。为此,使用mutate()和if_else()函数,它们会返回 TRUE(如果国家人口超过 2000 万)或 FALSE(否则),然后将结果存储在名为 population_above_20_million 的变量中:
africa_countries %>%
select(name, pop_est) %>%
**mutate(population_above_20_million = if_else(pop_est > 20000000, TRUE, FALSE))**
然后,你可以将此代码传递到 ggplot 中,将填充美学属性设置为 population_above_20_million:
africa_countries %>%
select(name, pop_est) %>%
mutate(population_above_20_million = if_else(pop_est > 20000000, TRUE, FALSE)) %>%
**ggplot(aes(fill = population_above_20_million)) +**
**geom_sf()**
此代码生成了图 4-13 中显示的地图。

图 4-13:一张突出显示人口超过 2000 万的非洲国家的地图
这是你可以在简单特征数据上执行数据整理和分析的一个基本示例。更大的启示是:你在 R 中开发的任何数据处理技能,都将在处理地理空间数据时派上用场。
总结
在这次简短的 R 地图制作之旅中,你学习了简单特征地理空间数据的基础知识,回顾了 Abdoul Madjid 如何运用这些知识制作他的地图,探索了如何获取自己的地理空间数据,并看到了如何适当地投影它们以制作你自己的地图。
R 很可能是制作地图的最佳工具。它还允许你利用为处理传统数据框和 ggplot 代码所开发的技能,使你的可视化效果看起来很棒。毕竟,Madjid 不是 GIS 专家,但他结合了对地理空间数据的基本理解、R 的基础技能和数据可视化原则的知识,制作了一张美丽的地图。现在轮到你来做同样的事了。
额外资源
-
Kieran Healy, “绘制地图”,载于 《数据可视化:实用入门》(新泽西州普林斯顿:普林斯顿大学出版社,2018 年),
socviz.co。 -
Andrew Heiss, “来自数据可视化的空间课:使用 R、ggplot2 和图形设计原理创建美观且真实的数据可视化”,在线课程,最后更新于 2022 年 7 月 11 日,
datavizs22.classes.andrewheiss.com/content/12-content/。 -
Robin Lovelace,Jakub Nowosad 和 Jannes Muenchow, 《R 地理计算》(佛罗里达州博卡拉顿:CRC 出版社,2019 年),
r.geocompx.org。 -
Kyle Walker,《分析美国人口普查数据:R 中的方法、地图和模型》(佛罗里达州博卡拉顿:CRC 出版社,2013 年)。
第五章:5 设计有效的表格

在他的《数据可视化基础》一书中,Claus Wilke 写道,表格是“可视化数据的重要工具”。这一说法可能看起来有些奇怪。表格通常被认为是数据可视化(如图形)的对立面:一个用来存放数字的地方,供那些愿意阅读的人(少数极客)使用。但 Wilke 有不同的看法。
表格不必也不应该是没有设计的“数据堆砌”。尽管图表中的条形、线条和点是可视化,表格中的数字也是可视化,我们应该关注它们的外观。例如,看看可靠新闻来源制作的表格;这些可不是简单的数据堆砌。媒体组织,作为有效沟通的职责所在,非常注重表格设计。但在其他地方,由于表格看起来似乎很简单,Wilke 写道,“[表格]可能不会总是得到它们所需要的关注。”
许多人使用 Microsoft Word 制作表格,这种策略可能会带来潜在的陷阱。Wilke 发现,他的 Word 版本中包括了 105 种内置表格样式。在这些样式中,大约 80%,包括默认样式,违反了表格设计的一些关键原则。好消息是,R 是制作高质量表格的好工具。它有许多用于此目的的包,并且在这些包中,有几个函数旨在确保你的表格遵循重要的设计原则。
此外,如果你在 R Markdown 中编写报告(你将在第六章学习到),你可以包含一些代码,这些代码会在导出文档时生成表格。通过使用一个工具来创建表格、文本和其他可视化内容,你就不必复制粘贴数据,从而降低了人为错误的风险。
本章探讨了表格设计原则,并向你展示如何使用 R 的 gt 包(这是最受欢迎的表格制作包之一,默认使用良好的设计原则)将这些原则应用到你的表格中。这些原则和本章中的代码改编自 Tom Mock 的博客文章《R 中更好表格的 10+准则》。Mock 在 Posit 公司工作,Posit 是 RStudio 的开发公司,他已经成为 R 表格的行家。本章通过 Mock 的代码示例,展示了如何通过小的调整让表格设计产生大的影响。
创建数据框
首先,你将创建一个数据框,可以在本章中使用它来制作表格。首先,加载你需要的包(用于一般数据处理的 tidyverse、用于数据的 gapminder、用于制作表格的 gt 以及用于表格格式化的 gtExtras):
**library(tidyverse)**
**library(gapminder)**
**library(gt)**
**library(gtExtras)**
正如你在第二章中看到的,gapminder 包提供了国家级的人口统计数据。为了创建你表格的数据框,你将只使用几个国家(按字母顺序排列的前四个:阿富汗、阿尔巴尼亚、阿尔及利亚和安哥拉)和三个年份(1952 年、1972 年和 1992 年)。gapminder 数据包含许多年份,但这些年份足以演示表格制作的基本原则。以下代码创建了一个名为 gdp 的数据框:
gdp <- gapminder %>%
filter(country %in% c("Afghanistan", "Albania", "Algeria", "Angola")) %>%
select(country, year, gdpPercap) %>%
mutate(country = as.character(country)) %>%
pivot_wider(
id_cols = country,
names_from = year,
values_from = gdpPercap
) %>%
select(country, `1952`, `1972`, `1992`) %>%
rename(Country = country)
下面是 gdp 的样式:
#> # A tibble: 4 × 4
#> Country `1952` `1972` `1992`
#> <chr> <dbl> <dbl> <dbl>
#> 1 Afghanistan 779\. 740\. 649.
#> 2 Albania 1601\. 3313\. 2497.
#> 3 Algeria 2449\. 4183\. 5023.
#> 4 Angola 3521\. 5473\. 2628.
现在你有了一些数据,可以用它来制作表格。
表格设计原则
不出所料,良好的表格设计原则与数据可视化的原则有许多相似之处。本节介绍了其中六个最重要的原则。
减少杂乱
你可以通过去除不必要的元素来减少表格中的杂乱。例如,一个常见的表格杂乱来源是网格线,如图 5-1 所示。

图 5-1:一个到处都有网格线的表格可能会让人分心。
在表格中的每个单元格周围加上网格线是没有必要的,这会分散你清晰传达信息的目标。一个只有最少甚至没有网格线的表格(如图 5-2 所示)是一种更有效的沟通工具。

图 5-2:一个只有水平网格线的表格更为有效。
我提到过,gt 默认使用良好的表格设计原则,这是一个很好的例子。第二个表格,只有极少的网格线,只需要两行代码——将 gdp 数据传入 gt() 函数,就能创建一个表格:
gdp %>%
gt()
要向示例的每个部分添加网格线,你需要添加更多的代码。这里,gt() 函数后的代码将添加网格线:
gdp %>%
gt() %>%
tab_style(
style = cell_borders(
side = "all",
color = "black",
weight = px(1),
style = "solid"
),
locations = list(
cells_body(
everything()
),
cells_column_labels(
everything()
)
)
) %>%
opt_table_lines(extent = "none")
由于我不推荐采用这种方法,我将不会向你演示这段代码。不过,如果你想去除额外的网格线,可以这样做:
gdp %>%
gt() %>%
tab_style(
style = cell_borders(color = "transparent"),
locations = cells_body()
)
tab_style() 函数采用两步法。首先,它识别需要修改的样式(在此案例中是边框),然后指定应用这些修改的位置。这里,tab_style() 告诉 R 使用 cell_borders() 函数修改边框,将边框设为透明,并将此转换应用于 cells_body() 位置(与 cells_column_labels() 相对,仅作用于第一行)。
注意
要查看所有选项,请查看 gt 包文档网站上的所谓助手函数列表:gt.rstudio.com/reference/index.xhtml#helper-functions。
运行这段代码会输出一个没有网格线的表格(如图 5-3 所示)。

图 5-3:一个干净整洁的表格,只有在标题行和底部才有网格线
将此表格保存为名为 table_no_gridlines 的对象,以便后续可以继续添加内容。
区分表头和表体
尽管减少杂乱是一个重要目标,但过度减少可能会产生负面影响。例如,可以参考图 5-4。

图 5-4:去除所有网格线的模糊表格
通过加粗表头行,您可以更好地使其突出:
table_no_gridlines %>%
tab_style(
style = cell_text(weight = "bold"),
locations = cells_column_labels()
)
从 table_no_gridlines 对象开始,这段代码通过 tab_style() 函数分两步应用格式化。首先,它指定使用 cell_text() 函数将文本样式设置为粗体。其次,它使用 cells_column_labels() 函数将此转换应用于表头行。图 5-5 显示了表格在表头行加粗后的样子。

图 5-5:通过使用粗体使表头行更加明显
将此表格保存为 table_bold_header,以便进行进一步的格式化。
适当对齐
高质量表格设计的第三个原则是适当的对齐。具体来说,表格中的数字应右对齐。Tom Mock 解释道,左对齐或居中对齐数字“会影响清晰比较数字和小数位数的能力。右对齐可以使您对齐小数位和数字,便于解析。”
让我们看看这个原则的实际应用。在图 5-6 中,1952 年列是左对齐,1972 年列是居中对齐,1992 年列是右对齐。

图 5-6:比较左对齐(1952 年)、居中对齐(1972 年)和右对齐(1992 年)的数值数据
您可以看到,比较 1992 年列中的值比其他两列要容易得多。在 1952 年和 1972 年列中,比较数值是有挑战性的,因为在同一位置(例如,十位数)的数字没有垂直对齐。然而,在 1992 年列中,阿富汗(4)和阿尔巴尼亚(9)以及所有其他国家的十位数对齐,使得浏览表格变得更容易。
与其他表格一样,实际上您需要覆盖默认设置,才能使 gt 包使列错位,正如下面的代码所示:
table_bold_header %>%
cols_align(
align = "left",
columns = 2
) %>%
cols_align(
align = "center",
columns = 3
) %>%
cols_align(
align = "right",
columns = 4
)
默认情况下,gt 会将数字值右对齐。不要更改任何设置,您就会得到正确的结果。
数值列最好使用右对齐,而文本列则应使用左对齐。正如 Jon Schwabish 在《效益-成本分析期刊》中的文章《更好的表格设计十大准则》里指出的,左对齐的长文本单元格更易阅读。为了看到左对齐的好处,您可以在表格中添加一个名称较长的国家。我添加了波斯尼亚和黑塞哥维那,并将其保存为名为 gdp_with_bosnia 的数据框。您会看到,我几乎使用了之前创建 gdp 数据框时相同的代码:
gdp_with_bosnia <- gapminder %>%
filter(country %in% c("Afghanistan", "Albania", "Algeria", "Angola",
"Bosnia and Herzegovina")) %>%
select(country, year, gdpPercap) %>%
mutate(country = as.character(country)) %>%
pivot_wider(
id_cols = country,
names_from = year,
values_from = gdpPercap
) %>%
select(country, `1952`, `1972`, `1992`) %>%
rename(Country = country)
下面是gdp_with_bosnia数据框的样子:
#> # A tibble: 5 × 4
#> Country `1952` `1972` `1992`
#> <chr> <dbl> <dbl> <dbl>
#> 1 Afghanistan 779\. 740\. 649.
#> 2 Albania 1601\. 3313\. 2497.
#> 3 Algeria 2449\. 4183\. 5023.
#> 4 Angola 3521\. 5473\. 2628.
#> 5 Bosnia and Herzegovina 974\. 2860\. 2547.
现在取gdp_with_bosnia数据框,创建一个国家列居中对齐的表格。在图 5-7 中的表格里,国家名称很难扫描,而居中的列看起来有些奇怪。

图 5-7:居中文本可能很难阅读,尤其是当它包含较长的值时。
这是另一个需要改变gt默认设置以调整内容的例子。除了默认右对齐数值列外,gt还会左对齐字符列。只要你不做任何修改,你就会得到你想要的对齐方式。
如果你确实希望覆盖默认的对齐方式,可以使用cols_align()函数。例如,下面是如何让国家名称居中对齐的表格:
gdp_with_bosnia %>%
gt() %>%
tab_style(
style = cell_borders(color = "transparent"),
locations = cells_body()
) %>%
tab_style(
style = cell_text(weight = "bold"),
locations = cells_column_labels()
) %>%
cols_align(
columns = "Country",
align = "center"
)
columns参数告诉gt对哪些列进行对齐,align参数则选择对齐方式(左对齐、右对齐或居中)。
使用正确的精度
在你目前制作的所有表格中,你都直接使用了原始数据。例如,数字列中的数据扩展到四个小数位——这几乎肯定是过多的。更多的小数位会让表格变得更加难以阅读,所以你应该始终在 Jon Schwabish 所描述的“必要精度与干净、简洁表格”之间找到平衡。
这里有一个好的经验法则:如果增加更多的小数位会改变某个操作,就保留它们;否则,去掉它们。根据我的经验,人们往往会保留过多的小数位,过于重视高精度(并且在这个过程中降低了表格的可读性)。
在 GDP 表格中,你可以使用fmt_currency()函数来格式化数值:
table_bold_header %>%
fmt_currency(
columns = c(`1952`, `1972`, `1992`),
decimals = 0
)
gt包有一系列用于格式化表格中值的函数,这些函数都以fmt_开头。此代码将fmt_currency()应用于 1952 年、1972 年和 1992 年的列,然后使用decimals参数告诉fmt_currency()以零小数位格式化这些值。毕竟,GDP 为$779.4453 和$779 之间的差异不太可能导致不同的决策。
这会将值格式化为美元。fmt_currency()函数会自动添加千位分隔符,使得数值更加易读(图 5-8)。

图 5-8:将美元金额四舍五入为整数并添加美元符号,可以简化数据。
将你的表格保存为table_whole_numbers以便重用。
有意使用颜色
到目前为止,你的表格没有使用任何颜色,现在你将添加一些颜色来突出显示异常值。这样做可以帮助表格更有效地传达信息,特别是对于那些希望快速浏览表格的读者。为了使 1952 年最高的值显示为不同的颜色,你可以再次使用tab_style()函数:
table_whole_numbers %>%
tab_style(
style = cell_text(
color = "orange",
weight = "bold"
),
locations = cells_body(
columns = `1952`,
rows = `1952` == max(`1952`)
)
)
这个函数使用cell_text()将文本颜色改为橙色,并加粗。在cells_body()函数内,locations()函数指定了哪些列和行会应用这些更改。columns参数简单地设置为正在更改值的年份,但设置rows则需要更复杂的公式。代码rows = 1952 == max(1952)将文本转化应用于该年最大值的行。
对 1972 年和 1992 年列重复这一代码,生成的结果如图 5-9 所示(该图使用灰度表示橙色值,以便打印)。

图 5-9:使用颜色突出显示重要值,比如每年最大值
gt包使得为异常值添加颜色变得简单明了。
在适当的地方添加数据可视化
为了突出显示异常值,添加颜色是引导读者注意力的一种方法。另一种方法是将图表嵌入到表格中。Tom Mock 为 gt 开发了一个名为 gtExtras 的插件包,使得这一切变得可能。例如,假设你想展示各国 GDP 随时间变化的情况。为此,你可以添加一个新列,通过sparkline(本质上是一个简单的折线图)来可视化这一趋势:
gdp_with_trend <- gdp %>%
group_by(Country) %>%
mutate(Trend = list(c(`1952`, `1972`, `1992`))) %>%
ungroup()
gt_plt_sparkline()函数要求你提供所需的值,并将这些值放在单独的一列中以生成 sparkline。为此,代码使用group_by()和mutate()创建一个名为 Trend 的变量,用于存储每个国家的值列表。例如,对于阿富汗,Trend 将包含 779.4453145、739.9811058 和 649.3413952。将这些数据保存为名为 gdp_with_trend 的对象。
现在,你可以像之前一样创建表格,但在代码的末尾添加gt_plt_sparkline()函数。在这个函数内,指定使用哪一列来创建 sparkline(趋势图),如下所示:
gdp_with_trend %>%
gt() %>%
tab_style(
style = cell_borders(color = "transparent"),
locations = cells_body()
) %>%
tab_style(
style = cell_text(weight = "bold"),
locations = cells_column_labels()
) %>%
fmt_currency(
columns = c(`1952`, `1972`, `1992`),
decimals = 0
) %>%
tab_style(
style = cell_text(
color = "orange",
weight = "bold"
),
locations = cells_body(
columns = `1952`,
rows = `1952` == max(`1952`)
)
) %>%
tab_style(
style = cell_text(
color = "orange",
weight = "bold"
),
locations = cells_body(
columns = `1972`,
rows = `1972` == max(`1972`)
)
) %>%
tab_style(
style = cell_text(
color = "orange",
weight = "bold"
),
locations = cells_body(
columns = `1992`,
rows = `1992` == max(`1992`)
)
) %>%
gt_plt_sparkline(
column = Trend,
label = FALSE,
palette = c("black", "transparent", "transparent", "transparent", "transparent")
)
设置label = FALSE会移除gt_plt_sparkline()默认添加的文本标签,然后添加一个调色板参数,使得 sparkline 为黑色,其他元素则保持透明。(默认情况下,该函数会使 sparkline 的不同部分采用不同颜色。)图 5-10 中的简化版 sparkline 使得读者可以一目了然地看到每个国家的趋势。

图 5-10:带有 sparklines 的表格能够展示数据随时间变化的情况。
gtExtras包不仅仅可以创建 sparklines。它的主题函数集让你能够将表格设计成类似于 FiveThirtyEight、《纽约时报》、《卫报》和其他新闻媒体发布的风格。
举个例子,试着移除你目前应用的所有格式,而改为使用gt_theme_538()函数来为表格设置样式。然后,浏览一下 FiveThirtyEight 网站上的表格。你应该能看到与图 5-11 中的表格相似之处。

图 5-11:按照 FiveThirtyEight 风格重新制作的表格
像 gtExtras 这样的附加包在表格制作中很常见。例如,如果你使用 reactable 包来制作互动表格,你还可以使用 reactablefmtr 来添加互动火花图、主题等。你将在第九章中学习更多关于制作互动表格的内容。
总结
本章中你对表格所做的许多调整都非常微妙。像是去除多余的网格线、加粗标题文字、将数字值右对齐以及调整精度水平等更改,往往会被忽视,但如果跳过这些步骤,表格的效果会大打折扣。最终产品虽然不花哨,但能够清晰地传达信息。
你使用了 gt 包来制作高质量的表格,正如你多次看到的,这个包自带了一些优秀的默认设置。通常你不需要在代码中做太多改动就能制作有效的表格。但无论使用哪个包,重要的是要把表格当作数据可视化的一部分,给予同样的重视。
在第六章中,你将学习如何使用 R Markdown 创建报告,它可以将表格直接集成到最终文档中。用几行代码就能生成出版级别的表格,岂不是更好?
额外资源
-
Thomas Mock, “R 中更好的表格的 10+条准则,”The MockUp,2020 年 9 月 4 日,
themockup.blog/posts/2020-09-04-10-table-rules-in-r/。 -
Albert Rapp, “在 R 中使用{gt}创建美丽的表格,”2022 年 11 月 27 日,
gt.albert-rapp.de。 -
Jon Schwabish, “十条更好的表格准则,”《成本效益分析杂志》 11 卷,第 2 期(2020),
doi.org/10.1017/bca.2020.11。
第二部分 报告、演示文稿和网站
第六章:6 R MARKDOWN 报告

假设你已经收集了有关客户对新产品满意度的调查数据。现在你准备好分析数据并编写结果报告了。首先,你从 Google Sheets 下载数据,并将其导入到像 SPSS 这样的统计分析工具中。接着,使用 SPSS 清理和分析数据,将数据摘要导出为 Excel 表格,然后用 Excel 制作一些图表。最后,你用 Word 撰写报告,同时将 Excel 中的图表粘贴进报告里。
听起来很熟悉吗?如果是的话,你并不孤单。许多人使用这种工作流程进行数据分析。但当下个月新的调查数据进来,你必须重新做报告时会怎样呢?没错,又得从第一步做到第五步。这种多工具的流程可能适用于一次性的项目,但说实话,很少有项目真的是一次性的。例如,你可能发现自己犯了个错误,或者意识到原来的分析里漏掉了一些调查数据。
R Markdown 将数据分析、数据可视化以及其他 R 代码与叙述文本结合起来,创建一个可以导出为多种格式的文档,包括 Word、PDF 和 HTML,以便与非 R 用户分享。当你使用单一工具时,工作流程变得更加高效。如果你需要在二月重新创建一份一月的客户满意度报告,你只需重新运行代码,就能生成一份包含最新数据的新文档;而且若要修正分析中的错误,只需调整代码即可。
随时轻松更新报告的能力被称为可重复性,这是 R Markdown 价值的核心。本章将拆解 R Markdown 文档的各个部分,并描述一些潜在的陷阱和最佳实践。你将学习如何处理 YAML 元数据、R 代码块和 Markdown 格式的文本;创建内联 R 代码,动态改变报告中的文本;以及以不同方式运行文档的代码。
创建 R Markdown 文档
要在 RStudio 中创建 R Markdown 文档,点击文件新建文件R Markdown。选择标题、作者和日期,并设置默认的输出格式(HTML、PDF 或 Word)。这些值稍后可以更改。点击确定,RStudio 将创建一个 R Markdown 文档,其中包含一些占位符内容,如 图 6-1 所示。

图 6-1:新建 R Markdown 文档中的占位符内容
RStudio 顶部的 Knit 菜单将 R Markdown 文档转换为你在创建时选择的格式。在这个例子中,输出格式设置为 Word,因此当你进行 Knit 操作时,RStudio 会创建一个 Word 文档。
删除文档中的占位符内容。在下一部分,你将用你自己的内容替换它。
文档结构
为了探索 R Markdown 文档的结构,你将使用 第三章 中介绍的 palmerpenguins 包的数据,创建一个关于企鹅的报告。我已经将数据按年份分开,你将只使用 2007 年的数据。图 6-2 显示了完整的 R Markdown 文档,文档中的每个部分都被框住了。

图 6-2: R Markdown 文档的组成部分
所有 R Markdown 文档有三个主要部分:一个 YAML 部分、多个 R 代码块和多个 Markdown 文本部分。
YAML 元数据
YAML 部分是 R Markdown 文档的开头。YAML 的名字来自于递归首字母缩写 YAML ain’t markup language,它的意义对我们来说并不重要。三个破折号表示它的开始和结束,里面的文本包含关于 R Markdown 文档的元数据:
---
title: Penguins Report
author: David Keyes
date: 2024-01-12
output: word_document
---
如你所见,YAML 提供了标题、作者、日期和输出格式。YAML 的所有元素都是以 key: value 语法给出的,其中每个 key 是元数据的一种标签(例如标题),后面跟着值。
R 代码块
R Markdown 文档与你可能熟悉的 R 脚本文件(.R 后缀的文件)结构不同。R 脚本文件会将所有内容视为代码,除非你通过在前面加上井号 (#) 来注释掉一行。在以下列表中,第一行是注释,第二行是代码:
# Import our data
data <- read_csv("data.csv")
在 R Markdown 中,情况正好相反。YAML 之后的所有内容默认被视为文本,除非你通过创建 代码块 指定为代码。这些代码块以三个反引号开始(```), followed by the lowercase letter r surrounded by curly brackets ({}). Another three backticks indicate the end of the code chunk:
```{r}
library(tidyverse)
If you’re working in RStudio, code chunks should have a light gray background.
R Markdown treats anything in the code chunk as R code when you knit. For example, this code chunk will produce a histogram in the final Word document:
penguins %>%
ggplot(aes(x = bill_length_mm)) +
geom_histogram() +
theme_minimal()
Figure 6-3 shows the resulting histogram.

Figure 6-3: A simple histogram generated by an R Markdown code chunk
A code chunk at the top of each R Markdown document, known as the *setup code chunk*, gives instructions for what should happen when knitting a document. It contains the following options:
echo Do you want to show the code itself in the knitted document?
include Do you want to show the output of the code chunk?
message Do you want to include any messages that code might generate? For example, this message shows up when you run library(tidyverse):
—— 附加核心 tidyverse 包 ————— tidyverse 1.x.x——
dplyr 1.x.x readr 2.x.x
forcats 0.x.x stringr 1.x.x
ggplot2 3.x.x tibble 3.x.x
lubridate 1.x.x tidyr 1.x.x
purrr 1.x.x
—— 冲突————— tidyverse_conflicts() ——
x dplyr::filter() 遮蔽了 stats::filter()
x dplyr::lag() 遮蔽了 stats::lag()
warning Do you want to include any messages that the code might generate? For example, here’s the message you get when creating a histogram using geom_histogram():
stat_bin() 使用 bins = 30。使用 binwidth 选择更好的值。
> NOTE
*To see the full list of code chunk options, visit* [`yihui.org/knitr/options/`](https://yihui.org/knitr/options/)*.*
In cases where you’re using R Markdown to generate a report for a non-R user, you likely would want to hide the code, messages, and warnings but show the output (which would include any visualizations you generate). The following setup code chunk does this:
knitr::opts_chunk$set(include = TRUE,
echo = FALSE,
message = FALSE,
warning = FALSE)
The include = FALSE option on the first line applies to the setup code chunk itself. It tells R Markdown not to include the output of the setup code chunk when knitting. The options within knitr::opts_chunk$set() apply to all future code chunks. However, you can also override these global code chunk options on individual chunks. If you wanted your Word document to show both the plot itself and the code used to make it, for example, you could set echo = TRUE for that code chunk only:
penguins %>%
ggplot(aes(x = bill_length_mm)) +
geom_histogram() +
theme_minimal()
Because include is already set to TRUE within knitr::opts_chunk$set() in the setup code chunk, you don’t need to specify it again.
#### Markdown Text
Markdown is a way to style text. If you were writing directly in Word, you could just press the B button to make text bold, for example, but R doesn’t have such a button. If you want your knitted Word document to include bold text, you need to use Markdown to indicate this style in the document.
Markdown text sections (which have a white background in RStudio) will be converted into formatted text in the Word document after knitting. Figure 6-4 highlights the equivalent sections in the R Markdown and Word documents.

Figure 6-4: Markdown text in R Markdown and its equivalent in a knitted Word document
The text # Introduction in R Markdown gets converted to a first-level heading, while ## Bill Length becomes a second-level heading. By adding hashes, you can create up to six levels of headings. In RStudio, headings are easy to find because they show up in blue.
Text without anything before it becomes body text in Word. To create italic text, add single asterisks around it (*like this*). To make text bold, use double asterisks (**as shown here**).
You can make bulleted lists by placing a dash at the beginning of a line and adding your text after it:
-
Adelie
-
Gentoo
-
Chinstrap
To make ordered lists, replace the dashes with numbers. You can either number each line consecutively or, as done below, repeat 1. In the knitted document, the proper numbers will automatically generate.
1. Adelie
1. Gentoo
1. Chinstrap
Formatting text in Markdown might seem more complicated than doing so in Word. But if you want to switch from a multi-tool workflow to a reproducible R Markdown–based workflow, you need to remove all manual actions from the process so that you can easily repeat it in the future.
#### Inline R Code
R Markdown documents can also include little bits of code within Markdown text. To see how this inline code works, take a look at the following sentence in the R Markdown document:
平均鸟喙长度是 r average_bill_length 毫米。
Inline R code begins with a backtick and the lowercase letter r and ends with another backtick. In this example, the code tells R to print the value of the variable average_bill_length, which is defined as follows in the code chunk before the inline code:
average_bill_length <- penguins %>%
summarize(avg_bill_length = mean(
bill_length_mm,
na.rm = TRUE
)) %>%
pull(avg_bill_length)
This code calculates the average bill length and saves it as average_bill_length. Having created this variable, you can now use it in the inline code. As a result, the Word document includes the sentence “The average bill length is 43.9219298.”
One benefit of using inline R code is that you avoid having to copy and paste values, which is error-prone. Inline R code also makes it possible to automatically calculate values on the fly whenever you reknit the R Markdown document with new data. To see how this works, you’ll make a new report using data from 2008\. To do this, you need to change only one line, the one that reads the data:
penguins <- read_csv("https://data.rfortherestofus.com/penguins-2008.csv")
Now that you’ve switched *penguins-2007.csv* to *penguins-2008.csv*, you can reknit the report and produce a new Word document, complete with updated results. Figure 6-5 shows the new document.

Figure 6-5: The knitted Word document with 2008 data
The new histogram is based on the 2008 data, as is the average bill length of 43.5412281\. These values update automatically because every time you press Knit, the code is rerun, regenerating plots and recalculating values. As long as the data you use has a consistent structure, updating a report requires just a click of the Knit button.
### Running Code Chunks Interactively
You can run the code in an R Markdown document in two ways. The first is by knitting the entire document. The second is to run code chunks manually (also known as *interactively*) by pressing the green play button at the top right of a code chunk. The down arrow next to the green play button will run all code until that point. You can see these buttons in Figure 6-6.

Figure 6-6: The buttons on code chunks in RStudio
You can also use COMMAND-ENTER on macOS or CTRL-ENTER on Windows to run sections of code, as in an R script file. Running code interactively is a good way to test that portions of it work before you knit the entire document.
The one downside to running code interactively is that you can sometimes make mistakes that cause your R Markdown document to fail to knit. That is because, in order to knit, an R Markdown document must contain all the code it uses. If you’re working interactively and, say, load data from a separate file, you won’t be able to knit your document. When working in R Markdown, always keep all your code within a single document.
The code must also appear in the right order. An R Markdown document that looks like this, for example, will give you an error if you try to knit it:
标题: Penguins Report
作者: David Keyes
日期: 2024-01-12
输出格式: word_document
knitr::opts_chunk$set(
include = TRUE,
echo = FALSE,
message = FALSE,
warning = FALSE
)
penguins <- read_csv("https://data.rfortherestofus.com/penguins-2008.csv")
penguins %>%
ggplot(aes(x = bill_length_mm)) +
geom_histogram() +
theme_minimal()
library(tidyverse)
这个错误发生是因为你在加载 tidyverse 包之前尝试使用了像 `read_csv()` 这样的 tidyverse 函数,以及各种 ggplot 函数。
Alison Hill,一位研究科学家,也是最 prolific 的 R Markdown 教育者之一,告诉她的学生要早早且经常地进行编织。这个做法有助于更容易地找出导致编织失败的问题。Hill 描述她的典型 R Markdown 工作流是:花费 75% 的时间在编写新文档上,剩余的 25% 的时间进行编织,以检查 R Markdown 文档是否有效。
### Quarto
2022 年,Posit 发布了一款类似 R Markdown 的发布工具。这个工具叫做 Quarto,它将 R Markdown 为 R 做的事情扩展到其他语言,包括 Python、Julia 和 Observable JS。随着我写这本书时,Quarto 正在获得越来越多的关注。幸运的是,你在本章学到的概念同样适用于 Quarto。Quarto 文档有 YAML 部分、代码块和 Markdown 文本。你可以将 Quarto 文档导出为 HTML、PDF 和 Word。然而,R Markdown 和 Quarto 文档在语法上有所不同,具体内容将在第十章进一步探讨。
### 摘要
本章一开始,你考虑了一个需要每月重新生成的报告场景。你学习了如何使用 R Markdown 每月重新生成这个报告,而无需更改代码。即使丢失了最终的 Word 文档,你也可以快速重新创建它。
最重要的是,使用 R Markdown 可以在几秒钟内完成以前需要数小时才能完成的工作。当制作一个报告需要三种工具和五个步骤时,你可能不愿意去做。但正如 Alison Hill 所指出的,使用 R Markdown,你甚至可以在收到所有数据之前开始工作。你只需编写与部分数据兼容的代码,随时可以用最终数据重新运行它。
本章仅仅触及了 R Markdown 能够做的皮毛。下一章将展示如何使用它瞬间生成数百份报告。真是魔法般的存在!
### 其他资源
+ Yihui Xie, J. J. Allaire 和 Garrett Grolemund,*R Markdown: The Definitive Guide*(佛罗里达州博卡拉顿:CRC 出版社,2019 年),*[`bookdown.org/yihui/rmarkdown/`](https://bookdown.org/yihui/rmarkdown/)*。
+ Yihui Xie, Christophe Dervieux 和 Emily Riederer,*R Markdown Cookbook*(佛罗里达州博卡拉顿:CRC 出版社,2021 年),*[`bookdown.org/yihui/rmarkdown-cookbook/`](https://bookdown.org/yihui/rmarkdown-cookbook/)*。
## 第七章:7 参数化报告

*参数化报告*是一种可以同时生成多个报告的技术。通过使用参数化报告,你可以按照相同的过程生成 3,000 份报告,和制作一份报告的过程一样。这项技术还使你的工作更加准确,因为它避免了复制粘贴错误。
华盛顿特区的智库城市研究所的工作人员,使用了参数化报告来为所有美国各州以及哥伦比亚特区开发财政简报。每份报告都需要大量的文本和多个图表,因此手动创建这些报告是不现实的。相反,员工 Safia Sayed、Livia Mucciolo 和 Aaron Williams 自动化了这个过程。本章将解释参数化报告的工作原理,并带你走过城市研究所使用的代码的简化版本。
### R Markdown 中的报告模板
如果你曾经需要同时创建多个报告,你会知道这有多么令人沮丧,尤其是当你使用第六章中描述的多工具工作流时。仅制作一份报告就可能需要很长时间。如果把这个工作量乘以 10、20,或者像城市研究所团队一样,乘以 51,那么它很快就会让人感到不堪重负。幸运的是,通过参数化报告,你可以使用以下工作流一次性生成成千上万份报告:
1. 在 R Markdown 中制作报告模板。
2. 在 R Markdown 文档的 YAML 部分添加一个参数(例如,表示美国各州的参数),以表示在报告间变化的值。
3. 使用该参数生成一个州的报告,以确保你可以编织(knit)文档。
4. 创建一个独立的 R 脚本文件,设置参数值并生成报告。
5. 对所有州运行该脚本。
你将从为一个州创建报告模板开始。我已经将城市研究所员工用于制作州财政简报的代码进行了大幅简化。所有使用的包都是你在前几章中见过的,唯一的例外是`urbnthemes`包。这个包包含了一个自定义的 ggplot 主题。你可以通过在控制台运行`remotes::install_github("UrbanInstitute/urbnthemes")`来安装它。我没有专注于财政数据,而是使用了你可能更熟悉的数据:2022 年中期的 COVID-19 感染率。以下是 R Markdown 文档:
title: "Urban Institute COVID Report"
output: html_document
params:
state: "Alabama"
knitr::opts_chunk$set(
echo = FALSE,
warning = FALSE,
message = FALSE
)
library(tidyverse)
library(urbnthemes)
library(scales)
r params$state
cases <- tibble(state.name) %>%
rbind(state.name = "哥伦比亚特区") %>%
left_join(
read_csv(
"https://data.rfortherestofus.com/united_states_covid19_cases_deaths_and_testing_by_state.csv",
skip = 2
),
by = c("state.name" = "State/Territory")
) %>%
select(
total_cases = `总病例数`,
state.name,
cases_per_100000 = `每 10 万人病例数`
) %>%
mutate(cases_per_100000 = parse_number(cases_per_100000)) %>%
mutate(case_rank = rank(-cases_per_100000, ties.method = "min"))
state_text <- if_else(params$state == "District of Columbia", str_glue(
"the District of Columbia"), str_glue("state of {params$state}"))
state_cases_per_100000 <- cases %>%
filter(state.name == params$state) %>%
pull(cases_per_100000) %>%
comma()
state_cases_rank <- cases %>%
filter(state.name == params$state) %>%
pull(case_rank)
In r state_text, there were r state_cases_per_100000 cases per 100,000
people in the last seven days. This puts r params$state at number
r state_cases_rank of 50 states and the District of Columbia.
set_urbn_defaults(style = "print")
cases %>%
mutate(highlight_state = if_else(state.name == params$state, "Y", "N")) %>%
mutate(state.name = fct_reorder(state.name, cases_per_100000)) %>%
ggplot(aes(
x = cases_per_100000,
y = state.name,
fill = highlight_state
)) +
geom_col() +
scale_x_continuous(labels = comma_format()) +
theme(legend.position = "none") +
labs(
y = NULL,
x = "Cases per 100,000"
)
报告中的文本和图表来自名为 cases 的数据框,如下所示:
> # A tibble: 51 × 4
> total_cases state.name cases_per_100000 case_rank
>
> 1 1302945 Alabama 26573 18
> 2 246345 Alaska 33675 2
> 3 2025435 Arizona 27827 10
> 4 837154 Arkansas 27740 12
> 5 9274208 California 23472 35
> 6 1388702 Colorado 24115 33
> 7 766172 Connecticut 21490 42
> 8 264376 Delaware 27150 13
> 9 5965411 Florida 27775 11
> 10 2521664 Georgia 23750 34
> # ... with 41 more rows
当你编织文档时,你会得到如 图 7-1 所示的简单 HTML 文件。

图 7-1:通过 R Markdown 生成的阿拉巴马州报告
你应该能从 第六章 中认出 R Markdown 文档的 YAML、R 代码块、内联代码和 Markdown 文本。
#### 定义参数
在 R Markdown 中,*参数* 是你在 YAML 中设置的变量,允许你创建多个报告。看看 YAML 中的这两行:
params:
state: "Alabama"
这段代码定义了一个名为 state 的变量。你可以在 R Markdown 文档的其余部分使用这个 state 变量,语法为 params$variable_name,variable_name 替换为 state 或者你在 YAML 中设置的任何其他名称。例如,考虑以下内联 R 代码:
r params$state
当你编织文档时,params$state 参数的任何实例都会被转换为 "Alabama"。这个参数和其他几个参数出现在以下代码中,用于设置第一级标题,在 图 7-1 中可见:
In r state_text, there were r state_cases_per_100000 cases per 100,000
people in the last seven days. This puts r **params$state** at number
r state_cases_rank of 50 states and the District of Columbia.
编织文档后,你应该看到以下文本:
> 在阿拉巴马州,过去七天内每 10 万人中有 26,573 例病例。这使得阿拉巴马州在 50 个州和哥伦比亚特区中排名第 18。
这段文本是自动生成的。内联 R 代码 `r state_text` 打印变量 state_text 的值,该值由之前的 if_else() 调用决定,代码片段如下所示:
state_text <- if_else(params\(state == "District of Columbia", str_glue("the District of Columbia"), str_glue("state of {params\)state}"))
如果 params$states 的值是 "District of Columbia",这段代码将 state_text 设置为 "the District of Columbia"。如果 params$state 不是 "District of Columbia",那么 state_text 会得到 "state of",后跟州名。这样,你可以将 state_text 放入句子中,并且无论 state 参数是州名还是哥伦比亚特区,它都能正常工作。
#### 使用参数生成数字
你还可以使用参数生成数字值并将其包含在文本中。例如,要动态计算 state_cases_per_100000 和 state_cases_rank 变量的值,可以使用 state 参数,如下所示:
state_cases_per_100000 <- cases %>%
filter(state.name == params$state) %>%
pull(cases_per_100000) %>%
comma()
state_cases_rank <- cases %>%
filter(state.name == params$state) %>%
pull(case_rank)
首先,这段代码过滤了 cases 数据框(它包含所有州的数据),只保留 params$state 所指定的州的数据。接着,pull() 函数从该数据中提取单一值,scales 包中的 comma() 函数对其进行格式化,使 state_cases_per_100000 显示为 26,573(而不是 26573)。最后,state_cases_per_100000 和 state_case_rank 变量被集成到内联 R 代码中。
#### 在可视化代码中包含参数
params$state 参数在其他地方也有使用,比如在报告的柱状图中突出显示某个州。要查看如何实现这一点,请看以下来自最后一个代码块的部分:
cases %>%
mutate(highlight_state = if_else(state.name == params$state, "Y", "N"))
这段代码创建了一个名为 highlight_state 的变量。在 cases 数据框中,代码检查 state.name 是否等于 params$state。如果相等,highlight_state 的值为 Y;否则为 N。运行这两行代码后,相关列的情况如下所示:
> # A tibble: 51 × 2
> state.name highlight_state
>
> 1 Alabama Y
> 2 Alaska N
> 3 Arizona N
> 4 Arkansas N
> 5 California N
> 6 Colorado N
> 7 Connecticut N
> 8 Delaware N
> 9 Florida N
> 10 Georgia N
> # ... with 41 more rows
后面,ggplot 代码使用 highlight_state 变量作为柱状图填充美学属性,用黄色突出显示 params$state 中的州,将其他州染成蓝色。图 7-2 显示了突出显示 Alabama 的图表。

图 7-2:使用参数在柱状图中突出显示数据
如你所见,在 YAML 中设置参数允许你在编织报告时动态生成文本和图表。但到目前为止,你只生成了一个报告。那么如何生成所有 51 个报告呢?你可能会想到手动更新 YAML,将参数的值从“Alabama”改为“Alaska”,然后重新编织文档。虽然你*可以*按照这个流程为所有州生成报告,但这样做会很繁琐,这正是你想避免的。相反,你可以自动化报告生成过程。
### 创建 R 脚本
为了基于你创建的模板自动生成多个报告,你将使用一个 R 脚本,该脚本会更改 R Markdown 文档中参数的值,然后编织文档。你将首先创建一个名为 *render.R* 的 R 脚本文件。
#### 使用代码编织文档
你的脚本需要能够编织 R Markdown 文档。虽然你已经看到过如何使用 Knit 按钮做到这一点,但你也可以通过代码来完成相同的操作。加载 rmarkdown 包,然后使用其 render() 函数,如下所示:
library(rmarkdown)
render(
input = "urban-covid-budget-report.Rmd",
output_file = "Alaska.xhtml",
params = list(state = "Alaska")
)
该函数生成一个名为*urban-covid-budget-report.xhtml*的 HTML 文档。默认情况下,生成的文件与 R Markdown(*.Rmd*)文档同名,但扩展名不同。`output_file`参数为文件指定一个新名称,`params`参数指定会覆盖 R Markdown 文档中的参数。例如,这段代码告诉 R 使用阿拉斯加作为`state`参数,并将生成的 HTML 文件保存为*Alaska.xhtml*。
这种生成报告的方法有效,但要生成所有 51 个报告,您必须手动更改 YAML 中的州名并更新`render()`函数,然后为每个报告运行它。在下一节中,您将更新代码以提高效率。
#### 创建包含参数数据的 Tibble
要编写生成所有报告的自动化代码,首先必须创建一个*向量*(通俗地说,就是一个项的列表),其中包含所有州名和哥伦比亚特区。为此,您将使用内置数据集`state.name`,它包含了所有 50 个州的名称,存储在一个向量中:
state <- tibble(state.name) %>%
rbind("District of Columbia") %>%
pull(state.name)
这段代码将`state.name`转换为一个 tibble,并使用`rbind()`函数将哥伦比亚特区添加到列表中。`pull()`函数获取单一列并将其保存为`state`。以下是`state`向量的样子:
> [1] "Alabama" "Alaska"
> [3] "Arizona" "Arkansas"
> [5] "California" "Colorado"
> [7] "Connecticut" "Delaware"
> [9] "Florida" "Georgia"
> [11] "Hawaii" "Idaho"
> [13] "Illinois" "Indiana"
> [15] "Iowa" "Kansas"
> [17] "Kentucky" "Louisiana"
> [19] "Maine" "Maryland"
> [21] "Massachusetts" "Michigan"
> [23] "Minnesota" "Mississippi"
> [25] "Missouri" "Montana"
> [27] "Nebraska" "Nevada"
> [29] "New Hampshire" "New Jersey"
> [31] "New Mexico" "New York"
> [33] "North Carolina" "North Dakota"
> [35] "Ohio" "Oklahoma"
> [37] "Oregon" "Pennsylvania"
> [39] "Rhode Island" "South Carolina"
> [41] "South Dakota" "Tennessee"
> [43] "Texas" "Utah"
> [45] "Vermont" "Virginia"
> [47] "Washington" "West Virginia"
> [49] "Wisconsin" "Wyoming"
> [51] "District of Columbia"
与之前使用`render()`并传递`input`和`output_file`参数不同,您可以将`params`参数传递给它,以便在编织时使用这些参数。为此,创建一个包含渲染所有 51 个报告所需信息的 tibble,并将其保存为名为`reports`的对象,然后将其传递给`render()`函数,如下所示:
reports <- tibble(
input = "urban-covid-budget-report.Rmd",
output_file = str_glue("{state}.xhtml"),
params = map(state, ~ list(state = .))
)
这段代码生成一个包含 51 行和 3 个变量的 tibble。在所有行中,输入变量被设置为 R Markdown 文档的名称。`output_file`的值通过`str_glue()`函数设置为等于州名,后跟.*html*(例如,*Alabama.xhtml*)。
`params`变量是三者中最复杂的。它被称为*命名列表*。这种数据结构将数据以`state: state_name`的格式放入 R Markdown 文档的 YAML 中。`purrr`包中的`map()`函数创建命名列表,告诉 R 将每一行的值设置为`state = "Alabama"`,然后是`state = "Alaska"`,依此类推,直到所有州的名称。您可以在`reports` tibble 中看到这些变量:
> # A tibble: 51 × 3
> input output_file params
>
> 1 urban-covid-budget-report.Rmd Alabama.xhtml
> 2 urban-covid-budget-report.Rmd Alaska.xhtml
> 3 urban-covid-budget-report.Rmd Arizona.xhtml
> 4 urban-covid-budget-report.Rmd Arkansas.xhtml
> 5 urban-covid-budget-report.Rmd California...
> 6 urban-covid-budget-report.Rmd Colorado.xhtml
> 7 urban-covid-budget-report.Rmd Connecticut...
> 8 urban-covid-budget-report.Rmd Delaware.xhtml
> 9 urban-covid-budget-report.Rmd Florida.xhtml
> 10 urban-covid-budget-report.Rmd Georgia.xhtml
> # ... with 41 more rows
`params`变量显示为<命名列表>,但如果您在 RStudio 查看器中打开 tibble(点击环境选项卡中的**reports**),您可以更清晰地看到输出,如图 7-3 所示。

图 7-3:RStudio 查看器中的命名列表列
此视图允许您在`params`变量中查看命名列表,并且`state`变量等于每个州的名称。
一旦您创建了`reports` tibble,就可以准备渲染报告。渲染报告的代码只有一行:
pwalk(reports, render)
这个`pwalk()`函数(来自`purrr`包)有两个参数:一个数据框或 tibble(在这里是`reports`)和一个对 tibble 每一行运行的函数(即`render()`)。
> 注意
*在将`render()`函数传递给`pwalk()`时,不要包含开闭括号。*
运行这段代码会为报告中的每一行运行`render()`函数,并传入 input、output_file 和 params 的值。这相当于输入如下代码,执行`render()`函数 51 次(涵盖 50 个州和哥伦比亚特区):
render(
input = "urban-covid-budget-report.Rmd",
output_file = "Alabama.xhtml",
params = list(state = "Alabama")
)
render(
input = "urban-covid-budget-report.Rmd",
output_file = "Alaska.xhtml",
params = list(state = "Alaska")
)
render(
input = "urban-covid-budget-report.Rmd",
output_file = "Arizona.xhtml",
params = list(state = "Arizona")
)
这是完整的 R 脚本文件:
Load packages
library(tidyverse)
library(rmarkdown)
Create a vector of all states and the District of Columbia
state <- tibble(state.name) %>%
rbind("District of Columbia") %>%
pull(state.name)
Create a tibble with information on the:
input R Markdown document
output HTML file
parameters needed to knit the document
reports <- tibble(
input = "urban-covid-budget-report.Rmd",
output_file = str_glue("{state}.xhtml"),
params = map(state, ~ list(state = .))
)
Generate all of our reports
pwalk(reports, render)
在运行`pwalk(reports, render)`代码后,你应该能在 RStudio 的文件面板中看到 51 个 HTML 文档。每个文档都包含该州的报告,附带定制的图表和相应的文本。
### 最佳实践
尽管参数化报告功能强大,但也可能会带来一些挑战。例如,要确保考虑到数据中的离群值。在州报告的情况下,哥伦比亚特区是一个离群值,因为它 technically 不是一个州。Urban Institute 团队通过使用`if_else()`语句(如本章开头所示)修改了报告文本的语言,使其不再将哥伦比亚特区称为州。
另一个最佳实践是手动生成并审核那些参数值最短(例如州财政简报中的爱荷华州、俄亥俄州和犹他州)和最长(哥伦比亚特区)文本长度的报告。这样,你可以识别出可能会导致意外结果的地方,例如图表标题被截断或文本溢出导致页面断裂。花几分钟进行手动审查,可以使自动生成多个报告的过程更加顺畅。
### 摘要
在本章中,你重新创建了 Urban Institute 的州财政简报,采用了参数化报告的方法。你学会了如何在 R Markdown 文档中添加一个参数,然后使用 R 脚本设置该参数的值并生成报告。
自动化报告生产可以节省大量时间,尤其是在你需要生成大量报告时。考虑 Urban Institute 的另一个项目:制作县级报告。美国有超过 3000 个县,手工制作这些报告不现实。而且,如果 Urban Institute 的员工使用 SPSS、Excel 和 Word 来制作报告,他们将不得不在程序之间复制粘贴值。人类是会犯错的,无论我们如何努力避免,错误总会发生。而计算机则不会发生复制粘贴错误。让计算机处理生成多个报告的繁琐工作,可以大大减少错误的发生几率。
刚开始时,参数化报告可能会让人觉得有些困难,因为你需要确保代码适用于报告的每个版本。但一旦你拥有了 R Markdown 文档和配套的 R 脚本文件,你会发现一次性生成多个报告变得很容易,最终能够节省大量工作量。
### 额外资源
+ Data@Urban 团队,“使用 R Markdown 创建迭代版数据报告,”Medium,2018 年 7 月 24 日,*[`urban-institute.medium.com/iterated-fact-sheets-with-r-markdown-d685eb4eafce`](https://urban-institute.medium.com/iterated-fact-sheets-with-r-markdown-d685eb4eafce)*。
+ Data@Urban 团队,“使用 R Markdown 跟踪和发布州数据,”Medium,2021 年 4 月 21 日,*[`urban-institute.medium.com/using-r-markdown-to-track-and-publish-state-data-d1291bfa1ec0`](https://urban-institute.medium.com/using-r-markdown-to-track-and-publish-state-data-d1291bfa1ec0)*。
## 第八章:8 幻灯片演示文稿

如果你需要制作类似 PowerPoint 那样的幻灯片演示,R 可以帮你实现。在本章中,你将学习如何使用 xaringan 制作演示文稿。这个使用 R Markdown 的包是 R 中最广泛使用的制作幻灯片的工具。
你将使用 xaringan 将第六章中的企鹅报告转换为幻灯片。你将学习如何创建新的幻灯片,选择性地揭示内容,调整文本和图像对齐方式,并使用 CSS 样式化你的演示文稿。
### 为什么使用 xaringan?
在 RStudio 中创建新 R Markdown 文档时,你可能注意到了演示文稿选项。这个选项提供了多种制作幻灯片的方式,例如将 R Markdown 文档编织为 PowerPoint 文件。然而,使用 xaringan 包相较于这些选项有其优势。
例如,因为 xaringan 将幻灯片创建为 HTML 文档,你可以将它们发布到网上,而不需要通过电子邮件发送或打印出来给观众。你可以通过共享链接轻松地将演示文稿发送给别人。第九章将讨论如何将你的演示文稿发布到网上。
使用 xaringan 的另一个好处是可访问性。HTML 文档易于操作,使观众能够控制其外观。例如,视力有限的人可以通过增加文本大小或使用屏幕阅读器来访问 HTML 文档,从而查看内容。使用 xaringan 制作演示文稿可以让更多人参与到你的幻灯片中。
### xaringan 的工作原理
要开始使用 xaringan,在 RStudio 中运行`install.packages("xaringan")`来安装该包。接下来,点击**文件****新建文件****R Markdown**来创建一个新项目。选择**从模板**选项卡,选择名为**Ninja Presentation**的模板,然后点击**确定**。
你应该会得到一个包含一些默认内容的 R Markdown 文档。删除这些内容,并添加你在第六章创建的企鹅 R 报告。然后,像下面这样在 YAML 中更改输出格式为 xaringan::moon_reader:
title: "Penguins Report"
author: "David Keyes"
date: "2024-01-12"
output: xaringan::moon_reader
moon_reader 输出格式将 R Markdown 文档转化为幻灯片并进行编织。尝试点击**编织**,看看效果。你应该会得到一个 HTML 文件,文件名与 R Markdown 文档相同(例如*xaringan-example.xhtml*),如图 8-1 所示。

图 8-1:xaringan 包会自动生成一个标题幻灯片。
如果你按右箭头键滚动到下一张幻灯片,你应该会看到熟悉的内容。图 8-2 显示了第二张幻灯片,它的文本与第六章的报告相同,并且包含一个被截断的直方图。

图 8-2:第二张幻灯片需要调整,因为直方图被截断了。
尽管使用 xaringan 制作幻灯片的语法几乎与使用 R Markdown 制作报告的语法完全相同,但你需要做一些调整,以确保内容适合幻灯片。当你在一个将被编织成 Word 文档的文档中工作时,长度并不重要,因为报告可以有 1 页或 100 页。然而,使用 xaringan 时,你需要考虑每张幻灯片上能容纳多少内容。被截断的直方图演示了如果你不这样做会发生什么。接下来你将解决这个问题。
#### 创建新幻灯片
你可以通过将直方图放入单独的幻灯片来使其完全可见。要创建新幻灯片,在你希望幻灯片开始的地方添加三个破折号(---)。我已经在直方图代码之前添加了它们:
---
Bill Length
We can make a histogram to see the distribution of bill lengths.
penguins %>%
ggplot(aes(x = bill_length_mm)) +
geom_histogram() +
theme_minimal()
当你再次编织文档时,原本是一张幻灯片的内容现在应该被分成两张:一张介绍幻灯片和一张鸟嘴长度幻灯片。然而,如果你仔细看,你会发现直方图的底部仍然稍微被截断了。为了解决这个问题,你需要调整它的大小。
#### 调整图形的大小
使用代码块选项 fig.height 来调整直方图的大小:
Bill Length
We can make a histogram to see the distribution of bill lengths.
penguins %>%
ggplot(aes(x = bill_length_mm)) +
geom_histogram() +
theme_minimal()
这样做可以让直方图完全显示在幻灯片上,并且显现出被遮住的文本。请记住,fig.height 仅调整图形的输出高度;有时你可能还需要使用 fig.width 来调整输出的宽度,或者替代它。
#### 逐步显示内容
在展示幻灯片时,你可能希望每次只显示一部分内容。比如,当你展示第一张幻灯片时,你可能想先简单介绍一下每种企鹅物种。与其在打开幻灯片时一次性显示所有三种物种,你可能更希望让每个物种的名字依次出现。
你可以使用 xaringan 的一个功能叫做*逐步显示*。在你希望逐步显示的任何内容之间放置两个破折号(--),如下所示:
Introduction
We are writing a report about the Palmer Penguins. These penguins are
really amazing. There are three species:
- Adelie
--
- Gentoo
--
- Chinstrap
这段代码让你首先显示 Adelie 物种;然后是 Adelie 和 Gentoo;最后是 Adelie、Gentoo 和 Chinstrap。
在展示幻灯片时,使用右箭头逐步显示物种。
#### 使用内容类对齐内容
你可能还想控制内容的对齐方式。为此,你可以添加*内容类* .left[]、.right[]和.center[],以指定某部分内容的对齐方式。例如,要将直方图居中对齐,可以使用.center[],如下所示:
.center[
penguins %>%
ggplot(aes(x = bill_length_mm)) +
geom_histogram() +
theme_minimal()
]
这段代码将图表居中显示在幻灯片上。
其他内置选项可以制作两栏布局。添加.pull-left[]和.pull-right[]将创建两个等间距的列。使用以下代码将直方图显示在幻灯片的左侧,伴随的文本显示在右侧:
.pull-left[
penguins %>%
ggplot(aes(x = bill_length_mm)) +
geom_histogram() +
theme_minimal()
]
.pull-right[
average_bill_length <- penguins %>%
summarize(avg_bill_length = mean(bill_length_mm,
na.rm = TRUE)) %>%
pull(avg_bill_length)
The chart shows the distribution of bill lengths. The average bill length is
r average_bill_length millimeters.
]
图 8-3 显示了结果。

图 8-3:一个包含两个大小相等列的幻灯片
要制作一个窄的左列和宽的右列,使用内容类 .left-column[] 和 .right-column[]。 图 8-4 显示了文本在左侧,直方图在右侧的幻灯片效果。

图 8-4:一个左列较小、右列较大的幻灯片
除了对幻灯片上的特定内容进行对齐外,你还可以使用左、右和居中类来水平对齐整个内容。为此,在表示新幻灯片的三条破折号后,但在任何内容之前,指定类名:
class: center
Bill Length
We can make a histogram to see the distribution of bill lengths.
penguins %>%
ggplot(aes(x = bill_length_mm)) +
geom_histogram() +
theme_minimal()
这段代码生成一个水平居中的幻灯片。要调整垂直位置,你可以使用类 top、middle 和 bottom。
#### 向幻灯片添加背景图像
使用刚才用来居中整个幻灯片的相同语法,你还可以添加背景图像。创建一个新幻灯片,使用类名 center 和 middle 来水平和垂直对齐内容,并通过在 url() 的括号内指定图片路径来添加背景图像:
class: center, middle
background-image: url("penguins.jpg")
Penguins
要运行此代码,你需要在项目中有一个名为*penguins.jpg*的文件(你可以从 *[`data.rfortherestofus.com/penguins.jpg`](https://data.rfortherestofus.com/penguins.jpg)* 下载)。编织文档后,应该会生成一个使用该图片作为背景,并在前面显示*Penguins*文本的幻灯片,如图 8-5 所示。

图 8-5:一个使用背景图像的幻灯片
现在你将添加自定义 CSS 来进一步改进这个幻灯片。
### 为幻灯片应用 CSS
你刚刚制作的幻灯片有一个问题,就是*Penguins*这个词很难阅读。如果你能让文字更大些,颜色也不同一些,那就更好了。要做到这一点,你需要使用*层叠样式表 (CSS)*,这是一种用于样式化 HTML 文档的语言。如果你在想,*我读这本书是为了学习 R,不是 CSS*,别担心:你只需要一点点 CSS 来调整你的幻灯片样式。为了应用这些样式,你可以编写自定义代码,使用 CSS 主题,或者结合这两种方法,利用 xaringanthemer 包。
#### 自定义 CSS
要添加自定义 CSS,请创建一个新的代码块,并在大括号之间放入 CSS:
**.remark-slide-content h2 {**
**font-size: 150px;**
**color: white;**
}
这段代码告诉 R Markdown 将二级标题(h2)设置为 150 像素大且为白色。在标题前添加 .remark-slide-content 以便定位幻灯片中的特定元素。“remark”一词来自于 *remark.js*,这是一个用于制作幻灯片的 JavaScript 库,xaringan 在背后使用了它。
为了改变字体以及文本的大小和颜色,添加以下 CSS:
**@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap');**
.remark-slide-content h2 {
font-size: 150px;
color: white;
**font-family: Inter;**
**font-weight: bold;**
}
第一行新代码使得名为 Inter 的字体在幻灯片中可用,因为有些人可能没有在他们的计算机上安装该字体。接下来,这段代码将 Inter 字体应用于标题并使其加粗。你可以在图 8-6 中看到使用加粗的 Inter 字体的幻灯片。

图 8-6:应用 CSS 字体更改后的标题幻灯片
由于 xaringan 幻灯片是作为 HTML 文档构建的,你可以根据自己的喜好使用 CSS 对其进行自定义。天空是极限!
#### 主题
你可能不关心 CSS 的细节。幸运的是,你可以通过两种方式自定义你的幻灯片,而无需自己编写 CSS。第一种方式是应用其他 R 用户创建的 xaringan 主题。运行以下代码以获取所有可用主题的列表:
names(xaringan:::list_css())
输出应该看起来像这样:
> [1] "chocolate-fonts" "chocolate"
> [3] "default-fonts" "default"
> [5] "duke-blue" "fc-fonts"
> [7] "fc" "glasgow_template"
> [9] "hygge-duke" "hygge"
> [11] "ki-fonts" "ki"
> [13] "kunoichi" "lucy-fonts"
> [15] "lucy" "metropolis-fonts"
> [17] "metropolis" "middlebury-fonts"
> [19] "middlebury" "nhsr-fonts"
> [21] "nhsr" "ninjutsu"
> [23] "rladies-fonts" "rladies"
> [25] "robot-fonts" "robot"
> [27] "rutgers-fonts" "rutgers"
> [29] "shinobi" "tamu-fonts"
> [31] "tamu" "uio-fonts"
> [33] "uio" "uo-fonts"
> [35] "uo" "uol-fonts"
> [37] "uol" "useR-fonts"
> [39] "useR" "uwm-fonts"
> [41] "uwm" "wic-fonts"
> [43] "wic"
一些 CSS 文件仅更改字体,而其他文件则更改一般元素,比如文本大小、颜色以及是否显示幻灯片编号。使用预构建主题通常需要同时使用一个通用主题和一个字体主题,如下所示:
title: "Penguins Report"
author: "David Keyes"
date: "2024-01-12"
output:
xaringan::moon_reader:
css: [default, metropolis, metropolis-fonts]
这段代码告诉 xaringan 使用默认的 CSS,并应用在 metropolis 和 metropolis-fonts CSS 主题中所做的自定义。这些主题已经捆绑在 xaringan 中,因此你无需安装任何额外的包即可使用它们。图 8-7 展示了主题如何改变幻灯片的外观和感觉。

图 8-7:使用 metropolis 主题的幻灯片
如果编写自定义 CSS 是完全灵活但更具挑战性的选项,那么使用自定义主题则更简单,但灵活性较低。自定义主题允许你轻松使用他人预构建的 CSS,但无法进一步修改它。
#### xaringanthemer 包
在编写自定义 CSS 和应用他人主题之间找到一个合适的折衷方法,就是使用 Garrick Aden-Buie 的 xaringanthemer 包。该包包含了几个内置的主题,同时也允许你轻松创建自己的自定义主题。安装该包后,调整 YAML 文件中的 css 行,使用 *xaringan-themer.css* 文件,如下所示:
title: "Penguins Report"
author: "David Keyes"
date: "2024-01-12"
output:
xaringan::moon_reader:
css: xaringan-themer.css
现在,你可以通过使用 style_xaringan() 函数来定制你的幻灯片。这个函数有超过 60 个参数,使你可以调整 xaringan 幻灯片的几乎任何部分。要使用 xaringanthemer 重现本章之前编写的自定义 CSS,你只需使用其中的一些参数:
**library(xaringanthemer)**
**style_xaringan(**
**header_h2_font_size = "150px",**
**header_color = "white",**
**header_font_weight = "bold",**
**header_font_family = "Inter"**
**)**
这段代码将标题大小设置为 150 像素,并使所有标题使用加粗的白色 Inter 字体。
关于 xaringanthemer 包的一个特别好处是,你可以通过简单地将其名称添加到 header_font_family 或其他设置字体系列的参数中,使用 Google Fonts 上的任何字体(text_font_family 和 code_font_family 分别用于设置正文文本和代码的字体)。这意味着你不需要再包含使 Inter 字体可用的那一行代码。
### 摘要
在本章中,你学习了如何使用 xaringan 包创建演示文稿。你了解了如何逐步展示幻灯片内容、创建多列布局,并向幻灯片添加背景图片。你还通过应用自定义主题、编写 CSS 和使用 xaringanthemer 包来改变幻灯片的外观。
使用 xaringan,你可以创建任何类型的演示文稿,并根据你的需求自定义外观和风格。使用 xaringan 创建演示文稿还可以让你轻松分享 HTML 格式的幻灯片,并提高可访问性。
### 其他资源
+ Garrick Aden-Buie, Silvia Canelón, 和 Shannon Pileggi,“专业、精致、易于展示:使用 xaringan 制作出色的幻灯片”,研讨会材料,未注明日期, *[`presentable-user2021.netlify.app`](https://presentable-user2021.netlify.app)*。
+ Silvia Canelón,“通过 xaringan 分享你的工作:xaringan 演示文稿简介:基础知识及更多”,NHS-R 社区 2020 年虚拟大会的研讨会,2020 年 11 月 2 日, *[`spcanelon.github.io/xaringan-basics-and-beyond/index.xhtml`](https://spcanelon.github.io/xaringan-basics-and-beyond/index.xhtml)*。
+ Alison Hill,“认识 xaringan:在 R Markdown 中制作幻灯片”,幻灯片演示,2019 年 1 月 16 日, *[`arm.rbind.io/slides/xaringan.xhtml`](https://arm.rbind.io/slides/xaringan.xhtml)*。
+ Yihui Xie, J. J. Allaire, 和 Garrett Grolemund,“xaringan 演示文稿”,见 *R Markdown: The Definitive Guide*(佛罗里达州博卡拉顿:CRC Press,2019 年), *[`bookdown.org/yihui/rmarkdown/`](https://bookdown.org/yihui/rmarkdown/)*。
## 第九章:9 网站

2020 年夏天,Matt Herman 的家人从布鲁克林搬到了纽约的 Westchester 县。那时 COVID-19 疫情刚开始,Herman 震惊于该县几乎没有发布任何关于感染率的数据。疫苗还未上市,是否去公园等日常选择也依赖于是否能获得准确的数据。
当时,Herman 是纽约市儿童服务局研究与分析办公室儿童统计数据单元的副主任。这个冗长的职位意味着他擅长处理数据,使他能够创建所需的 COVID 资源:Westchester COVID-19 跟踪网站。
这个网站完全使用 R 构建,利用图表、地图、表格和文字来总结 Westchester 县的最新 COVID 数据。虽然该网站不再每日更新,但你仍然可以访问它,网址是*[`westchester-covid.mattherman.info`](https://westchester-covid.mattherman.info)*。
为了创建这个网站,Herman 编写了一组 R Markdown 文件,并通过 distill 包将它们连接起来。本章通过创建一个简单的网站来解释该包的基本用法。你将学习如何生成不同的页面布局、导航菜单和交互式图形,然后探索托管网站的策略。
### 创建一个新的 distill 项目
网站仅仅是一些 HTML 文件的集合,就像你在第八章中创建幻灯片展示时所制作的那样。distill 包使用多个 R Markdown 文档来生成多个 HTML 文件,然后通过导航菜单等将它们连接起来。
要创建一个 distill 网站,首先使用 install.packages("distill")安装该包。然后,通过在 RStudio 中选择**文件****新建项目****新建目录**,并选择**Distill Website**作为项目类型,来开始一个项目。
指定项目在计算机上所在的目录和子目录,然后为你的网站命名。勾选“配置 GitHub Pages”选项,这将提供一种简便的方式来发布你的网站在线(你将在“GitHub 托管”一节中了解它是如何工作的,第 154 页)。如果你希望使用此部署选项,请选中它。
### 项目文件
你现在应该已经有了一个包含多个文件的项目。除了表示你正在使用 RStudio 项目的*covid-website.Rproj*文件外,你还应该有两个 R Markdown 文档,一个*_site.yml*文件,以及一个*docs*文件夹,渲染后的 HTML 文件将存放在该文件夹内。让我们来看看这些网站文件。
#### R Markdown 文档
每个 R Markdown 文件代表网站的一个页面。默认情况下,distill 会创建一个主页(*index.Rmd*)和一个关于页面(*about.Rmd*),其中包含占位符内容。如果你想生成额外的页面,只需添加新的 R Markdown 文件,并在接下来的部分中提到的*_site.yml*文件中列出它们。
如果你打开 *index.Rmd* 文件,你会注意到 YAML 中包含了两个在前几章的 R Markdown 文档中没有出现过的参数:description 和 site:
title: "COVID Website"
description: |
Welcome to the website. I hope you enjoy it!
site: distill::distill_website
description 参数指定了每个页面标题下方应该显示的文本,如图 9-1 所示。

图 9-1:默认网站描述
site: distill::distill_website 行标识了 distill 网站的根页面。这意味着当你编织文档时,R Markdown 会知道要创建一个网站,而不是一个独立的 HTML 文件,而且网站应该首先显示这个页面。网站的其他页面不需要这一行代码。只要它们在 *_site.yml* 文件中列出,它们就会被添加到网站中。
你还会注意到在其他 R Markdown 文档中出现过的一个参数缺失:output,它指定了在编织时 R 应该使用的输出格式。这里缺少 output 的原因是,你将在 *_site.yml* 文件中为整个网站指定输出格式。
#### _site.yml 文件
*_site.yml* 文件告诉 R 哪些 R Markdown 文档构成了网站,编织后的文件应该是什么样子,网站应该叫什么,以及更多信息。当你打开它时,应该能看到以下代码:
name: "covid-website"
title: "COVID Website"
description: |
COVID Website
output_dir: "docs"
navbar:
right:
- text: "Home"
href: index.xhtml
- text: "About"
href: about.xhtml
output: distill::distill_article
name 参数决定了你网站的 URL。默认情况下,这应该是你的 distill 项目所在目录的名称;在我的例子中,这就是 *covid-website* 目录。title 参数为整个网站创建标题,并默认显示在导航栏的左上方。description 参数提供了所谓的 *meta description*,它将出现在 Google 搜索结果中,给用户一个网站内容的概览。
output_dir 参数决定了在生成网站时渲染后的 HTML 文件存放位置。你应该会看到列出了 *docs* 目录。不过,你可以将输出目录更改为任何你选择的文件夹。
接下来,导航栏部分定义了网站的导航。在这里它出现在页眉的右侧,但将右侧参数换成左侧就能改变它的位置。导航栏包含指向网站两个页面的链接,即主页和关于页面,如图 9-2 所示。

图 9-2:网站导航栏
在导航栏代码中,text 参数指定菜单中显示的文本。(例如,试着将“About”改为**About This Website**,然后再改回来。)href 参数决定了导航栏中的文本链接到哪个 HTML 文件。如果你想在菜单中添加其他页面,你需要同时添加文本和 href 参数。
最后,output 参数指定所有 R Markdown 文档应使用 distill_article 格式渲染。此格式允许不同宽度的布局,*侧边栏*(位于主内容旁边的括注项目),易于自定义的 CSS 等。
### 构建网站
我们已经浏览了项目的文件,但还没有使用它们来创建网站。为此,请点击 RStudio 右上角面板中的 **构建网站**。 (你也可以在控制台中或 R 脚本文件中运行 rmarkdown::render_site()。)
这应该会呈现所有 R Markdown 文档,并在其上添加顶部导航栏,包含在 *_site.yml* 文件中指定的选项。要找到已渲染的文件,请查看 *docs*(或你指定的任何输出目录)。打开 *index.xhtml* 文件,你将看到你的网站,应该像 图 9-3 一样。

图 9-3:带有默认内容的 COVID 网站
你也可以打开任何其他的 HTML 文件来查看其渲染后的版本。
### 应用自定义 CSS
使用 distill 制作的网站往往看起来相似,但你可以通过自定义 CSS 来改变它们的设计。distill 包甚至提供了一个函数来简化这一过程。在控制台中运行 distill::create_theme() 来创建一个名为 *theme.css* 的文件,如下所示:
/* base variables */
/* Edit the CSS properties in this file to create a custom
Distill theme. Only edit values in the right column
for each row; values shown are the CSS defaults.
To return any property to the default,
you may set its value to: unset
All rows must end with a semi-colon. */
/* Optional: embed custom fonts here with @import /
/ This must remain at the top of this file. */
html {
/-- Main font sizes --/
--title-size: 50px;
--body-size: 1.06rem;
--code-size: 14px;
--aside-size: 12px;
--fig-cap-size: 13px;
/-- Main font colors --/
--title-color: #000000;
--header-color: rgba(0, 0, 0, 0.8);
--body-color: rgba(0, 0, 0, 0.8);
--aside-color: rgba(0, 0, 0, 0.6);
--fig-cap-color: rgba(0, 0, 0, 0.6);
/-- Specify custom fonts ~~~ must be imported above --/
--heading-font: sans-serif;
--mono-font: monospace;
--body-font: sans-serif;
--navbar-font: sans-serif; /* websites + blogs only */
}
/-- ARTICLE METADATA --/
d-byline {
--heading-size: 0.6rem;
--heading-color: rgba(0, 0, 0, 0.5);
--body-size: 0.8rem;
--body-color: rgba(0, 0, 0, 0.8);
}
/-- ARTICLE TABLE OF CONTENTS --/
.d-contents {
--heading-size: 18px;
--contents-size: 13px;
}
/-- ARTICLE APPENDIX --/
d-appendix {
--heading-size: 15px;
--heading-color: rgba(0, 0, 0, 0.65);
--text-size: 0.8em;
--text-color: rgba(0, 0, 0, 0.5);
}
/-- WEBSITE HEADER + FOOTER --/
/* These properties only apply to Distill sites and blogs */
.distill-site-header {
--title-size: 18px;
--text-color: rgba(255, 255, 255, 0.8);
--text-size: 15px;
--hover-color: white;
--bkgd-color: #0F2E3D;
}
.distill-site-footer {
--text-color: rgba(255, 255, 255, 0.8);
--text-size: 15px;
--hover-color: white;
--bkgd-color: #0F2E3D;
}
/-- Additional custom styles --/
/* Add any additional CSS rules below */
在这个文件中,有一组 CSS 变量,允许你自定义你网站的设计。它们大多数的名字清楚地表明了它们的作用,你可以将它们的默认值更改为任何你想要的值。例如,以下对站点头部的编辑使标题和文本大小变大,并将背景色更改为浅蓝色:
.distill-site-header {
--title-size: 28px;
--text-color: rgba(255, 255, 255, 0.8);
--text-size: 20px;
--hover-color: white;
--bkgd-color: #6cabdd;
}
然而,在你看到这些更改之前,你需要在 *_site.yml* 文件中添加一行,告诉 distill 在渲染时使用这个自定义 CSS:
name: "covid-website"
title: "COVID Website"
description: |
COVID Website
theme: theme.css
output_dir: "docs"
navbar:
--snip--
现在你可以再次生成站点,应该能看到你的更改已反映出来。
在 *theme.css* 中有很多其他的 CSS 变量,你可以修改它们来调整你网站的外观。尝试调整这些变量并重新生成站点是了解每个变量作用的好方法。
> 注意
*要了解更多关于自定义网站外观和感觉的信息,请查看其他人使用 distill 制作的网站,访问* [`distillery.rbind.io`](https://distillery.rbind.io)*。*
### 处理网站内容
你可以通过在页面的 R Markdown 文档中创建 Markdown 文本和代码块来向你网站的页面添加内容。例如,若要突出显示 COVID 病例的变化趋势,你将用显示表格、地图和图表的代码替换 *index.Rmd* 的内容,并展示在网站的首页。以下是文件的开头部分:
title: "COVID Website"
description: "Information about COVID rates in the United States over time"
site: distill::distill_website
knitr::opts_chunk$set(echo = FALSE,
warning = FALSE,
message = FALSE)
# 加载包
library(tidyverse)
library(janitor)
library(tigris)
library(gt)
library(lubridate)
在 YAML 和设置代码块之后,这段代码加载了几个包,许多你在前面的章节中见过:用于数据导入、操作和绘图的 tidyverse(结合 ggplot);janitor 用于 clean_names()函数,它使得变量名称更容易使用;tigris 用于导入关于州的地理空间数据;gt 用于制作漂亮的表格;lubridate 用于处理日期。
接下来,为了导入和清理数据,添加以下新的代码块:
# 导入数据
us_states <- states(
cb = TRUE,
分辨率 = "20m",
progress_bar = FALSE
) %>%
shift_geometry() %>%
clean_names() %>%
选择(geoid, name) %>%
重命名(state = name) %>%
过滤(state %in% state.name)
covid_data <- read_csv("https://raw.githubusercontent.com/nytimes/covid-19-data/master/rolling-averages/us-states.csv") %>%
过滤(state %in% state.name) %>%
mutate(geoid = str_remove(geoid, "USA-"))
last_day <- covid_data %>% ❶
slice_max(
按日期排序,
n = 1
) %>%
distinct(date) %>%
mutate(date_nice_format = str_glue("{month(date, label = TRUE, abbr = FALSE)} {day(date)}, {year(date)}")) %>%
提取(date_nice_format)
COVID Death Rates as of r last_day ❷
这段代码使用 slice_max()函数从 covid_data 数据框中获取最新的日期。(数据已添加到 2023 年 3 月 23 日,因此该日期是最新的。)接下来,使用 distinct()获取该日期的唯一观测值(每个日期在 covid_data 数据框中出现多次)。然后,代码使用 str_glue()函数创建一个 date_nice_format 变量,将月份、日期和年份组合成易读的格式。最后,使用 pull()函数将数据框转换为一个名为 last_day ❶的单一变量,稍后在文本部分中引用。通过内联 R 代码,标题现在会显示当前日期 ❷。
包含以下代码,显示四个州每 10 万人死亡率的表格(使用所有州会导致表格过大):
covid_data %>%
过滤(state %in% c(
"阿拉巴马州",
"阿拉斯加州",
"亚利桑那州",
"阿肯色州"
)) %>%
slice_max(
按照日期排序,
n = 1
) %>%
选择(state, deaths_avg_per_100k) %>%
按照州排序 %>%
set_names("State", "Death rate") %>%
gt() %>%
tab_style(
style = cell_text(weight = "bold"),
locations = cells_column_labels()
)
这个表格类似于你在第五章中看到的代码。首先,filter()函数将数据过滤为四个州,然后 slice_max()函数获取最新日期。接着,代码选择相关变量(state 和 deaths_avg_per_100k),按州的字母顺序排列数据,设置变量名,并将输出通过管道传递给使用 gt 包生成的表格。
添加以下代码,使用在第四章中介绍的技术,制作所有州的地图:
We can see this same death rate data for all states on a map.
most_recent <- us_states %>%
left_join(covid_data, by = "state") %>%
slice_max(order_by = date,
n = 1)
most_recent %>%
ggplot(aes(fill = deaths_avg_per_100k)) +
geom_sf() +
scale_fill_viridis_c(option = "rocket") +
labs(fill = "每 10 万人死亡人数") +
theme_void()
这段代码通过将 us_states 地理数据与 covid_data 数据框连接,然后筛选出最新日期来创建一个 most_recent 数据框。接着,使用 most_recent 创建一个显示每 10 万人死亡人数的地图。
最后,为了显示四个州随时间变化的 COVID 死亡率图表,添加以下内容:
COVID Death Rates Over Time
The following chart shows COVID death rates from the start of COVID in early
2020 until r last_day.
covid_data %>%
filter(state %in% c(
"阿拉巴马",
"阿拉斯加",
"亚利桑那",
"阿肯色"
)) %>%
ggplot(aes(
x = date,
y = deaths_avg_per_100k,
group = state,
fill = deaths_avg_per_100k
)) +
geom_col() +
scale_fill_viridis_c(option = "rocket") +
theme_minimal() +
labs(title = "每 10 万人死亡人数随时间变化") +
theme(
legend.position = "none",
plot.title.position = "plot",
plot.title = element_text(face = "bold"),
panel.grid.minor = element_blank(),
axis.title = element_blank()
) +
facet_wrap(
~state,
nrow = 2
)
使用 geom_col() 函数,这段代码创建了一组条形图,展示了不同州随时间变化的情况(分面图在第二章中讨论过)。最后,它应用了火箭色彩调色板,使用 theme_minimal() 并对该主题进行了一些微调。图 9-4 展示了疫情开始三年后网站首页的样子。

图 9-4:展示表格、地图和图表的 COVID 网站
现在你已经有了一些内容,可以进行调整。例如,由于许多州非常小,特别是在东北部,因此查看它们有点困难。我们来看一下如何让整个地图更大。
#### 应用 distill 布局
distill 的一个优点是,它包括四种布局,可以应用于代码块以扩大输出:l-body-outset(创建比默认值稍宽的输出),l-page(创建更宽的输出),l-screen(创建全屏输出),以及 l-screen-inset(创建带有一点缓冲的全屏输出)。
通过修改代码块的第一行来将 l-screen-inset 应用于地图,如下所示:
This makes the map wider and taller and, as a result, much easier to read.
Making the Content Interactive
The content you’ve added to the website so far is all static; it has none of the interactivity typically seen in websites, which often use JavaScript to respond to user behavior. If you’re not proficient with HTML and JavaScript, you can use R packages like distill, plotly, and DT, which wrap JavaScript libraries, to add interactive elements like the graphics and maps Matt Herman uses on his Westchester County COVID website. Figure 9-5, for example, shows a tooltip that allows the user to see results for any single day.

Figure 9-5: An interactive tooltip showing new COVID cases by day
Using the DT package, Herman also makes interactive tables that allow the user to scroll through the data and sort the values by clicking any variable in the header, as shown in Figure 9-6.

Figure 9-6: An interactive table made with the DT package
Next, you’ll add some interactivity to your COVID website, beginning with your table.
Adding Pagination to a Table with reactable
Remember how you included only four states in the table to keep it from getting too long? By creating an interactive table, you can avoid this limitation. The reactable package is a great option for interactive tables. First, install it with install.packages("reactable"). Then, swap out the gt package code you used to make your static table with the reactable() function to show all states:
library(reactable)
covid_data %>%
slice_max(
order_by = date,
n = 1
) %>%
select(state, deaths_avg_per_100k) %>%
arrange(state) %>%
set_names("州", "死亡率") %>%
reactable()
The reactable package shows 10 rows by default and adds pagination, as shown in Figure 9-7.

Figure 9-7: An interactive table built with reactable
The reactable() function also enables sorting by default. Although you used the arrange() function in your code to sort the data by state name, users can click the “Death rate” column to sort values using that variable instead.
Creating a Hovering Tooltip with plotly
Now you’ll add some interactivity to the website’s chart using the plotly package. First, install plotly with install.packages("plotly"). Then, create a plot with ggplot and save it as an object. Pass the object to the ggplotly() function, which turns it into an interactive plot, and run the following code to apply plotly to the chart of COVID death rates over time:
**library(plotly)**
**covid_chart <- covid_data %>%**
filter(state %in% c(
"阿拉巴马",
"阿拉斯加",
"亚利桑那",
"阿肯色"
)) %>%
ggplot(aes(
x = date,
y = deaths_avg_per_100k,
group = state,
fill = deaths_avg_per_100k
)) +
geom_col() +
scale_fill_viridis_c(option = "rocket") +
theme_minimal() +
labs(title = "每 10 万人死亡人数随时间变化") +
theme(
legend.position = "none",
plot.title.position = "plot",
plot.title = element_text(face = "bold"),
panel.grid.minor = element_blank(),
axis.title = element_blank()
) +
facet_wrap(
~state,
nrow = 2
)
**ggplotly(covid_chart)**
This is identical to the chart code shown earlier in this chapter, except that now you’re saving your chart as an object called covid_chart and then running ggplotly(covid_chart). This code produces an interactive chart that shows the data for a particular day when a user mouses over it. But the tooltip that pops up, shown in Figure 9-8, is cluttered and overwhelming because the ggplotly() function shows all data by default.

Figure 9-8: The plotly default produces a messy tooltip.
To make the tooltip more informative, create a single variable containing the data you want to display and tell ggplotly() to use it:
covid_chart <- covid_data %>%
filter(state %in% c(
"阿拉巴马",
"阿拉斯加",
"亚利桑那",
"阿肯色州"
)) %>%
mutate(date_nice_format = str_glue("{month(date, label = TRUE, abbr = FALSE)} {day(date)}, {year(date)}")) %>% ❶
mutate(tooltip_text = str_glue("{state}<br>{date_nice_format}<br>{deaths_avg_per_100k} 每 10 万人")} %>% ❷
ggplot(aes(
x = date,
y = deaths_avg_per_100k,
group = state,
text = tooltip_text, ❸
fill = deaths_avg_per_100k
)) +
geom_col() +
scale_fill_viridis_c(option = "rocket") +
theme_minimal() +
labs(title = "每 10 万人死亡人数随时间变化") +
theme(
legend.position = "none",
plot.title.position = "plot",
plot.title = element_text(face = "bold"),
panel.grid.minor = element_blank(),
axis.title = element_blank()
) +
facet_wrap(
~state,
nrow = 2
)
ggplotly(
covid_chart,
tooltip = "tooltip_text" ❹
)
This code begins by creating a date_nice_format variable that produces dates in the more readable format January 1, 2023, instead of 2023-01-01 ❶. This value is then combined with the state and death rate variables, and the result is saved as tooltip_text ❷. Next, the code adds a new aesthetic property in the ggplot() function ❸. This property doesn’t do anything until it’s passed to ggplotly()❹.
Figure 9-9 shows what the new tooltip looks like: it displays the name of the state, a nicely formatted date, and that day’s death rate.

Figure 9-9: Easy-to-read interactive tooltips on the COVID-19 death rate chart
Adding interactivity is a great way to take advantage of the website medium. Users who might feel overwhelmed looking at the static chart can explore the interactive version, mousing over areas to see a summary of the results on any single day.
Hosting the Website
Now that you’ve made a website, you need a way to share it. There are various ways to do this, ranging from simple to quite complex. The easiest solution is to compress the files in your docs folder (or whatever folder you put your rendered website in) and email your ZIP file to your recipients. They can unzip it and open the HTML files in their browser. This works fine if you know you won’t want to make changes to your website’s data or styles. But, as Chapter 5 discussed, most projects aren’t really one-time events.
Cloud Hosting
A better approach is to put your entire docs folder in a place where others can see it. This could be an internal network, Dropbox, Google Drive, Box, or something similar. Hosting the files in the cloud this way is simple to implement and allows you to control who can see your website.
You can even automate the process of copying your docs folder to various online file-sharing sites using R packages: the rdrop2 package works with Dropbox, googledrive works with Google Drive, and boxr works with Box. For example, code like the following would automatically upload the project to Dropbox:
library(tidyverse)
library(rmarkdown)
library(fs)
library(rdrop2)
# 渲染网站
render_site()
# 上传到 Dropbox
website_files <- dir_ls(
path = "docs",
type = "file",
recurse = TRUE
)
walk(website_files, drop_upload, path = "COVID Website")
这段代码通常我会添加到一个名为render.R的独立文件中,渲染网站,使用来自fs包的 dir_ls()函数来识别docs目录中的所有文件,然后将这些文件上传到 Dropbox。现在你可以一次性运行整个文件,生成并上传你的完整网站。
GitHub 托管
使用更复杂但功能强大的替代方案是使用静态托管服务,如 GitHub Pages。每次你提交(拍摄代码快照)并推送(同步)到 GitHub 时,这项服务会将你的网站部署到你设定的 URL 上。学习如何使用 GitHub 是时间和精力的投资(Jenny Bryan 的自出版书籍Happy Git and GitHub for the useR,网址是* happygitwithr.com*,是一个非常好的资源),但能够免费托管你的网站是值得的。
这是 GitHub Pages 的工作原理。大多数时候,当你在 GitHub 上查看一个文件时,你看到的是它的源代码,因此,如果你查看一个 HTML 文件,你只会看到 HTML 代码。而 GitHub Pages 则显示渲染后的 HTML 文件。要在 GitHub Pages 上托管你的网站,你需要首先将代码推送到 GitHub。一旦你在 GitHub 上设置了一个代码库,进入该库,然后点击设置标签页,应该会看到像图 9-10 那样的界面。

图 9-10:设置 GitHub Pages
现在选择你希望 GitHub 如何部署原始 HTML 文件。最简单的方法是保持默认的 source。为此,选择从分支部署,然后选择你的默认分支(通常是main或master)。接下来,选择包含你希望被渲染的 HTML 文件的目录。如果你在本章开始时已为 GitHub Pages 配置了你的网站,那么文件应该位于docs目录下。点击保存并等待几分钟,GitHub 应该会显示你网站所在的 URL。
在 GitHub Pages 上托管网站的最大好处是,每当你更新代码或数据时,网站也会随之更新。R Markdown、distill 和 GitHub Pages 使得构建和维护网站变得轻松便捷。
总结
在本章中,你学习了如何使用 distill 包在 R 中制作网站。这个包提供了一种简单的方法,可以使用你已经在处理数据时使用的工具,快速创建一个网站。你已经了解了如何:
-
创建新页面并将它们添加到顶部导航栏
-
通过调整 CSS 来定制网站的外观和感觉
-
使用更宽的布局使内容在单个页面上更好地展示
-
将静态数据可视化和表格转换为交互式版本
-
使用 GitHub Pages 托管始终保持最新版本的网站
Matt Herman 一直在使用 R 构建网站。他和他在州政府司法中心的同事们使用 Quarto(R Markdown 的语言无关版本)制作了一个出色的网站。这个网站可以在 projects.csgjusticecenter.org/tools-for-states-to-address-crime/ 找到,展示了美国各地的犯罪趋势,使用了本章中介绍的许多相同的技巧。
无论你更喜欢 distill 还是 Quarto,使用 R 都是开发复杂网站的一种快捷方式,无需成为一名复杂的前端网页开发者。这些网站既美观又有效沟通,它们是 R 如何帮助你高效地与世界分享工作的一例。
附加资源
-
The Distillery, “Welcome to the Distillery!,” 访问于 2023 年 11 月 30 日,
distillery.rbind.io。 -
Thomas Mock, “Building a Blog with distill,” The MockUp, 2020 年 8 月 1 日,
themockup.blog/posts/2020-08-01-building-a-blog-with-distill/。
第十章:10 QUARTO

Quarto,作为 R Markdown 的下一代版本,提供了一些相较于其前身的优势。首先,Quarto 在不同输出类型之间的语法更加一致。如你在本书中所看到的,R Markdown 文档可能使用各种不同的约定;例如,xaringan 使用三个破折号表示新的一页,这在其他输出格式中会生成一条水平线,而 distill 包则有一些布局选项,在 xaringan 中无法使用。
Quarto 还支持比 R Markdown 更多的语言,并且支持多个代码编辑器。虽然 R Markdown 专为 RStudio IDE 设计,但 Quarto 不仅可以在 RStudio 中使用,还可以在 Visual Studio (VS) Code 和 JupyterLab 等代码编辑器中使用,使其能够轻松支持多种语言。
本章重点介绍了作为 R 用户使用 Quarto 的好处。它解释了如何设置 Quarto,然后讲解了 Quarto 和 R Markdown 之间的一些最重要的区别。最后,你将学习如何使用 Quarto 来生成前几章中涉及的参数化报告、演示文稿和网站。
创建 Quarto 文档
从 2022.07.1 开始的 RStudio 版本已预装 Quarto。要检查你的 RStudio 版本,点击顶部菜单栏中的RStudio关于 RStudio。如果你的 RStudio 版本较旧,请按照第一章中的步骤重新安装以进行更新,这样 Quarto 应该就会被安装了。
安装 Quarto 后,点击文件新建文件Quarto 文档来创建文档。你应该看到一个菜单,如图 10-1 所示,类似于创建 R Markdown 文档时使用的菜单。

图 10-1:用于创建新 Quarto 文档的 RStudio 菜单
给文档指定一个标题,并选择输出格式。Engine 选项允许你选择不同的文档渲染方式。默认情况下,它使用 Knitr,这是 R Markdown 使用的相同渲染工具。Use Visual Markdown Editor 选项提供了一个更像 Microsoft Word 的界面,但它可能会有些问题,因此我在这里不会详细介绍。
生成的 Quarto 文档应包含默认内容,就像 R Markdown 文档一样:
title: "My Report"
format: html
## Quarto
Quarto enables you to weave together content and executable code into a
finished document. To learn more about Quarto see <https://quarto.org>.
## Running Code
When you click the **Render** button a document will be generated that includes
both content and the output of embedded code. You can embed code like this:
```{r}
1 + 1
You can add options to executable code like this:
#| echo: false
2 * 2
The echo: false option disables the printing of code (only output is displayed).
尽管 R Markdown 和 Quarto 有许多相似的功能,但它们也有一些需要注意的差异。
### 比较 R Markdown 和 Quarto
Quarto 和 R Markdown 文档有相同的基本结构——YAML 元数据,后面跟着 Markdown 文本和代码块的组合——但它们在语法上有一些差异。
#### 格式和执行 YAML 字段
Quarto 在其 YAML 中使用了稍微不同的选项。它将 output 字段替换为 format 字段,并使用 html 代替 html_document:
title: "My Report"
format: html
其他 Quarto 格式与它们的 R Markdown 对应格式使用了不同的名称,例如,使用 docx 代替 word_document,使用 pdf 代替 pdf_document。所有可能的格式可以在*[`quarto.org/docs/guide/`](https://quarto.org/docs/guide/)*找到。
R Markdown 和 Quarto 语法之间的第二个区别是,Quarto 不使用一个 setup 代码块来设置显示代码、图表和其他元素的默认选项。在 Quarto 中,这些选项是在 YAML 的 execute 字段中设置的。例如,以下代码会隐藏代码,以及所有警告和信息,从渲染后的文档中去除:
title: "My Report"
format: html
execute:
echo: false
warning: false
message: false
Quarto 还允许你将 true 和 false 写成小写字母。
#### 单个代码块选项
在 R Markdown 中,你可以通过在代码块开始的花括号内添加新的选项来覆盖单个代码块级别的选项。例如,以下代码会显示 2 * 2 和它的输出:
2 * 2
Quarto 则使用这种语法来设置单个代码块级别的选项:
#| echo: false
2 * 2
该选项是在代码块本身内设置的。行首的字符 #|(称为 *hash pipe*)表示你正在设置选项。
#### 选项名称中的连字符
从 R Markdown 切换到 Quarto 时,你可能会看到的另一个区别是,包含两个单词的选项名称用连字符而不是句点分隔。例如,R Markdown 使用代码块选项 fig.height 来指定图形的高度。而 Quarto 使用 fig-height,如下所示:
#| fig-height: 10
library(palmerpenguins)
library(tidyverse)
ggplot(
penguins,
aes(
x = bill_length_mm,
y = bill_depth_mm
)
) +
geom_point()
对于从 R Markdown 转过来的人来说,fig.height 和类似的包含句点的选项仍然会继续有效,即使你忘记进行转换。所有代码块选项的列表可以在 Quarto 网站上找到,链接为 *[`quarto.org/docs/reference/cells/cells-knitr.xhtml`](https://quarto.org/docs/reference/cells/cells-knitr.xhtml)*。
#### 渲染按钮
你可以按照与 R Markdown 相同的过程来渲染 Quarto 文档,但在 Quarto 中,按钮被称为 Render,而不是 Knit。点击 Render 会将 Quarto 文档转换为 HTML 文件、Word 文档或任何其他你选择的输出格式。
### 参数化报告
现在你已经了解了 Quarto 的一些基本操作,接下来你将使用它制作几个不同的文档,从参数化报告开始。用 Quarto 制作参数化报告的过程几乎与用 R Markdown 完成相同。事实上,你可以通过复制 *.Rmd* 文件,将其扩展名改为 *.qmd*,然后做一些其他调整,将你用来制作 Urban Institute COVID 报告的 R Markdown 文档直接适配到 Quarto:第七章中提到的文档。
title: "Urban Institute COVID Report"
format: html ❶
params:
state: "Alabama"
execute: ❷
echo: false
warning: false
message: false
library(tidyverse)
library(urbnthemes)
library(scales)
r params$state
cases <- tibble(state.name) %>%
rbind(state.name = "District of Columbia") %>%
left_join(
read_csv("https://data.rfortherestofus.com/united_states_covid19_cases_deaths
_and_testing_by_state.csv", skip = 2),
by = c("state.name" = "State/Territory")
) %>%
select(
total_cases = `总病例数`,
state.name,
cases_per_100000 = `每 10 万人病例率`
) %>%
mutate(cases_per_100000 = parse_number(cases_per_100000)) %>%
mutate(case_rank = rank(-cases_per_100000, ties.method = "min"))
state_text <- if_else(params$state == "District of Columbia", str_glue("the District of
Columbia"), str_glue("state of {params$state}"))
state_cases_per_100000 <- cases %>%
filter(state.name == params$state) %>%
pull(cases_per_100000) %>%
comma()
state_cases_rank <- cases %>%
filter(state.name == params$state) %>%
pull(case_rank)
In r state_text, there were r state_cases_per_100000 cases per 100,000 people in the last
seven days. This puts r params$state at number r state_cases_rank of 50 states and the
District of Columbia.
#| fig-height: 8 ❸
set_urbn_defaults(style = "print")
cases %>%
mutate(highlight_state = if_else(state.name == params$state, "Y", "N")) %>%
mutate(state.name = fct_reorder(state.name, cases_per_100000)) %>%
ggplot(aes(
x = cases_per_100000,
y = state.name,
fill = highlight_state
)) +
geom_col() +
scale_x_continuous(labels = comma_format()) +
theme(legend.position = "none") +
labs(
y = NULL,
x = "每 10 万人病例数"
)
这段代码将输出格式从: html_document 切换为格式: html ❶,然后移除设置代码块,并在 YAML 的 execute 字段中设置这些选项 ❷。最后,最后一个代码块中的 fig.height 选项被替换为 fig-height,并用哈希管道符标记为选项 ❸。
接下来,要为每个州创建一个报告,你需要调整在第七章中用于生成参数化报告的 *render.R* 脚本文件:
Load packages
library(tidyverse)
❶ library(quarto)
Create a vector of all states and the District of Columbia
state <- tibble(state.name) %>%
rbind("District of Columbia") %>%
pull(state.name)
Create a tibble with information on the:
input R Markdown document
output HTML file
parameters needed to knit the document
reports <- tibble(
❷ input = "urban-covid-budget-report.qmd",
output_file = str_glue("{state}.xhtml"),
❸ execute_params = map(state, ~list(state = .))
)
Generate all of our reports
reports %>%
❹ pwalk(quarto_render)
这个更新后的 *render.R* 文件加载了 quarto 包,而不是 rmarkdown 包 ❶,并将输入文件更改为 *urban-covid-budget-report.qmd* ❷。报告的数据框使用 execute_params 代替 params ❸,因为这是 quarto_render() 函数期望的参数。为了生成报告,quarto_render() 函数替代了 markdown 包中的 render() 函数 ❹。如同第七章中的方法,运行这段代码应该会为每个州生成报告。
### 制作演示文稿
Quarto 还可以生成类似于你在第八章中使用 xaringan 包制作的幻灯片演示文稿。要使用 Quarto 创建演示文稿,请点击**文件****新建文件****Quarto 演示文稿**。选择 **Reveal JS** 来制作幻灯片,并保持引擎和编辑器选项不变。
你将制作的幻灯片使用了 reveal.js JavaScript 库,这与使用 xaringan 制作幻灯片的方法类似。以下代码更新了你在第八章中制作的演示文稿,使其可以与 Quarto 一起使用:
title: "Penguins Report"
author: "David Keyes"
format: revealjs
execute:
echo: false
warning: false
message: false
Introduction
library(tidyverse)
penguins <- read_csv("https://raw.githubusercontent.com/rfortherestofus/r-without-statistics/
main/data/penguins-2008.csv")
We are writing a report about the Palmer Penguins. These penguins are really amazing.
There are three species:
- Adelie
- Gentoo
- Chinstrap
Bill Length
We can make a histogram to see the distribution of bill lengths.
penguins %>%
ggplot(aes(x = bill_length_mm)) +
geom_histogram() +
theme_minimal()
average_bill_length <- penguins %>%
summarize(avg_bill_length = mean(
bill_length_mm,
na.rm = TRUE
)) %>%
pull(avg_bill_length)
The chart shows the distribution of bill lengths. The average bill length is
r average_bill_length millimeters.
这段代码在 YAML 中设置了 format: revealjs 来制作演示文稿,并在执行部分添加了多个全局代码块选项。然后,它移除了用于制作幻灯片分隔的三个破折号,因为在 Quarto 中,一级或二级标题会自动生成新的幻灯片(尽管你仍然可以使用三个破折号手动添加幻灯片分隔)。渲染这段代码后,你应该会得到一个包含幻灯片的 HTML 文件。输出应类似于第八章中默认的 xaringan 幻灯片。
#### 逐步显示内容
Quarto 幻灯片可以逐步显示内容。要让项目符号和编号列表默认逐项显示,可以将 incremental: true 添加到文档的 YAML 部分,如下所示:
title: "Penguins Report"
author: "David Keyes"
format:
revealjs:
incremental: true
execute:
echo: false
warning: false
message: false
由于这段代码,演示文稿中的所有列表内容应会逐项出现在幻灯片上。
你也可以使用这种格式,仅让某些列表项逐步显示:
::: {.incremental}
- Adelie
- Gentoo
- Chinstrap
:::
使用:::来开始和结束文档的一个部分,会在生成的 HTML 文件中创建一个称为*div*的部分。HTML 中的<div>标签允许你在该部分内定义属性。在这段代码中,添加{.incremental}设置了一个自定义 CSS 类,能让列表逐步显示。
#### 对齐内容并添加背景图片
你也可以使用<div>标签在 Quarto 幻灯片中创建列。例如,假设你想要创建一个包含两列内容的幻灯片,如图 10-2 所示。

图 10-2:使用<div>标签创建两列
以下代码创建了这张两列幻灯片:
:::: {.columns}
::: {.column width="50%"}
penguins %>%
ggplot(aes(x = bill_length_mm)) +
geom_histogram() +
theme_minimal()
:::
::: {.column width="50%"}
penguins %>%
ggplot(aes(x = bill_depth_mm)) +
geom_histogram() +
theme_minimal()
:::
::::
注意:::以及::::,它们创建了嵌套的<div>部分。columns 类告诉 HTML,所有位于::::内的内容应按列布局。接着,::: {.column width="50%"}开始了一个占据幻灯片一半宽度的<div>。闭合的:::和::::表示该部分的结束。
在使用 xaringan 时,你可以通过将内容包围在.center[]中轻松地将其居中显示在幻灯片上。而在 Quarto 中,居中稍微复杂一些。Quarto 没有内建的 CSS 类来居中内容,因此你需要自己创建一个。首先开始一个 CSS 代码块,并创建一个名为 center-slide 的自定义类:
.center-slide {
text-align: center;
}
这个 CSS 会将所有内容居中对齐。(text-align 属性也会对图片进行对齐,而不仅仅是文本。)
要应用新的 center-slide 类,请将其放置在幻灯片标题旁,如下所示:
Bill Length
应用自定义 CSS 后,幻灯片现在应该能将所有内容居中显示。
最后,在 xaringan 中工作时,你可以给幻灯片添加背景图片。要在 Quarto 中做同样的事,可以将 background-image 属性应用到幻灯片中,如下所示:
Penguins
这应当在选定的图片前添加一个包含文本 *Penguins* 的幻灯片。
#### 使用主题和 CSS 自定义你的幻灯片
你已经开始对 Quarto 幻灯片的外观和感觉进行一些更改,但你可以在设计中加入更多自定义内容。与 xaringan 类似,有两种主要方法可以进一步自定义 Quarto 幻灯片:使用现有主题和更改 CSS。
主题是更改幻灯片设计的最简单方法。在 Quarto 中应用主题,只需将其名称添加到你的 YAML 中:
title: "Penguins Report"
format:
revealjs:
theme: dark
使用此选项应将主题从浅色(默认)切换为深色。你可以在图 10-3 中看到应用了深色主题的标题幻灯片。要查看所有可用主题的完整列表,请访问 *[`quarto.org/docs/presentations/revealjs/themes.xhtml`](https://quarto.org/docs/presentations/revealjs/themes.xhtml)*。

图 10-3:应用了深色主题的幻灯片
更改幻灯片设计的第二个选项是编写自定义 CSS。Quarto 使用一种名为 Sass 的 CSS 类型,允许你在 CSS 中包含变量。这些变量与 xaringanthemer 包中的变量类似,后者允许你使用 header_h2_font_size 和 header_color 来设置标题格式的值。
转到**文件****新建文件****新建文本文件**,创建一个名为 *theme.scss* 的 Sass 文件,并添加以下两个必填部分:
/-- scss:defaults --/
/-- scss:rules --/
scss:defaults 部分是你使用 Quarto Sass 变量的地方。例如,要更改一级标题的颜色和大小,可以添加以下代码:
/-- scss:defaults --/
\(presentation-heading-color: red;
\)presentation-h1-font-size: 150px;
/-- scss:rules --/
所有 Quarto Sass 变量都以美元符号开始,后跟变量名称。要将这些调整应用到幻灯片中,可以调整 YAML 告诉 Quarto 使用自定义的 *theme.scss* 文件:
title: "Penguins Reports"
format:
revealjs:
theme: theme.scss
图 10-4 展示了应用于渲染幻灯片的更改。

图 10-4:使用自定义 CSS 修改的幻灯片
所有预定义的变量应放在 scss:defaults 部分。你可以在*[`quarto.org/docs/presentations/revealjs/themes.xhtml#sass-variables`](https://quarto.org/docs/presentations/revealjs/themes.xhtml#sass-variables)*找到这些变量的完整列表。
scss:rules 部分是你可以添加没有现成变量的 CSS 调整的地方。例如,你可以将编写的使幻灯片内容居中的代码放在该部分:
/-- scss:defaults --/
\(presentation-heading-color: red;
\)presentation-h1-font-size: 150px;
/-- scss:rules --/
.center-slide {
text-align: center;
}
因为渲染后的 Quarto 幻灯片是 HTML 文档,你可以通过自定义 CSS 对其进行调整。更重要的是,由于幻灯片使用 reveal.js 作为底层技术,任何嵌入该 JavaScript 库的功能在 Quarto 中都能使用。这个库包括了添加过渡效果、动画、互动内容等许多简便的方式。位于*[`quarto.org/docs/presentations/revealjs/demo/`](https://quarto.org/docs/presentations/revealjs/demo/)*的 Quarto 演示展示了这些功能的实际应用。
### 制作网站
Quarto 可以在不使用 distill 等外部包的情况下创建网站。要创建 Quarto 网站,请进入**File****New Project**,选择**New Directory**,然后选择**Quarto Website**。系统会提示你选择一个目录来存放你的项目。保持默认的引擎(Knitr),勾选**Create a Git Repository**(仅在你已经安装了 Git 时才会显示),并保持其他选项不选中。
点击**Create Project**,这将创建一系列文件:*index.qmd*、*about.qmd*、*._quarto.yml* 和 *styles.css*。这些文件与 distill 包创建的文件类似。*.qmd* 文件是你添加内容的地方,*._quarto.yml* 文件是你设置整个网站选项的地方,而 *styles.css* 文件是你添加 CSS 来自定义网站外观的地方。
#### 构建网站
你将从修改*.qmd*文件开始。打开首页文件(*index.qmd*),删除 YAML 后的默认内容,并用你在第九章中制作的网站内容替换它。删除 layout = "l-page" 元素,这是你用来扩展布局的。我将在本节稍后讨论如何在 Quarto 中更改页面布局。
要渲染一个 Quarto 网站,请在 RStudio 的右上角查找“Build”选项卡并点击**Render Website**。渲染后的网页现在应出现在 RStudio 右下角的“Viewer”窗格中。如果你切换到同一面板上的“Files”窗格,你应该也能看到一个*._site* 文件夹已被创建,用来存放渲染网站的内容。试着在你的浏览器中打开*index.xhtml*文件,你应该能看到网站,参考图 10-5。

图 10-5:带有警告和消息的 Quarto 网站
如你所见,网页中包含了许多你不希望显示的警告和消息。在 R Markdown 中,你可以在设置代码块中移除这些内容;在 Quarto 中,你可以在 YAML 中进行设置。将以下代码添加到*index.qmd*的 YAML 中,以移除输出中的所有代码、警告和消息:
execute:
echo: false
warning: false
message: false
然而,请注意,这些选项只会影响单个文件。接下来,你将看到如何为整个网站设置这些选项。
#### 设置选项
当使用 distill 时,你修改了*._site.yml*文件来更改网站中的所有文件。在 Quarto 中,你使用*._quarto.yml*文件来达到同样的目的。如果你打开它,你应该能看到三个部分:
Project:
type: website
website:
title: "covid-website-quarto"
navbar:
left:
- href: index.qmd
text: Home
- about.qmd
format:
html:
theme: cosmo
css: styles.css
toc: true
顶部部分设置了项目类型(在此案例中是网站)。中间部分定义了网站的标题并确定其导航栏的选项。底部部分则修改了网站的外观。
你将从底部开始。为了移除网站中每一页的代码、警告和消息,将你之前写的那部分 YAML 添加到*._quarto.yml*文件中。底部部分现在应该像这样:
format:
html:
theme: cosmo
css: styles.css
toc: true
execute:
echo: false
warning: false
message: false
如果你再次构建网站,现在你应该只看到内容,参考图 10-6。

图 10-6:移除警告和消息后的网站
在 *_quarto.yml* 文件的这一部分,你可以添加任何原本应放在单个 *.qmd* 文件中的选项,使它们应用到网站的所有页面。
#### 更改网站外观
*_quarto.yml* 文件中的格式部分决定了渲染文件的外观。默认情况下,Quarto 应用名为 cosmo 的主题,但有许多其他主题可供选择。(你可以在 *[`quarto.org/docs/output-formats/html-themes.xhtml`](https://quarto.org/docs/output-formats/html-themes.xhtml)* 查看完整列表。)要查看不同主题对输出的影响,可以进行如下更改:
format:
html:
theme: minty
css: styles.css
toc: true
minty 主题会更改网站的字体,并将配色方案更新为灰色和浅绿色。
除了使用预构建的主题外,你还可以通过 CSS 自定义你的网站。*_quarto.yml* 文件中的 css: styles.css 部分表示 Quarto 在渲染时将使用 *styles.css* 文件中的任何 CSS。尝试将以下 CSS 添加到 *styles.css* 中,使一级标题为红色并且字体大小为 50 像素:
h1 {
color: red;
font-size: 50px;
}
重新渲染的 *index.xhtml* 现在有了大号红色标题(在 图 10-7 中以灰度显示)。

图 10-7:应用自定义 CSS 后的网站
自定义网站的另一种方法是使用 *.scss* 文件中的 Sass 变量,正如你在演示中所做的那样。例如,创建一个名为 *styles.scss* 的文件,并添加如下代码行,将页面背景设置为明亮的黄色:
/-- scss:defaults --/
$body-bg: #eeeeee;
要让 Quarto 使用 *styles.scss* 文件,请按如下方式调整主题行:
format:
html:
theme: [minty, styles.scss]
css: styles.css
toc: true
该语法告诉 Quarto 使用 minty 主题,然后根据 *styles.scss* 文件进行额外调整。如果你重新渲染网站,你应该会看到整个页面都有明亮的黄色背景(图 10-8,在打印版中为灰度)。

图 10-8:通过 styles.scss 应用自定义 CSS 后的网站
请注意,当你添加 *.scss* 文件时,*styles.css* 文件中所做的调整将不再适用。如果你想使用那些调整,需要将它们添加到 *styles.scss* 文件中。
toc: true 这一行会在网页的右侧创建一个目录(你可以在 图 10-5 到 10-7 中看到,标记为“本页内容”)。你可以通过将 true 改为 false 来移除目录。在 *_quarto.yml* 文件的底部部分添加任何其他选项,例如图像高度。
#### 调整标题和导航栏
*_quarto.yml* 文件的中间部分设置了网站的标题和导航。按如下方式更改标题和关于页面链接的文本:
website:
title: "Quarto COVID Website"
navbar:
left:
- href: index.qmd
text: Home
- href: about.qmd
text: About This Website
更改标题需要调整标题行。导航栏部分的功能几乎与 distill 中相同。href 行列出了导航栏应该链接的文件。可选的文本行指定该链接应显示的文本。图 10-9 展示了这些更改应用到网站的情况。

图 10-9:导航栏的更改
首页的标题仍然是 covid-website-quarto,但你可以在*index.qmd*文件中修改它。
#### 创建更宽的布局
当你使用 distill 创建网站时,你使用了 layout = "l-page" 来使地图在网页上变得更宽。你可以通过使用 ::: 语法添加 HTML <div> 标签来在 Quarto 中实现相同的效果:
:::{.column-screen-inset}
#| out-width: 100%
# 创建地图
most_recent <- us_states %>%
left_join(covid_data, by = "state") %>%
slice_max(
order_by = date,
n = 1
)
most_recent %>%
ggplot(aes(fill = deaths_avg_per_100k)) +
geom_sf() +
scale_fill_viridis_c(option = "rocket") +
labs(fill = "每 10 万人死亡数") +
theme_void()
:::
这段代码在地图生成代码块的开始处添加了 :::{.column-screen-inset},并在结束处添加了 :::。这个代码块现在还包含了行 #| out-width: 100%,用于指定地图应占据所有可用宽度。如果没有这一行,地图只会占据窗口的一部分。你可以使用许多不同的输出宽度;完整的列表可以查看 *[`quarto.org/docs/authoring/article-layout.xhtml`](https://quarto.org/docs/authoring/article-layout.xhtml)*。
#### 托管网站到 GitHub Pages 和 Quarto Pub
你可以像托管 distill 网站一样,使用 GitHub Pages 托管你的 Quarto 网站。回顾一下,GitHub Pages 要求你将网站的文件保存在*docs*文件夹中。修改*_quarto.yml*文件,使得网站输出到该文件夹:
project:
type: website
output-dir: docs
现在,当你渲染网站时,HTML 和其他文件应出现在*docs*目录中。此时,你可以将仓库推送到 GitHub,像在第九章中一样调整 GitHub Pages 设置,并查看你 Quarto 网站的 URL。
作为 GitHub Pages 的替代方案,Quarto 提供了一个免费的服务叫做 Quarto Pub,它使得将你的资料发布到网上变得非常容易。如果你不是 GitHub 用户,这是发布你作品的一个不错选择。为了查看它是如何工作的,你将把刚刚创建的网站发布到它上面。在 RStudio 的左下角面板中点击**Terminal**标签页。在提示符下,输入 quarto publish。这样会显示出一些发布你网站的方式,如图 10-10 所示。

图 10-10:发布 Quarto 网站的提供者列表
按 **Enter** 键选择 Quarto Pub。系统会提示你授权 RStudio 发布到 Quarto Pub。输入 **Y** 进行授权,这将带你到 *[`quartopub.com`](https://quartopub.com)*。注册一个账户(如果已经有账户则直接登录)。你应该看到一个屏幕,显示你已成功登录并授权 RStudio 连接到 Quarto Pub。从那里,你可以返回 RStudio,系统将提示你为网站选择一个名称。最简单的选项是使用你的项目名称。一旦输入名称,Quarto Pub 应该会发布该网站并带你到网站,正如在图 10-11 中所示。

图 10-11:发布在 Quarto Pub 上的网站
当你更新网站时,可以使用相同的步骤将其重新发布到 Quarto Pub。Quarto Pub 可能是发布 Quarto 制作的 HTML 文件的最简单方法。
### 总结
正如你在本章中所看到的,你可以使用 Quarto 做所有在 R Markdown 中做的事情,而不需要加载任何外部包。此外,Quarto 的不同输出格式使用更一致的语法。例如,因为你可以通过添加一级或二级标题在 Quarto 中制作新幻灯片,所以你用来创建报告的 Quarto 文档应该能够轻松转化为演示文稿。
你现在可能在想,究竟应该使用 R Markdown 还是 Quarto?这是一个好问题,也是许多 R 社区成员在思考的问题。R Markdown 不会消失,所以如果你已经在使用它,完全不需要切换。然而,如果你是 R 的新手,你可能是使用 Quarto 的理想人选,因为其未来的功能可能不会回溯到 R Markdown。
最终,R Markdown 和 Quarto 之间的差异相对较小,切换工具的影响应该是微乎其微的。R Markdown 和 Quarto 都可以帮助你提高效率,避免手动错误,并以各种格式分享结果。
### 其他资源
+ Andrew Bray,Rebecca Barter,Silvia Canelón,Christophe Dervieu,Devin Pastor 和 Tatsu Shigeta,“从 R Markdown 到 Quarto”,来自 rstudio::conf 2022 的研讨会材料,华盛顿特区,2022 年 7 月 25-26 日,* [`rstudio-conf-2022.github.io/rmd-to-quarto/`](https://rstudio-conf-2022.github.io/rmd-to-quarto/)*。
+ Tom Mock,“Quarto 入门”,在线课程,访问日期:2023 年 12 月 1 日,* [`jthomasmock.github.io/quarto-in-two-hours/`](https://jthomasmock.github.io/quarto-in-two-hours/)*。
# 第三部分 自动化与协作
## 第十一章:11 自动访问在线数据

到目前为止,你已经从 CSV 文件中将数据导入到项目中。许多在线数据集允许你导出 CSV 文件,但在此之前,你应该查找能够自动化数据访问的包。如果你能消除获取数据过程中手动的步骤,你的分析和报告将更加准确。同时,当数据发生变化时,你也能够高效地更新报告。
R 提供了多种方法来自动化访问在线数据的过程。在本章中,我将讨论两种方法。首先,你将使用 googlesheets4 包直接从 Google Sheets 中获取数据。你将学习如何将 R Markdown 项目连接到 Google,这样当 Google Sheet 更新时,你就可以自动下载数据。接下来,你将使用 tidycensus 包访问美国人口普查局的数据。你将处理两个大型人口普查数据集——十年人口普查和美国社区调查,并进行可视化练习。
### 使用 googlesheets4 从 Google Sheets 导入数据
通过使用 googlesheets4 包直接访问 Google Sheets 中的数据,你无需手动下载数据、将其复制到项目中,并调整代码以便每次更新报告时导入新数据。这个包让你编写代码,自动从 Google Sheets 获取新数据。每当你需要更新报告时,只需运行代码来刷新数据。此外,如果你使用 Google 表单,你可以将数据导入 Google Sheets,从而完全自动化从数据收集到数据导入的工作流程。
使用 googlesheets4 包可以帮助你管理频繁更新的复杂数据集。例如,在她位于布法罗大学初级护理研究所的工作中,Meghan Harris 使用该包进行了一项关于受阿片类药物使用障碍影响的人的研究项目。数据来自各种调查,所有调查的数据汇总在一堆 Google Sheets 中。通过使用 googlesheets4,Harris 能够将所有数据集中到一个地方,并使用 R 对其进行处理。曾经由于访问复杂而基本未被使用的数据,现在可以为阿片类药物使用障碍的研究提供信息。
本节演示了如何使用 googlesheets4 包,通过一个关于视频游戏偏好的假数据集(Harris 用它替代了她的阿片类药物调查数据,后者由于显而易见的原因是保密的)来展示该包的工作原理。
#### 连接到 Google
首先,通过运行 install.packages("googlesheets4") 安装 googlesheets4 包。接着,通过在控制台运行 gs4_auth() 函数连接到你的 Google 账户。如果你有多个 Google 账户,选择一个可以访问你要使用的 Google Sheet 的账户。
一旦完成,屏幕应该会显示出来。在 **See, Edit, Create, and Delete All Your Google Sheets Spreadsheets** 旁边勾选框,这样 R 就可以访问你的 Google Sheets 数据。点击 **Continue**,你应该会看到“认证完成。请关闭此页面并返回 R”的消息。googlesheets4 包现在会保存你的认证信息,以便将来使用时无需重新认证。
#### 从表格中读取数据
现在,你已经将 R 连接到你的 Google 账户,可以导入 Harris 创建的关于视频游戏偏好的虚拟数据(可以通过 *[`data.rfortherestofus.com/google-sheet`](https://data.rfortherestofus.com/google-sheet)* 访问)。图 11-1 显示了它在 Google Sheets 中的样子。

图 11-1:Google Sheets 中的视频游戏数据
googlesheets4 包提供了一个名为 read_sheet() 的函数,可以直接从 Google Sheets 中提取数据。通过将电子表格的 URL 传递给该函数来导入数据,如下所示:
library(googlesheets4)
survey_data_raw <- read_sheet("https://docs.google.com/spreadsheets/d/
1AR0_RcFBg8wdiY4Cj-k8vRypp_txh27MyZuiRdqScog/edit?usp=sharing")
查看 survey_data_raw 对象,确认数据已成功导入。使用 dplyr 包中的 glimpse() 函数可以更轻松地阅读:
library(tidyverse)
survey_data_raw %>%
glimpse()
glimpse() 函数为每个变量创建一行输出,显示你已经成功直接从 Google Sheets 导入了数据:
> Rows: 5
> Columns: 5
> $ Timestamp 05-16 15:20:50
> $ How old are you? "25-34", "45-54"...
> $ Do you like to play video games? "Yes", "No", "Ye...
> $ What kind of games do you like? "Sandbox, Role-P...
> $ What's your favorite game? "It's hard to ch...
一旦将数据导入到 R 中,你可以使用你一直在使用的相同工作流来创建 R Markdown 报告。
#### 在 R Markdown 中使用数据
以下代码摘自 Harris 为总结视频游戏数据所编写的 R Markdown 报告。你可以看到 YAML、设置代码块、加载包的代码块以及从 Google Sheets 导入数据的代码:
title: "Video Game Survey"
output: html_document
knitr::opts_chunk$set(echo = FALSE,
warning = FALSE,
message = FALSE)
library(tidyverse)
library(janitor)
library(googlesheets4)
library(gt)
# 从 Google Sheets 导入数据
❶ survey_data_raw <- read_sheet("https://docs.google.com/spreadsheets/d/
1AR0_RcFBg8wdiY4Cj-k8vRypp_txh27MyZuiRdqScog/edit?usp=sharing")
这个 R Markdown 文档与之前章节讨论的文档类似,不同之处在于你导入数据的方式 ❶。由于你是直接从 Google Sheets 导入数据,因此不会有例如错误地读取了错误的 CSV 文件的风险。自动化这一步骤可以减少错误的风险。
接下来的代码块清理了 survey_data_raw 对象,并将结果保存为 survey_data_clean:
# 清理数据
survey_data_clean <- survey_data_raw %>%
clean_names() %>%
mutate(participant_id = as.character(row_number())) %>%
rename(
age = how_old_are_you,
like_games = do_you_like_to_play_video_games,
game_types = what_kind_of_games_do_you_like,
favorite_game = whats_your_favorite_game
) %>%
relocate(participant_id, .before = age) %>%
mutate(age = factor(age, levels = c("Under 18", "18-24", "25-34",
"35-44", "45-54", "55-64", "65 岁以上")))
这里,来自 janitor 包的 clean_names() 函数使得变量名称更易于处理。使用 row_number() 函数定义 participant_id 变量,然后为每一行添加一个连续增加的编号,as.character() 函数将该编号转为字符。接下来,代码使用 rename() 函数更改几个变量名。mutate() 函数将 age 变量转化为一种数据结构,称为 *factor*,这可以确保年龄在图表中按正确的顺序显示。最后,relocate() 函数将 participant_id 放置在 age 变量之前。
现在,你可以再次使用 glimpse() 函数查看更新后的 survey_data_clean 数据框,结果如下:
> Rows: 5
> Columns: 6
> $ timestamp 2024-05-16 15:20:50, 2024-05-16 15:21:28, 2024-05...
> $ participant_id "1", "2", "3", "4", "5"
> $ age 25-34, 45-54, Under 18, Over 65, Un...
> $ like_games "Yes", "No", "Yes", "No", "Yes"
> $ game_types "Sandbox, Role-Playing (RPG), Simul...
> $ favorite_game "It's hard to choose. House Flipper...
剩下的报告使用这些数据来突出显示各种统计信息:
Respondent Demographics
# 计算响应者数量
number_of_respondents <- nrow(survey_data_clean) ❶
We received responses from r number_of_respondents respondents. Their ages are below.
survey_data_clean %>%
select(participant_id, age) %>%
gt() %>% ❷
cols_label(
participant_id = "参与者 ID",
age = "年龄"
) %>%
tab_style(
style = cell_text(weight = "bold"),
locations = cells_column_labels()
) %>%
cols_align(
align = "left",
columns = everything()
) %>%
cols_width(
participant_id ~ px(200),
age ~ px(700)
)
Video Games
We asked if respondents liked video games. Their responses are below.
survey_data_clean %>%
count(like_games) %>%
ggplot(aes(
x = like_games, ❸
y = n,
fill = like_games
)) +
geom_col() +
scale_fill_manual(values = c(
"No" = "#6cabdd",
"Yes" = "#ff7400"
)) +
labs(
title = "有多少人喜欢电子游戏?",
x = NULL,
y = "参与者数量"
) +
theme_minimal(base_size = 16) +
theme(
legend.position = "none",
panel.grid.minor = element_blank(),
panel.grid.major.x = element_blank(),
axis.title.y = element_blank(),
plot.title = element_text(
face = "bold",
hjust = 0.5
)
)
这些部分计算了调查响应者的数量 ❶,然后将该值放入文本中,使用内联 R 代码;创建一个表格,按年龄组划分响应者 ❷;并生成一个图表,显示有多少响应者喜欢电子游戏 ❸。 图 11-2 显示了生成的报告。

图 11-2:渲染后的电子游戏报告
你可以在任何时候重新运行代码以获取更新的数据。今天调查有五个回答,但如果明天再运行,新增的回答也会被包含在导入数据中。如果你使用 Google 表单进行调查并将结果保存到 Google 表格中,只需点击 RStudio 中的 Knit 按钮,你就可以生成这个最新的报告。
#### 仅导入特定列
在前面的章节中,你读取了整个 Google 表格的数据,但你也可以选择仅导入表格的某一部分。例如,调查数据包含一个时间戳列。每当有人提交通过 Google 表单将数据导入 Google 表格时,这个变量会自动添加,但你在分析中不使用它,因此可以将其去掉。
要做到这一点,可以在导入数据时使用 read_sheet() 函数中的 range 参数,像这样:
read_sheet("https://docs.google.com/spreadsheets/d/1AR0_RcFBg8wdiY4Cj-k8vRypp_
txh27MyZuiRdqScog/edit?usp=sharing",
range = "Sheet1!B:E") %>%
glimpse()
这个参数让你指定一个数据范围进行导入。它使用与你可能在 Google 表格中选择列时相同的语法。在这个示例中,range = "Sheet1!B:E" 导入 B 到 E 列(但不包括 A 列,因为它包含时间戳)。添加 glimpse() 然后运行这段代码会输出没有时间戳变量的结果:
> Rows: 5
> Columns: 4
> $ How old are you? "25-34", "45-54"...
> $ Do you like to play video games? "Yes", "No", "Ye...
> $ What kind of games do you like? "Sandbox, Role-P...
> $ What's your favorite game? "It's hard to ch...
在 googlesheets4 包中,还有许多其他有用的功能。例如,如果你需要将输出写回 Google 表格,write_sheet() 函数可以帮助你。要探索包中的其他函数,可以访问其文档网站 *[`googlesheets4.tidyverse.org/index.xhtml`](https://googlesheets4.tidyverse.org/index.xhtml)*。
现在我们将关注另一个 R 包,它可以让你自动从美国人口普查局获取数据。
### 使用 tidycensus 访问人口普查数据
如果你曾经处理过来自美国人口普查局的数据,你就知道这有多麻烦。通常,这个过程包括访问人口普查局的网站,搜索所需的数据,下载它,然后在你选择的工具中进行分析。经过一段时间,这种点击操作会变得非常繁琐。
德克萨斯基督教大学的地理学家 Kyle Walker 和 Matt Herman(第九章中讨论的 Westchester COVID-19 网站的创建者)开发了 tidycensus 包,以自动化将人口普查局数据导入 R 的过程。使用 tidycensus,你只需要写几行代码,就能获取比如所有美国县的中位数收入数据。
在这一部分,我将通过使用两个数据集的示例来展示 tidycensus 包的工作原理,这两个数据集是该包提供访问权限的:十年一次的人口普查数据和年度美国社区调查数据。我还将向你展示如何利用这两个数据源的数据进行额外分析,并通过同时访问地理空间和人口统计数据来制作地图。
#### 使用 API 密钥连接到人口普查局
首先通过 install.packages("tidycensus") 安装 tidycensus。要使用 tidycensus,你必须从人口普查局获取一个应用程序编程接口(API)密钥。*API 密钥*就像密码,在线服务通过它来判断你是否有权限访问数据。
要获得这个免费的密钥,请访问 *[`api.census.gov/data/key_signup.xhtml`](https://api.census.gov/data/key_signup.xhtml)* 并填写你的详细信息。一旦你通过电子邮件收到密钥,你需要将它放在 tidycensus 可以找到的位置。census_api_key() 函数会为你完成这个操作,因此在加载 tidycensus 包之后,运行以下函数,并用你的实际 API 密钥替换 123456789:
library(tidycensus)
census_api_key("123456789", install = TRUE)
install = TRUE 参数将你的 API 密钥保存在*.Renviron*文件中,该文件用于存储机密信息。以后包会在此文件中查找 API 密钥,这样你就不必每次使用包时都重新输入它。
现在你可以使用 tidycensus 访问人口普查局的数据集。虽然十年一度人口普查和美国社区调查是最常用的,Kyle Walker 的《分析美国人口普查数据:R 中的方法、地图和模型》第二章讨论了你可以访问的其他数据集。
#### 使用十年一度的人口普查数据
tidycensus 包包含多个专门用于特定人口普查局数据集的函数,例如 get_decennial()用于十年一度人口普查数据。要访问 2020 年关于每个州亚洲人口的数据,可以使用带有三个参数的 get_decennial()函数,如下所示:
get_decennial(geography = "state",
variables = "P1_006N",
year = 2020)
将地理参数设置为“state”会告诉 get_decennial()访问州级别的数据。除了 50 个州,它还会返回哥伦比亚特区和波多黎各的数据。变量参数指定你想要访问的变量。在这里,P1_006N 是亚洲总人口的变量名。我将在下一节中讨论如何识别你可能需要使用的其他变量。最后,year 指定你想要访问数据的年份——在本例中是 2020 年。
运行此代码将返回以下内容:
> # A tibble: 52 × 4
> GEOID NAME variable value
>
> 1 42 Pennsylvania P1_006N 510501
> 2 06 California P1_006N 6085947
> 3 54 West Virginia P1_006N 15109
> 4 49 Utah P1_006N 80438
> 5 36 New York P1_006N 1933127
> 6 11 District of Columbia P1_006N 33545
> 7 02 Alaska P1_006N 44032
> 8 12 Florida P1_006N 643682
> 9 45 South Carolina P1_006N 90466
> 10 38 North Dakota P1_006N 13213
--snip--
结果数据框包含四个变量。GEOID 是人口普查局分配给各州的地理标识符。每个州都有一个地理标识符,所有县、普查区和其他地理区域也都有各自的标识符。NAME 是每个州的名称,variable 是你传递给 get_decennial()函数的变量名称。最后,value 是每一行中该州和变量的数值。在这种情况下,它表示每个州的亚洲总人口。
#### 识别人口普查变量值
你刚刚看到如何检索每个州的亚洲居民总数,但假设你想将这个数字计算为所有州居民的百分比。为此,首先你需要检索该州的总人口变量。
tidycensus 包有一个名为 load_variables()的函数,可以显示所有来自十年一度人口普查的变量。将 year 参数设置为 2020,dataset 设置为 pl,运行如下:
load_variables(year = 2020,
dataset = "pl")
运行此代码将从所谓的重划选区汇总数据文件中提取数据(根据公共法案 94-171,人口普查局每 10 年必须制作这些文件),并返回所有可用变量的名称、标签(描述)和概念(类别):
> # A tibble: 301 × 3
> name label concept
>
> 1 H1_001N " !!Total:" OCCU...
> 2 H1_002N " !!Total:!!Occupied" OCCU...
> 3 H1_003N " !!Total:!!Vacant" OCCU...
> 4 P1_001N " !!Total:" RACE
> 5 P1_002N " !!Total:!!Population of one race:" RACE
> 6 P1_003N " !!Total:!!Population of one race:!!Whi... RACE
> 7 P1_004N " !!Total:!!Population of one race:!!Bla... RACE
> 8 P1_005N " !!Total:!!Population of one race:!!Ame... RACE
> 9 P1_006N " !!Total:!!Population of one race:!!Asi... RACE
> 10 P1_007N " !!Total:!!Population of one race:!!Nat... RACE
--snip--
通过查看此列表,你可以看到变量 P1_001N 返回的是总人口。
#### 使用多个人口普查变量
现在你知道了需要哪些变量,可以再次使用 get_decennial()函数,一次获取两个变量:
get_decennial(geography = "state",
variables = c("P1_001N", "P1_006N"),
year = 2020) %>%
arrange(NAME)
在`get_decennial()`后添加`arrange(NAME)`会按州名对结果进行排序,让你能轻松看到输出中包含每个州的这两个变量:
> # A tibble: 104 × 4
> GEOID NAME variable value
>
> 1 01 Alabama P1_001N 5024279
> 2 01 Alabama P1_006N 76660
> 3 02 Alaska P1_001N 733391
> 4 02 Alaska P1_006N 44032
> 5 04 Arizona P1_001N 7151502
> 6 04 Arizona P1_006N 257430
> 7 05 Arkansas P1_001N 3011524
> 8 05 Arkansas P1_006N 51839
> 9 06 California P1_001N 39538223
> 10 06 California P1_006N 6085947
--snip--
当你处理多个类似的人口普查变量时,可能会难以记住像 P1_001N 和 P1_006N 这样的名称的含义。幸运的是,你可以调整`get_decennial()`调用中的代码,通过以下语法为这些变量赋予更有意义的名称:
get_decennial(geography = "state",
variables = c(total_population = "P1_001N",
asian_population = "P1_006N"),
year = 2020) %>%
arrange(NAME)
在变量参数中,这段代码指定了变量的新名称,后面跟着等号和原始变量名称。`c()`函数允许你一次重命名多个变量。
现在更容易看到你正在处理的变量了:
> # A tibble: 104 × 4
> GEOID NAME variable value
>
> 1 01 Alabama total_population 5024279
> 2 01 Alabama asian_population 76660
> 3 02 Alaska total_population 733391
> 4 02 Alaska asian_population 44032
> 5 04 Arizona total_population 7151502
> 6 04 Arizona asian_population 257430
> 7 05 Arkansas total_population 3011524
> 8 05 Arkansas asian_population 51839
> 9 06 California total_population 39538223
> 10 06 California asian_population 6085947
> # ... with 94 more rows
代替 P1_001N 和 P1_006N,变量显示为 total_population 和 asian_population。好多了!
#### 分析人口普查数据
现在你已经拥有了计算每个州亚洲人口占总人口百分比所需的数据。只需要在上一部分的代码中添加一些函数:
get_decennial(
geography = "state",
variables = c(
total_population = "P1_001N",
asian_population = "P1_006N"
),
year = 2020
) %>%
arrange(NAME) %>%
group_by(NAME) %>%
mutate(pct = value / sum(value)) %>%
ungroup() %>%
filter(variable == "asian_population")
`group_by(NAME)`函数为每个州创建一个分组,因为你想要计算每个州的亚洲人口百分比(而不是整个美国)。然后,`mutate()`函数计算每个百分比,将每行的值除以每个州的`total_population`和`asian_population`行。`ungroup()`函数移除州级分组,`filter()`函数仅显示亚洲人口百分比。
当你运行这段代码时,你应该能看到每个州的总亚洲人口和亚洲人口占总人口的百分比:
> # A tibble: 52 × 5
> GEOID NAME variable value pct
>
> 1 01 Alabama asian_popula... 76660 0.015029
> 2 02 Alaska asian_popula... 44032 0.056638
> 3 04 Arizona asian_popula... 257430 0.034746
> 4 05 Arkansas asian_popula... 51839 0.016922
> 5 06 California asian_popula... 6085947 0.133390
> 6 08 Colorado asian_popula... 199827 0.033452
> 7 09 Connecticut asian_popula... 172455 0.045642
> 8 10 Delaware asian_popula... 42699 0.041349
> 9 11 District of Columbia asian_popula... 33545 0.046391
> 10 12 Florida asian_popula... 643682 0.029018
--snip--
这是一种合理的计算每个州亚洲人口占总人口百分比的方法——但并不是唯一的方法。
#### 使用汇总变量
Kyle Walker 知道,像你刚才做的那样计算汇总数据将是 tidycensus 的常见用例。比如,要计算亚洲人口占总人口的百分比,你需要有一个分子(亚洲人口)和一个分母(总人口)。为了简化操作,Walker 在`get_decennial()`中加入了`summary_var`参数,可以用来导入总人口作为单独的变量。你可以像下面这样将 P1_001N(总人口)分配给`summary_var`参数,而不是在变量参数中输入并重命名它:
get_decennial(
geography = "state",
variables = c(asian_population = "P1_006N"),
summary_var = "P1_001N",
year = 2020
) %>%
arrange(NAME)
这将返回一个几乎相同的数据框,只不过总人口现在是一个单独的变量,而不是每个州的额外行:
> # A tibble: 52 × 5
> GEOID NAME variable value summar...
>
> 1 01 Alabama asian_popula... 76660 5024279
> 2 02 Alaska asian_popula... 44032 733391
> 3 04 Arizona asian_popula... 257430 7151502
> 4 05 Arkansas asian_popula... 51839 3011524
> 5 06 California asian_popula... 6085947 39538223
> 6 08 Colorado asian_popula... 199827 5773714
> 7 09 Connecticut asian_popula... 172455 3605944
> 8 10 Delaware asian_popula... 42699 989948
> 9 11 District of Columbia asian_popula... 33545 689545
> 10 12 Florida asian_popula... 643682 21538187
--snip--
> # summary_value
使用这种新格式的数据后,现在你可以通过将值变量除以 summary_value 变量来计算亚洲人口占总人口的百分比。然后,你可以删除 summary_value 变量,因为在完成此计算后你不再需要它:
get_decennial(
geography = "state",
variables = c(asian_population = "P1_006N"),
summary_var = "P1_001N",
year = 2020
) %>%
arrange(NAME) %>%
mutate(pct = value / summary_value) %>%
select(-summary_value)
结果输出与前一部分的输出完全相同:
> # A tibble: 52 × 5
> GEOID NAME variable value pct
>
> 1 01 Alabama asian_popula... 76660 0.015258
> 2 02 Alaska asian_popula... 44032 0.060039
> 3 04 Arizona asian_popula... 257430 0.035997
> 4 05 Arkansas asian_popula... 51839 0.017214
> 5 06 California asian_popula... 6085947 0.153930
> 6 08 Colorado asian_popula... 199827 0.034610
> 7 09 Connecticut asian_popula... 172455 0.047825
> 8 10 Delaware asian_popula... 42699 0.043133
> 9 11 District of Columbia asian_popula... 33545 0.048648
> 10 12 Florida asian_popula... 643682 0.029886
> # 42 more rows
你选择如何计算汇总统计数据取决于你自己;tidycensus 让你无论哪种方式都能轻松实现。
### 可视化美国社区调查数据
一旦你使用 tidycensus 包访问了数据,你可以随意操作它。在这一部分,你将练习使用美国社区调查数据进行分析和可视化。该调查每年进行一次,与十年一次的人口普查相比,主要有两个区别:它是发放给一部分人群,而不是全体人口;并且它包含了更广泛的问题。
尽管有这些差异,你可以几乎以与访问十年一次人口普查数据相同的方式访问美国社区调查数据。你使用 get_acs()函数代替 get_decennial(),但传递给它的参数是相同的:
get_acs(
geography = "state",
variables = "B01002_001",
year = 2020
)
这段代码使用 B01002_001 变量从 2020 年获取每个州的中位年龄数据。以下是输出的样子:
> # A tibble: 52 × 5
> GEOID NAME variable estimate moe
>
> 1 01 Alabama B01002_001 39.2 0.1
> 2 02 Alaska B01002_001 34.6 0.2
> 3 04 Arizona B01002_001 37.9 0.2
> 4 05 Arkansas B01002_001 38.3 0.2
> 5 06 California B01002_001 36.7 0.1
> 6 08 Colorado B01002_001 36.9 0.1
> 7 09 Connecticut B01002_001 41.1 0.2
> 8 10 Delaware B01002_001 41.0 0.2
> 9 11 District of Columbia B01002_001 34.1 0.1
> 10 12 Florida B01002_001 42.2 0.2
--snip--
你应该注意到,get_acs()的输出与 get_decennial()的输出有两个不同之处。首先,get_acs()生成了一个名为 estimate 的列,而不是 value 列。其次,它增加了一个名为 moe 的列,用于表示误差范围。这些变化是由于美国社区调查只发放给人群的一部分,因而从样本中推算值来生成总体估算会带来误差范围。
在州级数据中,误差范围相对较小,但在较小的地理区域中,误差通常较大。当你的误差范围相对于估算值较大时,这表示数据对整体人口的代表性存在较大不确定性,因此你应谨慎解释此类结果。
#### 制作图表
要将中位年龄数据传递给 ggplot 并创建柱状图,请添加以下代码:
get_acs(
geography = "state",
variables = "B01002_001",
year = 2020
) %>%
ggplot(aes(
x = estimate,
y = NAME
)) +
geom_col()
使用 get_acs()函数导入数据后,ggplot()函数会将数据直接传递给 ggplot。州(使用变量 NAME)将出现在 y 轴上,中位年龄(估算值)将出现在 x 轴上。一个简单的 geom_col()创建了图 11-3 所示的柱状图。

图 11-3:使用 get_acs()函数获取数据生成的柱状图
这个图表没什么特别的,但它仅用六行代码就能创建,这一点无疑是非常特别的!
#### 使用几何参数制作人口地图
除了共同创建 tidycensus 外,Kyle Walker 还创建了 tigris 包,用于处理地理空间数据。因此,这些包紧密集成。在 get_acs()函数中,你可以将 geometry 参数设置为 TRUE,来同时获取来自人口普查局的人口统计数据和来自 tigris 的地理空间数据:
get_acs(
geography = "state",
variables = "B01002_001",
year = 2020,
geometry = TRUE
)
在结果数据中,你可以看到它包含了你在第四章中看到的简单特征对象的元数据和几何列:
> Simple feature collection with 52 features and 5 fields
> Geometry type: MULTIPOLYGON
> Dimension: XY
> Bounding box: xmin: -179.1489 ymin: 17.88328 xmax: 179.7785 ymax: 71.36516
> Geodetic CRS: NAD83
> First 10 features:
> GEOID NAME variable estimate moe
> 1 35 New Mexico B01002_001 38.1 0.1
> 2 72 Puerto Rico B01002_001 42.4 0.2
> 3 06 California B01002_001 36.7 0.1
> 4 01 Alabama B01002_001 39.2 0.1
> 5 13 Georgia B01002_001 36.9 0.1
> 6 05 Arkansas B01002_001 38.3 0.2
> 7 41 Oregon B01002_001 39.5 0.1
> 8 28 Mississippi B01002_001 37.7 0.2
> 9 08 Colorado B01002_001 36.9 0.1
> 10 49 Utah B01002_001 31.1 0.1
> geometry
> 1 MULTIPOLYGON (((-109.0502 3...
> 2 MULTIPOLYGON (((-65.23805 1...
> 3 MULTIPOLYGON (((-118.6044 3...
> 4 MULTIPOLYGON (((-88.05338 3...
> 5 MULTIPOLYGON (((-81.27939 3...
> 6 MULTIPOLYGON (((-94.61792 3...
> 7 MULTIPOLYGON (((-123.6647 4...
> 8 MULTIPOLYGON (((-88.50297 3...
> 9 MULTIPOLYGON (((-109.0603 3...
> 10 MULTIPOLYGON (((-114.053 37...
几何类型是 MULTIPOLYGON,你在第四章中学习过。要将这些数据传递到 ggplot 以制作地图,请添加以下代码:
get_acs(
geography = "state",
variables = "B01002_001",
year = 2020,
geometry = TRUE
) %>%
ggplot(aes(fill = estimate)) +
geom_sf() +
scale_fill_viridis_c()
在使用 get_acs()导入数据并将其传递到 ggplot()函数后,这段代码设置了用于填充美学属性的 estimate 变量;也就是说,每个州的填充颜色将根据其居民的中位年龄有所变化。然后,geom_sf()绘制地图,scale_fill_viridis_c()函数则为地图提供了色盲友好的调色板。
结果地图,如图 11-4 所示,效果不尽如人意,因为阿拉斯加的阿留申群岛跨越了 180 度经线,也就是国际日期变更线。因此,大部分阿拉斯加位于地图的一侧,只有一小部分出现在另一侧。更糟糕的是,夏威夷和波多黎各很难看清。

图 11-4:显示各州中位年龄的难以阅读的地图
为了解决这些问题,加载 tigris 包,然后使用 shift_geometry()函数将阿拉斯加、夏威夷和波多黎各移动到更容易看见的位置:
library(tigris)
get_acs(
geography = "state",
variables = "B01002_001",
year = 2020,
geometry = TRUE
) %>%
shift_geometry(preserve_area = FALSE) %>%
ggplot(aes(fill = estimate)) +
geom_sf() +
scale_fill_viridis_c()
将 preserve_area 参数设置为 FALSE 可以缩小阿拉斯加州的面积,使夏威夷和波多黎各变得更大。尽管地图中州的大小不再精确,但地图会更易于阅读,正如图 11-5 所示。

图 11-5:使用 tigris 函数调整后的更易读地图
现在,尝试将地理参数更改为“county”,制作涵盖所有 3,000 个县的相同地图。其他地理选项包括 region、tract(人口普查区)、place(人口普查指定地点,更常见的是指城镇和城市)以及国会选区。在 get_decennial()和 get_acs()函数中,还有许多其他参数;我只展示了一些最常见的。如果你想了解更多,Walker 的书籍*《分析美国人口普查数据:R 中的方法、地图与模型》*是一本极好的资源。
### 总结
本章介绍了两个使用 API 直接从数据源获取数据的包。googlesheets4 包允许你从 Google 表格导入数据。当你处理调查数据时,它尤其有用,因为当新的结果到达时,可以轻松更新报告。如果你不使用 Google 表格,你还可以使用类似的包从 Excel365(Microsoft365R)、Qualtrics(qualtRics)、Survey Monkey(svmkrR)等其他来源获取数据。
如果你处理美国人口普查局数据,tidycensus 包将大大节省时间。你无需手动从人口普查局网站下载数据,可以通过 tidycensus 编写 R 代码自动访问数据,准备好进行分析和报告。由于该包与 tigris 的集成,你也可以轻松绘制这些人口统计数据的地图。
如果你在寻找其他国家的人口普查数据,也有一些 R 包可以获取来自加拿大(cancensus)、肯尼亚(rKenyaCensus)、墨西哥(mxmaps 和 inegiR)、欧洲(eurostat)以及其他地区的数据。在点击数据收集工具中的下载按钮以获取 CSV 文件之前,值得寻找一个可以直接将数据导入 R 的包。
### 其他资源
+ Isabella Velásquez 和 Curtis Kephart, “使用 googlesheets4、pins 和 R Markdown 自动化调查报告,”Posit,2022 年 6 月 15 日,*[`posit.co/blog/automated-survey-reporting/`](https://posit.co/blog/automated-survey-reporting/)*。
+ Kyle Walker, *《分析美国人口普查数据:R 中的方法、地图与模型》*(佛罗里达州博卡拉顿:CRC 出版社,2023 年),*[`walker-data.com/census-r/`](https://walker-data.com/census-r/)*。
## 第十二章:12 创建函数和包

在本章中,你将学习如何定义你自己的 R 函数,包括它们应该接受的参数。然后,你将创建一个包来分发这些函数,添加你的代码和依赖项,编写文档,并选择发布时的许可证。
将代码保存为自定义函数,然后将其分发到包中,可以带来许多好处。首先,包可以使你的代码更容易供他人使用。例如,当莫菲特癌症中心的研究人员需要从数据库访问代码时,数据科学家 Travis Gerke 和 Garrick Aden-Buie 以前会为每个研究人员编写 R 代码,但他们很快意识到自己不断重复相同的代码。于是,他们制作了一个包含数据库访问功能的包。现在,研究人员不再需要寻求帮助;他们只需安装 Gerke 和 Aden-Buie 创建的包并自行使用其中的函数。
更重要的是,开发包可以让你塑造他人的工作方式。假设你创建了一个遵循第三章中讨论的高质量数据可视化原则的 ggplot 主题。如果你将这个主题放入包中,你就能为他人提供一种简便的方式来遵循这些设计原则。简而言之,函数和包帮助你通过共享代码与他人合作。
### 创建你自己的函数
tidyverse 包的开发者 Hadley Wickham 推荐,当你复制某段代码三次后,就应该考虑将其封装为函数。函数由三个部分组成:名称、主体和参数。
#### 编写一个简单的函数
你将首先编写一个相对简单的函数示例。这个名为 `show_in_excel_penguins()` 的函数会在 Microsoft Excel 中打开第七章中的企鹅数据:
❶ penguins <- read_csv("https://data.rfortherestofus.com/penguins-2007.csv")
❷ show_in_excel_penguins <- function() {
csv_file <- str_glue("{tempfile()}.csv")
write_csv(
x = penguins,
file = csv_file,
na = ""
)
file_show(path = csv_file)
}
这段代码首先加载 tidyverse 和 fs 包。你将使用 tidyverse 来为 CSV 文件创建文件名并保存它,使用 fs 来在 Excel 中打开 CSV 文件(或你的计算机默认用来打开 CSV 文件的程序)。
接下来,`read_csv()` 函数导入企鹅数据,并将数据框命名为 penguins ❶。然后,它创建了新的 `show_in_excel_penguins` 函数,使用赋值操作符 (<-) 和 `function()` 来指定 `show_in_excel_penguins` 不是变量名,而是函数名 ❷。行尾的左花括号({)表示函数体的开始,函数的“核心”部分就在其中。在这个例子中,函数体完成了三件事:
+ 使用 `str_glue()` 函数与 `tempfile()` 函数结合,创建一个保存 CSV 文件的位置。这会在临时位置创建一个 *.csv* 扩展名的文件,并将其保存为 csv_file。
+ 将企鹅数据写入在 csv_file 中设置的位置。`write_csv()` 中的 x 参数指代要保存的数据框。它还指定所有的 NA 值应该显示为空白。(默认情况下,它们会显示为 *NA*。)
+ 使用 fs 包中的 file_show() 函数在 Excel 中打开临时的 CSV 文件。
要使用 show_in_excel_penguins() 函数,选中定义函数的代码行,然后在 macOS 上按 COMMAND-ENTER,或在 Windows 上按 CTRL-ENTER。现在你应该能在全局环境中看到这个函数,如 图 12-1 所示。

图 12-1:全局环境中的新函数
从现在开始,每次你运行 show_in_excel_penguins() 代码时,R 都会在 Excel 中打开企鹅数据框。
#### 添加参数
你可能会觉得这个函数似乎没什么用。它做的只是打开企鹅数据框。为什么要一直这样做呢?一个更实用的函数是能让你在 Excel 中打开*任何*数据,这样你可以在各种场景下使用它。
show_in_excel() 函数正是做这个:它接受 R 中的任何数据框,将其保存为 CSV 文件,并在 Excel 中打开该 CSV 文件。卢森堡高等教育与研究部统计与数据策略部主任 Bruno Rodrigues 编写了 show_in_excel(),以便轻松与不使用 R 的同事共享数据。每当他需要将数据保存为 CSV 文件时,他就可以运行这个函数。
用 Rodrigues 使用的这个稍微简化版的代码替换你的 show_in_excel_penguins() 函数定义:
show_in_excel <- function(data) {
csv_file <- str_glue("{tempfile()}.csv")
write_csv(
x = data,
file = csv_file,
na = ""
)
file_show(path = csv_file)
}
这段代码与 show_in_excel_penguins() 完全相同,只有两个例外。注意第一行现在写的是 function(data)。函数定义中的括号内列出的项目是参数。如果往下看,你会看到第二个变化。在 write_csv() 中,原来的 x = penguins 现在改成了 x = data。这使得你可以使用该函数处理任何数据,而不仅仅是企鹅数据。
要使用这个函数,你只需告诉 show_in_excel() 要使用哪些数据,函数就会在 Excel 中打开这些数据。例如,告诉它打开企鹅数据框,如下所示:
show_in_excel(data = penguins)
创建了带有数据参数的函数后,现在你可以使用任何你想要的数据来运行它。例如,这段代码导入 第十章 的 COVID 病例数据,并在 Excel 中打开:
covid_data <- read_csv("https://data.rfortherestofus.com/
us-states-covid-rolling-average.csv")
show_in_excel(data = covid_data)
你还可以在管道的末尾使用 show_in_excel()。这段代码将 covid_data 数据框过滤,仅包含来自加利福尼亚的数据,然后在 Excel 中打开:
covid_data %>%
filter(state == "California") %>%
show_in_excel()
Rodrigues 本可以复制 show_in_excel() 函数中的代码,并在每次想查看数据时重新运行。但通过创建一个函数,他只需编写一次代码,之后可以根据需要多次运行它。
#### 创建一个格式化种族和族裔数据的函数
希望现在你能更好地理解函数是如何工作的,接下来我们将通过一个示例函数,帮助你简化一些前面章节中的操作。
在第十一章中,当你使用 tidycensus 包从美国人口普查局自动导入数据时,你了解到人口普查数据有许多变量,其名称不直观。假设你经常需要访问来自美国社区调查的种族和族裔数据,但你总是记不住哪些变量可以提供这些数据。为了提高效率,你将在本节中一步步创建一个 get_acs_race_ethnicity() 函数,并在此过程中学习一些关于自定义函数的重要概念。
get_acs_race_ethnicity() 函数的第一个版本可能如下所示:
library(tidycensus)
get_acs_race_ethnicity <- function() {
race_ethnicity_data <-
get_acs(
geography = "state",
variables = c(
"White" = "B03002_003",
"Black/African American" = "B03002_004",
"American Indian/Alaska Native" = "B03002_005",
"Asian" = "B03002_006",
"Native Hawaiian/Pacific Islander" = "B03002_007",
"Other race" = "B03002_008",
"Multi-Race" = "B03002_009",
"Hispanic/Latino" = "B03002_012"
)
)
race_ethnicity_data
}
在函数体内,这段代码调用了 tidycensus 中的 get_acs() 函数,从州级别检索人口数据。但与返回函数的默认输出不同,它将难以记住的变量名更新为易于理解的名称,例如 White 和 Black/African American,并将其保存为名为 race_ethnicity_data 的对象。然后,代码使用 race_ethnicity_data 对象来返回 get_acs_race_ethnicity() 函数运行时的数据。
要运行这个函数,输入以下内容:
get_acs_race_ethnicity()
这样做应该返回易于阅读的种族和族裔组名:
> # A tibble: 416 × 5
> GEOID NAME variable estimate moe
>
> 1 01 Alabama White 3241003 2076
> 2 01 Alabama Black/African American 1316314 3018
> 3 01 Alabama American Indian/Alaska Na... 17417 941
> 4 01 Alabama Asian 69331 1559
> 5 01 Alabama Native Hawaiian/Pacific I... 1594 376
> 6 01 Alabama Other race 12504 1867
> 7 01 Alabama Multi-Race 114853 3835
> 8 01 Alabama Hispanic/Latino 224659 413
> 9 02 Alaska White 434515 1067
> 10 02 Alaska Black/African American 22787 769
> # 406 more rows
你可以通过一些方法来改进这个函数。例如,你可能希望结果变量名遵循一致的语法格式,因此可以使用 janitor 包中的 clean_names() 函数将其格式化为 *snake case*(即所有单词小写,并用下划线分隔)。不过,你也可能希望保留原始的变量名。为此,你可以按以下方式将 clean_variable_names 参数添加到函数定义中:
get_acs_race_ethnicity <- function(clean_variable_names = FALSE) {
race_ethnicity_data <-
get_acs(
geography = "state",
variables = c(
"White" = "B03002_003",
"Black/African American" = "B03002_004",
"American Indian/Alaska Native" = "B03002_005",
"Asian" = "B03002_006",
"Native Hawaiian/Pacific Islander" = "B03002_007",
"Other race" = "B03002_008",
"Multi-Race" = "B03002_009",
"Hispanic/Latino" = "B03002_012"
)
)
if (clean_variable_names == TRUE) {
❶ race_ethnicity_data <- clean_names(race_ethnicity_data)
}
race_ethnicity_data
}
这段代码将 clean_variable_names 参数添加到 get_acs_race_ethnicity() 中,并指定其默认值为 FALSE。然后,在函数体内,一个 if 语句表示如果参数为 TRUE,则变量名应被覆盖为 snake case 格式 ❶。如果参数为 FALSE,变量名将保持不变。
如果你现在运行该函数,什么也不会改变,因为新参数默认设置为 FALSE。尝试将 clean_variable_names 设置为 TRUE,如下所示:
get_acs_race_ethnicity(clean_variable_names = TRUE)
这个函数调用应该返回具有一致变量名的数据:
> # A tibble: 416 × 5
> geoid name variable estimate moe
>
> 1 01 Alabama White 3241003 2076
> 2 01 Alabama Black/African American 1316314 3018
> 3 01 Alabama American Indian/Alaska Na... 17417 941
> 4 01 Alabama Asian 69331 1559
> 5 01 Alabama Native Hawaiian/Pacific I... 1594 376
> 6 01 Alabama Other race 12504 1867
> 7 01 Alabama Multi-Race 114853 3835
> 8 01 Alabama Hispanic/Latino 224659 413
> 9 02 Alaska White 434515 1067
> 10 02 Alaska Black/African American 22787 769
> # 406 more rows
请注意,GEOID 和 NAME 现在显示为 geoid 和 name。
现在你已经了解了如何为两个独立的函数添加参数,接下来你将学习如何将参数从一个函数传递到另一个函数。
#### 使用 … 将参数传递给另一个函数
你创建的 get_acs_race_ethnicity() 函数通过将 geography = "state" 参数传递给 get_acs() 函数来获取州级别的人口数据。但是,如果你想获取县级或普查区的数据呢?你可以使用 get_acs() 来实现,但当前的 get_acs_race_ethnicity() 并没有以允许这种操作的方式编写。你可以如何修改这个函数,使其更加灵活?
您的第一个想法可能是为要检索的数据级别添加一个新参数。您可以如下编辑函数的前两行,以添加一个 my_geography 参数,然后在 `get_acs()` 函数中使用它,如下所示:
get_acs_race_ethnicity <- function(clean_variable_names = FALSE, my_geography) {
race_ethnicity_data <- get_acs(geography = my_geography,
--snip--
但是,如果您还想选择要检索数据的年份怎么办?那么,您也可以为此添加一个参数。但是,正如您在第十一章中看到的那样,`get_acs()` 函数有很多参数,并且在您的代码中重复所有这些参数将很快变得繁琐。
... 语法为您提供了一个更有效的选项。将 ... 放置在 `get_acs_race_ethnicity()` 函数中,您可以通过在函数中包含 ...,自动将其任何参数传递给 `get_acs()`:
get_acs_race_ethnicity <- function(
clean_variable_names = FALSE,
...
) {
race_ethnicity_data <-
get_acs(
...,
variables = c(
"White" = "B03002_003",
"Black/African American" = "B03002_004",
"American Indian/Alaska Native" = "B03002_005",
"Asian" = "B03002_006",
"Native Hawaiian/Pacific Islander" = "B03002_007",
"Other race" = "B03002_008",
"Multi-Race" = "B03002_009",
"Hispanic/Latino" = "B03002_012"
)
)
if (clean_variable_names == TRUE) {
race_ethnicity_data <- clean_names(race_ethnicity_data)
}
race_ethnicity_data
}
尝试通过传递设置为 "state" 的 geography 参数来运行您的函数:
get_acs_race_ethnicity(geography = "state")
这应该返回以下内容:
> # A tibble: 416 × 5
> GEOID NAME variable estimate moe
>
> 1 01 Alabama White 3241003 2076
> 2 01 Alabama Black/African American 1316314 3018
> 3 01 Alabama American Indian/Alaska Na... 17417 941
> 4 01 Alabama Asian 69331 1559
> 5 01 Alabama Native Hawaiian/Pacific I... 1594 376
> 6 01 Alabama Other race 12504 1867
> 7 01 Alabama Multi-Race 114853 3835
> 8 01 Alabama Hispanic/Latino 224659 413
> 9 02 Alaska White 434515 1067
> 10 02 Alaska Black/African American 22787 769
> # 406 more rows
您会看到 GEOID 和 NAME 变量是大写的,因为默认情况下 `clean_variable_names` 参数设置为 FALSE,并且我们在使用 `get_acs_race_ethnicity()` 函数时没有更改它。
或者,您可以更改参数的值以按县获取数据:
get_acs_race_ethnicity(geography = "county")
您还可以使用 `geometry = TRUE` 参数运行该函数,以返回地理空间数据以及人口统计数据:
get_acs_race_ethnicity(geography = "county", geometry = TRUE)
该函数应返回如下数据:
> Simple feature collection with 416 features and 5 fields
> Geometry type: MULTIPOLYGON
> Dimension: XY
> Bounding box: xmin: -179.1489 ymin: 17.88328 xmax: 179.7785 ymax: 71.36516
> Geodetic CRS: NAD83
> First 10 features:
> GEOID NAME variable estimate
> 1 56 Wyoming White 478508
> 2 56 Wyoming Black/African American 4811
> 3 56 Wyoming American Indian/Alaska Na... 11330
> 4 56 Wyoming Asian 4907
> 5 56 Wyoming Native Hawaiian/Pacific I... 397
> 6 56 Wyoming Other race 1582
> 7 56 Wyoming Multi-Race 15921
> 8 56 Wyoming Hispanic/Latino 59185
> 9 02 Alaska White 434515
> 10 02 Alaska Black/African American 22787
> moe geometry
> 1 959 MULTIPOLYGON (((-111.0546 4...
> 2 544 MULTIPOLYGON (((-111.0546 4...
> 3 458 MULTIPOLYGON (((-111.0546 4...
> 4 409 MULTIPOLYGON (((-111.0546 4...
> 5 158 MULTIPOLYGON (((-111.0546 4...
> 6 545 MULTIPOLYGON (((-111.0546 4...
> 7 1098 MULTIPOLYGON (((-111.0546 4...
> 8 167 MULTIPOLYGON (((-111.0546 4...
> 9 1067 MULTIPOLYGON (((179.4825 51...
> 10 769 MULTIPOLYGON (((179.4825 51...
... 语法允许您创建自己的函数,并将参数从该函数传递到另一个函数,而无需在您自己的代码中重复该函数的所有参数。这种方法在保持代码简洁的同时,为您提供了灵活性。
现在让我们看看如何将您的自定义函数放入一个包中。
### 创建一个包
包捆绑您的函数,以便您可以在多个项目中使用它们。如果您发现自己将函数从一个项目复制到另一个项目,或者从 *functions.R* 文件复制到每个新项目中,那么这是一个很好的迹象,表明您应该创建一个包。
虽然您可以在自己的环境中从 *functions.R* 文件运行这些函数,但此代码可能无法在其他人的计算机上运行。其他用户可能没有安装必要的包,或者他们可能对您的函数的参数如何工作感到困惑,并且不知道去哪里寻求帮助。将您的函数放入一个包中,可以使它们更有可能为每个人工作,因为它们包括必要的依赖项以及内置的文档,以帮助其他人自己使用这些函数。
#### 启动包
要在 RStudio 中创建一个包,请转到 **File****New Project****New Directory**。从选项列表中选择 **R Package** 并为您的包命名。在图 12-2 中,我将其命名为 dk。同时确定您希望将您的包存储在计算机上的哪个位置。您可以将其他所有内容保持原样。

图 12-2:用于创建您自己的包的 RStudio 菜单
RStudio 现在会创建并打开包。包应该已经包含一些文件,其中包括 *hello.R*,该文件包含一个名为 hello() 的预构建函数,当运行时,会在控制台中打印 "Hello, world!"。你将删除这个文件和其他一些默认文件,以便从零开始。删除 *hello.R*、*NAMESPACE* 和 *hello.Rd* 文件(位于 *man* 目录中)。
#### 使用 use_r() 添加函数
包中的所有函数应该放在 *R* 文件夹中的单独文件里。为了自动将这些文件添加到包中并测试它们是否正常工作,你将使用 usethis 和 devtools 包。可以通过如下方式使用 install.packages() 安装它们:
install.packages("usethis")
install.packages("devtools")
要向包中添加一个函数,可以在控制台中运行 usethis 包中的 use_r() 函数:
usethis::use_r("acs")
package::function() 语法允许你在不加载关联包的情况下使用函数。use_r() 函数应该在 *R* 目录中创建一个文件,文件名为你提供的参数名称——在这种情况下,文件名是 *acs.R*。文件名本身并不重要,但选择一个能指示文件中包含函数的名称是一种好习惯。现在,你可以打开该文件并添加代码。将 get_acs_race_ethnicity() 函数复制到包中。
#### 使用 devtools 检查包
你需要以几种方式修改 get_acs_race_ethnicity() 函数,以使其在包中正常工作。弄清楚需要做哪些修改的最简单方法是使用内置工具检查你的包是否构建正确。在控制台中运行 devtools::check() 函数,执行 R CMD check 这个命令,它在幕后运行,以确保其他人能够在他们的系统上安装你的包。运行 R CMD check 对 dk 包进行检查时,会输出这条长消息:
—— R CMD check results ——————————————— dk ————
Duration: 4s
checking DESCRIPTION meta-information ... WARNING
Non-standard license specification:
What license is it under?
Standardizable: FALSE
checking for missing documentation entries ... WARNING
Undocumented code objects:
'get_acs_race_ethnicity'
All user-level objects in a package should have documentation entries.
See chapter 'Writing R documentation files' in the 'Writing R
Extensions' manual.
❶ > checking R code for possible problems ... NOTE
get_acs_race_ethnicity: no visible global function definition for
'get_acs'
get_acs_race_ethnicity: no visible global function definition for
'clean_names'
Undefined global functions or variables:
clean_names get_acs
0 errors | 2 warnings x | 1 note x
最后部分是最重要的,因此我们从下到上回顾输出内容。行 0 错误 | 2 警告 x | 1 提示 x 突出了包中识别出的三种问题级别。错误是最严重的,因为它们意味着其他人无法安装你的包,而警告和提示可能会导致其他问题。最佳实践是消除所有的错误、警告和提示。
我们将从处理 ❶ 处的提示开始。为了帮助你理解 R CMD check 在这里的提示内容,我需要解释一下包是如何工作的。当你使用 install.packages() 函数安装包时,这通常需要一些时间。这是因为你告诉 R 安装的包很可能使用了其他包中的函数。为了访问这些函数,R 必须为你安装这些包(被称为 *依赖项*);毕竟,如果每次安装新包时你都需要手动安装一整套依赖包,那会很麻烦。不过,为了确保 dk 包的任何用户都能安装适当的包,你仍然需要做一些修改。
R CMD check 提示该包包含几个“未定义的全局函数或变量”以及多个“无可见的全局函数定义”。这是因为你在尝试使用 tidycensus 和 janitor 包中的函数,但没有指定这些函数的来源。我可以在我的环境中运行这些代码,因为我已经安装了 tidycensus 和 janitor 包,但不能假设所有人都有相同的环境。
#### 添加依赖包
为了确保包的代码能够正常工作,你需要在用户安装 dk 包时,为他们安装 tidycensus 和 janitor 包。为此,可以在控制台中运行 `use_package()` 函数,首先指定 "tidycensus" 作为包参数:
usethis::use_package(package = "tidycensus")
你应该看到以下信息:
Setting active project to '/Users/davidkeyes/Documents/Work/R for the Rest of Us/dk'
Adding 'tidycensus' to Imports field in DESCRIPTION
• Refer to functions with tidycensus::fun()
“Setting active project...” 这一行表示你正在工作于 dk 项目中。第二行表示 *DESCRIPTION* 文件已被编辑。此文件提供了关于你正在开发的包的元数据。
接下来,像添加 tidyverse 包一样添加 janitor 包。
usethis::use_package(package = "janitor")
这应该会给你以下输出:
Adding 'janitor' to Imports field in DESCRIPTION
• Refer to functions with janitor::fun()
如果你打开项目根目录下的 *DESCRIPTION* 文件,你应该看到如下内容:
Package: dk
Type: Package
Title: What the Package Does (Title Case)
Version: 0.1.0
Author: Who wrote it
Maintainer: The package maintainer yourself@somewhere.net
Description: More about what it does (maybe more than one line)
Use four spaces when indenting paragraphs within the Description.
License: What license is it under?
Encoding: UTF-8
LazyData: true
Imports:
janitor,
tidycensus
文件底部的 Imports 部分表明,当用户安装 dk 包时,tidycensus 和 janitor 包也会被导入。
#### 正确引用函数
运行 `usethis::use_package(package = "janitor")` 后的输出还包括了这一行:Refer to functions with tidycensus::fun()(其中 fun() 代表函数名)。这告诉你,为了在 dk 包中使用其他包的函数,你需要同时指定包名和函数名,以确保始终使用正确的函数。在少数情况下,你会发现不同包中有相同名称的函数,这种语法避免了歧义。记得 R CMD check 中的这一行吗?
Undefined global functions or variables:
clean_names get_acs
这个警告出现是因为你在使用函数时没有指定它们来自哪个包。`clean_names()` 函数来自 janitor 包,而 `get_acs()` 来自 tidycensus 包,因此你需要在每个函数前添加这些包的名称:
get_acs_race_ethnicity <- function(
clean_variable_names = FALSE,
...
) {
race_ethnicity_data <- tidycensus::get_acs(
...,
variables = c(
"White" = "B03002_003",
"Black/African American" = "B03002_004",
"American Indian/Alaska Native" = "B03002_005",
"Asian" = "B03002_006",
"Native Hawaiian/Pacific Islander" = "B03002_007",
"Other race" = "B03002_008",
"Multi-Race" = "B03002_009",
"Hispanic/Latino" = "B03002_012"
)
)
if (clean_variable_names == TRUE) {
race_ethnicity_data <- janitor::clean_names(race_ethnicity_data)
}
race_ethnicity_data
}
现在你可以再次运行 `devtools::check()`,应该可以看到这些警告已经消失:
checking DESCRIPTION meta-information ... WARNING
Non-standard license specification:
What license is it under?
Standardizable: FALSE
checking for missing documentation entries ... WARNING
Undocumented code objects:
'get_acs_race_ethnicity'
All user-level objects in a package should have documentation entries.
See chapter 'Writing R documentation files' in the 'Writing R
Extensions' manual.
0 errors | 2 warnings x | 0 notes
然而,仍然有两个警告需要处理。接下来你将处理这些警告。
#### 使用 Roxygen 创建文档
缺少文档条目的警告表示你需要为 `get_acs_race_ethnicity()` 函数添加文档。创建包的一个好处是你可以添加文档,帮助他人使用你的代码。就像用户可以输入 `?get_acs()` 查看该函数的文档一样,你希望他们能够输入 `?get_acs_race_ethnicity()` 来了解你的函数是如何工作的。
要为 get_acs_race_ethnicity() 创建文档,你将使用 Roxygen 这一文档工具,该工具使用了名为 roxygen2 的包。开始时,将光标放置在你的函数中的任何位置。然后,在 RStudio 中,点击 **Code** **Insert Roxygen Skeleton**。这将会在 get_acs_race_ethnicity() 函数之前添加以下文本:
' Title
'
' @param clean_variable_names
' @param ...
'
' @return
' @export
'
' @examples
这段文本是文档的骨架。每一行都以特殊字符 #' 开头,表示你正在使用 Roxygen。现在,你可以编辑这些文本来创建文档。从用一句话描述该函数来替换 Title 开始:
' Access race and ethnicity data from the American Community Survey
接下来,关注以 @param 开头的行。Roxygen 会为每个函数参数自动创建一行,但你需要填写每一行的描述。首先描述 clean_variable_names 参数的作用。接着,指定 ... 将会把额外的参数传递给 tidycensus::get_acs() 函数:
' @param clean_variable_names Should variable names be cleaned (i.e. snake case)
' @param ... Other arguments passed to tidycensus::get_acs()
@return 行应告知用户 get_acs_race_ethnicity() 函数返回的内容。在本例中,它返回数据,你可以按照以下方式进行文档说明:
' @return A tibble with five variables: GEOID, NAME, variable, estimate, and moe
在 @return 之后是 @export。你无需在此做任何更改。包中的大多数函数被称为*导出函数*,意味着它们对包的用户可用。相比之下,内部函数仅供包开发者使用,在 Roxygen 骨架中没有 @export。
最后一部分是 @examples。在这里,你可以提供用户可以运行的代码示例,以便了解该函数的工作原理。由于这增加了复杂性,因此不是必须的,你可以跳过它并删除包含 @examples 的那一行。
> 注意
*如果你想了解更多关于如何为文档添加示例的内容,Hadley Wickham 和 Jenny Bryan 的第二版《R Packages》一书是一个很好的资源。*
现在你已经使用 Roxygen 添加了文档,请在控制台中运行 devtools::document()。这应该会在 *man* 目录中创建一个 *get_acs_race_ethnicity.Rd* 文档文件,采用 R 包所要求的非常特定的格式。你可以查看它,但不能更改它;它是只读的。
运行该函数时还应创建一个*NAMESPACE* 文件,该文件列出你包中可供用户使用的函数。它应该像这样:
Generated by roxygen2: do not edit by hand
export(get_acs_race_ethnicity)
你的 get_acs_race_ethnicity() 函数现在几乎已经可以供用户使用了。
#### 添加许可证和元数据
再次运行 devtools::check() 来检查是否修复了导致警告的问题。关于缺失文档的警告应该不再出现。然而,你仍然会看到一个警告:
checking DESCRIPTION meta-information ... WARNING
Non-standard license specification:
What license is it under?
Standardizable: FALSE
0 errors | 1 warning x | 0 notes
这个警告提醒你,你还没有为你的包指定许可。如果你计划公开发布你的包,选择一个许可证非常重要,因为它告诉其他人他们可以做什么和不能做什么。有关如何为你的包选择合适的许可证,请参阅*[`choosealicense.com`](https://choosealicense.com)*。
在本例中,你将使用 MIT 许可证,通过运行 usethis::use_mit_license(),该许可证允许用户基本上随意使用你的代码。usethis 包有类似的函数可以用于其他常见许可证。你应该会看到如下输出:
Setting active project to '/Users/davidkeyes/Documents/Work/R for the Rest of Us/dk'
Setting License field in DESCRIPTION to 'MIT + file LICENSE'
Writing 'LICENSE'
Writing 'LICENSE.md'
Adding '^LICENSE\.md$' to '.Rbuildignore'
use_mit_license() 函数处理了将许可证添加到包中的许多繁琐部分。对我们来说,最重要的是,它在 *DESCRIPTION* 文件中指定了许可证。如果你打开它,应该会看到你已经添加了 MIT 许可证的确认信息:
License: MIT + file LICENSE
除了许可证,*DESCRIPTION* 文件还包含关于包的元数据。你可以进行一些更改,以标识其标题,并添加作者、维护者和描述。最终的 *DESCRIPTION* 文件可能如下所示:
Package: dk
Type: Package
Title: David Keyes's Personal Package
Version: 0.1.0
Author: David Keyes
Maintainer: David Keyes david@rfortherestofus.com
Description: A package with functions that David Keyes may find
useful.
License: MIT + file LICENSE
Encoding: UTF-8
LazyData: true
Imports:
janitor,
tidycensus
做完这些更改后,再次运行 devtools::check(),确保一切正常:
0 errors | 0 warnings | 0 notes
这正是你想要看到的!
#### 编写附加函数
现在你已经有了一个包含一个可用函数的包。如果你想添加更多的函数,可以遵循相同的步骤:
1. 使用 usethis::use_r() 创建一个新的 *.R* 文件,或将另一个函数复制到现有的 *.R* 文件中。
2. 使用 package::function() 语法开发你的函数,引用其他包中的函数。
3. 使用 use_package() 添加任何依赖包。
4. 为你的函数添加文档。
5. 运行 devtools::check() 确保你做的一切都正确。
你的包可以包含一个函数,比如 dk,也可以包含任意数量的函数。
#### 安装包
现在你已经准备好安装并使用新的包。当你在开发自己的包时,为自己安装它是相对简单的。只需运行 devtools::install(),包就会准备好在任何项目中使用。
当然,如果你在开发一个包,你可能不仅是为了自己,而是为了其他人。将你的包提供给他人最常见的方式是使用代码共享网站 GitHub。如何将代码上传到 GitHub 的细节超出了我在这里能涉及的范围,但 Jenny Bryan 自出版的书 *Happy Git and GitHub for the useR*(网址 *[`happygitwithr.com`](https://happygitwithr.com)*) 是一个很好的入门书籍。
我已经将 dk 包推送到 GitHub,你可以在 *[`github.com/dgkeyes/dk`](https://github.com/dgkeyes/dk)* 找到它。如果你想安装它,首先确保安装了 remotes 包,然后在控制台中运行代码 remotes::install_github("dgkeyes/dk")。
### 总结
在本章中,你看到包非常有用,因为它们让你将多个元素打包在一起,确保代码能够可靠运行:一组函数、自动安装依赖包的指令以及代码文档。
创建你自己的 R 包在你为一个组织工作时尤其有益,因为包可以让高级 R 用户帮助经验较少的同事。当 Travis Gerke 和 Garrick Aden-Buie 为莫菲特癌症中心的研究人员提供了一个包含易于访问其数据库功能的包时,这些研究人员开始更加富有创意地使用 R。
如果你创建了一个包,你还可以引导他人按照你认为最好的方式使用 R 语言。包是一种确保他人遵循最佳实践的方式(即使他们未意识到这一点)。它们使得跨项目重用函数变得容易,能够帮助他人,并保持一致的风格。
### 额外资源
+ Malcolm Barrett, “使用 R 进行包开发”,在线课程,访问日期:2023 年 12 月 2 日,*[`rfortherestofus.com/courses/package-development`](https://rfortherestofus.com/courses/package-development)*。
+ Hadley Wickham 和 Jennifer Bryan, *R Packages*, 第 2 版(加利福尼亚州塞巴斯托波尔:O'Reilly Media,2023 年),*[`r-pkgs.org`](https://r-pkgs.org)*。
### 总结
R 语言于 1993 年作为一款统计工具诞生,至今它已经被广泛应用于各种统计分析。但在过去三十年里,R 语言也成为了一个可以做远超统计分析的工具。
正如你在本书中看到的,R 语言非常适合制作可视化图表。你可以用它来创建高质量的图形和地图,制作自己的主题以保持视觉效果的一致性并符合品牌形象,还能生成既美观又有效传达信息的表格。使用 R Markdown 或 Quarto,你可以创建报告、演示文稿和网站。最棒的是,这些文档都是可重复的,这意味着更新它们就像重新运行你的代码一样简单。最后,你也看到 R 能够帮助你自动化数据访问,并通过你创建的函数和包来促进与他人的协作。
如果当你开始阅读这本书时 R 对你来说是全新的,我希望现在你已经有了使用它的灵感。如果你是一个经验丰富的 R 用户,我希望这本书能够展示一些你之前未曾考虑过的 R 使用方式。无论你的背景如何,我希望你现在能够像专业人士一样使用 R。因为它不仅仅是统计学家的工具——R 也是我们所有人的工具。
# 第十三章:索引
+ 符号
+ <- 赋值运算符,11
+ #|(哈希管道),160
+ ()(括号),7–8
+ ... 语法,207–209
+ A
+ Aden-Buie, Garrick,201,218
+ 美学属性
+ 改变,29–31,34–36
+ 将数据映射到,25–26,33–34
+ albersusa 包,68,70,79
+ 对齐
+ 幻灯片中的内容,129–131,165–167
+ 表格中,90–92
+ 美国社区调查数据,194–199
+ *分析美国普查数据:R 中的方法、地图和模型*(Walker),189,199
+ 应用程序编程接口(API)密钥,188–189
+ 参数
+ 向自定义函数添加,203–204
+ 在函数中,8
+ 从一个函数传递到另一个函数,207–209
+ 算术运算符,6–7
+ as.character() 函数,185
+ 附注,140
+ 自动访问在线数据,xxii,181,199
+ 美国社区调查数据,194–199
+ 十年一次的普查数据,189–194
+ 使用 googlesheets4 包,182–188
+ 使用 tidycensus 包,188–189
+ 自动化报告生成。*见* 参数化报告
+ 坐标轴
+ 在 bbplot 包中,55
+ 将数据映射到美学属性,25–26
+ 调整外观,35
+ B
+ 背景图片,添加到幻灯片,131,167
+ bbplot 包中的背景,56–57
+ 条形图,29,33–34。*另见* 数据可视化
+ 基础 R,12
+ BBC 自定义主题(bbplot 包),47–48,59
+ 坐标轴,55
+ 背景,56–57
+ bbc_style() 函数,48,51–58
+ 颜色,58–59
+ 格式化文本,52–54
+ 函数定义,51–52
+ 网格线,56
+ 安装 bbplot,50
+ 图例,54
+ 小多个图,57–58
+ 使用样式绘制图形,48–51
+ 边界框,66
+ boxr 包,153
+ Bryan, Jenny, 154, 215, 217
+ RStudio 中的构建网站选项, 140
+ C
+ 层叠样式表 (CSS)
+ 应用到幻灯片, 131–135
+ 自定义 CSS
+ 应用到幻灯片, 132–133, 167, 168–169
+ 应用到网站, 141–143, 172–174
+ case_when() 函数, 71
+ cells_body() 函数, 94
+ cells_column_labels() 函数, 90
+ cell_text() 函数, 90
+ census_api_key() 函数, 188–189
+ 人口普查局数据
+ 访问, 188
+ 美国社区调查数据, 194–199
+ 通过 API 密钥连接, 188–189
+ 十年一度的普查数据, 189–194
+ 居中对齐
+ 在幻灯片中, 129, 167
+ 在表格中, 90–91, 92
+ .center[] 内容类, 129–131
+ Çetinkaya-Rundel, Mine, 16
+ c() 函数, 8–9, 15, 191
+ chartjunk, 20, 23
+ 图表。*见* 数据可视化
+ clean_names() 函数, 144, 184, 206, 213
+ 云托管, 153–154
+ 混乱
+ chartjunk, 20, 23
+ 在图表中, 22–23
+ 在表格中最小化, 87–89
+ 代码块
+ 在 Quarto 中, 160–161
+ 在 R Markdown 文档中, 104–106, 109–111, 144
+ 颜色
+ bbplot 包, 58–59
+ 填充数据可视化, 30–31, 45
+ 在表格中的有意使用, 94
+ colorspace 包, 68–69, 78
+ cols_align() 函数, 92
+ 列, 从 Google Sheets 导入, 187–188
+ comma() 函数, 118
+ 命令行, 与 R 一起使用, 4
+ 逗号分隔值 (CSV) 文件, 9–10, 13–14
+ 注释, 16–17, 104
+ 比较运算符, 7–8
+ 控制台, RStudio, 5
+ 内容, 网站
+ 添加, 143–148
+ 应用 distill 布局, 148
+ 互动的, 148–153
+ 坐标参考系统(CRS), 66–67, 70, 81–82
+ 美国州政府司法中心网站, 155
+ count() 函数, 49
+ COVID-19 地图, 61, 68
+ 添加地理空间数据, 73–74
+ 计算每日 COVID-19 病例, 70–71
+ 计算发生率, 71–73
+ 导入数据, 69–70
+ 导入包, 68–69
+ 制作, 74–79
+ 在新西兰, xix–xx
+ create_theme() 函数, 141
+ CRS(坐标参考系统), 66–67, 70, 81–82
+ crsuggest 包, 81
+ CSS. *另见* 层叠样式表
+ CSV(逗号分隔值)文件, 9–10, 13, 14
+ 自定义数据可视化主题, xxi, 47–48, 59. *另见* BBC 自定义主题
+ 使用样式化绘图, 48–51
+ 自定义字体, 52
+ 自定义函数, 201. *另见* 包
+ 添加参数, 203–204
+ 格式化种族和民族数据, 204–207
+ 向另一个函数传递参数(... 语法), 207–209
+ 编写简单函数, 202–203
+ 自定义主题, 134
+ cut() 函数, 72
+ D
+ 数据. *另见* 自动访问在线数据; 地理空间数据
+ 使用 tidyverse 进行分析, 14–16
+ 导入数据以创建地图, 69–70
+ 在 R 中使用, 9–13
+ 数据框, 11
+ 用于创建干旱可视化, 32
+ 用于创建表格, 86–87
+ 与 tibbles 对比, 13
+ 整理地理空间数据, 82–83
+ 数据可视化, xxi, 19–20, 45–46. *另见* 自定义数据可视化主题; 地图; 使用 R 编程; 表格
+ 完整的干旱可视化代码, 42–45
+ 干旱可视化效果, 20–23
+ 图形语法, 23–25
+ 在代码中包含参数, 118–119
+ 在网站上进行交互式操作, 150–153
+ 重新创建干旱可视化, 32–42
+ 使用 tidycensus 包, 195–196
+ 在网站中, 146–147
+ 使用 ggplot2 包,25–32
+ *数据可视化*(Healy),17
+ 十年一度的人口普查数据,189–194
+ 依赖关系,包,211–213
+ *DESCRIPTION* 文件,216
+ devtools 包,210–211,214–217
+ 尺寸,地理空间数据,66
+ dir_ls() 函数,154
+ distill_article 格式,140
+ distill 包,138,155,157
+ 添加网站内容,143–148
+ 应用自定义 CSS,141–143
+ 应用布局,148
+ 建立网站,140–141
+ 创建新项目,138
+ 托管网站,153–155
+ 互动内容,148–153
+ 项目文件,138–140
+ distinct() 函数,144
+ div 标签,165–167,175
+ dk 包,209,211–213,217
+ 文档
+ 访问,17–18
+ 用于创建包,214–215
+ dplyr 包,49,183
+ Dropbox,通过它共享网站,153–154
+ drop_na() 函数,72
+ 干旱数据可视化,19–20,45
+ 添加最终润色,38–42
+ 改变美学属性,34–36
+ 有效性,20–23
+ 分面,36–38
+ 绘制一个区域和年份,32–34
+ 重新创建,32–42
+ DT 包,148,149
+ E
+ 元素 _ 函数,53
+ 环境窗格,RStudio,5
+ 示例,添加到文档中,215
+ 执行字段,Quarto YAML 部分,159–160
+ 导出的函数,215
+ F
+ facet_grid() 函数,36
+ 分面数据可视化
+ 干旱数据可视化,36–38,41,45
+ 在地图中,76
+ 通过减少杂乱,23
+ 在网站中,146
+ facet_wrap() 函数,58,76
+ fct_inorder() 函数,74
+ fig-height 选项,Quarto,160–161,163
+ fig.height 选项,R Markdown,128
+ file_show() 函数,203
+ 文件窗格, RStudio, 5
+ 填充美学属性, 29–31, 34, 59, 83, 198
+ filter() 函数, 16, 32, 145, 192
+ finalise_plot() 函数, 48, 53
+ FiveThirtyEight 网站上的表格, 96–97
+ fmt_currency() 函数, 93
+ 字体
+ 自定义, 52–53
+ 在幻灯片中, 132–133, 135
+ 格式字段, Quarto YAML 部分, 159
+ format() 函数, 74
+ 格式部分, 172
+ fs 包, 154, 202–203
+ 函数。*另见* 包
+ 访问文档, 17
+ 访问地理空间数据, 79–81
+ 添加到参数, 203–204
+ 添加到包, 210, 217
+ 基本 R 语法, 8–9
+ 创建, xxii, 201–209
+ bbplot 包中定义的, 51–52
+ 导出, 215
+ 内部, 215
+ 在包中正确引用, 213–214
+ tidyverse 包, 15–16
+ 编写简单的, 202–203
+ *Fundamentals of Data Visualization*(Wilke), 85–86
+ G
+ gapminder 包, 25, 86
+ gdp 数据框, 86–87
+ *Geocomputation with R*(Lovelace, Nowosad, 和 Muenchow), 79, 81–82
+ GeoJSON 文件, 79
+ geom_col() 函数, 29, 33–34, 146
+ 几何对象(geoms), 26–29, 40
+ geom_line() 函数, 27–28
+ geom_point() 函数, 27
+ geom_rect() 函数, 40–42
+ geom_sf() 函数, 62–63, 76, 198
+ 地理空间数据, 62
+ 访问, 79–81
+ 添加到 COVID-19 地图, 73–74
+ 边界框, 66
+ 坐标参考系统, 66–67
+ 尺寸, 66
+ 几何列, 62, 67
+ 几何类型, 62–66
+ 数据整理, 82–83
+ Gerke, Travis, 201, 218
+ get_acs() 函数, 194–199, 207, 213
+ get_decennial() 函数, 189–191, 193–194, 199
+ ggplot2 包, 20, 25. *参见自定义数据可视化主题* (custom data visualization themes)
+ 修改美学属性, 29–31
+ 选择几何对象, 26–29
+ 完整的主题, 38
+ 文档, 53
+ 入口主题, 31–32
+ 分面, 36–38
+ 和图形语法, 24–25
+ 将数据映射到美学属性, 25–26, 33–34
+ 和 tidycensus 包, 195–198
+ ggplot() 函数, 26, 33, 195, 198
+ ggplotly() 函数, 150–153
+ GitHub, 在其上共享包, 217
+ GitHub Pages 托管, 138, 154–155, 175–176
+ glimpse() 函数, 183
+ googledrive 包, 153
+ googlesheets4 包, 181–182, 199
+ 连接到 Google, 182
+ 仅导入特定列, 187–188
+ 从工作表读取数据, 182–183
+ 在 R Markdown 中使用数据, 183–187
+ 图形语法, 20, 23–25, 31–32. *参见数据可视化* (data visualization)
+ *图形语法*, *The* (Wilkinson), 24
+ 网格线, 56, 87–89
+ Grolemund, Garrett, 16
+ group_by() 函数, 71
+ group_by(NAME) 函数, 192
+ gs4_auth() 函数, 182
+ gtExtras 包, 86, 95–97
+ gt() 函数, 88
+ gt 包, 86, 88–94, 97, 145
+ gt_plt_sparkline() 函数, 95–96
+ gt_theme_538() 函数, 96–97
+ Guibourg, Clara, 47, 59
+ guide_legend() 函数, 78
+ H
+ *Happy Git and GitHub for the useR* (Bryan), 154, 217
+ Harris, Meghan, 182–187
+ hash pipe (#|), 160
+ Healy, Kieran, 17
+ *hello.R* 文件, 210
+ 帮助资源, 17–18
+ Herman, Matt, 137–138, 148–149, 155, 188
+ Hill, Alison, 110–111
+ 网站托管, 153–155, 175–177
+ HTML 文档
+ div 标签, 165–167, 175
+ 幻灯片作为, 126
+ 用于网站, 138, 154–155
+ I
+ IDE(集成开发环境), 4–5
+ if_else() 函数, 82–83
+ import() 函数, 32
+ 导入数据
+ 创建地图, 69–70
+ 从 CSV 文件, 9–10
+ 使用 googlesheets4 包, 182–188
+ 原始地理空间数据, 79
+ 将文件导入项目, 14
+ 发病率计算, 71–73
+ 幻灯片中的增量显示, 128–129, 165
+ *index.qmd* 文件, 170–171
+ 在 R Markdown 中的内联 R 代码, 108–109, 117
+ install.packages() 函数, 12, 211
+ 集成开发环境(IDE), 4–5
+ 互动表格, 97
+ 互动工具提示, 148, 150–153
+ 互动网站内容, 148–153
+ 内部函数, 215
+ Ismay, Chester, 4, 12
+ J
+ janitor 包, 144, 184, 206, 212–213
+ JavaScript, 148
+ JavaScript 对象表示法(JSON)格式, 32
+ K
+ Karamanis, Georgios, 19–20, 32, 34–42
+ Kim, Albert, 4, 12
+ Knitr 包, 158, 169
+ knitr::opts_chunk$set() 函数, 106
+ 编织, 103–107, 109–111, 120
+ L
+ labs() 函数, 78
+ lag() 函数, 71
+ 布局, distill 包, 148
+ 表格中的左对齐, 90–92
+ .left-column[] 内容类, 130
+ 图例
+ bbplot 包, 54
+ 干旱数据可视化, 42
+ 包的许可证, 215–217
+ 折线图, 27–28
+ LINESTRING 几何类型, 64
+ 列表
+ 幻灯片中的逐步显示, 165
+ 在 R Markdown 文档中,107
+ load_variables() 函数,190
+ Lovelace, Robin, 79, 81–82
+ l-screen-inset 布局,148
+ lubridate 包,144
+ M
+ Madjid, Abdoul, 61, 68–69, 83–84
+ map() 函数,121
+ 将数据映射到美学属性,25–26, 33–34
+ 地图,xxi, 61, 83–84
+ 地理空间数据
+ 访问简单特征,79–81
+ 简明指南,62–67
+ 数据整理,82–83
+ 制作,79–83
+ 重新创建 COVID-19 地图,68–79
+ 使用 tidycensus 包,196–199
+ 使用适当的投影,81–82
+ 在网站中,145–147
+ margin() 函数,53
+ markdown 文本,106–108
+ mean() 函数,8–9, 15
+ 元数据,包,215–217
+ 元描述,139
+ 中间内容类,131
+ Mock, Tom, 86, 90, 95
+ Moffitt 癌症中心,201, 218
+ moon_reader 输出格式,R Markdown,126
+ Mucciolo, Livia, 113
+ Muenchow, Jannes, 79, 81–82
+ MULTILINESTRING 几何类型,65
+ 同时生成多个报告。*参见* 参数化报告
+ MULTIPOINT 几何类型,64
+ MULTIPOLYGON 几何类型,65–66
+ 用于报告的多工具工作流,101–102, 114
+ mutate() 函数,82–83, 185, 192
+ N
+ 命名列表,121–122
+ *NAMESPACE* 文件,215
+ ne_countries() 函数,80
+ Nowosad, Jakub, 79, 81–82
+ O
+ 对象,保存数据为,11
+ 在线数据。*参见* 自动访问在线数据
+ Quarto 中的选项,160–161
+ 网站的输出目录,140
+ Quarto 网站中的输出宽度,175
+ P
+ 包
+ 添加函数,210, 217
+ 添加许可证和元数据,215–217
+ 使用 devtools 检查,211–212
+ 创建,xxii,201–202,209–218
+ 创建文档,214–215
+ 依赖关系,211,212–213
+ 与之相关的文档网站,18
+ 导入以创建 COVID-19 地图,68–69
+ 安装,12–13,217
+ 加载,12
+ 正确引用函数,213–214
+ palmerpenguins 包,48–49,103
+ 参数化报告,xxi–xxii,113,124
+ 最佳实践,124
+ 创建 R 脚本,119–123
+ 使用 Quarto,161–163
+ R Markdown 中的报告模板,114–119
+ params 变量,121–122
+ 括号 (())
+ 在函数中,8
+ 使用算术运算符,7
+ plotly 包,148,150–153
+ 绘图。*见* 数据可视化
+ POINT 几何类型,63
+ 向图表添加点,27
+ POLYGON 几何类型,62–63
+ 精度,93
+ 演示文稿。*见* 幻灯片演示
+ print() 函数,8
+ 使用 R 编程,xxi,3,18,218
+ 基本语法,6–9
+ 注释,16–17
+ 帮助资源,17–18
+ 安装,4
+ R 脚本文件,5–6,104,119–123
+ RStudio 项目,13–14
+ 设置,4–5
+ 处理数据,9–13
+ 使用 tidyverse 进行分析,14–16
+ 投影,66–67,81–82
+ 项目,RStudio,13–14
+ pull() 函数,118,120,144–145
+ .pull-left[] 内容类,129–130
+ .pull-right[] 内容类,129–130
+ purrr 包,121–123
+ pwalk() 函数,122–123
+ Q
+ .*qmd* 文件,169–171
+ Quarto,xxii,157–158,177
+ 创建文档,158–159
+ 制作幻灯片,163–169
+ 创建网站,169–177
+ 参数化报告,161–163
+ Quarto Pub,176–177
+ 与 R Markdown 相比,111,157,159–161,177
+ *_quarto.yml* 文件,169,171–172,174
+ R
+ ragg 包,52
+ 栅格数据,62
+ rbind() 函数,120
+ R CMD check,211–213
+ rdrop2 包,153
+ reactable 包,97,149–150
+ read_csv() 函数,12–13,202
+ read.csv() 函数,10,17
+ read_sf() 函数,79,81
+ read_sheet() 函数,183,187–188
+ 矩形数据,13
+ 重新编织 R Markdown 文档,108–109
+ 相对路径,13
+ relocate() 函数,74,185
+ .remark-slide-content,132
+ remotes 包,50
+ rename() 函数,185
+ 渲染按钮,Quarto,161
+ render() 函数,120–123,163
+ 渲染网站选项,RStudio,170
+ replace_na() 函数,71
+ 报告,xxi–xxii。*另见* 参数化报告;R Markdown
+ 和 googlesheets4 包,183–187
+ 多工具工作流,101–102,114
+ 模板,114–119
+ 可重现性,102
+ 在幻灯片中逐步展示内容,128–129,165
+ reveal.js JavaScript 库,163,169
+ *R for Data Science*(Wickham, Çetinkaya-Rundel, 和 Grolemund),16
+ 表格中的右对齐,90–91
+ .right-column[] 内容类,130
+ rio 包,32
+ R Markdown,xxi–xxii,102,111
+ 文档中的代码块,104–106
+ 创建文档,102–103
+ 创建 R 脚本,119–123
+ 创建表格,86
+ 格式化文本,107
+ 使用参数生成数字,117–118
+ 和 googlesheets4 包,183–187
+ 标题,107
+ 内联 R 代码,108–109
+ markdown 文本,106–108
+ 与 Quarto 比较, 111, 157, 159–161, 177
+ 报告模板, 114–119
+ 交互式运行代码块, 109–111
+ 设置代码块, 105–106
+ 网站项目文件, 138–139
+ xaringan 包, 使用, 126–127
+ 文档中的 YAML 部分, 104
+ rmarkdown 包, 120–123
+ rnaturalearth 包, 80
+ Rodrigues, Bruno, 203–204
+ 滚动平均, 71–73
+ rollmean() 函数, 72
+ row_number() 函数, 184–185
+ Roxygen, 214–215
+ *R 包*(Wickham 和 Bryan), 215
+ R 编程语言, xx, 3, 18, 218
+ 基本语法, 6–9
+ 注释, 16–17
+ 帮助资源, 17–18
+ 安装, 4
+ R 脚本文件, 5–6, 104, 119–123
+ RStudio 项目, 13–14
+ 设置, 4–5
+ 数据处理, 9–13
+ 使用 tidyverse 进行分析, 14–16
+ R 脚本文件, 5–6, 104, 119–123
+ RStudio
+ 构建网站, 140, 170
+ 创建文档
+ Quarto, 158–159
+ R Markdown, 102–103
+ 探索界面, 4–5
+ 安装, 4
+ 包
+ 创建, 209–210
+ 安装, 12–13
+ 项目, 13–14
+ 创建新 distill 项目, 138
+ 发布 Quarto 网站, 176
+ R 脚本文件, 5–6
+ 脚本文件窗格, 5–6
+ 使用自定义字体, 52
+ 数据处理, 9–13
+ S
+ Sass(样式表语言), 168–169, 173–174
+ scss:defaults 部分, 168–169
+ scss:rules 部分, 169
+ Sayed, Safia, 113
+ scale_fill_discrete_sequential() 函数, 78
+ scale_fill_viridis_c() 函数, 30–31, 198
+ scale_fill_viridis_d() 函数, 34, 42
+ scales 包, 118
+ Scherer, Cédric, 19–20, 32, 34–42
+ Schwabish, Jon,91,93
+ *Scientific American*。*参见* 干旱数据可视化
+ sf 包,68–70,79,81
+ shift_geometry() 函数,198–199
+ show_in_excel() 函数,203–204
+ show_in_excel_penguins() 函数,202–203
+ 简单特征 (sf) 数据,62
+ 访问地理空间数据,79–81
+ 边界框,66
+ 坐标参考系统,66–67
+ 维度,66
+ 字段,62
+ 几何列,62,67
+ 几何类型,62–66
+ 处理地理空间数据,82–83
+ *_site.yml* 文件,139–140,143
+ slice_max() 函数,144,145
+ 幻灯片演示,xxii,125,135
+ 添加背景图片,131
+ 调整图形大小,128
+ 使用内容类对齐内容,129–131
+ 将 CSS 应用于幻灯片,131–135
+ 创建新幻灯片,127–128
+ 入门,126–127
+ 使用 Quarto,163–169
+ 逐步揭示内容,128–129
+ 双列布局,129–130,165–167
+ xaringan 包的优势,125–126
+ 小型多图表,57–58。*另见* 分面数据可视化
+ snake case,206
+ 火花线,95–96
+ states() 函数,79–80
+ 统计分析,xx
+ *Statistical Inference via Data Science*(Ismay 和 Kim),4,12
+ str_glue() 函数,144,202
+ 剥离文本,使用 bbplot 包进行修改,57–58
+ st_transform() 函数,70,81
+ *styles.css* 文件,169,172
+ *styles.scss* 文件,173–174
+ style_xaringan() 函数,135
+ Stylianou, Nassos,47,59
+ suggest_top_crs() 函数,81
+ summarize() 函数,15–16
+ 语法,R,6–9
+ systemfonts 包,52
+ T
+ 表格,xxi,85–86,97
+ 添加数据可视化,95–97
+ 对齐,90–92
+ 创建数据框,86–87
+ 区分标题与正文,89–90
+ 有意使用颜色,94
+ 交互式,在网站上,149–150
+ 最小化杂乱,87–89
+ 使用正确的精度级别,93
+ 在网站中,145,147
+ tab_style() 函数,89–90,94
+ tempfile() 函数,202
+ 模板,报告,114–119
+ 文本
+ 在表格中的对齐,91–92
+ 格式化,使用 bbplot 包,52–54
+ *theme.css* 文件,141–143
+ 主题,BBC。*见* BBC 自定义主题
+ 主题,ggplot2 包,38
+ theme() 函数,38–40,52,58–59,78
+ theme_light() 函数,38
+ theme_minimal() 函数,31,146
+ 主题,Quarto,167–168,172
+ 主题,xaringan 包,133–134
+ tibble,13,120–123
+ tidycensus 包,181,188,199
+ 添加依赖包,212–213
+ 使用 API 密钥连接到人口普查局,188–189
+ 自定义函数,204–205,207
+ 可视化美国社区调查数据,194–199
+ 处理十年人口普查数据,189–194
+ tidyverse 包,3,12
+ 以及 bbplot 包,48–49
+ 创建地图,68–69
+ 数据分析与,14–16
+ 函数,15–16
+ 加载,14
+ 管道,15–16
+ R Markdown 文档,编织时出现的错误,110
+ 表格,86
+ 网站,144
+ 处理地理空间数据,82–83
+ 编写简单函数,202
+ tigris 包,79–80,144,196–199
+ 工具提示,交互式,148,150–153
+ 塔夫特,爱德华,20
+ U
+ ungroup() 函数,71,192
+ 城市研究所参数化报告,113,124,161–163
+ county-level reports,124
+ 财政简报,113,124
+ urbnthemes 包,114
+ url() 函数,131
+ usa_sf() 函数,70,79
+ 美国人口普查局数据,188
+ 美国社区调查数据,194–199
+ 连接到 API 密钥,188–189
+ 十年人口普查数据,189–194
+ use_mit_license() 函数,216
+ use_package() 函数,212
+ use_r() 函数,210
+ usethis 包,210,216
+ V
+ variables
+ 数据框,11
+ 十年人口普查数据,处理,189–194
+ 映射到美学属性,25–26,33–34
+ Sass,168–169,173–174
+ 向量地理空间数据,62
+ 创建向量,120–121
+ *Visual Display of Quantitative Information, The*(Tufte),20
+ 数据可视化。*见* 数据可视化
+ W
+ Walker, Kyle,188,189,193,196,199
+ 网站,xxii,137–138,155
+ 应用自定义 CSS,141–143
+ 应用布局,148
+ building,140–141
+ 创建新的 distill 项目,138
+ 托管,153–155
+ 互动内容,148–153
+ 使用 Quarto 制作,169–177
+ 导航栏,140,174–175
+ 分页,149–150
+ 项目文件,138–140
+ 目录,174
+ 标题,174
+ 文档网站上的 vignette,18
+ 与内容一起工作,143–148
+ Westchester County COVID-19 网站项目,137–138,148–149
+ Wickham, Hadley,16,24–25,202,215
+ Wilke, Claus,85–86
+ Wilkinson, Leland,24
+ Williams, Aaron,113
+ write_csv() 函数,204
+ write_sheet() 函数,188
+ X
+ xaringan 包,125,135,157
+ 向幻灯片添加背景图片,131
+ 调整图形大小,128
+ 优势,125–126
+ 使用内容类对齐内容,129–131
+ 将 CSS 应用于幻灯片,131–135
+ 创建新幻灯片,127–128
+ 开始使用,126–127
+ 逐步揭示内容,128–129
+ xaringanthemer 包,134–135
+ x 轴
+ 将数据映射到美学属性,25–26
+ 调整外观,35
+ Y
+ YAML 部分
+ Quarto 文档,159–160,163,165,167–168,170–171
+ R Markdown 文档,104,117–119,138–139
+ y 轴
+ 将数据映射到美学属性,25–26
+ 调整外观,35
+ Z
+ zoo 包,68–69,72


浙公网安备 33010602011771号