Yulong Niu

个人博客

R包制作和roxygen2使用说明

Posted at — May 29, 2012

查看创建R包的各种细节,权威的文献是Writing R Extensions

1. 创建R包目录

像盖房子一样,创建R包需要先搭建一个骨架,这个骨架往往是固定的,即一些文件夹(如Rman等)和文件(如DESCRIPTIONNAMESPACE)是必须的,而另外一些则可选择性添加。一个典型的R包目录,比如Biobase包,如下图:

r_catalog

1.1 DESCRIPTION文件

一个纯文本ASCII文件,其中Package, Version, License, Description, Title, Author和Maintainer是必备条目,一个例子knitr包的DESCRIPTION文件:

c(person("Hadley", "Wickham", email = "hadley@rice.edu", role ="ctb"),  
person("Yihui", "Xie", email = "xie@yihui.name", role = c("aut", "cre")))

注意:

  1. 加上版本号,没有版本号等于没用,因为版本之间可能差异很大;

  2. 对于base等这样的包就不用写了,因为是必然依赖的,同时也是R启动自动载入的。对于依赖的包名称,在R 2.14.0之后完全没有必要写,因为从这个版本后,所有包都有NAMESPACE,直接使用Imports就可以了。

1.2 包目录下的各种文件夹说明

2. NAMESPACE文件

2.1 文件说明

NAMESPACE文件非常重要,因为它指明哪些函数是“输出(export)”给用户使用,哪些又是从其他包中“输入(import)”的。如果作者没有指明这个文件,那么在创建包时会被自动创建,此时所有的对象都被输出,在ImportsDepends下的包全部被输入。

“输出”格式export(f, g),其中fg是变量名,一般不用引号括起。也可以使用正则表达式批量输出,比如 exportPattern("^[^\\.]")

“输入”格式import(for, bar),其中forbar是包名称,这样做会导致forbar中所有的输出对象都被引用。如果我们只想引用其中的某几个对象, 可以使用importFrom(foo, f, g),其中fgfoo包的变量名。Base包的命名空间被默认引用。当然,也可以输出其他包的变量,前提是这些变量需要先被输入。

2.2 S3类

S3method(Mymethod, Myclass)

2.3 S4类

“输出”格式:如果创建的S4类需要被外部调用,使用exportClasses(Myclass1, Myclass2),同样可以使用exportClassPattern()批量输出。对于S4,输出“方法(methods)”也会输出“泛函(generic)”,输出泛函同样也会输出方法。如果一个方法,在要创建的包中没有找到对应的方法,有两种可能的情况:

  1. 泛函已经从别的包中输入,比如importMethods()

  2. 从别的包中输入函数,但被转换为默认方法,比如importFrom("graphics", plot)plot()函数又被分配其他的方法,这种情况下输入自己包的plot()函数,使用exportMethods(plot)

“输入”格式:输入某个包的全部类或者特定的类,ImportClasses(pkg)或者ImportClassesFrom(pkg, class1);输入某个包全部方法或者特定方法,ImportMethods(pkg)或者ImportMethodsFrom(pkg, method1)

stats4给出的例子非常好,R 2.15.0之后,对隐式泛函要求严格,必须先输入显示函数:

  export(mle) 
  importFrom("graphics", plot) 
  importFrom("stats", optim, qchisq) 
  ## For these, we define methods or (AIC, BIC, nobs) an implicit generic: 
  importFrom("stats", AIC, BIC, coef, confint, logLik, nobs, profile, update, vcov)   exportClasses(mle, profile.mle, summary.mle) 
  ## All methods for imported generics: 
  exportMethods(coef, confint, logLik, plot, profile, summary, show, update, vcov)
  ## implicit generics which do not have any methods here 
  export(AIC, BIC, nobs)

3. 书写R函数帮助文档

书写函数文档的利器当属Emacs+ESS+roxygen2包。Emacs+ESS搭建了一个非常棒的统计学软件(R、SAS、S-Plus等)的代码编辑和运行环境,从ESS命名Emacs Speaks Statistics上就可以感受到它外漏的霸气。而roxygen2是R的一个包,仿照Doxygen构建的工具,实现了文档和源码用一个文件共同管理的想法。

这个想法相当棒:

  1. 在写代码时,方便“就地”编写文档(函数功能、参数解释、实现功能、注意事项、依赖关系和例子等);

  2. 在源码修改时,及时地更新文档。

对于编辑R包来说,对应的文档是*.Rd(R documentation)格式的文件,以下是一个框架示意图:

