R包编写详细教程

最近在写一个R包,遇到了很多的坑,网上几乎没有一个教程能涵盖我遇到的所有问题,因此决定写一个教程。

当然,这不是说网上的教程都不好,网上那些教程都是写一个非常简单的示例,让你很快能够建立一个包,但是我们写包通常不是为了练练手,而是真的有开发的需求的,涉及到的点要比网上的示例和教程都多得多,动不动就会踩坑。所以决定把我踩到的坑都记录下来,方便自己也方便别人。

本文使用的环境是windows10系统Rstudio IDE,因此在写包的时候,需要先安装Rtools才能打包,如果要生成manual.pdf,同时还需要安装Latex的排版引擎,比如MiKTeX,本文不详述。还有如果你想写一个使用手册,最好使用RMarkdown,用它可以写出非常漂亮的使用手册,windows系统在Rstudio里可以通过install.packages('rmarkdown')直接安装,linux系统不知道如何安装可以看Linux配置R markdown

本人是第一次写包,有些问题虽然各种查资料解决了,但是可能也没有真正的解决,欢迎各位在评论里批评指正。

写一个R包,大概要经过以下几个步骤:

1)准备一个R包,含有一些必需文件和文件夹。
2)准备R包需要用到的函数和测试数据,并写函数和测试数据的注释。
3)将函数封装成一个包。
4)写说明书。(非必须)
5)将R包上传到github,方便修改和维护。(非必须)
6)补充:遇到问题


以下介绍各个步骤的要点:

1、准备一个R包


在介绍这个之前,需要先了解一下R包的结构。我们可以通过Rstudio来获得一个基本的R包模板,模板里就是R包的结构和必需内容。通过Rstudio→File→New Project→New Directory→R package,填写R包名称和创建路径,获得一个名为“myPackage”的R包,里面有以下的文件或文件夹,是一个R包必须有的:

myPackage(见图1)
   | ------  man文件夹:用于存放.Rd文件的文件夹,里面有一个示例文件hello.Rd。
   | ------  R文件夹:用于存放R函数的文件夹,里面有一个示例文件hello.R。
   | ------  DESCRIPTION: R包的描述文件。
   | ------  NAMESPACE:用来指定R包输入和输出的包,还有一些特殊的名称。

   此外,以后如果想要加载这个包的编辑环境,可以用Rstudio→File→Open Project加载myPackage.Rproj文件,很方便就可以加载环境。

图1:R包的创建和结果

其中,由两个部分不需要做任何人工改动
1)NAMESPACE 命名空间文件,最好不要经过人工编辑,roxygen2包可以帮助你新建这个文件,建议你先把这个文件删掉,否则没办法新建。
2)man文件夹,存放.Rd文件,全部可以由roxygen2包自动生成,不经过人工编辑。

以下是需要人工进行修改的部分:

1.1 R文件夹

这个文件夹用来存放你的所有R函数。如果你的包使用了其他的语言,需要新建一个src文件夹来分开存放。详见参考资料[1]

1.2 DESCRIPTION文件

这个文件描述R包的重要信息,详细可以参照Hadley WickhamR包描述部分说明,里面包含内容用名称+冒号+空格为开头。我们就根据Rstudio生成的模板进行修改即可,里面的项目和内容,基本上都能见名知义。可能会影响后面检查包(check)出现warning或note的地方如下:

1) Depends
格式为R+空格+左括弧+大于号+等于号+空格+版本号
如果你想声明R (>= 3.4.3),需要写成R (>= 3.4.0), 否则会出现警告:Dependence on R version '3.4.3' not with patchlevel 0。

另外,depends可以写R版本,以及你需要声明特殊版本号的R包,如果不需要就仅写R版本即可。否则check时会出现提示。

import和suggest主要取决于这个包是否参与你包的主要功能,如果是一些辅助功能,有了这个包你的软件功能会更加强大,可以放在suggest里。

2)每个内容末尾句号的问题
除了Description末尾必须有实心句号外,其他所有,包括Title 末尾均不能有句号,否则就会出现警告。(这点网上也有人吐槽,这不合理,标题也应该是完整的句子。)

3)Encoding
该文件里必须声明 Encoding: UTF-8,没有这一行就会报错。

4)License
该文件里必须声明License,不知道选哪个可以参考R CRAN上关于License的说明

还需要注意的是这个文件,包括后面的R脚本注释,最好不要超过每行80个字符,否则在自动生成注释文件和manual的时候会超出边框,check的时候也会警告。

