把函数包起来就是一个R包 - 完整开发指南
生物信息學習的正確姿勢
NGS系列文章包括NGS基礎、高顏值在線繪圖和分析、轉錄組分析?(Nature重磅綜述|關于RNA-seq你想知道的全在這)、ChIP-seq分析?(ChIP-seq基本分析流程)、單細胞測序分析?(重磅綜述:三萬字長文讀懂單細胞RNA測序分析的最佳實踐教程)、DNA甲基化分析、重測序分析、GEO數據挖掘(典型醫學設計實驗GEO數據分析 (step-by-step))、批次效應處理等內容。
也參考次條:在Rstudio中點一點就出來了一個R包
devtools的?cheatsheet ()
首先來看一下整個創建R包的基本流程,然后在具體的講解每一個部分
第一步是確保開發所需要的R包已經安裝好了,主要是devtools
install.packages("devtools") library(devtools)使用create_package()函數初始化一個新的package:
usethis::create_package("~/test/toypackages")??Creating?'/Users/macos/test/toypackages/' ??Setting?active?project?to?'/Users/macos/test/toypackages' ??Creating?'R/' ??Writing?'DESCRIPTION' Package:?toypackages Title:?What?the?Package?Does?(One?Line,?Title?Case) Version:?0.0.0.9000 Authors@R?(parsed):*?First?Last?<first.last@example.com>?[aut,?cre]?(YOUR-ORCID-ID) Description:?What?the?package?does?(one?paragraph). License:?`use_mit_license()`,?`use_gpl3_license()`?or?friends?topick?a?license Encoding:?UTF-8 LazyData:?true Roxygen:?list(markdown?=?TRUE) RoxygenNote:?7.1.1 ??Writing?'NAMESPACE' ??Writing?'toypackages.Rproj' ??Adding?'.Rproj.user'?to?'.gitignore' ??Adding?'^toypackages\\.Rproj$',?'^\\.Rproj\\.user$'?to?'.Rbuildignore' ??Opening?'/Users/macos/test/toypackages/'?in?new?RStudio?session ??Setting?active?project?to?'<no?active?project>'執行這個命令后會自動創建一個新的目錄和project,并打開一個新的Rstudio界面進入該project
我們需要注意這個項目和一般的項目的區別:
在Environment那個pane多了一個bulid
image-20200916100402065另外多了一些文件
image-20200916100336889
現在添加第一個函數:使用use_r()創建一個R/*.R的文件來存放這個函數:
usethis::use_r("fbind") ??Setting?active?project?to?'/Users/macos/test/toypackages' ●?Modify?'R/fbind.R' ●?Call?`use_test()`?to?create?a?matching?test?fileuse_r會自動打開一個R腳本文件,現在添加fbind函數,這個函數將兩個因子向量聚合起來:
fbind?<-?function(a,?b)?{factor(c(as.character(a),?as.character(b))) }然后測試一下這個函數是否工作正常,先使用load_all來載入我們創建的函數(load_all()模擬了building, installing, attaching步驟)
>?devtools::load_all() Loading?toypackages >?a?<-?factor(c("character",?"hits",?"your",?"eyeballs")) >?b?<-?factor(c("but",?"integer",?"where?it",?"counts")) >?fbind(a,b) [1]?character?hits??????your??????eyeballs??but???????integer???where?it??counts??? Levels:?but?character?counts?eyeballs?hits?integer?where?it?yourLoad_all 快捷鍵:Ctrl + Shift + L (Windows & Linux) or Cmd + Shift + L (macOS)
另外在添加函數之后,上面的文件結構也會發生變化,多了一個man文件夾:
image-20200916101446150我們已經檢查過fbind函數工作正常,那我們怎么確定我們添加這個函數后包的其他部分也運作正常呢?可以使用check函數自動檢查:
>?devtools::check()Updating?toypackages?documentation Loading?toypackages ─?Building?────────────────────────────────?toypackages?─ Setting?env?vars: ●?CFLAGS????:?-Wall?-pedantic?-fdiagnostics-color=always ●?CXXFLAGS??:?-Wall?-pedantic?-fdiagnostics-color=always ●?CXX11FLAGS:?-Wall?-pedantic?-fdiagnostics-color=always ───────────────────────────────────────────── ???checking?for?file?‘/Users/macos/test/toypackages/DESCRIPTION’?... ─??preparing?‘toypackages’: ???checking?DESCRIPTION?meta-information?... ─??checking?for?LF?line-endings?in?source?and?make?files?and?shell?scripts ─??checking?for?empty?or?unneeded?directoriesRemoved?empty?directory?‘toypackages/man’ ─??building?‘toypackages_0.0.0.9000.tar.gz’─?Checking?────────────────────────────────?toypackages?─ Setting?env?vars: ●?_R_CHECK_CRAN_INCOMING_REMOTE_:?FALSE ●?_R_CHECK_CRAN_INCOMING_???????:?FALSE ●?_R_CHECK_FORCE_SUGGESTS_??????:?FALSE ●?NOT_CRAN??????????????????????:?true ──?R?CMD?check?─────────────────────────────────────────────────────────────────────────── ─??using?log?directory?‘/private/var/folders/s1/z_mkhs313cgbplp38856sxk40000gn/T/Rtmp7UAmyG/toypackages.Rcheck’ ─??using?R?version?4.0.2?(2020-06-22) ─??using?platform:?x86_64-apple-darwin17.0?(64-bit) ─??using?session?charset:?UTF-8 ─??using?options?‘--no-manual?--as-cran’ ???checking?for?file?‘toypackages/DESCRIPTION’ ─??this?is?package?‘toypackages’?version?‘0.0.0.9000’ ─??package?encoding:?UTF-8 ???checking?package?namespace?information ???checking?package?dependencies?(1.3s) ???checking?if?this?is?a?source?package ???checking?if?there?is?a?namespace?... ???checking?for?executable?files?... ???checking?for?hidden?files?and?directories ???checking?for?portable?file?names ???checking?for?sufficient/correct?file?permissions ???checking?serialization?versions ???checking?whether?package?‘toypackages’?can?be?installed?(1.1s) ???checking?installed?package?size?... ???checking?package?directory?... N??checking?for?future?file?timestamps?(3.7s)unable?to?verify?current?time W??checking?DESCRIPTION?meta-information?...Non-standard?license?specification:`use_mit_license()`,?`use_gpl3_license()`?or?friends?to?pick?alicenseStandardizable:?FALSE ???checking?top-level?files?... ???checking?for?left-over?files ???checking?index?information ???checking?package?subdirectories?... ???checking?R?files?for?non-ASCII?characters?... ???checking?R?files?for?syntax?errors?... ???checking?whether?the?package?can?be?loaded?... ???checking?whether?the?package?can?be?loaded?with?stated?dependencies?... ???checking?whether?the?package?can?be?unloaded?cleanly?... ???checking?whether?the?namespace?can?be?loaded?with?stated?dependencies?... ???checking?whether?the?namespace?can?be?unloaded?cleanly?... ???checking?loading?without?being?on?the?library?search?path?... ???checking?dependencies?in?R?code?... ???checking?S3?generic/method?consistency?(377ms) ???checking?replacement?functions?... ???checking?foreign?function?calls?... ???checking?R?code?for?possible?problems?(1.5s) ???checking?for?missing?documentation?entries?... ─??checking?examples?...?NONE ???checking?for?non-standard?things?in?the?check?directory ???checking?for?detritus?in?the?temp?directorySee‘/private/var/folders/s1/z_mkhs313cgbplp38856sxk40000gn/T/Rtmp7UAmyG/toypackages.Rcheck/00check.log’for?details.──?R?CMD?check?results?───────────────────────────────────────?toypackages?0.0.0.9000?──── Duration:?9.9s>?checking?DESCRIPTION?meta-information?...?WARNINGNon-standard?license?specification:`use_mit_license()`,?`use_gpl3_license()`?or?friends?to?pick?alicenseStandardizable:?FALSE>?checking?for?future?file?timestamps?...?NOTEunable?to?verify?current?time0?errors???|?1?warning?x?|?1?note?xcheck 快捷鍵:Ctrl + Shift + E (Windows & Linux) or Cmd + Shift + E (macOS).
可以看到有1個警告,1個note
waring是因為Non-standard license specification
添加license需要在DESCRIPTION文件修改,這個文件是提供包的metadata的
默認生成的內容:
Package:?toypackages Title:?What?the?Package?Does?(One?Line,?Title?Case) Version:?0.0.0.9000 Authors@R:?person(given?=?"First",family?=?"Last",role?=?c("aut",?"cre"),email?=?"first.last@example.com",comment?=?c(ORCID?=?"YOUR-ORCID-ID")) Description:?What?the?package?does?(one?paragraph). License:?`use_mit_license()`,?`use_gpl3_license()`?or?friends?topick?a?license Encoding:?UTF-8 LazyData:?true Roxygen:?list(markdown?=?TRUE) RoxygenNote:?7.1.1現在對其進行修改:加上作者 包的名稱 簡介等
Package:?toypackages Title:?learn?how?to?bulid?a?package Version:?0.0.0.9000 Authors@R:?person(given?=?"aa",family?=?"bb",role?=?c("aut",?"cre"),email?=?"11111@qq.com",comment?=?c(ORCID?=?"YOUR-ORCID-ID")) Description:?learn?how?to?bulid?a?package. License:?`use_mit_license()`,?`use_gpl3_license()`?or?friends?topick?a?license Encoding:?UTF-8 LazyData:?true Roxygen:?list(markdown?=?TRUE) RoxygenNote:?7.1.1然后就可以用use_mit_license()來解決剛才的那個警告,加上license,這里使用MIT license
usethis::use_mit_license("aa?bb") ??Setting?License?field?in?DESCRIPTION?to?'MIT?+?file?LICENSE' ??Writing?'LICENSE.md' ??Adding?'^LICENSE\\.md$'?to?'.Rbuildignore' ??Writing?'LICENSE'再check一下:
>?load_all() Loading?toypackages >?devtools::check() Updating?toypackages?documentation Loading?toypackages Writing?NAMESPACE Writing?NAMESPACE ─?Building?────────────────────────────────?toypackages?─ Setting?env?vars: ●?CFLAGS????:?-Wall?-pedantic?-fdiagnostics-color=always ●?CXXFLAGS??:?-Wall?-pedantic?-fdiagnostics-color=always ●?CXX11FLAGS:?-Wall?-pedantic?-fdiagnostics-color=always ───────────────────────────────────────────── ???checking?for?file?‘/Users/macos/test/toypackages/DESCRIPTION’?... ─??preparing?‘toypackages’: ???checking?DESCRIPTION?meta-information?... ─??checking?for?LF?line-endings?in?source?and?make?files?and?shell?scripts ─??checking?for?empty?or?unneeded?directories ─??building?‘toypackages_0.0.0.9000.tar.gz’─?Checking?────────────────────────────────?toypackages?─ Setting?env?vars: ●?_R_CHECK_CRAN_INCOMING_REMOTE_:?FALSE ●?_R_CHECK_CRAN_INCOMING_???????:?FALSE ●?_R_CHECK_FORCE_SUGGESTS_??????:?FALSE ●?NOT_CRAN??????????????????????:?true ──?R?CMD?check?─────────────────────────────────────────────────────────────────────────── ─??using?log?directory?‘/private/var/folders/s1/z_mkhs313cgbplp38856sxk40000gn/T/Rtmp7UAmyG/toypackages.Rcheck’ ─??using?R?version?4.0.2?(2020-06-22) ─??using?platform:?x86_64-apple-darwin17.0?(64-bit) ─??using?session?charset:?UTF-8 ─??using?options?‘--no-manual?--as-cran’ ???checking?for?file?‘toypackages/DESCRIPTION’ ─??this?is?package?‘toypackages’?version?‘0.0.0.9000’ ─??package?encoding:?UTF-8 ???checking?package?namespace?information ???checking?package?dependencies?(1.7s) ???checking?if?this?is?a?source?package ???checking?if?there?is?a?namespace?... ???checking?for?executable?files?... ???checking?for?hidden?files?and?directories ???checking?for?portable?file?names ???checking?for?sufficient/correct?file?permissions?... ???checking?serialization?versions ???checking?whether?package?‘toypackages’?can?be?installed?(1.1s) ???checking?installed?package?size?... ???checking?package?directory?... N??checking?for?future?file?timestamps?(6.7s)unable?to?verify?current?time ???checking?DESCRIPTION?meta-information?... ???checking?top-level?files?... ???checking?for?left-over?files ???checking?index?information ???checking?package?subdirectories?... ???checking?R?files?for?non-ASCII?characters?... ???checking?R?files?for?syntax?errors?... ???checking?whether?the?package?can?be?loaded?... ???checking?whether?the?package?can?be?loaded?with?stated?dependencies?... ???checking?whether?the?package?can?be?unloaded?cleanly?... ???checking?whether?the?namespace?can?be?loaded?with?stated?dependencies?... ???checking?whether?the?namespace?can?be?unloaded?cleanly?... ???checking?loading?without?being?on?the?library?search?path?... ???checking?dependencies?in?R?code?... ???checking?S3?generic/method?consistency?(375ms) ???checking?replacement?functions?... ???checking?foreign?function?calls?... ???checking?R?code?for?possible?problems?(1.5s) ???checking?Rd?files?... ???checking?Rd?metadata?... ???checking?Rd?line?widths?... ???checking?Rd?cross-references?... ???checking?for?missing?documentation?entries?... ???checking?for?code/documentation?mismatches?... ???checking?Rd?\usage?ps?(463ms) ???checking?Rd?contents?... ???checking?for?unstated?dependencies?in?examples?... ???checking?examples?(444ms) ???checking?for?non-standard?things?in?the?check?directory ???checking?for?detritus?in?the?temp?directorySee‘/private/var/folders/s1/z_mkhs313cgbplp38856sxk40000gn/T/Rtmp7UAmyG/toypackages.Rcheck/00check.log’for?details.──?R?CMD?check?results?───────────────────────────────────────?toypackages?0.0.0.9000?──── Duration:?14.9s>?checking?for?future?file?timestamps?...?NOTEunable?to?verify?current?time0?errors???|?0?warnings???|?1?note?x可以為函數加上一些幫助文檔,文檔放在man文件夾里面后綴是.Rd是Rmarkdown文件
寫文檔的時候可以直接在函數上面以標準格式的形式寫注釋,再使用roxygen2包來生成.Rd文件
打開fbind.R文件,然后點擊Code>Insert roxygen skeleton(注意,這個時候光標要在函數內部)
就可以插入模板了,可以在此基礎上進行修改:
image-20200916104410372#'?Title #' #'?@param?a? #'?@param?b? #' #'?@return #'?@export #' #'?@examples fbind?<-?function(a,?b)?{factor(c(as.character(a),?as.character(b))) } #'?bind?two?factors #' #'Create?a?new?factor?from?two?existing?factors,?where?the?new?factor's?levels #'are?the?union?of?the?levels?of?the?input?factors. #' #'?@param?a?factor #'?@param?b?factor #' #'?@return?factor #'?@export #' #'?@examples #'?fbind(iris$Species[c(1,?51,?101)],?PlantGrowth$group[c(1,?11,?21)]) fbind?<-?function(a,?b)?{factor(c(as.character(a),?as.character(b))) }使用document函數來生成man/fbind.Rd文件:
>?document() Updating?toypackages?documentation Loading?toypackages Writing?NAMESPACE Writing?NAMESPACEDocument 快捷鍵:Ctrl + Shift + D (Windows & Linux) or Cmd + Shift + D (macOS).
然后我們就可以查看文檔了:
>??fbind Rendering?development?documentation?for?'fbind' image-20200916105225723通過install函數安裝這個包:
>?install() ???checking?for?file?‘/Users/macos/test/toypackages/DESCRIPTION’?... ─??preparing?‘toypackages’: ???checking?DESCRIPTION?meta-information?... ─??checking?for?LF?line-endings?in?source?and?make?files?and?shell?scripts ─??checking?for?empty?or?unneeded?directories ─??building?‘toypackages_0.0.0.9000.tar.gz’Running?/Library/Frameworks/R.framework/Resources/bin/R?CMD?INSTALL?\/var/folders/s1/z_mkhs313cgbplp38856sxk40000gn/T//Rtmp7UAmyG/toypackages_0.0.0.9000.tar.gz?\--install-tests? *?installing?to?library?‘/Library/Frameworks/R.framework/Versions/4.0/Resources/library’ *?installing?*source*?package?‘toypackages’?... **?using?staged?installation **?R **?byte-compile?and?prepare?package?for?lazy?loading **?help ***?installing?help?indices **?building?package?indices **?testing?if?installed?package?can?be?loaded?from?temporary?location **?testing?if?installed?package?can?be?loaded?from?final?location **?testing?if?installed?package?keeps?a?record?of?temporary?installation?path *?DONE?(toypackages)現在重啟Rstudio,測試包:
Restarting?R?session...>?library(toypackage) >?a?<-?factor(c("character",?"hits",?"your",?"eyeballs")) >?b?<-?factor(c("but",?"integer",?"where?it",?"counts")) >? >?fbind(a,?b) [1]?character?hits??????your??????eyeballs??but???????integer???where?it??counts??? Levels:?but?character?counts?eyeballs?hits?integer?where?it?your前面對于函數的測試都是非正式和規范的,接下來我們對這個函數做一些正式的單元測試(unit tests)
使用use_testthat()函數:
library(devtools) use_testthat() ??Setting?active?project?to?'/Users/macos/test/toypackages' ??Adding?'testthat'?to?Suggests?field?in?DESCRIPTION ??Creating?'tests/testthat/' ??Writing?'tests/testthat.R' ●?Call?`use_test()`?to?initialize?a?basic?test?file?and?open?it?for?editing.這個函數做了這些變動:
在DESCRIPTION中加入了suggests:testthat:
Package:?toypackages Title:?learn?how?to?bulid?a?package Version:?0.0.0.9000 Authors@R:?person(given?=?"aa",family?=?"bb",role?=?c("aut",?"cre"),email?=?"11111@qq.com",comment?=?c(ORCID?=?"YOUR-ORCID-ID")) Description:?learn?how?to?bulid?a?package. License:?MIT?+?file?LICENSE Encoding:?UTF-8 LazyData:?true Roxygen:?list(markdown?=?TRUE) RoxygenNote:?7.1.1 Suggests:?testthat創建了tests/testthat文件夾,并且在tests文件夾里生成了testthat.R文件:
image-20200916105702127
接下來我們使用use_test()函數創建一個測試文件:
>?use_test("fbind") ??Writing?'tests/testthat/test-fbind.R' ●?Modify?'tests/testthat/test-fbind.R'生成的文件tests/testthat/test-fbind.R中原始內容為:
test_that("multiplication?works",?{expect_equal(2?*?2,?4) })修改生成的默認文件,寫入以下測試內容:
test_that("fbind()?binds?factor?(or?character)",?{x?<-?c("a",?"b")x_fact?<-?factor(x)y?<-?c("c",?"d")z?<-?factor(c("a",?"b",?"c",?"d"))expect_identical(fbind(x,?y),?z)expect_identical(fbind(x_fact,?y),?z) })進行測試:
>?test() Loading?toypackages Testing?toypackages ??|??OK?F?W?S?|?Context ??|???2???????|?fbind═?Results?════════════════════════════════════════ OK:???????2 Failed:???0 Warnings:?0 Skipped:??0test 快捷鍵:Ctrl + Shift + T (Windows & Linux) or Cmd + Shift + T (macOS)
有些時候我們想要在自己的包中調用其他包的函數,可以使用use_package()函數
比如想要對因子創建排序的頻率表,需要引用forcats::fct_count():
>?use_package("forcats") ??Adding?'forcats'?to?Imports?field?in?DESCRIPTION ●?Refer?to?functions?with?`forcats::fun()`這個函數向DESCRIPTION文件中添加了Imports::forcats:
Package:?toypackages Title:?learn?how?to?bulid?a?package Version:?0.0.0.9000 Authors@R:?person(given?=?"aa",family?=?"bb",role?=?c("aut",?"cre"),email?=?"11111@qq.com",comment?=?c(ORCID?=?"YOUR-ORCID-ID")) Description:?learn?how?to?bulid?a?package. License:?MIT?+?file?LICENSE Encoding:?UTF-8 LazyData:?true Roxygen:?list(markdown?=?TRUE) RoxygenNote:?7.1.1 Suggests:?testthat Imports:?forcats最后可以use_readme_rmd()和build_readme()生成readme文件,對R包進行說明,包括包的描述;安裝;示例
基本的流程是:
create_package 創建一個R包
use_r()寫函數,插入roxygen注釋和tag,使用document()生成文檔
load_all()載入包
use_***_license 添加license
install安裝包
use_testthat() 和use_test()生成測試文件;test()進行測試
check對整個包進行檢查
use_readme_rmd()和build_readme()生成readme文件
下面對上圖所展示的R包的結構做具體介紹
創建包
注意包的名稱只能包含字母數字和點號(不建議使用點號,可能會和文件拓展名或者S3方法混淆);必須以字母開頭并且不能以點號結尾
創建包使用usethis::create_package(path)函數
如何將之前已經存在的源碼包文件夾轉化成一個Rstudio項目:
File > New Project > Existing Directory
使用create_package()參數是已經存在的目錄
使用usethis::use_rstudio() 在已經存在的源碼包目錄內部使用
注意在開發的時候,工作路徑最好是源碼包的top-level
元數據 DESCRIPTION
元數據是存儲在DESCRIPYION中,Rstudio和devtools將含有該文件的目錄就認為是包目錄
初始化的內容(usethis::create_package):
Package:?toypackages Title:?What?the?Package?Does?(One?Line,?Title?Case) Version:?0.0.0.9000 Authors@R:?person(given?=?"First",family?=?"Last",role?=?c("aut",?"cre"),email?=?"first.last@example.com",comment?=?c(ORCID?=?"YOUR-ORCID-ID")) Description:?What?the?package?does?(one?paragraph). License:?`use_mit_license()`,?`use_gpl3_license()`?or?friends?topick?a?license Encoding:?UTF-8 LazyData:?true Roxygen:?list(markdown?=?TRUE) RoxygenNote:?7.1.1DESCRIPTION文件的格式叫做DCF (Debian contral format);每一行都有一個filed名稱和值,兩者用冒號分開,當值有多行的時候需要縮進
Title字段一般比較短,只顯示65個字符
Version表示版本號,版本號最少要有2個整數中間用點號或者橫線隔開
推薦的格式:
releaesd版本由3個數字構成:<major>.<minor>.<patch>
In-development版本由4個數字構成,第四個是開發版本,從9000開始,所以包的第一個版本是0.0.0.9000
Auther@R字段是作者的信息,是R代碼:
Authors@R:?person(given?=?"First",family?=?"Last",role?=?c("aut",?"cre"),email?=?"first.last@example.com",comment?=?c(ORCID?=?"YOUR-ORCID-ID"))person函數有4個主要的參數:
前兩個參數(位置)是名字,given在前(名),family在后(姓)
email地址
role有四個:
cre creator or maintainer 有問題時應該聯系額人
aut 對包貢獻最大的人
ctb 貢獻者
cph copyright holder ?如果版權是作者以外的人或機構,要注明
Description是對包的描述,每行不超過80個字符,行間使用4個空格分開
license 字段可以是開源許可或者是一個文件file LICENCE
開源許可一般有3個:
MIT
GPL-2/GPL-3
CC0
DESCRIPTION中會列出我們的R包需要依賴的R包
描述依賴用的是Imports和Suggests:
Imports:pkgname Suggests:pkgname兩者的區別:
Imports描述的是包工作所必需的包,在我們的包被安裝的時候,如果這些包之前沒有被安裝,這個時候會被安裝
Suggests不是必需安裝的,可能在示例數據,運行測試,創建vignettes或者包里面只有少量函數使用這些包,所以我們要在需要這些包的函數里面檢查這些包是否安裝(requireNamesapce(x,quietly=TRUE)):
最簡單的方式去添加Imports和suggests就是使用usethis::use_package():
use_package(package,?type?=?"Imports",?min_version?=?NULL)type參數指定是Imports還是Suggests,min_version參數指定包的最低版本
還有其他的fields可以用來表述依賴:
Depends 在R2.14之前只有這一種方法來表示依賴
LinkingTo在這里列出的包依賴于其他包的C或者C++代碼
Enhances在這里列出的包可以被我們的包增強
Depends和Imports的區別
當R調用一個函數的時候,會先在全局環境中搜索,如果沒有在去search path中搜索
search path 是attached的包列表,可以通過search函數來獲得當前的search path:
>?search()[1]?".GlobalEnv"????????"tools:rstudio"?????"package:stats"?????"package:graphics"?[5]?"package:grDevices"?"package:utils"?????"package:datasets"??"package:methods"??[9]?"Autoloads"?????????"package:base"?????>?library(tidyverse) ─?Attaching?packages?─────────────────────────?tidyverse?1.3.0?─ ??ggplot2?3.3.2???????purrr???0.3.4 ??tibble??3.0.3???????dplyr???1.0.0 ??tidyr???1.1.0???????stringr?1.4.0 ??readr???1.3.1???????forcats?0.5.0 ─?Conflicts?──────────────────────────?tidyverse_conflicts()?─ x?dplyr::filter()?masks?stats::filter() x?dplyr::lag()????masks?stats::lag() >?search()[1]?".GlobalEnv"????????"package:forcats"???"package:stringr"???"package:dplyr"????[5]?"package:purrr"?????"package:readr"?????"package:tidyr"?????"package:tibble"???[9]?"package:ggplot2"???"package:tidyverse"?"tools:rstudio"?????"package:stats"???? [13]?"package:graphics"??"package:grDevices"?"package:utils"?????"package:datasets"? [17]?"package:methods"???"Autoloads"?????????"package:base"????load和attach的區別:
loading會載入代碼,數據和DLL(動態共享庫),S3,S4方法并運行.onLoad load后包會在內存中,但是不在search path里面所有只有通過::才能使用包的元素(::也會自動載入包);也可以使用requireNamespace() or loadNamespace()來載入包
attaching 將包放到search path中,library()和require都會先load包再attach
Depends和Imports的唯一的區別就是Depends ?attach包;而Imports只load包;一般情況下只需在Imports里面列出需要的包,寫函數的時候使用::來獲取需要的函數;另外Imports或者Depends里面的包在安裝的時候如果沒有安裝會自動安裝,確保我們可以使用::
在DESCRIPTION中還可以使用URL字段提供額外的網址,使用BugReports提供錯誤報告額網址:
URL:?https://yihui.name/knitr/ BugReports:?https://github.com/yihui/knitr/issues對象文檔化 man/
標準方法是在man/文件夾下寫.Rd文件,再渲染成HTML和PDF;但是可以使用roxygen2可以將特定格式的注釋轉化成.Rd文件,roxygen2除了生成.Rd文件外還可以更改NAMESPACE和DESVRIPTION中的Collate字段
基本的流程有4步:
將roxygen格式的注釋添加到.R文件中
使用devtools::document()(或者使用快捷鍵:Ctrl/Cmd + Shift + D)將注釋轉化成.Rd文件
使用?預覽文檔
修改,直到滿意
舉個例子:
use_r("add") #'?Add?together?two?numbers #'? #'?@param?x?A?number. #'?@param?y?A?number. #'?@return?The?sum?of?\code{x}?and?\code{y}. #'?@examples #'?add(1,?1) #'?add(10,?1) add?<-?function(x,?y)?{x?+?y } devtools::document()現在man/add.Rd文件就生成了:
%?Generated?by?roxygen2:?do?not?edit?by?hand %?Please?edit?documentation?in?R/add.R \name{add} \alias{add} \title{Add?together?two?numbers} \usage{ add(x,?y) } \arguments{ \item{x}{A?number.}\item{y}{A?number.} } \value{ The?sum?of?\code{x}?and?\code{y}. } \description{ Add?together?two?numbers } \examples{ add(1,?1) add(10,?1) }使用?就可以調出文檔界面:
?add image-20200916142434573可以使用Rstudio的install&Restart功能(bulid里面),他會完全重建包,包括更新所有的文檔,安裝包,重啟R并且重新載入我們的包;進行這個操作后我們再?一下
image-20200916144829096點擊index就會到顯示所有函數的頁面:
image-20200916144852749Roxygen 注釋
Roxygen注釋以#'開頭,并且在函數的前面;所有在函數前面的roxygen行叫做一個block,每一行不超過80個字符
block被拆分成不同的tags,格式為:@tagName details; tag的內容包括一個tag名稱后到下一個tag起始
因為@在tag里有特殊含義,所以當文檔中出現@的時候要用@@來表示(比如email或者S4對象的slots)
每一個block在第一個tag前會包含一些文字,這部分叫做introduction,會被特殊地解析:
第一句是文檔的名稱,在文檔頁面的頂部顯示
第二段是描述
第三段及以后 是details,在幫助文檔中顯示在參數描述的后面
可以使用@p tag加其他的信息,p的title必須是句子以冒號結尾如:
#'?@p?Warning: #'?Do?not?operate?heavy?machinery?within?8?hours?of?using?this?function.還有兩個有用的tag:
@seealso 可以導向其他的內容,如
web資源 \url{https://www.r-project.org}
包中的內容:\code{\link{functioname}}
其他包中的內容\code{\link[packagename]{functioname}}
還有一些tag可以方便用戶找到文檔:
@aliases alias1 alias2 ... 添加額外的別名,可以使用?
@keywords keyword1 keyword2 ...添加關鍵詞
注意 名稱和描述也可以加上tag,@title和@description,但是一般不用加
文檔化函數
大部分函數有3個tag: @param, @examples ?@return
@param name description ?@param參數后面接參數的名稱和描述;描述必須以大寫字母開頭,點號結尾,可以是多行甚至多段;也可以同時對多個參數進行說明,用逗號隔開如:@param x,y Numeric vectors.
@examples 提供如何使用這個函數的R代碼,可以使用\dontrun{}來包含會報錯的代碼;還可以將示例放到另外的文件夾中,并使用@example path/relative/to/package/root來插入,注意這種用法是@example沒有s
@return description 對輸出的描述
下面是sum函數的例子:
#'?Sum?of?vector?elements #' #'?\code{sum}?returns?the?sum?of?all?the?values?present?in?its?arguments. #' #'?This?is?a?generic?function:?methods?can?be?defined?for?it?directly #'?or?via?the?\code{\link{Summary}}?group?generic.?For?this?to?work?properly, #'?the?arguments?\code{...}?should?be?unnamed,?and?dispatch?is?on?the #'?first?argument. #' #'?@param?...?Numeric,?complex,?or?logical?vectors. #'?@param?na.rm?A?logical?scalar.?Should?missing?values?(including?NaN) #'???be?removed? #'?@return?If?all?inputs?are?integer?and?logical,?then?the?output #'???will?be?an?integer.?If?integer?overflow #'???\url{https://en.wikipedia.org/wiki/Integer_overflow}?occurs,?the?output #'???will?be?NA?with?a?warning.?Otherwise?it?will?be?a?length-one?numeric?or #'???complex?vector. #' #'???Zero-length?vectors?have?sum?0?by?definition.?See #'???\url{https://en.wikipedia.org/wiki/Empty_sum}?for?more?details. #'?@examples #'?sum(1:10) #'?sum(1:5,?6:10) #'?sum(F,?F,?F,?T,?T) #' #'?sum(.Machine$integer.max,?1L) #'?sum(.Machine$integer.max,?1) #' #'?\dontrun{ #'?sum("a") #'?} sum?<-?function(...,?na.rm?=?TRUE)?{}文檔化數據
有3個主要的方法可以在包中包含數據:
如果想要在包中包含二進制數據,并且可以被用戶使用,將這些數據放在data/文件夾中,這是放示例數據的最好的地方
如果想要包含解析后的數據,并且用戶不可以使用,將這些數據放到R/sysdata.rda,這些數據可以是函數運行所需要的
如果想要存儲原始數據,可以放到inst/extdata里面
data/文件夾中應該是.Rdata格式,含有單個對象,并且名字和文件名是一樣的,可以使用usthis::use_data()來創建
>?x?<-?sample(1000) >?usethis::use_data(x,?mtcars) ??Adding?'R'?to?Depends?field?in?DESCRIPTION ??Creating?'data/' ??Saving?'x',?'mtcars'?to?'data/x.rda',?'data/mtcars.rda' ●?Document?your?data?(see?'https://r-pkgs.org/data.html')如果在DESCRIPTION文件中含有LazyData: true的時候,這些數據只在使用的時候才會load,使用 usethis::create_package()會自動加上
在data/文件夾中的數據是處理后的數據,可以保留產生這些數據的原始數據(存放在data-raw中)和代碼,可以使用usethis::use_data_raw()來完成,這個函數創建一個文件夾data-raw和DATASET.R文件,另外要注意在.Rbuildignore中加上這些原始數據,以便在build包中忽略這些文件:
usethis::use_data_raw() ??Creating?'data-raw/' ??Writing?'data-raw/DATASET.R' ●?Modify?'data-raw/DATASET.R' ●?Finish?the?data?preparation?script?in?'data-raw/DATASET.R' ●?Use?`usethis::use_data()`?to?add?prepared?data?to?package對這些數據進行docment的時候,是對這些數據的名稱進行說明,并存放到R/目錄下,比如在ggplot2包中對diamonds數據的說明存放在R/data.R中:
#'?Prices?of?50,000?round?cut?diamonds. #' #'?A?dataset?containing?the?prices?and?other?attributes?of?almost?54,000 #'?diamonds. #' #'?@format?A?data?frame?with?53940?rows?and?10?variables: #'?\describe{ #'???\item{price}{price,?in?US?dollars} #'???\item{carat}{weight?of?the?diamond,?in?carats} #'???... #'?} #'?@source?\url{http://www.diamondse.info/} "diamonds"對數據進行document有額外的兩個tag:
@format 是對數據的overview,包含對每個變量的說明
@source 是對數據來源的說明,通常是網址\url{}
R/sysdata.rda的數據是函數所需的數據,可以使用usethis::use_data()函數,但是要加上internal = TRUE參數:
x?<-?sample(1000) usethis::use_data(x,?mtcars,?internal?=?TRUE)??Saving?'x',?'mtcars'?to?'R/sysdata.rda'文檔化包
除了對函數,數據進行說明之外,也可以為整個包提供一個幫助頁面,這個頁面可以通過類似package?dplyr調出
對包進行document的時候,由于包沒有相關聯的對象,所以我們需要documentNULL然后再用tag @docType package 和 @name <package-name>來標記,也可以加p tag:
#'?foo:?A?package?for?computating?the?notorious?bar?statistic #' #'?The?foo?package?provides?three?categories?of?important?functions: #'?foo,?bar?and?baz. #'? #'?@p?Foo?functions: #'?The?foo?functions?... #' #'?@docType?package #'?@name?foo NULL #>?NULL測試 tests/
自動化測試主要使用的包是testthat
初始設置使用usethis::use_testthat()
這個函數做了一下三件事:
創建tests/testthat文件夾
將testthat加到DESRIPTION里面的Suggests字段
創建tests/testthat.R文件
基本流程是:
修改代碼或者測試
使用Ctrl/Cmd + Shift + T或者devtools::test()來測試包
重復直到通過測試
測試文件是在tests/testthat/里面,并且文件的名稱要以test開頭,下面是stringr包的一個test文件
context("String?length") library(stringr)test_that("str_length?is?number?of?characters",?{expect_equal(str_length("a"),?1)expect_equal(str_length("ab"),?2)expect_equal(str_length("abc"),?3) }) #>?Test?passed?????test_that("str_length?of?factor?is?length?of?level",?{expect_equal(str_length(factor("a")),?1)expect_equal(str_length(factor("ab")),?2)expect_equal(str_length(factor("abc")),?3) }) #>?Test?passed?????test_that("str_length?of?missing?is?missing",?{expect_equal(str_length(NA),?NA_integer_)expect_equal(str_length(c(NA,?1)),?c(NA,?1))expect_equal(str_length("NA"),?2) }) #>?Test?passed?????測試是分層的: expectations→tests→files
expectation 以expect_開頭的函數
test 是以單元組合起來的,一個test里面測試的是一個功能,以test_that開頭
file 將多個相關的測試組合起來 以context()開頭
expectation
所有的expectation有相似的結構:
以expect_開頭
有兩個參數,第一個是函數運行的結果,第二個是期望的結果
如果實際運行的結果和期望的不一樣,就會報錯
最重要的expectation函數有:
測試相等:expect_equal()和expect_identical() expect_equal()是基于all.equal()的,而expect_identical()是基于identical,所以前者是估計的,后者是精確的:
expect_equal(10,?10?+?1e-7) expect_identical(10,?10?+?1e-7) #錯誤:?10?not?identical?to?10?+?1e-07. #Objects?equal?but?not?identicalexpect_match 是基于grepl
string?<-?c("Testing?is?fun!","abc")#?expect_match(string,?"Testing")? #?錯誤:?`string`?does?not?match?"Testing". #?Actual?values: #???*?Testing?is?fun! #???*?abc expect_match(string,?"Testing",all?=?FALSE)#?Fails,?match?is?case-sensitive expect_match(string,?"testing")#?Additional?arguments?are?passed?to?grepl: expect_match(string,?"testing",?ignore.case?=?TRUE,all?=?FALSE)還有幾個expect_match()的變體:expect_output()匹配輸出;expect_message()檢查信息;expect_warning()檢查warning;expect_error()檢查錯誤
a?<-?list(1:10,?letters) str(a) #?List?of?2 #?$?:?int?[1:10]?1?2?3?4?5?6?7?8?9?10 #?$?:?chr?[1:26]?"a"?"b"?"c"?"d"?... expect_output(str(a),?"List?of?2") expect_output(str(a),?"int?\\[1:10\\]") ##?or? expect_output(str(a),?"int?[1:10]",fixed=TRUE)expect_message(library(mgcv),?"This?is?mgcv")expect_is()檢查某個對象是不是繼承自一個特定的類:
model?<-?lm(mpg?~?wt,?data?=?mtcars) class(model) #[1]?"lm" expect_is(model,?"lm") expect_is(model,?"glm") #錯誤:?`model`?inherits?from?`lm`?not?`glm`.expect_true() and expect_false()當沒有其他的expectation可用時使用
tests
使用test_that來寫測試,這個函數有兩個參數:第一個是test的名稱(一句話描述),第二個是測試代碼塊,以{}括起來,由多個expectations組成
file
使用context寫一個簡短的介紹文件中的測試內容
roxytest
roxytest以roxygen2注釋的形式來寫測試,可以自動生成tests/testthat/里的測試文件(.R)
需要首先在DESCRIPTION文件中加入以下的內容:
Roxygen:?list(roclets?=?c("namespace",?"rd",?"roxytest::testthat_roclet","roxytest::param_roclet","roxytest::return_roclet")) #'?bind?two?factors #' #'Create?a?new?factor?from?two?existing?factors,?where?the?new?factor's?levels #'are?the?union?of?the?levels?of?the?input?factors. #' #'?@param?a?factor #'?@param?b?factor #' #'?@return?factor #'?@export #' #'?@examples #'?fbind(iris$Species[c(1,?51,?101)],?PlantGrowth$group[c(1,?11,?21)]) #' #'?@tests #'?x?<-?c("a",?"b") #'?x_fact?<-?factor(x) #'?y?<-?c("c",?"d") #'?z?<-?factor(c("a",?"b",?"c",?"d")) #'?expect_identical(fbind(x,?y),?z) #'?expect_identical(fbind(x_fact,?y),?z)fbind?<-?function(a,?b)?{factor(c(as.character(a),?as.character(b))) }可以使用@tests和@testexamples tag來在注釋中寫測試代碼
再運行roxygen2::roxygenise()就會在tests/testthat/下面生成一個test-roxytest-tests-fbind文件:
#?Generated?by?roxytest:?Do?not?edit?by?hand!context("File?R/fbind.R:?@tests")test_that("Function?fbind()?@?L27",?{x?<-?c("a",?"b")x_fact?<-?factor(x)y?<-?c("c",?"d")z?<-?factor(c("a",?"b",?"c",?"d"))expect_identical(fbind(x,?y),?z)expect_identical(fbind(x_fact,?y),?z) })Namespace
Namespace就是給名字提供一個空間,比如我們在使用::的時候:已知plyr和Hmisc包都含有summarize這個函數,如果我們先載入了plyr再載入了Hmisc那么在使用summarize的時候就會在Hmisc的namespace中尋找summarize;相反,如果先載入Hmisc,那么就會使用plyr中的summarize函數,所以為了避免混淆,我們需要使用::來指定搜索的namespace
在NAMESPACE文件中主要使用imports和exports
imports 將外部的(其他包中)的函數導入
exports ?規定哪些函數在包外部可用
下面是testthat包的NAMESPACE的一個片段:
#?Generated?by?roxygen2?(4.0.2):?do?not?edit?by?hand S3method(as.character,expectation) S3method(compare,character) export(auto_test) export(auto_test_package) export(colourise) export(context) exportClasses(ListReporter) exportClasses(MinimalReporter) importFrom(methods,setRefClass) useDynLib(testthat,duplicate_) useDynLib(testthat,reassign_function)NAMESPACE里的每一行都是一個指令,描述了一個R對象,是從我們的包導出給外部使用,還是從其他的包導入供我們使用
一共有8個namesapce指令,4個表示exports,4個表示imports
4個exports為:
export(): 導出函數,包括S3 S4泛型函數.
exportPattern(): 導出可以匹配模式的所有函數
exportClasses(), exportMethods(): 導出所有S4類和方法
S3method(): 導出S3方法
4個imports為:
import(): 導入一個包的所有函數
importFrom(): 導入選擇的函數(包括S4泛型函數)
importClassesFrom(), importMethodsFrom(): 導入S4類和方法
useDynLib(): 從C導入一個函數
這些都是不需要手動改的,可以使用roxygen2來生成
基本流程是:
生成namespace和生成函數的document是一樣的,在R代碼前面使用roxygen塊(以#'開頭)和tags(以@開頭)
基本流程為:
在.R文件前面加上roxygen注釋
運行devtools::document()或者使用快捷鍵Ctrl/Cmd + Shift + D將注釋轉化為.Rd文件
查看NAMESPACE,運行test確保正確
重復直到測試通過
Exports
要export一個對象,需要在roxgen注釋塊里面加上@export標簽,如:
#'?@export foo?<-?function(x,?y,?z)?{... }Imports
注意DESCIPRTION中的Imports字段和NAMESPACE中的import()命令的區別:
Imports字段只是確保當我們的包被安裝的時候這些包也被安裝了,并沒有使函數可用,如果想要用這些函數就需要使用::或者import命令來導入函數,所以所有被NAMESPACE提到的包必須在DESCIPRTION的Imports或者Depends字段中
如果我們使用來自其他包的少量函數,建議是在DESCRIPYION文件的Imports字段中包含包的名稱,再使用::來調用函數,而如果我們需要重復使用函數,這個時候使用::就不太方便,可以使用@importFrom pkg fun的形式來導入,另外這種方法也有性能上的一些優勢:::會多用大概5微秒的時間;我們也可以使用類似的方法導入操作符如:@importFrom magrittr %>%(其實也是一種函數);如果我們需要使用另一個包的大量函數,可以使用@import package來導入另一個包的所有函數;另外要使得我們包中每個函數都可以使用外部包的函數,就需要對NULL加上注釋:
#'?@importFrom?pkg?fun NULLOthers
usethis::use_pipe 在R包中使用管道符
usethis::use_tidyeval 在R包中使用非標準計算
pkgdown 為包創建網站
rhub 對R包進行多平臺的測試
在包中使用管道符%>% 可以使用usethis::use_pipe
>?use_pipe(export?=?TRUE) ??Adding?'magrittr'?to?Imports?field?in?DESCRIPTION ??Writing?'R/utils-pipe.R' ●?Run?`devtools::document()`?to?update?'NAMESPACE'這個函數將magrittr包加到DESCRIPTION的Imports字段里面;生成R/utils-pipe.R文件;將管道符import,如果設置export=TRUE,可以使得管道符在外部可用(不需要用戶再去導入magrittr包了)
#'?Pipe?operator #' #'?See?\code{magrittr::\link[magrittr:pipe]{\%>\%}}?for?details. #' #'?@name?%>% #'?@rdname?pipe #'?@keywords?internal #'?@export #'?@importFrom?magrittr?%>% #'?@usage?lhs?\%>\%?rhs NULL參考:
R packages 2nd edition
往期精品(點擊圖片直達文字對應教程)
后臺回復“生信寶典福利第一波”或點擊閱讀原文獲取教程合集
總結
以上是生活随笔為你收集整理的把函数包起来就是一个R包 - 完整开发指南的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 转录组测序多少生物重复合适?2个?3个?
- 下一篇: 这篇纯数据分析文章被拒8次,发到行业顶刊