ixxmu / mp_duty

抓取网络文章到github issues保存
https://archives.duty-machine.now.sh/
110 stars 30 forks source link

如何生成一个R包 #3495

Closed ixxmu closed 1 year ago

ixxmu commented 1 year ago

https://mp.weixin.qq.com/s/7847toikls80qstZbqBP9Q

ixxmu commented 1 year ago

如何生成一个R包 by 生信媛

R包的基本结构

最小必须:

包的名字(包的名称只能包含字母、数字和点号。建议不要用点号,因为点号在函数名称有特殊含义)
|
|--DESCRIPTION(项目描述文件,包括包名、版本号、标题、描述、依赖关系等,用于设置项目的全局的配置)
|--NAMESPACE(包的命名空间文件,用于设置项目的全局的配置)
|--R(函数源码)
   |--hello.R
   |--……
|--man(帮助文档,存放函数说明文件的目录,语法大致基于 LaTeX。这些文件会被编译为 HTML、纯文本和 PDF 以供查看。)
   |--hello.Rd
   |--……
|--……

可变部分:
  • data(rdata)



  • 数据库(sqlite)

准备工作

你可以使用系统的原生的package.skeleton()函数,生成项目的骨架,也可以自己手动创建,或者使用Rstudio的("文件(File)" → " 新项目(New Project)" → " 新目录(New DIrectory)" → "R 包(R Package)" ,输入R包名字,点击创建项目(Create Project)创建一个新的 R 包)。但是都比较麻烦,因为创建之后,都是需要手动写相关的帮助文档(latex)、NAMESPACE等文件,并且测试起来也不是太方便。

这里给大家推荐由Hadley Wickham重新定义的开发流程。有3个武器,即devtools、roxygen2和testthat。

首先安装如下几个R包:

  • devtools:各种开发小工具的合集,让开发变得简单,非常实用。
  • roxygen2:将特殊格式的注释自动生成 .Rd 文件,远离LaTex的烦恼。
  • testthat:单元测试,让R包稳定、健壮,减少升级的痛苦。
if(!require("devtools"))
  install.packages("devtools")
if(!require("roxygen2"))
  install.packages("roxygen2")
if(!require("testthat"))
  install.packages("testthat")
if(!require("testthat"))
  install.packages("testthat")

R项目生成

dest_path="/Users/xxx/Desktop/20200911-How_to_build_Rpkg"
pkg_path <- paste(dest_path, "demo", sep=.Platform$file.sep)

devtools::create(pkg_path)

✓ Creating '/Users/xxx/Desktop/20200911-How_to_build_Rpkg/demo/'
✓ Setting active project to '/Users/xxx/Desktop/20200911-How_to_build_Rpkg/demo'
✓ Creating 'R/'
✓ Writing 'DESCRIPTION'
Package: demo
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] (<https://orcid.org/YOUR-ORCID-ID>)
Description: What the package does (one paragraph).
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
✓ Writing 'NAMESPACE'
✓ Writing 'demo.Rproj'
✓ Adding '.Rproj.user' to '.gitignore'
✓ Adding '^demo\\.Rproj$''^\\.Rproj\\.user$' to '.Rbuildignore'
✓ Opening '/Users/wangqingzhong/Desktop/20200911-How_to_build_Rpkg/demo/' in new RStudio session
✓ Setting active project to '<no active project>'


  • R目录是空的,因为我们还没有写任何代码
  • DESCRIPTION


  • NAMESPACE:通过roxygen2生成,不需要手动编辑


修改DESCRIPTION文件

可以直接编辑该文件,或者在R里用use_description函数进行修改。

setwd(pkg_path)

author.description = list(
  `Authors@R` = 'person("Ziru", "Chen", email = "xxx@xxx.cn", role = c("aut", "cre"),
                          comment = c(ORCID = "YOUR-ORCID-ID"))'
,
  License = "GPL-3",
  Language =  "es",
  Title = "How to build an R package",
  Description = "How to build an R package-20200911")

use_description(fields = author.description, check_name = TRUE, roxygen = TRUE)
Overwrite pre-existing file 'DESCRIPTION'?

1: Nope
2: Negative
3: I agree

