R语言之所以强大,很大的原因在于强大的R语言包。用过R的同学都有一个体验就是,每当我们要用R来做一件事情的时候,不免第一件事情就是安装一个(些)新的包。使用install.packages()来下载一个包。
或者,另外还有一些包发布在github上面,因此也不能用传统方法安装。
这一门课程中,我们会学习到如何开发一个R语言包,学习完这门课程之后,同学们可以开发一个自己属于自己的R包,并且上传到Github,供大家下载。
从一个R用户开始转变成为一个R语言开发者。
在R中,可以分享代码的基本单位是包。包把代码,数据,文档和测试整合在一起,这样可以很容易的和别人分享。
截至 2022 年 6 月,综合 R A rchive Network或CRAN 上有超过 18,000 个包可用。正是这么多的资源,使得R语言如此的成功,每当我们遇到一个问题,往往已经有人解决了你的问题,你则可以通过下载他们的包从他们的工作中受益。
本教程的目的是教会同学们如何开发包,让同学们写出自己的包,而不只是使用别人的包。通过本课程的学习,同学们可以开发自己的包,然后上传到Github。
我们为什么要开发包:
install.packages(c("devtools", "roxygen2", "testthat", "knitr"))
开始创建包的第一步需要为这个包取一个名字。
命名有三个正式的要求:
一个好名字对于这个包的使用与传播非常有帮助,以下是关于命名的一些建议:
需要注意的是,有一个包library(available)
可以帮助我们取名,以及判断名字是否合适:
library(available)
available("PPMr")
available("doofus")
#> Urban Dictionary can contain potentially offensive results,
#> should they be included? [Y]es / [N]o:
#> 1: 1
#> ── doofus ──────────────────────────────────────────────────────────────────
#> Name valid: ✔
#> Available on CRAN: ✔
#> Available on Bioconductor: ✔
#> Available on GitHub: ✔
#> Abbreviations: http://www.abbreviations.com/doofus
#> Wikipedia: https://en.wikipedia.org/wiki/doofus
#> Wiktionary: https://en.wiktionary.org/wiki/doofus
#> Sentiment:???
available::available()
做的事情包括:
备注:需要外网
通过代码:
usethis::create_package()。
或者可以在Rstudio中操作:File > New Project > New Directory > R Package。这本质上也是调用上面的代码。
R包的关键组成就是R的代码。使用包的第一个原则,就是包中的所有的R语言代码都要放到R/目录当中,本节,我们会学习到一些关于R/目录的知识,有关在文件中组织函数的建议,以及形成良好的代码风格的提示。
在文件中编写函数的时候,有两种极端的做法不是很好:把所有的函数都放在一个文件中,以及把每个函数都发在不同的文件,一般而言,文件名要包含函数的功能,并且文件名字要有意义并且以.R结尾
一个好的文件名: fit_model.R,好的文件名让我们知道这个文件是做什么的。
变量名称和函数名称应该小写,使用下划线进行分割,一般情况下,变量名应该是名词,函数名应该是动词。好的名字:day_one
到目前为止,大家可能已经在编写R脚本,通过source()来加载文件运行R代码。脚本中的代码和R包中的代码有两个主要的区别:
当使用source 加载脚本的时候,每一行代码都会被执行,执行的结果可以立即使用。对于包而言,有一点不一样,加载过程分为两步,包编译的时候,目录下的所有代码都会被执行,结果会保存下来。使用library 的时候,这些保存的结果就可以用了。
举个例子:x = Sys.time()
如果放在一个脚本里面,x显示的是脚本什么时候被source运行的。如果放在包中,则会告诉你什么包什么时候被编译的。
这就是说不应该在包里面直接运行代码:包只能创建对象,主要是函数。举一个例子:
我们有一个包叫做test,包含的代码如下:
library(ggplot2)
show_mtcars = function(){
qplot(mpg,wt,data=mtcars)
}
然后我们加载test包,运行函数:
library(test)
show_mtcars()
代码会报错,这是因为library(ggplot2)
不会执行,这行代码只会在编译包的时候运行。需要修改为:
show_mtcars = function(){
library(ggplot2)
qplot(mpg,wt,data = mtcars)
}
脚本和包的一个巨大的区别是:别人会使用你的包,并在一个你从未想到的环境中使用,这意味着你需要注意R的运行环境。如果使用library()加载的一个包,或者使用options()修改了一个全局变量,那么你就已经修改了R的运行环境,这样会造成R的代码非常难以理解,因此最好不要做以下事情:
还可以使用withr包来帮助管理环境,一般模式是捕获原始状态,安排其“退出时”最终恢复,然后进行状态更改。
f <- function(x, y, z) {
# some code
old <- options(mfrow = c(2, 2), pty = "s")
defer(options(old))
}
这个包还提供了一些局部修改环境的工具包括:
with_*()函数最适合执行具有临时修改状态的小段代码。我们看一个例子:
f <- function(x, sig_digits) {
# imagine lots of code here
withr::with_options(
list(digits = sig_digits),
print(x)
)
# ... and a lot more code here
}
local_*()函数最适合“从现在到函数退出”修改状态。
g <- function(x, sig_digits) {
withr::local_options(list(digits = sig_digits))
print(x)
# imagine lots of code here
}
大家在使用包的时候可能会注意到,很多包都提供了自带的数据集。那么如何将数据集添加到包中?
有三种主要方法可以在包中包含数据,具体取决于您要使用它做什么以及应该能够使用它的人:
此目录中的每个文件都应该是通过包含单个对象(与文件同名)创建的.rda(或.RData)文件
x <- sample(1000)
usethis::use_data(x, mtcars)
为数据集创建说明文档:
#' 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"
还有两个附加标签对于记录数据集很重要:
通常而言,data/是从其他地方收集的原始数据的清理版本,这里建议将数据处理的代码放到data-raw/.目录下,添加代码的语句是:
usethis::use_data_raw()
有些数据我们函数会用到,但是不会提供给用户。
x <- sample(1000)
usethis::use_data(x, mtcars, internal = TRUE)
如果要显示加载/解析原始数据的示例,请将原始文件放在inst/extdata. 使用system.file().
readr包用于inst/extdata存储用于示例的分隔文件:
system.file("extdata", "mtcars.csv", package = "readr")
#> [1] "/home/runner/work/_temp/Library/readr/extdata/mtcars.csv"
有两个重要文件提供有关包的元数据:DESCRIPTION和NAMESPACE
DESCRIPTION 描述整个包是做什么的
NAMESPACE 描述了从其他包调用的函数,以及该包提供的函数
每个包都必须有一个DESCRIPTION.通常,创建包项目的时候会自动创建一个最小的描述文件。
标题和描述字段描述了包的作用。它们仅在长度上有所不同:
ggplot2的TitleandDescription是:
Title: Create Elegant Data Visualisations Using the Grammar of Graphics
Description: A system for 'declaratively' creating graphics,
based on "The Grammar of Graphics". You provide the data, tell 'ggplot2'
how to map variables to aesthetics, what graphical primitives to use,
and it takes care of the details.
要识别包的作者以及出现问题时与谁联系,需要修改Authors@R字段,这个字段很不寻常,因为它包含可执行的 R 代码而不是纯文本
Authors@R: person("Hadley", "Wickham", email = "hadley@rstudio.com",
role = c("aut", "cre"))
关于角色:
可以多个作者:
Authors@R: c(
person("Hadley", "Wickham", email = "hadley@rstudio.com", role = "cre"),
person("Winston", "Chang", email = "winston@rstudio.com", role = "aut"),
person("RStudio", role = c("cph", "fnd")))
Imports您的用户在运行时需要其中列出的包。以下几行表明您的包绝对需要 dplyr 和 tidyr 才能工作。
Imports:
dplyr,
tidyr
Suggests要么是开发任务所必需的,要么可能为您的用户解锁可选功能。
Suggests:
ggplot2,
testthat
如何添加依赖:
usethis::use_package("dplyr") # Default is "Imports"
#> ✔ Adding 'dplyr' to Imports field in DESCRIPTION
#> • Refer to functions with `dplyr::fun()`
usethis::use_package("ggplot2", "Suggests")
#> ✔ Adding 'ggplot2' to Suggests field in DESCRIPTION
#> • Use `requireNamespace("ggplot2", quietly = TRUE)` to test if package is installed
#> • Then directly refer to functions with `ggplot2::fun()`
如果计划发布包,许可证是非常重要的,如果不打算发布,可以忽略这个部分。 对于R包,需要考虑三种许可证:
文档是包最重要的一部分。通过文档,用户才知道如何使用这个包,如何使用 这个包的某一个函数。通过?来访问某一函数的文档举例子:
?mean
制作文档一般有基本的四个步骤
我们会一步一步介绍,首先是在源文件添加注释:roxygen注释从#开始,区别于一般的注释.下面是一个简单的函数文档,这个文件名为add.R:
#'Add together two number
#'
#' @param x A number
#' @param y A number
#' @return The sum of \code{x} and 、code{y}
#' @example
#' 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 number}
\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 number
}
roxygen 的注释以#’开始,第一句是文档的标题,第二段是描述,写在文档的最前面,简要说明函数的功能,第三段以及以后的段落是细节描述
@seealso 用于添加其他指向其他有用资源
@family 用于添加一族的相关函数
@keywords 用于添加关键词
@param 用于添加函数参数
@examples 用于添加例子
@return 用途田间返回值描述
文档,是一个页面对于这个包的描述,通过packages?包名来访问它。因为没有一个包的对象,所以需要为NULL提供文档.这里提供一个包文档的例子:
#‘ test :A package for test
#'
#' The test packages provides some functions :
#'
#' @section test Functions:
#' The foo function ...
#'
#' @docType package
#' @name test
NULL
github让我们可以非常容易的分享包,任何用户只需要使用两行代码就可以安装包:
install.packages('devtools')
devtools::install_github('username/packagename')
Windows:http://git-scm.com/download/win OS X :http://git-scm.com/download/mac
git config –global user.name “YOUR FULL NAME” git config –global user.email “YOUR EMAIL ADDRESS”
创建Github账户
生成SSH秘钥
在Rstudio中选择Git/SVN,点击Create RSA Key
5.把你的SSH共钥上传给GitHub,
git init
重启Rstudio,再次打开Rstudio,就会出现Git一栏:
创建token,然后再电脑上面修改登陆github 的密码。(以mac电脑为例子)
将token替换这里的密码
git init
git add .
git commit -m 'first commit'
git remote add origin https://github.com/liamamilin/Fibonacci.git # 这里的连接需要根据你自己的情况修改
git push -u origin master
或者
git push git@github.com:github.com/liamamilin/Fibonacci.git
这样,其他人可以通过:
install.packages('devtools')
devtools::install_github('username/packagename')
来安装你写的包了