本文簡(jiǎn)單記錄R包開(kāi)發(fā)的流程
準(zhǔn)備
創(chuàng)建R包需要用到 devtools
,以及usethis
install.packages(c("devtools","usethis"))
library(devtools)
library(usethis)
創(chuàng)建Git repo (可選)
如果需要使用Git管理R包荣瑟,我們先要在GitHub上創(chuàng)建該包的Git repo微饥,
在GitHub登陸后疫粥,進(jìn)入[Your Repositories] → [New]
創(chuàng)建一個(gè)叫"regexcite"的庫(kù)進(jìn)行練習(xí)
然后寨躁,clone這個(gè)倉(cāng)庫(kù)到本地肤无,再開(kāi)始搭建我們的包
git clone https://github.com/thereallda/regexcite.git
注意,這里要將https://github.com/thereallda/regexcite.git換成你創(chuàng)建的地址。
初始化R包
打開(kāi)RStudio辩撑,使用函數(shù) usethis::create_package()
初始化克隆到本地電腦的包的目錄 (最好是絕對(duì)路徑)
usethis::create_package("~/path/to/regexcite")
使用后在目錄("~/path/to/regexcite")下創(chuàng)建以下文件和目錄:
.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).
創(chuàng)建第一個(gè)函數(shù)
函數(shù)的腳本應(yīng)當(dāng)存放在 R/
目錄下界斜。可以直接創(chuàng)建腳本保存于其中合冀。也可以使用函數(shù) use_r()
創(chuàng)建
use_r("strsplit1")
use_r("strsplit1")
直接創(chuàng)建"strsplit1.R"到 R/
下
我們寫(xiě)下第一個(gè)函數(shù) strsplit1
是對(duì) base::strsplit()
的包裝各薇,原函數(shù)返回一個(gè) list
,而 strsplit1
只取返回結(jié)果的第一個(gè)元素君躺,相當(dāng)于是 unlist(strsplit(x, split))
.
# split a single string
strsplit1 <- function(x, split) {
strsplit(x, split = split)[[1]]
}
測(cè)試第一個(gè)函數(shù)
load_all()
讀入我們?cè)?R/
目錄下所有的腳本峭判。這樣就可以載入剛寫(xiě)的 R/strsplit1.R
load_all()
(x <- "alfa,bravo,charlie,delta")
strsplit1(x, split = ",")
實(shí)際上,load_all()
并不會(huì)把函數(shù)載入到我們的全局環(huán)境(global environment)中晰洒,而是用一種 library()
的方式載入朝抖,可以使用以下命令檢查:
exists("strsplit1", where = globalenv(), inherits = FALSE)
這里應(yīng)當(dāng)返回 FALSE
, 如果返回 TRUE
可以通過(guò)重啟R清空全局環(huán)境,再運(yùn)行一次 load_all()
即可谍珊。
load_all()
simulates the process of building, installing, and attaching the regexcite package.
檢查R包構(gòu)建情況
使用 check()
函數(shù)可以檢查R包構(gòu)建情況治宣,同時(shí)也會(huì)自動(dòng)更新文檔之類(lèi)的。
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 √
這里提示我們沒(méi)有選 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
文件內(nèi)應(yīng)該是:
YEAR: 2022
COPYRIGHT HOLDER: regexcite authors
修改 DESCRIPTION
打開(kāi) DESCRIPTION
文件進(jìn)行修改,里面有一些樣式的內(nèi)容贝润,主要改一下 Authors@R
和 Description
區(qū)域
改好之后是這樣
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
添加說(shuō)明文檔
通過(guò) roxygen2
注釋系統(tǒng)在腳本內(nèi)部寫(xiě)下函數(shù)有關(guān)的文檔绊茧。
在RStudio內(nèi),移動(dòng)光標(biāo)到函數(shù)的代碼區(qū)段打掘,點(diǎn)擊 Code > Insert Roxygen Skeleton
將會(huì)自動(dòng)添加 #'
開(kāi)頭的 roxygen2
注釋
修改這段注釋?zhuān)谙鄳?yīng)區(qū)域內(nèi)寫(xiě)上對(duì)應(yīng)的描述即可
#' 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]]
}
運(yùn)行 document()
命令自動(dòng)生成文檔
> document()
i<U+00A0>Updating regexcite documentation
i<U+00A0>Loading regexcite
Writing NAMESPACE
Writing NAMESPACE
?strsplit1
可以查看該文檔
同時(shí)华畏, NAMESPACE
文件也會(huì)被寫(xiě)入以下內(nèi)容
# 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 √
安裝并載入
上述結(jié)果表明包構(gòu)建沒(méi)問(wèn)題尊蚁,可以通過(guò) install()
安裝當(dāng)前目錄的這個(gè)包
install()
載入并測(cè)試
library(regexcite)
x <- "alfa,bravo,charlie,delta"
strsplit1(x, split = ",")
輸出結(jié)果與測(cè)試一致亡笑,說(shuō)明這個(gè)測(cè)試包構(gòu)建成功了!
測(cè)試包
use_testthat()
聲明我們要對(duì)該包進(jìn)行測(cè)試横朋,會(huì)創(chuàng)建tests目錄仑乌,及其他用于自動(dòng)測(cè)試的相關(guān)文件
> 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.
添加對(duì)函數(shù)的測(cè)試腳本
> use_test("strsplit1")
√ Writing 'tests/testthat/test-strsplit1.R'
* Modify 'tests/testthat/test-strsplit1.R'
這會(huì)創(chuàng)建一個(gè)測(cè)試腳本 test-strsplit1.R
,我們需要往里面寫(xiě)入對(duì)函數(shù)的測(cè)試方法琴锭,以及期望輸出
test_that("strsplit1() splits a string", {
expect_equal(strsplit1("a,b,c", split = ","), c("a", "b", "c"))
})
使用 test()
進(jìn)行測(cè)試
> 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進(jìn)行測(cè)試不是必須的晰甚,只要你確保函數(shù)都可以運(yùn)行,就可以跳過(guò)這一步决帖。
引用外部R包
如果需要使用別的包的函數(shù)厕九,我們需要對(duì)其進(jìn)行引用。
通過(guò) use_package()
可以在 DESCRIPTION
文件中加入引用外部包的說(shuō)明(手動(dòng)寫(xiě)入應(yīng)當(dāng)也可以)地回。
這里我們引用 stringr
包
use_package("stringr")
√ Adding 'stringr' to Imports field in DESCRIPTION
* Refer to functions with `stringr::fun()`
使用后在 DESCRIPTION
文件插入一段Imports
Imports:
stringr
另外扁远,在函數(shù)的 roxygen2區(qū)域需要加入引用函數(shù)的說(shuō)明腺阳,例如使用
stringr::str_split
需要寫(xiě)入以下內(nèi)容:#' @importFrom stringr str_split
創(chuàng)建一個(gè) stringr
版本的函數(shù)
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
的內(nèi)容
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
現(xiàn)在測(cè)試新寫(xiě)的 str_split_one()
load_all()
str_split_one("a, b, c", pattern = ", ")
測(cè)試完成后穿香,add, commit and push
$ git add .
$ git commit -m "change to str_split_one"
$ git push
最后再 check()
一次,沒(méi)有問(wèn)題的話(huà)就可以安裝使用了
build ignore
如果想要在構(gòu)建R包的時(shí)候忽略某些文件或文件夾绎速,可以使用 usethis::use_build_ignore()
use_build_ignore(c('data/', 'data-raw/'))
向GitHub提交修改
測(cè)試無(wú)誤后皮获,可以將我們創(chuàng)建好的R包c(diǎn)ommit到GitHub
git add ./
git commit -m "Initial commit"
git push
總結(jié)
R包的開(kāi)發(fā)流程概況下來(lái)可以分為以下步驟:
- 如果需要在GitHub上管理,創(chuàng)建GitHub repo for R package纹冤,并Clone GitHub repo到本地(optional)
-
usethis::create_package()
將目錄初始化為R包的結(jié)構(gòu) - 修改
DESCRIPTION
洒宝,并選擇licenseusethis::use_mit_license()
orusethis::use_gpl3_license()
- 在
R\
目錄下添加腳本并寫(xiě)入函數(shù)use_r()
;如果需要引用外部R包萌京,則用use_package()
引入 - 測(cè)試函數(shù)
- 以
roxygen2
格式寫(xiě)文檔雁歌,document()
- 檢查R包構(gòu)建情況,沒(méi)問(wèn)題就安裝測(cè)試
check()
andinstall()
- README文檔幫助他人了解你的包
use_readme_rmd()
andbuild_readme()
- 同步到GitHub知残, git add, commit and push (optional)
Ref: