R Package 制作攻略

R Package 制作攻略

我们都会使用 R Package,但是为什么要学习如何制作 R Package 呢?

一个很好的理由是:分享自己的代码,并且让他人能够更加轻松地使用它。即使你不打算分享代码,以 R Package 的方式组织自己的代码也能够节省时间、规范工作。

工具集

R Package 的制作离不开 RStudio 和 devtools 包。devtools 是一个使通用开发流程自动化的包,它希望使用函数完成尽可能多的开发工作,让开发者关注包的功能,而不是包的结构。

提到 devtools 就不能不说到它的第一作者 Hadley Wickham。他同时也是 ggplot2 的作者,对 R 语言的发展贡献颇多。

另外,我们还需要使用 roxygen2 程序包构建函数文档,使用 testthat 程序包进行单元测试。

一个简单的示例

有时候我们需要对数据框中选定的字段进行运算,得到的结果再添加到该数据框中,如下所示:

graph TD subgraph "df_2" C["a"] D["b"] E["c"] end subgraph "df_1" A["a"] B["b"] end A --> F["c = a + b"] B --> F --> E

原数据框 df_1ab 两个字段,通过 a + b 产生新字段 c,添加到 df_1 中,产生新数据框 df_2

有时候还需要对数据框按照特定的字段进行排序,比如对 df_2 按照字段 a+b 的值进行降序排序。

将上述 2 个过程(函数)封装到一个 R Package 中。

加载 devtools

1
2
library(devtools)
># 载入需要的程辑包:usethis

初始化程序包目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
create_pacakge("~/path/to/pkgName")
># √ Creating './dfOperation/'
># √ Setting active project to 'F:/QCFWorkSpace/CurrentProjects/R_Packages_Summer/r-pkg-startup/dfOperation'
># √ Creating 'R/'
># √ Writing 'DESCRIPTION'
># Package: dfOperation
># Title: What the Package Does (One Line, Title Case)
># Version: 0.0.0.9000
># Authors@R (parsed):
># * First Last <first.last@example.com> [aut, cre (<https://orcid.org/YOUR-ORCID-ID>)
># Description: What the package does (one paragraph).
># License: `use_mit_license()`, `use_gpl3_license()` or friends to
># pick a license
># Encoding: UTF-8
># LazyData: true
># Roxygen: list(markdown = TRUE)
># RoxygenNote: 7.1.1
># √ Writing 'NAMESPACE'
># √ Writing 'dfOperation.Rproj'
># √ Adding '.Rproj.user' to '.gitignore'
># √ Adding '^dfOperation\\.Rproj$', '^\\.Rproj\\.user$' to '.Rbuildignore'
># √ Opening './dfOperation/' in new RStudio session
># √ Setting active project to '<no active project>'

指定 R Package 的名字和源代码的存放目录,devtools 就会自动创建目录并新建一个 R 工程,然后填充好文件框架。

  • .Rbuildignore 列出了我们编写 R 包时需要的,但是从源代码构建 R 包时并不需要的文件。
  • .gitignore 为 Git 的使用做好准备。它将忽略一些由 R 或 RStudio 创建的文件。
  • DESCRIPTION 描述了程序包的元数据。
  • NAMESPACE 声明了程序包导出的,供外部使用的函数以及从其他程序包导入的外部函数。目前,它包含暂未起作用的用于占位的内容。
  • R/ 目录是程序包的主要部分。它很快将包含带有函数声明的 .R 文件。
  • dfOperation.Rproj 是使得该目录成为 RStudio 项目的文件。

使用 Git 做版本控制

1
use_git()

在 R Package 的制作过程中,难免会出现想要放弃对代码的修改,恢复到上一个版本的情况,这时候使用版本控制系统会十分方便。当然,也可以选择不使用。

使用了 Git 版本控制系统后,RStudio 右上方窗格中出现 Git 选项卡,可以在这里方便地进行存档和提交更改。

编写函数

1
2
3
4
use_r("df_mutate")
># √ Setting active project to 'F:/QCFWorkSpace/CurrentProjects/R_Packages_Summer/r-pkg-startup/dfOperation'
># * Modify 'R/df_mutate.R'
># * Call `use_test()` to create a matching test file

程序包中的函数应该放在 /R 子目录中的 *.R 脚本文件内。一般而言,我们会为每一个函数创建一个 *.R 文件。

use_r() 函数能够自动化地帮助我们在 /R 目录下创建或打开脚本文件。

导入其他程序包的函数

1
2
3
use_package("dplyr")
># √ Adding 'dplyr' to Imports field in DESCRIPTION
># * Refer to functions with `dplyr::fun()`

在 R Package 中调用其他程序包中的函数时,不能够使用 library(),需要使用 pkg_name::func_name 的格式,通过命名空间进行调用。

使用 use_package 相当于告诉使用者,这个 R Package 需要使用其他 R Package 中的函数。

撰写并生成文档