2、准备R包需要用到的函数和测试数据,并写函数和测试数据的注释。


这一部分是R包的核心。关于R包函数怎么写笔者就不详述了,这里介绍一下如何写函数的注释。原本在写R包的时候,需要些一些后缀为rd的文件作为函数的说明文件,工作量很大,又需要学习latex,很不方便。自从有了roxygen2(roxygen现在已经不更新了,请安装roxygen2)这个R包,写rd文档就非常方便了。只需在R函数前面写一段话格式化的文字就可以。

建议使用Rstudio来写,因为它会把注释重要的地方标亮,还会提示错用的特殊符号。

2.1 编写注释


比如一个R函数和注释如下,虽然不是每一个注释都需要写成这样,但是这里为了讲解,写的具体一点:

#' @title Poisson vector.
#' @description  Creating poisson vector.
#' @details Input an integer and return the log density of a poisson distribution with lambda equals the input integer.
#' @param x A non-negative integer. or vector.
#' @return A numeric vector of log density.
#' @export
#' @import stats
#' @importFrom stats dpois
#' @examples
#' poiVec(5)
#' poiVec(6)
poiVec <- function(x) {
  dpois(x , x)
}

1)注释的每一行开头用#'或者##'开始。注释基本上都是完整的句子,请用实心句号结束。

2)注释的内容和格式,每一个项目和其内容之间用空格隔开:

@title 定义函数的标题,这个标题里的词可以搜索到

@description 是函数的描述,如果描述内容很长,则可以写在@details里。


@param 声明函数的参数,每个参数都需要声明,如果用一行同时声明2个输入类型一致的

参数,则用@param x,y 来表示,参数之间只用逗号隔开,不能有空格

@return 非必须,声明返回的内容和类型,如果没有返回就不需要加这个项,否则会报错。


@export 非必须,如果需要输出该函数到NAMESPACE里,则必须加上这个项


@import@importFrom 虽然不是必须的,但是非常需要注意,如果你的函数里用到了其他包的函数,最好声明一下,当然基本的函数可以不用声明。这些内容会写到NAMESPACE文件里。如果不声明的话,在check包的时候,就会因为运行不了函数的例子而报错。


而且不建议在函数里加library()或者require()这种命令,也不建议在函数里使用包名::函数名这种命令,最好通通写在import和importFrom里,这样在生成rd文件的时候就被roxygen2自动输出到NAMESPACE文件中,方便维护和修改。


@examples 也不是必须的,但是一旦你写了,就要保证里面的脚本能够运行。所以一定要声明好import的包。还有如果example里用到了其他的包,需要写require(包名),否则check的时候会找不到这个包。


3)需要注意格式化,这里只简单地列举一些,详细见Hadley Wickham写的Format说明

斜体   \emph{italics}

加粗:   \strong{bold}


标注R函数和代码:   \code{r_function_call(with = "arguments")}


标注NULL、TRUE或FALSE:    \code{NULL}


标注R包 :    \pkg{package_name}


引用本R包中的函数说明 :   \code{\link{function}}


引用其他R包里的函数说明,如MASS包中的abbey:    \code{\link[MASS]{abbey}}


插入链接:   \url{http://rstudio.com}


给某个字段插入链接 :  \href{http://rstudio.com}{Rstudio}


插入email,注意要写两个@:    \email{*@@rstudio.com}


4)如果想要在一个注释里添加多个函数,可以用@rdname, 而且一定要记得这个函数也要export,否则运行例子的时候如果写了这个函数,就会因为找不到函数而报错。

#' @rdname 写了注释的那个函数的名称
#' @export


5)注释的内容最好不要超过80个字符每行,否则在生成manuel.pdf文件的时候就会超出边界。Rstudio有一个工具可以帮助我们自动换行:

devtools::install_github("tjmahr/WrapRmd")

安装这个包以后,在Rstudio里面,选中注释然后Ctrl/Cmd + Shift + /  就可以自动换行。

6)注释因为是转成rd文件,有一些需要转义的符号,比如@号,\号,{}和[]都是latex里的重要符号,都需要转义。

2.2 生成rd文件

用roxygen2包的roxygenize函数即可生成rd文件。生成前最好先把之前Rstudio创建的NAMESPACE文件删掉。如果注释写错了就会报错,这一步需要反复修改。

roxygen2::roxygenize(package.dir = ".")


3、创建R包


3.1 check

在build之前,需要先check一下R包有没有问题,我们可以打开命令行工具,用以下命令来check。

R CMD check RPkgName

