R语言编程进阶

数据科学42 |数据产品开发-创建R包

2020-07-24  本文已影响0人  珠江肿瘤

7. 创建R包

R包是函数或其他数据、对象的集合,以一种系统的方式组织起来。R包可以由世界各地的用户/开发者撰写,可以扩展R的基本功能,实现很多强大的功能。

7.1 获取R包

👉可以从CRAN和Bioconductor获得

通过install.packages()进行安装来自CRAN / Bioconductor的R包

👉可以从GitHub,Bitbucket,Gitorious等其他地方获得

可以使用devtools包中的install_github()安装来自GitHub的R包

7.2 R包的开发过程

  1. 在R脚本文件(.R)中编写代码
  2. 将R脚本文件合并到R包结构中
  3. 编写用户功能文档,包括其他的一些材料(示例,演示,数据集,教程)
  4. 打包并提交到CRAN或Bioconductor
  5. 可以将源代码库上传到GitHub或其他源代码共享网站
  6. 用户发现代码存在的问题,可以向开发者反馈或直接向开发者提供更改方案
  7. 开发者可以合并更改并发布新版本

7.3 构成R包的必需文件

  1. 一个命名为R包名称的目录
  2. DESCRIPTION描述文件,包含有关这个R包的信息
  3. 含有R源代码的R脚本文件(.R),存放在命名为R的子目录中
  4. 存放在命名为man的子目录中的Documentation文件
  5. NAMESPACE文件
图1.R包的结构(gpclib包)

7.3.1 DESCRIPTION文件

必需包含的描述内容:

・Package:R包的名称

・Title:R包的全称

・Description:对R包的详细介绍

・Version:版本号

・Author:原作者名字

・Maintainer:维护者的名字及邮件

・License:源代码的许可证,常见许可证:GNU、BSD、MIT

非必需包含但常见的描述内容:

・Depends:编写R代码所依赖的其他R包

・Suggests:可能希望用户安装的R包

・Date:发布日期,格式:YYYY-MM-DD

・URL:R包的主页

・可以添加其他字段

图2.DESCRIPTION文件示例(gpclib包)

7.3.2 R代码文件

・将R代码文件保存到子目录R中

・子目录R中可以有任意数量的文件

・将代码按逻辑功能分成多个不同的文件(不必将所有代码放在同一个文件中)

・实现R包所有功能的代码都放在子目录R中

7.3.3 NAMESPACE文件

・NAMESPACE文件用于指示要导出的函数

・导出的函数可以作为公共API由用户调用 ・非导出函数不能由用户直接调用(但是可以查看代码) ・对用户隐藏实施细节,并提供更简洁的R包界面

・NAMESPACE文件还可以指示从其他包导入的函数

・允许R包使用其他包,而不会使其他包对用户可见 ・导入函数会加载程序包,但不会将其附加到搜索列表中

关键指令:

NAMESPACE文件示例(gpclib包):

#从graphics包导入plot函数
importFrom(graphics, plot)
import(methods)

#导出两个S4类对象
exportClasses("gpc.poly", "gpc.poly.nohole")

#导出到用户的多种方法,适用于已定义的这些新类的用户
exportMethods("show", "get.bbox", "plot", "intersect", "union",
              "setdiff", "[", "append.poly", "scale.poly", "area.poly",
              "get.pts", "coerce", "tristrip", "triangulate")

#导出两个函数
export("read.polyfile", "write.polyfile")

useDynLib(gpclib)

7.3.4 Documentation文件

・Documentation文件(.Rd)放在子目录man中,使用户可以了解应该如何使用函数

・用特定的标记语言编写

・每一个导出的函数都需要一个Documentation文件 限制导出到用户的函数数量的原因之一

・此外,还可以记录其他内容,如概念,R包概述

例:R的基本安装中Line函数的帮助文件,?line可以查看生成的帮助文件

\name{line}
\alias{line}
\alias{residuals.tukeyline}
\title{Robust Line Fitting}
\description{
  Fit a line robustly as recommended in \emph{Exploratory Data Analysis}.
}
\usage{
line(x, y)
}
\arguments{
  \item{x, y}{the arguments can be any way of specifying x-y pairs.  See
    \code{\link{xy.coords}}.}
}
\details{
  Cases with missing values are omitted.

  Long vectors are not supported.
}
\value{
  An object of class \code{"tukeyline"}.

  Methods are available for the generic functions \code{coef},
  \code{residuals}, \code{fitted}, and \code{print}.
}
\references{
  Tukey, J. W. (1977).
  \emph{Exploratory Data Analysis},
  Reading Massachusetts: Addison-Wesley.
}
图3.Line函数帮助文件

可以看到Documentation文件以特定的标记语言编写并生成了函数的帮助文件,包含了函数的标题、描述、用法、参数、细节、返回值、参考等内容。

7.4 构建和检查R包

