7  Workflow: scripts and projects

You are reading the work-in-progress second edition of R for Data Science. This chapter is largely complete and just needs final proof reading. You can find the complete first edition at https://r4ds.had.co.nz.

本章将向您介绍组织代码的两个基本工具:脚本(scripts)和项目(projects)。

7.1 Scripts

到目前为止,您已经使用控制台(console)来运行代码。 这是一个很好的起点,但是当您创建更复杂的 ggplot2 图形和更长的 dplyr 管道时,您会发现它很快就会变得拥挤。 为了给自己更多的工作空间,请使用脚本编辑器。 单击 File 菜单,选择 New File,然后选择 R script,或使用键盘快捷键 Cmd/Ctrl + Shift + N 将其打开。 现在您将看到四个窗格,如 Figure 7.1 所示。 脚本编辑器是试验代码的好地方。 当您想要更改某些内容时,不必重新输入整个内容,只需编辑脚本并重新运行即可。 一旦您编写了可以运行并执行您想要的操作的代码,您就可以将其保存为脚本文件,以便稍后轻松返回。

RStudio IDE with Editor, Console, and Output highlighted.

Figure 7.1: 打开脚本编辑器会在 IDE 的左上角添加一个新窗格。

7.1.1 Running code

脚本编辑器是构建复杂 ggplot2 绘图或长序列 dplyr 操作的绝佳场所。 有效使用脚本编辑器的关键是记住最重要的键盘快捷键之一:Cmd/Ctrl + Enter。 这将在控制台中执行当前的 R 表达式。 例如,采用下面的代码。

library(dplyr)
library(nycflights13)

not_cancelled <- flights |> 
  filter(!is.na(dep_delay)█, !is.na(arr_delay))

not_cancelled |> 
  group_by(year, month, day) |> 
  summarize(mean = mean(dep_delay))

如果光标位于 █ 处,按 Cmd/Ctrl + Enter 将运行生成 not_cancelled 的完整命令。 它还会将光标移动到以下语句(以 not_cancelled |> 开头)。 这样可以通过重复按 Cmd/Ctrl + Enter 轻松地逐步执行完整的脚本。

您还可以使用 Cmd/Ctrl + Shift + S 一步执行完整的脚本,而不是逐个表达式地运行代码。 定期执行此操作是确保您捕获脚本中代码的所有重要部分的好方法。

我们建议您始终使用所需的包来启动脚本。 这样,如果您与其他人共享代码,他们可以轻松查看需要安装哪些软件包。 但请注意,您绝对不应该在共享的脚本中包含 install.packages()。 如果他们不小心的话,交出一个会改变他们计算机上某些内容的脚本是不体贴的!

在学习后续章节时,我们强烈建议从脚本编辑器开始并练习键盘快捷键。 随着时间的推移,以这种方式向控制台发送代码将变得如此自然,您甚至不会想到它。

7.1.2 RStudio diagnostics

在脚本编辑器中,RStudio 将使用红色波浪线和侧边栏中的十字来突出显示语法错误:

Script editor with the script x y <- 10. A red X indicates that there is syntax error. The syntax error is also highlighted with a red squiggly line.

将鼠标悬停在十字上即可查看问题所在:

Script editor with the script x y <- 10. A red X indicates that there is syntax error. The syntax error is also highlighted with a red squiggly line. Hovering over the X shows a text box with the text unexpected token y and unexpected token <-.

RStudio 还会让您了解潜在的问题:

Script editor with the script 3 == NA. A yellow exclamation mark indicates that there may be a potential problem. Hovering over the exclamation mark shows a text box with the text use is.na to check whether expression evaluates to NA.

7.1.3 Saving and naming

当您退出时,RStudio 会自动保存脚本编辑器的内容,并在您重新打开时自动重新加载。 尽管如此,最好避免使用 Untitled1、Untitled2、Untitled3 等,而是保存脚本并为它们提供信息丰富的名称。

将文件命名为 code.Rmyscript.R 可能很诱人,但在为文件选择名称之前应该仔细考虑一下。 文件命名的三个重要原则如下:

  1. 文件名应该是机器可读的(machine readable):避免空格、符号和特殊字符。不要依靠大小写来区分文件。
  2. 文件名应该是人类可读的(human readable):使用文件名来描述文件中的内容。
  3. 文件名应该与默认顺序配合良好:以数字开头的文件名,以便按字母顺序排序将它们按照使用的顺序排列。

例如,假设项目文件夹中有以下文件。

alternative model.R
code for exploratory analysis.r
finalreport.qmd
FinalReport.qmd
fig 1.png
Figure_02.png
model_first_try.R
run-first.r
temp.txt

这里存在各种各样的问题:很难找到先运行哪个文件、文件名包含空格、有两个同名但大小写不同的文件(finalreport vs. FinalReport1)、有些名称没有描述其内容(run-first and temp)。

这是命名和组织同一组文件的更好方法:

01-load-data.R
02-exploratory-analysis.R
03-model-approach-1.R
04-model-approach-2.R
fig-01.png
fig-02.png
report-2022-03-20.qmd
report-2022-04-02.qmd
report-draft-notes.txt