不过这里推荐使用devtools来进行R包的检查和创建:

devtools::check()

如果想检查manual,可以运行下面的命令,就会在temp文件夹里生成一个pdf文件:

devtools::check(manual = TRUE)

这一步可能会遇到很多的error和warning,这都是因为之前的文件没有写对导致的。

笔者遇到的报错或者warning有:

1) 警告:no visible binding for global variable.

这个warning最好还是解决一下,比如ggplot2就经常出这个warning。可以aes改成aes_string, 然后x和y就可以传入字符串了。还有比如你在load Rdata的时候没有给Rdata赋值,之后直接调用也会报这个警告。

2)警告:no visible global function definition for '函数名'

这可能是因为你调用了其他包的函数,但是没有在NAMESPACE里声明(也就是没有在rd注释的import里声明)。也可能是你写的函数没有export到NAMESPACE里。

3)报错:checking examples 时“could not find function  ‘函数名’”

如果上述问题2)没有解决,那运行example的时候就会遇到这个报错。如果解决了,还遇到报错,那就可能是你的example里调用了其他包,你没有声明require(包)。

这步需要反复地修改和check,尽量修改到没有报错和警告为止。笔者改到出现如下结果的时候差点没有哭出来。(1 note是因为笔者的手册和测试数据太大了。)

0 errors √ | 0 warnings √ | 1 note x

3.2 Build


在确保R包没有错误之后,便可以创建了。你可以打开命令行工具,cd到R包文件夹所在的目录,然后build R包。它默认会创建vignettes和manual,然后打包成*.tar.gz。

R CMD build RPkgName

然后安装R包:

R CMD INSTALL RPkgName.tar.gz

不过这里推荐使用devtools来进行R包的build和install。

devtools::build()

会创建一个*.tar.gz, 然后

devtools::install()

则可以安装R包。

如果你的R包由vignettes,则为了能够使用browseVignettes()命令看到你的vignettes,需要build vignettes:

devtools::build_vignettes()
devtools::build_manual(pkg = ".", path = './doc')
devtools::build(vignettes = TRUE, manual = TRUE)

并且用以下命令安装

devtools::install(build_vignettes = TRUE)

这样就可以看到这个R包的vignette。

browseVignettes(RPkgName)


4、写说明书


这部分并不是R包的必要组成部分,但是有一个使用说明书能够让别人方便的使用,也方便自己回顾。很多人都把vignettes写的像文献一样详细和严谨。

在R包主目录里新建一个名为vignettes的文件夹(注意,不是vignette而是vignettes,否则会识别错误),说明书则存放在这个文件夹下。vignettes文件顾名思义就是手册,需要自己编写,当然可以越详细越好,可以是.Rd或者.Rmd。编写完后将.Rmd文件放到vignettes文件夹里。由于文件题头里声明了文件是pdf或者html,在打包的时候会自动生成。

推荐使用R markdown来编写,可以使用下述代码创建vignettes文件夹,它会在文件夹里创建vignettes模板,名字就是你传入的字符串,比如下述代码传入"my-vignette",模板名称就是"my-vignette.Rmd"。 模板里已经预先写好了vignettes需要声明的header等信息,自己再修改内容就可以了。怎么写就不详述了,具体可以看R markdown的使用手册

usethis::use_vignette("my-vignette")

还有如果是想写bioconductor的包,他们的vignettes有一个推荐的模板, 具体可以看BiocStyle

此外,*-manual.pdf文件里面是把上述所有rd文件整理在一个pdf文件里,可以在build包之后自动生成,不用自己写。笔者原来还以为得自己写呢。

devtools::build_manual(pkg = ".", path = './doc')


5、将R包上传到github


5.1 R包的上传


将R包发布到github可以方便版本管理和收集反馈。我们可以新建一个本地仓库,然后再把本地仓库远程到github仓库。可以按照以下步骤来执行,可见参考资料[5]

1) 申请一个github账号,并创建一个新的仓库repository,以R包命名.

2)cd到你创建R包的文件夹,然后执行以下命令git初始化你的R包文件夹为本地仓库。当然这需要你先安装git。

git init

然后执行以下命令把R包文件夹里的所有文件添加到仓库中, 并加上注释message。

git add .
git commit -m 'submit'

3)将本地仓库远程到github仓库

git remote add origin https://github.com/usrName/repositoryName

然后将文件传输到远程仓库,作为master:

git push -u origin master

以后每次更新,只需在本地仓库更新后再远程传输一次即可。

另外补充说明,从github上安装R包,也可以使用'devtools'包。