{% img middle /images/r_rd.png 600 600 ‘Rd file #1’ ‘a snap of Rd file framework’ %}

3.1 roxygen2使用注释

  • 重要参数的演示;

  • 参数之间的协调控制;

  • 特殊参数(用语言描述困难)的演示;

  • 可能的错误使用;

  • 来几张漂亮图。

这些例子可以直接使用R代码编排、“#”注释、多行也问题。这些例子可以用函数example()运行。如果不想运行某些例子(比如演示错误使用等),格式:\dontrun{Rcode}。如果例子中依赖其他包,使用if(require(pkg)){ }格式。如果函数使用了多核运算技术,例子最多只能允许使用2个核,如果使用多个核,会报错。

也可以在包的根目录下建立文件,通过@example path/relative/to/packge/root引用。如果函数不需要输出,则不用添加@example,否则在R包检查中会报错。

4. 写vignittes文件

推荐使用knitr包

5. 其他辅助文件

5.1 NEWS文件

在包根目录下,可以添加NEWS文件,简要说明版本更新特征。参考ggplot2的格式:

pkgName version
-------------------------
NEW FEATURES

* 1st feature

* 2nd feature

* 3rd feature

BUG FIXES

* fixed bug1

* fixed bug2

也可以在NEWS的最开头加入自己的TODO列表,提示将来需要作的更新。

6. 检查和压缩包

在提交代码之前,首先需要检查自己的代码,运行命名

R CMD check Mypkg

之后,因为R源码包(source package)的发布都是采用压缩文件(*.tar.gz)形式,所以需要将源代码打包,方法是运行命令

R CMD build Mypkg

当然,有时我们需要发布二进制包(binary packages),操作方法是

R CMD INSTALL --build Mypkg

具体执行过程是:首先,在默认目录树下安装包;之后,将成功安装的包转化成(平台依赖的)二进制代码,再将二进制代码打包输出。所以,默认安装目录要有“可写”权限,如果没有,执行R CMD INSTALL -l location --build Mypkg,指定一个“可写”权限目录(location)安装。

CRAN很体贴地考虑了Window以外平台的作者,通过网站上传源码包,之后编译成window平台二进制包。

最后是提交代码,依次执行以下命名或者操作:

R CMD build Mypkg
# 检查例子(example)、检查(test)和文档输出,如果有报错或者警告,需要仔细阅读提示,原则上消除所有报错和警告
R CMD check --as-cran

使用 ftp://CRAN.R-project.org/incoming/ (用户名: anonymous;密码:自己邮箱地址)上传自己的包,并向CRAN@R-project.org发邮件通知自己上传了包(名称、版本等)。

注意事项

1. 命名

给自己的包起一个漂亮的名字,注意大小写,因为一些操作系统可能对大小写不敏感。因此,为了平台兼容性,包的命名尽量不要用大小写区分;不仅如此,在包源代码文件夹下,文件命名也要注意这个问题。起了名字之后,最好到R包列表去看看,检查下自己包的名字是否与其他人的冲突。

2. 名称符号

  • 文件名中不允许出现如下符号"*:/<</font>>?\|

  • 文件命名使用的字符包括 A-Za-z0-9._!#$%&+,;=@^(){}'[],但是不推荐使用 (){}'[]$

  • *.Rd文件可以包括URL,但必须是ASCII,不能出现%

3. GNU版本号

GNU版本命名规则:Major_Version_Number.Minor_Version_Number[.Revision_Number[.Build_Number]]。

4. 关于“命名空间(namespace)”的讨论

在制作R包时候,有两种方法可以引用其他包的变量:

第一种方法:只需要在NAMESPACE中声明引用import()或者importFrom(),同时在DESCRIPTIONImports添加需引用的包即可。这样,在编写代码时不用加上require(pkg)之类载入包的语句。在安装过程中,通过引用可以很方便地获取其他包的变量(比如函数),因而避免了载入整个包;对应的,这很大程度上保证了代码的简洁,一个非常不错的设计。

这里需要再多啰嗦两句,require(pkg)NAMESPACE声明import()有不同,前一个函数载入包,并将其添加(attach)到搜索目录下;而后一个则只载入包,并有添加搜索目录。这有两点好处:1. 用户只将注意力集中在载入的包,如果import()的包中有与载入包命名冲突的函数,只会调用载入的包;2. 设计简洁。

第二种方法:也可以通过操作符:::::获得命名空间的“显变量(exported variables)和“隐变量(internal variables)”。需要注意的是,不推荐使用:::,这可能暗示这一个自己设计不足。因为“隐变量”之所以要隐藏起来,作者是有着充分的考虑;而且,这些“隐变量”可能随着作者对包的更新而改名。可以使用函数loadedNamespaces()查看已经载入的命名空间,unloadNamespace(name)卸载命名空间。

对于这两种方法,不推荐第二种方法,原因很简单:要多写包名+两个冒号。第二中方法适合在终端操作时,遇到函数名称冲突(见后)使用。

如果变量命名有冲突,R将采取“就近”策略应对,考虑两种特殊情况:

  1. 假定自己包的名称是Mypkg,如果在NAMESPACE中声明输入export(f),但是又从另外一包foo输入同名函数f,比如importFrom(foo, f)。这时,“就近”策略发挥作用,即在载入包Mypkg时,默认f函数是自己编写的,即声明输出的函数f,而不是foo中的f函数。总体而言,命名空间的查找顺序是:包的命名空间 –> import声明 –> Base包的命名空间 –> 最后是搜索路径

  2. 假定pkg1和pkg2两个包,都有命名空间,而且输出同名函数f。这是,R会怎么处理呢?同样是“就近”原则,哪个包后载入,先用哪个包的函数f。不过,如果函数命名有冲突,后载入的包会给出一个警告“marsked from package: ***”。

参考网址资料

更新记录

2021年05月21日