・R CMD build是一个命令行程序,用于创建R包归档文件(.tar.gz)

・R CMD check将对R包进行一系列检查测试

检查每个导出函数是否有对应的Documentation文件 检查代码是否可以加载且没有重大的编码问题或错误 运行Documentation文件中的示例 R包必须通过R CMD Check中的所有测试而没有任何警告或任何错误才能放在CRAN上

・可以在终端使用命令行运行R CMD build或 R CMD check程序

・也可以在R中使用system()函数运行

system("R CMD build newpackage")
system("R CMD check newpackage")

例:构建R包框架

library(utils)
package.skeleton("Newpackage")
Creating directories ...
Creating DESCRIPTION ...
Creating NAMESPACE ...
Creating Read-and-delete-me ...
Saving functions and data ...
Making help files ...
Done.
Further steps are described in './Newpackage/Read-and-delete-me'.

传递R包的名称给utils包中的package.skeleton()函数将创建该R包名称命名的目录框架,包括目录结构(子目录R和子目录man)、DESCRIPTION文件、NAMESPACE文件、Documentation文件。

图4.构建R包框架

package.skeleton()函数将创建一个可供填写的DESCRIPTION文件;创建一个可供编写的NAMESPACE文件。默认情况下,package.skeleton()函数将当前工作空间中的存在的所有函数生成R代码文件和Documentation文件,并将它们写到子目录R和子目录man中;当前工作空间中的所有数据集或变量都将存放到子目录data中。

7.5 类和方法classes and methods

R是一个基于使用泛型函数的面向对象的编程语言。

・R编写类和方法的基本思想是开发一个新结构来支持新的数据类型,并开发一套适用于该数据类型的新函数。

・R的类和方法是面向对象编程(object oriented programming,OOP)的系统,允许用户(利用系统功能)成为程序员(扩展系统功能)。

・R是交互式的,并且支持OOP,而许多其他常见的OOP语言(C ++,Java,Python,Perl)通常不是交互式的,R中的OOB结构与大多数其他语言的结构不同。

R中的两种类和方法:

・S3(S语言的第三版) S3模型相对更老、更简单、结构更少,非正式的。R中使用的许多基本功能都是基于S3类和方法构建的。

・S4(S语言的第四版) S4模型更新且更复杂,标准严格,更加正式。

两个系统独立运行,但鼓励开发人员用S4编写新项目。

7.5.1 面向对象的编程

  1. 类class

    ・类class是对某些事物、对象的描述(新数据类型、想法) ・可以在methods包中使用setClass()函数定义 ・R中的所有对象都有一个类,可以通过class()函数确定

    1)数值类型numeric:数值数据或数字序列组成的向量

    2)逻辑类型logical:TRUE、FALSE、NA 注意:默认情况下,NA是逻辑类型,但是可以将NA作为数字/字符运算结果

    3)字符类型character:字符串

    4)线性模型类型lm

class(1)
[1] "numeric"

class(TRUE)
[1] "logical"

class(rnorm(100))
[1] "numeric"

class(NA)
[1] "logical"

class("foo")
[1] "character"

x <- rnorm(100)
y <- x + rnorm(100)
fit <- lm(y ~ x)
class(fit)
[1] "lm"
  1. 对象object

    ・对象object是类的实例 ・可以使用new()创建

  2. 方法method

    ・方法method是在某些特定的类的对象上运行的函数,也可以认为是针对特定类的对象的泛型函数的实现・可以为现有的泛型函数编写新方法,或创建新的泛型函数及相关方法 ・getS3method(<genericFunction>, <class>)可以返回给定类的S3方法的代码

某些S3方法(如mean.default)可以直接被调用,但最好永远不要调用他们,通常始终使用泛型函数。

getMethod(<genericFunction>, <signature/class>)可以返回给定类的S4方法的代码

1)签名signature表示该方法接受的对象的类的字符向量 2)S4方法无法被调用

  1. 泛型函数generic function

    ・泛型函数generic function是R中用于调用实现特定功能的方法的函数(如plot,mean,predict) ・泛型函数本身不执行任何计算,单独输入函数名称(即绘图)将返回函数的内容 ・S3和S4函数看起来不同,但概念上相似 ・methods("mean")返回与S3泛型函数关联的方法 ・showMethods("show")返回与S4泛型函数关联的方法 ・函数的第一个参数为特定类的对象,调用针对特定类的对象相应的方法

S3泛型函数
mean
function (x, ...) 
UseMethod("mean")
<bytecode: 0x7fe4fca91470>
<environment: namespace:base>

print
function (x, ...) 
UseMethod("print")
<bytecode: 0x7fe4fcadbc88>
<environment: namespace:base>
S3方法
methods("mean")
[1] mean.Date        mean.default     mean.difftime    mean.IDate*     
[5] mean.ITime*      mean.POSIXct     mean.POSIXlt     mean.quosure*   
[9] mean.vctrs_vctr*
see '?methods' for accessing help and source code
S4泛型函数
show
standardGeneric for "show" defined from package "methods"

