本文简单记录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:
网友评论