Selection: 3
✓ Writing 'DESCRIPTION'
Package: demo
Title: How to build an R package
Version: 0.0.0.9000
Authors@R (parsed):
    * Ziru Chen <chenziru@picb.ac.cn> [aut, cre] (<https://orcid.org/YOUR-ORCID-ID>)
Description: How to build an R package-20200911
License: GPL-3
Encoding: UTF-8
Language: es
LazyData: true
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.1.1


依赖包的添加

如果你的代码用到了别的包的函数,那么需要将这个依赖关系导入到DESCRIPTION文件。

use_package("magrittr")
✓ Adding 'magrittr' to Imports field in DESCRIPTION
● Refer to functions with `magrittr::fun()`


依赖关系可以用type来指定,如果需要增加版本,可以设定min_version = T

> use_package(package = "dplyr", type = "Depends", min_version = TRUE)
✓ Adding 'dplyr' to Depends field in DESCRIPTION
● Are you sure you want Depends? Imports is almost always the better choice.


type:

  • Depends

    以上边例子为例,当使用demo包的时候,需要加载dplyr包到主搜索路径(即search()返回的环境列表)中,demo包才可以使用(即其使用了library()来加载包)。如果希望用户每次加载你的软件包时都加载其他软件包,就使用这种方式。

    存在的问题:如果加载的两个包有相同名称的函数,则后加载的包的函数会覆盖前面加载的。

    除非一个包是被设计为与另一个包一起使用,没有前者,后者也没办法使用,可以用这种方式。否则一般不建议使用,更好的方式是, 将这些包使用@import导入命名空间(NAMESPACE).

  • Imports

    在写代码的时候,使用Roxygen2注释的@import或@importFrom语句引用的包,或通过::运算符使用函数的包,都会显示在Imports下边。

    Imports在安装包的时候会被一同安装,但不会被加载到环境。

  • Suggests

    适用于并非确实必要,但在示例、编译使用指南或测试中用到的软件包。这里列出的R包不会与软件包一起被安装。

  • Enhance

    不是很常见。

NAMESPACE

NAMESPACE文件用于函数的访问控制。这个文件无需手动编辑,使用roxygenizedocument函数的时候,会将相关函数导出到这个文件。

R代码

在使用roxygen2包来写R包的好处是:

  • 自动将import的包和我们写的需要导出的函数自动添加到NAMESPACE文件
  • 自动根据代码生成man文档,无需我们手动编辑

来看一个例子:

我们先在R下边编辑一个名为"bmi.R"的文件。并输入如下代码:

#' Add together two numbers. 
#' @author Ziru Chen
#' \email{xxx@@xxx.cn}
#' @param weight weight.
#' @param height height.
#' @return The sum of \code{x} and \code{y}. 
#' @importFrom magrittr %>%
#' @export
#' @examples
#' bmi(50, 1.65)
bmi <- function(weight, height) {
  (weight/(height^2)) %>%  ceiling
}
  • roxygen的注释以#'开始,用以区别其他的注释。
  • @ 在 roxygen 中有特殊的意义,如果想在文档中添加 一个 @ 符号,需要多加一个@,即使用@@,以下情况会出现@符号:
    • 电子邮件地址
    • 访问 S4 成员变量
  • @importFrom magrittr %>%,你也可以只写@import magrittr,但是前者会使得代码更加清晰,有助于自己和别人查看代码。
> roxygenise(pkg_path)

Loading demo
Loading required package: dplyr

Attaching package: ‘dplyr’

The following object is masked from ‘package:testthat’:

    matches

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union

Writing NAMESPACE
Writing NAMESPACE
Writing bmi.Rd

或者使用document(pkg_path),This function is a wrapper for the roxygen2::roxygenize()

生成了帮助文档:bmi.Rd


将依赖的函数添加到了命名空间(NAMESPACE)文件,以及导出刚刚写的函数:


调试函数

加载R包里所有的函数:load_all()可以让你跳过安装而直接把源码包装到内存中。

load_all()
> weight <- c(50,60)
> height <- c(1.65,1.60)
> bmi(weight, height)
[119 24

更可重复的测试方法:testthat

# 开始testthat
usethis::use_testthat()


  • 创建了 tests/testthat目录
  • 创建了tests/testthat.R ,当运行 R CMD check 时,会自动运行所有代码测试。
  • 会将 testthat添加到  DESCRIPTIONSuggests 部分。


在demo项目下刚才新建tests/testthat目录下新建“test.源文件名.R”,用来写测试代码。

比如上边的文件名为bmi.R,其对应的测试脚本则命名为:test.bmi.R。

test_that("str_length is number of characters", {
  expect_equal(bmi(501.65), 19)
  expect_equal(bmi(c(50,60),c(1.65,1.60)), c(19,24))
})

然后运行测试:

devtools::test()


其他expect_请查看《R Packages·Chapter 11 Testing》:https://r-pkgs.org/tests.html

执行R包检查

check(pkg_path)


因为我刚才为了讲解方便,刚才导入了'dplyr',但其实并没有用上。

我们手动删除,再来检查一次。

check(pkg_path)


生成R包

> devtools::build()
✓  checking for file ‘/Users/wangqingzhong/Desktop/20200911-How_to_build_Rpkg/demo/DESCRIPTION’ ...
─  preparing ‘demo’:
✓  checking DESCRIPTION meta-information ...
─  checking for LF line-endings in source and make files and shell scripts
─  checking for empty or unneeded directories
─  building ‘demo_0.0.0.9000.tar.gz’
   
[1"/Users/wangqingzhong/Desktop/20200911-How_to_build_Rpkg/demo_0.0.0.9000.tar.gz"


安装R包

> install.packages(paste(dest_path, "demo_0.0.0.9000.tar.gz", sep = .Platform$file.sep),repos=NULL, type='source')

> library(demo)

一定要把设置repos=NULL,否则会到CRAN去寻找包(当然,找不到),于是会报错。


卸载R包

  • 在R语言的环境中用remove.packages()函数
  • 命令行卸载:R CMD REMOVE R包名称

参考资料

本文写于2020.09.10(当时为了给学生讲解,犹疑再三还是发出来了),重度参考了以下资料:

  • 《R包开发》:《R Packages》的中文版
  • 《R Packages》https://r-pkgs.org/
  • 《R的极客理想:高级开发篇 (数据分析技术丛书)》

文章

  • 开发 R 程序包之忍者篇:https://cosx.org/2011/05/write-r-packages-like-a-ninja/