function (object) 
standardGeneric("show")
<bytecode: 0x7fe4f9e3d088>
<environment: 0x7fe4f9b6aa10>
Methods may be defined for arguments: object
Use  showMethods("show")  for currently available ones.
(This generic function excludes non-simple inheritance; see ?setIs)

show相当于print,但是通常不会在R中直接调用这两个函数,因为大多数对象会在命令行自动打印。

S4方法
showMethods("show")
Function: show (package methods)
object="ANY"
object="C++Class"
object="C++Function"
object="C++Object"
object="classGeneratorFunction"
object="classRepresentation"
object="color"
object="Enum"
object="EnumDef"
object="envRefClass"
object="externalRefMethod"
object="function"
    (inherited from: object="ANY")
object="genericFunction"
object="genericFunctionWithTrace"
object="MethodDefinition"
object="MethodDefinitionWithTrace"
object="MethodSelectionReport"
object="MethodWithNext"
object="MethodWithNextWithTrace"
object="Module"
object="namedList"
object="ObjectsWithPackage"
object="oldClass"
object="refClassRepresentation"
object="refMethodDef"
object="refObjectGenerator"
object="signature"
object="sourceEnvironment"
object="standardGeneric"
    (inherited from: object="genericFunction")
object="SymbolicConstant"
object="traceable"

・工作过程

1)泛型函数检查对象的类>
2)如果存在用于该类的方法,对该对象调用该方法(完成处理)>
3)如果没有用于该类的方法,则搜索默认方法>
4)如果存在默认方法,则对该对象调用默认方法(完成处理)>
5)如果不存在默认方法,则报错(完成处理)

・对于data.frame这样的类,其中每个列可以属于不同的类,函数使用相应的方法

例:S3泛型函数工作过程

set.seed(2)
x <- rnorm(100)
mean(x)
[1] -0.03069816

x为数值类型,泛型函数mean没有对应数值类型的对象调用的方法,因此泛型函数mean将调用默认方法。

head(getS3method("mean", "default"), 10)
                                                                      
1  function (x, trim = 0, na.rm = FALSE, ...)                         
2  {                                                                  
3      if (!is.numeric(x) && !is.complex(x) && !is.logical(x)) {      
4          warning("argument is not numeric or logical: returning NA")
5          return(NA_real_)                                           
6      }                                                              
7      if (na.rm)                                                     
8          x <- x[!is.na(x)]                                          
9      if (!is.numeric(trim) || length(trim) != 1L)                   
10         stop("'trim' must be numeric of length one")

tail(getS3method("mean", "default"), 10)
                                                               
15         if (anyNA(x))                                       
16             return(NA_real_)                                
17         if (trim >= 0.5)                                    
18             return(stats::median(x, na.rm = FALSE))         
19         lo <- floor(n * trim) + 1                           
20         hi <- n + 1 - lo                                    
21         x <- sort.int(x, partial = unique(c(lo, hi)))[lo:hi]
22     }                                                       
23     .Internal(mean(x))                                      
24 }

getS3method()函数可以查看S3泛型函数的方法的具体运行代码。

7.5.2 创建新的类和方法

创建新类的原因

・是扩展R功能的强大方法

・可以代表新的数据类型(例如基因表达,时空,分层,稀疏矩阵)

・可以表示尚未想到的新概念、想法(例如,拟合点过程模型,混合效果模型,稀疏矩阵)

・可以从用户中抽象或隐藏实现细节

创建新类

・setClass()函数可以创建新类

・至少需要指定类型的名称name

・也可以指定数据属性attributes或插槽slots(一个类实际上是一个列表,所以插槽是该列表的元素)

定义类的方法

・setMethod() 函数可以定义类的方法

・@用于访问类的插槽或属性

・showClass()函数显示有关类的定义和信息。

例:定义一个新的多边形类ploygon class,并定义新的绘图方法

library(methods)
setClass("polygon", representation(x = "numeric", y = "numeric"))

使用setClass()函数创建具有(x,y)坐标集的ploygon类,以x和y坐标为插槽,包含多边形所有顶点的x坐标和y坐标。

setMethod("plot", "polygon",
          function(x, y, ...) {
            plot(x@x, x@y, type = "n", ...)
            xp <- c(x@x, x@x[1])
            yp <- c(x@y, x@y[1])
            lines(xp, yp)
          })

使用setMethod() 函数为多边形类创建绘图方法,在这种情况下,polygon为签名signature。在全局环境中为graphics包中的plot创建泛型函数。

showMethods("plot")
Function: plot (package graphics)
x="ANY"
x="color"
x="polygon"

查看新定义的绘图方法,扩展现有的plot函数。


上一篇下一篇

猜你喜欢

热点阅读