对关键脚本进行编号可以清楚地显示运行它们的顺序,并且一致的命名方案可以更容易地看到差异。 此外,数字的标签类似,报告通过文件名中包含的日期进行区分,并且 temp 被重命名为 report-draft-notes 以更好地描述其内容。 如果一个目录中有很多文件,建议进一步组织,将不同类型的文件(scripts, figures, etc.)放在不同的目录中。

7.2 Projects

有一天,您需要退出 R,去做其他事情,然后再返回分析。 有一天,您将同时进行多项分析,并且希望将它们分开。 有一天,您需要将外部世界的数据引入 R,并将 R 中的数值结果和数字发送回外部世界。

为了处理这些现实生活中的情况,您需要做出两个决定:

  1. 真实来源是什么? 您将保存什么作为所发生事件的永久记录?

  2. 您的分析在哪里?

7.2.1 What is the source of truth?

作为初学者,可以依赖当前的环境来包含您在分析过程中创建的所有对象。 但是,为了更轻松地处理大型项目或与其他人协作,您的真实来源应该是 R scripts。 使用 R scripts(和数据文件),您可以重新创建环境。 仅在您的环境中,重新创建 R scripts 要困难得多:您要么必须从内存中重新输入大量代码(一路上不可避免地会犯错误),要么必须仔细挖掘您的 R history。

为了帮助将 R scripts 保留为分析的真实来源,我们强烈建议您指示 RStudio 不要在会话之间保留工作区。 您可以通过运行 usethis::use_blank_slate()2Figure 7.2 中所示的选项来完成此操作。 这会给您带来一些短期的痛苦,因为现在当您重新启动 RStudio 时,它将不再记住您上次运行的代码,也不会再使用您创建的对象或读取的数据集。 但这种短期的痛苦可以避免长期的痛苦,因为它迫使您捕获代码中的所有重要过程。 没有什么比三个月后发现您只将重要计算的结果存储在环境中,而不是计算本身存储在代码中更糟糕的了。

RStudio Global Options window where the option Restore .RData into workspace at startup is not checked. Also, the option Save workspace to .RData on exit is set to Never.

Figure 7.2: 将这些选项复制到你的 RStudio 选项中,以便始终从头开始启动 RStudio 会话。

有一对很棒的键盘快捷键可以协同工作,以确保您在编辑器中捕获了代码的重要部分:

  1. 按 Cmd/Ctrl + Shift + 0/F10 重新启动 R。
  2. 按 Cmd/Ctrl + Shift + S 重新运行当前脚本。

我们每周都会使用这种模式数百次。

或者,如果您不使用键盘快捷键,则可以转到 Session > Restart R,然后突出显示并重新运行当前脚本。

RStudio server

如果您使用 RStudio server,默认情况下您的 R 会话永远不会重新启动。 当您关闭 RStudio server 选项卡时,可能感觉您正在关闭 R,但服务器实际上让它在后台运行。 下次您返回时,您将位于与离开时完全相同的位置。 这使得定期重新启动 R 变得更加重要,这样您才能从头开始。

7.2.2 Where does your analysis live?

R 有一个强大的工作目录(working directory)概念。 这是 R 查找您要求其加载的文件的位置,也是放置您要求其保存的任何文件的位置。 RStudio 在控制台顶部显示您当前的工作目录:

The Console tab shows the current working directory as ~/Documents/r4ds.

您可以通过运行 getwd() 在 R 代码中打印出来:

getwd()
#> [1] "/Users/hadley/Documents/r4ds"

在此 R 会话中,当前工作目录(将其视为”home”)位于 hadley 的 Documents 文件夹中名为 r4ds 的子文件夹中。 当您运行此代码时,它会返回不同的结果,因为您的计算机的目录结构与 Hadley 的不同!

作为 R 初学者,可以将工作目录设置为主目录、文档目录或计算机上任何其他奇怪的目录。 但你已经读了这本书的七章,并且你不再是初学者了。 很快您就应该将项目组织到目录中,并且在处理项目时将 R 的工作目录设置为关联的目录。

您可以在 R 中设置工作目录,但我们不推荐这样做:

setwd("/path/to/my/CoolProject")

有一个更好的方法;这种方式还可以让您像专家一样管理您的 R 工作。 就是 RStudio project

7.2.3 RStudio projects

将与给定项目关联的所有文件(input data, R scripts, analytical results, and figures)保存在一个目录中是一种明智且常见的做法,RStudio 通过项目(projects)对此提供了内置支持。 让我们创建一个 project 供您在学习本书其余部分时使用。 单击 File > New Project,然后按照 Figure 7.3 中所示的步骤操作。

Three screenshots of the New Project menu. In the first screenshot, the Create Project window is shown and New Directory is selected. In the second screenshot, the Project Type window is shown and Empty Project is selected. In the third screenshot, the Create New Project  window is shown and the directory name is given as r4ds and the project is being created as subdirectory of the Desktop.