R 函数的帮助文档也是 R Package 的重要组成部分。函数文档以特殊的标记格式放在 man/*.Rd 文件中,但是我们不需要掌握这种特殊标记语法,roxygen2 能够将特殊的注释转换成 *.Rd 文件。

将鼠标光标放在指定的函数体内,在 RStudio 中使用如下快捷键或菜单命令,就能够产生特殊格式的注释框架:

1
2
Ctrl + Shift + Alt + R
Code -> Insert Roxygen Skeleton
1
2
3
4
5
6
7
8
9
10
#' Title
#'
#' @param df
#' @param field
#' @param DESC
#'
#' @return
#' @export
#'
#' @examples
  • Title 替换为函数文档的标题,Title 之下还可以添加简要的说明文字;
  • @param 字段是函数参数的解释;
  • @return 是对函数返回值的说明;
  • @export 表示该函数能够被导出,以供用户使用;
  • @example 是该函数的使用示例。

可以按照如下格式填写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#' Data Frame Mutate
#'
#' Generate new field from old fields based on given expression.
#'
#' @param expr characters, expression that produce new field.
#' @param df dataframe contains old fields.
#' @param cname characters, name of new field.
#'
#' @return new dataframe contains old fields and new field.
#' @export
#'
#' @examples df_1 <- data.frame("a" = c(1, 2, 3), "b" = c(6, 5, 4))
#' df_2 <- df_mutate("a + b", df, "c")
df_mutate <- function(expr, df, cname) {

我们还需要用 document() 将这个新的 roxygen 注释转变为 man/df_mutate.Rd

1
document()

初步测试函数

1
2
load_all()
># Loading dfOperation

在程序包开发过程中,时刻对函数进行测试是常见且必要的。使用 load_all() 能够在防止干扰全局命名空间的情况下加载程序包。

但是,如果全局命名空间内存在同名函数,则会覆盖掉程序包中的对应函数。

Check

1
check()

check() 能够检查程序包结构是否完整,代码是否有问题,是否处于良好的工作状态。它会输出相当多的内容,但是阅读输出内容并尽早解决出现的问题是一个良好的习惯。

RStudio 提供了 check() 的快速调用。它位于右上角的 Build 选项卡中,通过 Check 或者键盘快捷键 Ctrl + Shift + E (WIndows & Linux) 或者 Cmd + Shift + E (MacOS)调用。

编辑程序包的基本信息

DESCRIPTION 文件提供了程序包的元数据,包括:

  • 程序包名称 Package
  • 程序包标题 Title
  • 版本 Version
  • 相关开发人员 Author@R
  • 功能描述 Description
  • 开源许可证 License
  • 依赖的其他 Packages Imports
  • ……

其中,PackgeTitleVersionAuthor@RDescription 字段需要我们手动填写,其他字段能够通过函数进行设定,比如添加开源许可证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Package: dfOperation
Title: 2 data frame operations
Version: 0.0.0.9000
Authors@R:
person(given = "Yuanchen",
family = "Zhu",
role = c("aut", "cre"),
email = "first.last@example.com")
Description: 2 data frame operations of generating new field from old fields
and sorting.
License: CC0
Encoding: UTF-8
LazyData: true
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.1.1
Imports:
dplyr

添加开源许可证

1
2
3
4
use_cc0_license("Your_Name")
># √ Setting License field in DESCRIPTION to 'CC0'
># √ Writing 'LICENSE.md'
># √ Adding '^LICENSE\\.md$' to '.Rbuildignore'

此类函数的名称格式为:use_[License_Name]_license("Your_Name")。比如,如果使用 MIT 协议,就调用函数 use_mit_license("Your_Name")

安装测试

1
check()

check() 的输出没有 Errors、Warnings 和 Notes 时,说明我们的 R Package 已经是一个成熟完整的程序包了,可以进行安装测试:

1
install()

现在,我们可以向使用其他程序包一样使用 dfOperation 了。

添加 README

将 R Package 发布到 CRAN 上是一项耗时的工作,不仅需要经过严格的测试,还需要撰写详细的文档和 Vignette。因此,我们通常会将制作的 R Package 发布到 GitHub 上。

一些 CRAN 上的 R Package 也会出现在 GitHub 上,通常在 CRAN 上发布稳定版本,在 GitHub 上发布开发版本。

既然程序包位于 GitHub 上,那么 README.md 作为程序包的主页就很重要。使用如下命令创建一个 README.Rmd 文件,它会为 README.md 文件的生成提供很多的便利。

1
2
3
4
use_readme_rmd()
#> ✔ Writing 'README.Rmd'
#> ✔ Adding '^README\\.Rmd$' to '.Rbuildignore'
#> ✔ Writing '.git/hooks/pre-commit'

生成的 README.Rmd 文件已经包含了一般的框架,我们只需要填充上内容即可(通常直接从 DESCRIPTION 文件中复制粘贴)。然后使用如下命令生成 README.md 文件:

1
rmarkdown::render("README.Rmd")

或者使用 Knit 菜单命令:

这一步之后,工程根目录下的文件有:

导出

可以导出为 Source Package 或者 Binary Package。二者的主要区别在于:Binary Package 已经经过了 RTools 的编译,可以直接安装;而 Source Package 属于源代码,安装时会先使用 RTools 进行编译,然后再安装。

生成源码包:

生成二进制包:

References

Hadley Wickham, Jennifer Bryan. R Packages second edition. https://r-pkgs.org/