devtools::install_github("usrName/repositoryName")

如果你想查看手册需要build这个包,可以执行:

devtools::install_github('repositoryName/repositoryName', 
  build = TRUE, build_opts = c("--no-resave-data"), force = TRUE)


5.2 R包维护

通常,我们上传github之后,会写一个README.md,我们需要把它第一时间同步到我们的本地库里。

1)查看远程仓库:

 git remote -v

2)把远程库更新到本地

git fetch origin master

3)比较远程更新和本地版本库的差异

git log master.. origin/master

4)合并远程库

git merge origin/master

然后我们一般会在本地仓库进行代码修改(如果是自己维护的话),修改完后将本地库更新

git add .
git commit -m "message"

最后将本地库更新推送到远程库:

git push origin master

查看本地库的状态:

git status

6、遇到问题

6.1 qpdf

换了个电脑,装的R3.7,重新打包时在 devtools::check()的时候遇到warning:

''qpdf' is needed for checks on size reduction of PDFs

查找发现没有qpdf

Sys.which(Sys.getenv("R_QPDF", "qpdf"))

qpdf
  ""

解决:

1)下载qpdf源代码,windows下载qpdf-[version]-bin-mingw32
2)解压zip,把bin里的文件拷贝到%SystemRoot%\System32
3)重启R或rstudio

然后再运行

Sys.which(Sys.getenv("R_QPDF", "qpdf"))

就可以看到:

 qpdf 
"C:\\Windows\\SYSTEM32\\qpdf.exe" 

6.2 pandoc document conversion failed with error 11

换了个电脑,装的R3.7,重新打包时在 devtools::check()的时候遇到error:

pandoc document conversion failed with error 1

用这个issue可以解决(亲测),他还非常やさしい地写了一段r代码,运行后重启R就行了:

# Download pandoc 2.7.1 built with ghc-8.6.4, and instruct
# RStudio + rmarkdown to use it.

local({

    # The directory where Pandoc will be extracted. Feel free
    # to adjust this path as appropriate.
    dir <- "~/rstudio-pandoc"

    # The version of Pandoc to be installed.
    version <- "2.7.1"

    # Create and move to the requested directory.
    dir.create(dir, showWarnings = FALSE, recursive = TRUE)
    owd <- setwd(dir)
    on.exit(setwd(owd), add = TRUE)

    # Construct path to pandoc.
    root <- "https://s3.amazonaws.com/rstudio-buildtools"
    suffix <- sprintf("pandoc-%s-windows-x86_64.zip", version)
    url <- file.path(root, "pandoc-rstudio", version, suffix)

    # Download and extract pandoc.
    file <- basename(url)
    utils::download.file(url, destfile = file)
    utils::unzip(file)
    unlink(file)

    # Write .Renviron to update the version of Pandoc used.
    entry <- paste("RSTUDIO_PANDOC", shQuote(path.expand(dir)), sep = " = ")
    contents <- if (file.exists("~/.Renviron")) readLines("~/.Renviron")
    filtered <- grep("^RSTUDIO_PANDOC", contents, value = TRUE, invert = TRUE)
    amended <- union(filtered, entry)
    writeLines(amended, "~/.Renviron")

    # Report change to the user.
    writeLines("Updated .Renviron:\n")
    writeLines(amended)
    writeLines("\nPlease restart RStudio for these changes to take effect.")

})





参考资料:
[1] R语言忍者秘笈
[2] Hadley Wickham R包编写说明
[3] Hadley Wickham 如何写Rd文件
[4] Writing R Extensions 最全面的也是最繁琐的说明
[5] R包上传github
[6] github本地库和远程库代码合并

评论

  1. 求教,如果在title,description ,param等注释里面写中文,在roxygen2::roxygenise()之后会变乱码,这个情况博主有遇到过吗?如何解决呢~
    多谢~

    回复删除
    回复
    1. 抱歉才看到你的问题,这个问题我也遇到过,结论就是另外写一个中文文档,你可以看看这个帖子:https://d.cosx.org/d/420182-r/2

      删除
  2. 求教,如果在函数里用到了中间变量,以及处理数据框的列名,应该怎么声明变量呀?

    回复删除
    回复
    1. 请问有没有具体的例子?没明白您的问题。R里面不需要声明变量,赋值就是声明了。中间变量和列名具体是什么场景要处理成什么?

      删除

发表评论

此博客中的热门博文

Hadley Wickham的R语言编写规范

RMarkdown中文报错的问题【解决】