R-搭建R包

本文简单记录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@RDescription 区域

改好之后是这样

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包的开发流程概况下来可以分为以下步骤:

  1. 如果需要在GitHub上管理,创建GitHub repo for R package,并Clone GitHub repo到本地(optional)
  2. usethis::create_package() 将目录初始化为R包的结构
  3. 修改 DESCRIPTION ,并选择license usethis::use_mit_license() or usethis::use_gpl3_license()
  4. R\ 目录下添加脚本并写入函数 use_r() ;如果需要引用外部R包,则用 use_package() 引入
  5. 测试函数
  6. roxygen2 格式写文档,document()
  7. 检查R包构建情况,没问题就安装测试 check() and install()
  8. README文档帮助他人了解你的包 use_readme_rmd() and build_readme()
  9. 同步到GitHub, git add, commit and push (optional)

Ref:

https://r-pkgs.org/whole-game.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容