R-搭建R包

本文簡(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@RDescription 區(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)可以分為以下步驟:

  1. 如果需要在GitHub上管理,創(chuàng)建GitHub repo for R package纹冤,并Clone GitHub repo到本地(optional)
  2. usethis::create_package() 將目錄初始化為R包的結(jié)構(gòu)
  3. 修改 DESCRIPTION 洒宝,并選擇license usethis::use_mit_license() or usethis::use_gpl3_license()
  4. R\ 目錄下添加腳本并寫(xiě)入函數(shù) use_r() ;如果需要引用外部R包萌京,則用 use_package() 引入
  5. 測(cè)試函數(shù)
  6. roxygen2 格式寫(xiě)文檔雁歌,document()
  7. 檢查R包構(gòu)建情況,沒(méi)問(wèn)題就安裝測(cè)試 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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末靠瞎,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子求妹,更是在濱河造成了極大的恐慌乏盐,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件制恍,死亡現(xiàn)場(chǎng)離奇詭異父能,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)净神,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)何吝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人鹃唯,你說(shuō)我怎么就攤上這事爱榕。” “怎么了俯渤?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵呆细,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我八匠,道長(zhǎng)絮爷,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任梨树,我火速辦了婚禮坑夯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘抡四。我一直安慰自己柜蜈,他們只是感情好仗谆,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著淑履,像睡著了一般隶垮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上秘噪,一...
    開(kāi)封第一講書(shū)人閱讀 48,954評(píng)論 1 283
  • 那天狸吞,我揣著相機(jī)與錄音,去河邊找鬼指煎。 笑死蹋偏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的至壤。 我是一名探鬼主播威始,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼像街!你這毒婦竟也來(lái)了黎棠?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤镰绎,失蹤者是張志新(化名)和其女友劉穎葫掉,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體跟狱,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡俭厚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了驶臊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挪挤。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖关翎,靈堂內(nèi)的尸體忽然破棺而出扛门,到底是詐尸還是另有隱情,我是刑警寧澤纵寝,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布论寨,位于F島的核電站,受9級(jí)特大地震影響爽茴,放射性物質(zhì)發(fā)生泄漏葬凳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一室奏、第九天 我趴在偏房一處隱蔽的房頂上張望火焰。 院中可真熱鬧当娱,春花似錦淘太、人聲如沸梧税。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)纯赎。三九已至谦疾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間犬金,已是汗流浹背餐蔬。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留佑附,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓仗考,卻偏偏與公主長(zhǎng)得像音同,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子秃嗜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容