Figure 7.3: 要创建 new project:(top)首先单击 New Directory,然后(middle)单击 Create Project,然后(bottom)填写目录(project)名称,选择一个好的子目录作为其主目录,然后单击 Create Project。

将您的项目命名为 r4ds,并仔细考虑将项目放在哪个子目录中。 如果您不将其存储在合理的地方,将来将很难找到它!

此过程完成后,您将获得一个专门用于本书的新 RStudio 项目。 检查项目的 “home” 是否是当前工作目录:

getwd()
#> [1] /Users/hadley/Documents/r4ds

现在在脚本编辑器中输入以下命令,然后保存文件,将其命名为 “diamonds.R”。 然后,创建一个名为 “data” 的新文件夹。 您可以通过单击 RStudio Files 窗格中的 “New Folder” 按钮来完成此操作。 最后,运行完整的脚本,将 PNG 和 CSV 文件保存到您的项目目录中。 不用担心细节,你会在本书后面学到它们。

library(tidyverse)

ggplot(diamonds, aes(x = carat, y = price)) + 
  geom_hex()
ggsave("diamonds.png")

write_csv(diamonds, "data/diamonds.csv")

退出 RStudio。 检查与您的项目关联的文件夹 — 注意 .Rproj 文件。 双击该文件以重新打开该项目。 请注意,您回到了上次离开的位置:它是相同的工作目录和命令历史记录,并且您正在处理的所有文件仍然打开。 然而,由于您遵循了我们上面的说明,因此您将拥有一个全新的环境,保证您从头开始。

以您最喜欢的特定于操作系统的方式,在计算机中搜索 diamonds.png,您会找到 PNG(毫不奇怪),而且还会找到创建它的脚本(diamonds.R)。 这是一个巨大的胜利! 有一天,您会想要重新制作一个人物,或者只是想了解它的来源。 如果您使用 R 代码严格地将图形保存到文件中,而不是使用鼠标或剪贴板,那么您将能够轻松地重现旧作品!

7.2.4 Relative and absolute paths

一旦进入项目,您应该只使用相对路径而不是绝对路径。 有什么不同? 相对路径是相对于工作目录的,即项目的主目录。 当 Hadley 在上面写入 data/diamonds.csv 时,它是 /Users/hadley/Documents/r4ds/data/diamonds.csv 的快捷方式。 但重要的是,如果 Mine 在她的计算机上运行此代码,它将指向 /Users/Mine/Documents/r4ds/data/diamonds.csv。 这就是为什么相对路径很重要:无论 R 项目文件夹位于何处,它们都会起作用。

无论您的工作目录如何,绝对路径都指向同一位置。 根据您的操作系统,它们看起来略有不同。 在 Windows 上,它们以驱动器号(例如 C:)或两个反斜杠(例如 \\servername)开头,在 Mac/Linux 上,它们以斜杠”/“开头(例如 /users/hadley)。 您永远不应该在脚本中使用绝对路径,因为它们会妨碍共享:其他人不会拥有与您完全相同的目录配置。

操作系统之间还有另一个重要的区别:如何分离路径的组成部分。 Mac 和 Linux 使用斜杠(例如 data/diamonds.csv),Windows 使用反斜杠(例如 data\diamonds.csv)。 R 可以使用任何一种类型(无论您当前使用什么平台),但不幸的是,反斜杠对 R 来说意味着一些特殊的东西,并且要在路径中获得单个反斜杠,您需要键入两个反斜杠! 这让生活变得令人沮丧,因此我们建议始终使用带有正斜杠的 Linux/Mac 风格。

7.3 Exercises

  1. 转到 RStudio Tips Twitter account,https://twitter.com/rstudiotips 并找到一个看起来有趣的提示。 练习使用它!

  2. RStudio 诊断还会报告哪些其他常见错误? 阅读 https://support.posit.co/hc/en-us/articles/205753617-Code-Diagnostics 找出。

7.4 Summary

在本章中,您学习了如何在 scripts (files) 和 projects (directories) 中组织 R 代码。 就像代码风格一样,一开始这可能感觉像是在忙活。 但是,当您在多个项目中积累更多代码时,您将学会欣赏一点预先的组织如何可以为您节省大量时间。

总之,scripts 和 projects 为您提供了可靠的工作流程,将在未来为您提供良好的服务:

  • 为每个数据分析项目创建一个 RStudio project。
  • 将脚本(具有信息丰富的名称)保存在项目中,对其进行编辑,部分或整体运行它们。经常重新启动 R 以确保您已捕获脚本中的所有内容。
  • 只使用相对路径,而不是绝对路径。

然后,您需要的所有内容都集中在一处,并与您正在处理的所有其他项目完全分开。

到目前为止,我们已经使用了 R 包中捆绑的数据集。 这样可以更轻松地对预先准备的数据进行一些练习,但显然您的数据无法以这种方式获得。 因此,在下一章中,您将学习如何使用 readr 包将数据从磁盘加载到 R 会话中。


  1. Not to mention that you’re tempting fate by using “final” in the name 😆 The comic Piled Higher and Deeper has a fun strip on this.↩︎

  2. If you don’t have usethis installed, you can install it with install.packages("usethis").↩︎