本文简单记录R包开发的流程
准备
创建R包需要用到 devtools
,以及usethis
install.packages(c("devtools","usethis"))
library(devtools)
library(usethis)
创建Git repo (可选)
如果需要使用Git管理R包,我们先要在GitHub上创建该包的Git repo,
在GitHub登陆后,进入[Your Repositories] → [New]
创建一个叫"regexcite"的库进行练习
然后,clone这个仓库到本地,再开始搭建我们的包
git clone https://github.com/thereallda/regexcite.git
注意,这里要将https://github.com/thereallda/regexcite.git换成你创建的地址。
初始化R包
打开RStudio,使用函数 usethis::create_package()
初始化克隆到本地电脑的包的目录 (最好是绝对路径)
usethis::create_package("~/path/to/regexcite")
使用后在目录("~/path/to/regexcite")下创建以下文件和目录:
.Rbuildignore*
.Rhistory*
.Rproj.user/
.gitignore*
DESCRIPTION*
NAMESPACE*
R/
regexcite.Rproj*
.Rbuildignore
lists files that we need to have around but that should not be included when building the R package from source..Rproj.user
, if you have it, is a directory used internally by RStudio..gitignore
anticipates Git usage and ignores some standard, behind-the-scenes files created by R and RStudio. Even if you do not plan to use Git, this is harmless.DESCRIPTION
provides metadata about your package. We edit this shortly.NAMESPACE
declares the functions your package exports for external use and the external functions your package imports from other packages. At this point, it is empty, except for a comment declaring that this is a file we will not edit by hand.The
R/
directory is the "business end" of your package. It will soon contain .R files with function definitions.regexcite.Rproj
is the file that makes this directory an RStudio Project. Even if you don't use RStudio, this file is harmless. Or you can suppress its creation with create_package(..., rstudio = FALSE).
创建第一个函数
函数的脚本应当存放在 R/
目录下。可以直接创建脚本保存于其中。也可以使用函数 use_r()
创建
use_r("strsplit1")
use_r("strsplit1")
直接创建"strsplit1.R"到 R/
下
我们写下第一个函数 strsplit1
是对 base::strsplit()
的包装,原函数返回一个 list
,而 strsplit1
只取返回结果的第一个元素,相当于是 unlist(strsplit(x, split))
.
# split a single string
strsplit1 <- function(x, split) {
strsplit(x, split = split)[[1]]
}
测试第一个函数
load_all()
读入我们在 R/
目录下所有的脚本。这样就可以载入刚写的 R/strsplit1.R
load_all()
(x <- "alfa,bravo,charlie,delta")
strsplit1(x, split = ",")
实际上,load_all()
并不会把函数载入到我们的全局环境(global environment)中,而是用一种 library()
的方式载入,可以使用以下命令检查:
exists("strsplit1", where = globalenv(), inherits = FALSE)
这里应当返回 FALSE
, 如果返回 TRUE
可以通过重启R清空全局环境,再运行一次 load_all()
即可。
load_all()
simulates the process of building, installing, and attaching the regexcite package.
检查R包构建情况
使用 check()
函数可以检查R包构建情况,同时也会自动更新文档之类的。
check()
-- R CMD check results ------------------------------------------------------------------ regexcite 0.0.0.9000 ----
Duration: 7.5s
> checking DESCRIPTION meta-information ... WARNING
Non-standard license specification:
`use_mit_license()`, `use_gpl3_license()` or friends to pick a
license
Standardizable: FALSE
0 errors √ | 1 warning x | 0 note √
这里提示我们没有选 license
https://blog.csdn.net/midnight_time/article/details/83989131
MIT: 软件可以随便用,随便改
GPL3 / Apache2: 软件可以随便用,但不能随便改
我们使用 MIT license usethis::use_mit_license()
> use_mit_license()
√ Setting License field in DESCRIPTION to 'MIT + file LICENSE'
√ Writing 'LICENSE'
√ Writing 'LICENSE.md'
√ Adding '^LICENSE\\.md$' to '.Rbuildignore'
LICENSE
文件内应该是:
YEAR: 2022
COPYRIGHT HOLDER: regexcite authors
修改 DESCRIPTION
打开 DESCRIPTION
文件进行修改,里面有一些样式的内容,主要改一下 Authors@R
和 Description
区域
改好之后是这样
Package: regexcite
Title: Toy package for paracticing the process about developing R package.
Version: 0.0.0.9000
Authors@R:
person(given = "Dean",
family = "Li",
role = c("aut", "cre"),
email = "")
Description: Convenience functions to make some common tasks with string
manipulation and regular expressions a bit easier.
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
添加说明文档
通过 roxygen2
注释系统在脚本内部写下函数有关的文档。
在RStudio内,移动光标到函数的代码区段,点击 Code > Insert Roxygen Skeleton
将会自动添加 #'
开头的 roxygen2
注释
修改这段注释,在相应区域内写上对应的描述即可
#' Split a string
#'
#' @param x A character vector with one element.
#' @param split What to split on.
#'
#' @return A character vector.
#' @export
#'
#' @examples
#' x <- "alfa,bravo,charlie,delta"
#' strsplit1(x, split = ",")
strsplit1 <- function(x, split) {
strsplit(x, split = split)[[1]]
}
运行 document()
命令自动生成文档
> document()
i<U+00A0>Updating regexcite documentation
i<U+00A0>Loading regexcite
Writing NAMESPACE
Writing NAMESPACE
?strsplit1
可以查看该文档
同时, NAMESPACE
文件也会被写入以下内容
# Generated by roxygen2: do not edit by hand
export(strsplit1)
最后,再 check()
一遍
> check()
-- R CMD check results ------------------------------------------------------------------ regexcite 0.0.0.9000 ----
Duration: 9s
0 errors √ | 0 warnings √ | 0 notes √
安装并载入
上述结果表明包构建没问题,可以通过 install()
安装当前目录的这个包
install()
载入并测试
library(regexcite)
x <- "alfa,bravo,charlie,delta"
strsplit1(x, split = ",")
输出结果与测试一致,说明这个测试包构建成功了!
测试包
use_testthat()
声明我们要对该包进行测试,会创建tests目录,及其他用于自动测试的相关文件
> use_testthat()
√ Setting active project to 'D:/R_practice/regexcite'
√ Adding 'testthat' to Suggests field in DESCRIPTION
√ Setting Config/testthat/edition field in DESCRIPTION to '3'
√ Creating 'tests/testthat/'
√ Writing 'tests/testthat.R'
* Call `use_test()` to initialize a basic test file and open it for editing.
添加对函数的测试脚本
> use_test("strsplit1")
√ Writing 'tests/testthat/test-strsplit1.R'
* Modify 'tests/testthat/test-strsplit1.R'
这会创建一个测试脚本 test-strsplit1.R
,我们需要往里面写入对函数的测试方法,以及期望输出
test_that("strsplit1() splits a string", {
expect_equal(strsplit1("a,b,c", split = ","), c("a", "b", "c"))
})
使用 test()
进行测试
> test()
i<U+00A0>Loading regexcite
i<U+00A0>Testing regexcite
√ | OK F W S | Context
√ | 1 | strsplit1 [0.2 s]
== Results ================================================================================================================
Duration: 0.2 s
[ FAIL 0 | WARN 0 | SKIP 0 | PASS 1 ]
注意使用testthat进行测试不是必须的,只要你确保函数都可以运行,就可以跳过这一步。
引用外部R包
如果需要使用别的包的函数,我们需要对其进行引用。
通过 use_package()
可以在 DESCRIPTION
文件中加入引用外部包的说明(手动写入应当也可以)。
这里我们引用 stringr
包
use_package("stringr")
√ Adding 'stringr' to Imports field in DESCRIPTION
* Refer to functions with `stringr::fun()`
使用后在 DESCRIPTION
文件插入一段Imports
Imports:
stringr
另外,在函数的 roxygen2区域需要加入引用函数的说明,例如使用
stringr::str_split
需要写入以下内容:#' @importFrom stringr str_split
创建一个 stringr
版本的函数
str_split_one <- function(string, pattern, n = Inf) {
stopifnot(is.character(string), length(string) <= 1)
if (length(string) == 1) {
stringr::str_split(string = string, pattern = pattern, n = n)[[1]]
} else {
character()
}
}
保存后,使用 rename_files()
修改
√ Moving 'R/strsplit1.R' to 'R/str_split_one.R'
√ Moving 'tests/testthat/test-strsplit1.R' to 'tests/testthat/test-str_split_one.R'
更改 test-str_split_one.R
的内容
test_that("str_split_one() splits a string", {
expect_equal(str_split_one("a,b,c", ","), c("a", "b", "c"))
})
test_that("str_split_one() errors if input length > 1", {
expect_error(str_split_one(c("a,b","c,d"), ","))
})
test_that("str_split_one() exposes features of stringr::str_split()", {
expect_equal(str_split_one("a,b,c", ",", n = 2), c("a", "b,c"))
expect_equal(str_split_one("a.b", stringr::fixed(".")), c("a", "b"))
})
使用 document()
重新生成文档
> document()
i<U+00A0>Updating regexcite documentation
i<U+00A0>Loading regexcite
Writing NAMESPACE
Writing NAMESPACE
Writing str_split_one.Rd
Deleting strsplit1.Rd
Warning message:
In setup_ns_exports(path, export_all, export_imports) :
Objects listed as exports, but not present in namespace: strsplit1
现在测试新写的 str_split_one()
load_all()
str_split_one("a, b, c", pattern = ", ")
测试完成后,add, commit and push
$ git add .
$ git commit -m "change to str_split_one"
$ git push
最后再 check()
一次,没有问题的话就可以安装使用了
build ignore
如果想要在构建R包的时候忽略某些文件或文件夹,可以使用 usethis::use_build_ignore()
use_build_ignore(c('data/', 'data-raw/'))
向GitHub提交修改
测试无误后,可以将我们创建好的R包commit到GitHub
git add ./
git commit -m "Initial commit"
git push
总结
R包的开发流程概况下来可以分为以下步骤:
- 如果需要在GitHub上管理,创建GitHub repo for R package,并Clone GitHub repo到本地(optional)
-
usethis::create_package()
将目录初始化为R包的结构 - 修改
DESCRIPTION
,并选择licenseusethis::use_mit_license()
orusethis::use_gpl3_license()
- 在
R\
目录下添加脚本并写入函数use_r()
;如果需要引用外部R包,则用use_package()
引入 - 测试函数
- 以
roxygen2
格式写文档,document()
- 检查R包构建情况,没问题就安装测试
check()
andinstall()
- README文档帮助他人了解你的包
use_readme_rmd()
andbuild_readme()
- 同步到GitHub, git add, commit and push (optional)
Ref: