张金龙的博客分享 http://blog.sciencenet.cn/u/zjlcas 物种适应性、分布与进化

博文

如何编写R函数

已有 44258 次阅读 2011-10-26 19:28 |个人分类:统计分析|系统分类:论文交流|关键词:学者| R语言, 函数

如何编写R函数?

张金龙

jinlongzhang01@gmail.com

   R语言实际上是函数的集合,用户可以使用basestats等包中的基本函数,也可以自己编写函数完成一定的功能。但是初学者往往认为编写R函数十分困难,或者难以理解。这里对如何编写R函数进行简要的介绍。

   函数是对一些程序语句的封装。换句话说,编写函数,可以减少人们对重复代码书写,从而让R脚本程序更为简洁,高效。同时也增加了可读性。一个函数往往完成一项特定的功能。例如,求标准差sd,求平均值,求生物多样性指数等。R数据分析,就是依靠调用各种函数来完成的。但是编写函数也不是轻而易举就能完成的,需要首先经过大量的编程训练。特别是对R中数据的类型,逻辑判别、下标、循环等内容有一定了解之后,才好开始编写函数。 对于初学者来说,最好的方法就是研究现有的R函数。因为R程序包都是开源的,所有代码可见。研究现有的R函数能够使编程水平迅速提高。

   R函数无需首先声明变量的类型,大部分情况下不需要进行初始化。一个完整的R函数,需要包括函数名称,函数声明,函数参数以及函数体几部分。

1. 函数名称,即要编写的函数名称,这一名称就作为将来调用R函数的依据。

2. 函数声明,包括  <- function, 即声明该对象的类型为函数。

3. 函数参数,这里是输入的数据,函数参数是一个虚拟出来的一个对象。函数参数所等于的数据,就是在函数体内部将要处理的值,或者对应的数据类型。 函数体内部的程序语句进行数据处理,就是对参数的值进行处理 ,这种处理只在调用函数的时候才会发生。函数的参数可以有多种类型。R help的界面对每个函数,及其参数的意义及所需的数据类型都进行了说明。

4. 函数体

常常包括三部分.

1. 异常处理

输入的数据不能满足函数计算的要求,或者类型不符, 这时候一定要设计相应的机制告诉用户,输入的数据在什么地方有错误。 错误又分为两种。

第一种, 如果输入的数据错误不是很严重,可以经过转换,变为符合处理要求的数据时, 此时只需要给用户一个提醒,告知数据类型不符,但是函数本身已经 进行了相应的转换。

第二种,数据完全不符合要求,这种情况下,就 要终止函数的运行,而告知因为什么,函数不能运行。这样,用户在 使用函数的情况先才不至于茫然。

2. 运算过程

包括具体的运算步骤。 运算过程和该函数要完成的功能有关。

R运算过程中,应该尽量减少循环的使用,特别是嵌套循环。R提供了 applyreplicate等一系列函数,来代替循环,应该尽量应用这些函数, 提高效率。 如果在R中实在太慢,那么核心部分只能依靠C或者Fortran 等语言编写,然后再用R调用这些编译好的模块,达到更高的效率。

运算过程中,需要大量用到if等条件作为判别的标准。ifwhile都是需要数据TRUE/FALSE这样的逻辑类型变量,这就意味着,if内部,往往是对条件的判别,例如 is.na, is.matrix, is.numeric等等,或者对大小的比较,如,if(x > 0) if(x == 1) if(length(x)== 3)等等。if后面,如果是1行,则花括号可以省略,否则就必须要将所有的语句都放在花括号中。这和循环是一致的。

例子1

## if与条件判断

fun.test <- function(a, b, method = "add"){

    if(method == "add") { ## 如果if或者for/while

        res <- a + b       ## 等后面的语句只有一行,则无需使用花括号。

}

    if(method == "subtract"){

        res <- a - b

    }

    return(res)           ## 返回值

}

### 检验结果

fun.test(a = 10, b = 8, method = "add")

fun.test(a = 10, b = 8, method = "substract")

 

for循环有些时候是必须要用到的,for循环内部,往往需要用下标,访问数据内的一定元素,例如向量内的元素,这时候用方括号表示。一维的数据组合,或者数组,常常称为向量。二维的数据组合,往往称为矩阵,或者数据框。具体的访问方式主要是方括号内部有没有逗号的区别。for循环或者while循环有时候让人觉得比较困惑,可能需要专门的时间进行讲解。

2

### for循环与算法

test.sum <- function(x)

{

    res <- 0               ###  设置初始值,在第一次循环的时候使用

    for(i in 1:length(x)){

        res <- res + x[i] ## 这部分是算法的核心,

##总是总右面开始计算,结果存到左边的对象

    }

    return(res)

}

 

### 检验函数

a <- c(1,2,1,6,1,8,9,8)

test.sum(a)

sum(a)

 

无论是什么样的函数,算法才是最关键的。往往需要巧妙得设计算法,让函数快捷高效。

3. 返回值。

返回值就是函数给出的结果。打个比方,编写一个函数,就像自己攒一个机器,例如现在攒好 一台豆浆机,该豆浆机要求输入大豆,输入的大豆就是参数, 返回的结果,就是豆浆。如果该豆浆机需要不停地输入大豆, 而不能产出豆浆,这样的机器就一定会被扔掉。函数也是一样的, 需要给出返回值。 R中默认的情况是将最后一句作为返回值。但是为了函数的可读性起见,应该尽量指名返回值。返回值用return()函数给出。 函数在内部处理过程中,一旦遇到return(),就会终止运行, return()内的数据作为函数处理的结果给出。

下面举例说明R函数的编写方法。

3 计算标准差

sd2 <- function(x)

{

   # 异常处理,当输入的数据不是数值类型时报错   

   if(!is.numeric(x)){                          

      stop("the input data must be numeric!n") 

   }                                            

   # 异常处理,当仅输入一个数据的时候,告知不能计算标准差

   if(length(x) == 1){                          

      stop("can not compute sd for one number,

           a numeric vector required.n")       

   }

   ## 初始化一个临时向量,保存循环的结果,

   ## 求每个值与平均值的平方  

   x2 <- c()

   ## 求该向量的平均值 

   meanx <- mean(x)

   ## 循环 

   for(i in 1:length(x)){   

       xn <- x[i] - meanx  

       x2[i] <- xn^2

   }

      ## 求总平方和

   sum2 <- sum(x2)

   # 计算标准差

   sd <- sqrt(sum2/(length(x)-1))

   # 返回值

   return(sd)

}

 

## 程序的检验

## 正常的情况

sd2(c(2,6,4,9,12))

## 一个数值的情况

sd2(3)

## 输入数据不为数值类型时

sd2(c("1", "2"))

 

这样,一个完整的函数就编写完成了。当然,实际情况下,函数往往更为复杂,可能要上百行。但是好的编程人员往往将复杂的函数编写成小的函数。以便于程序的修改和维护,即使其中出现错误,也很好修改。

再有就是编写R函数时一定要注意缩进,编辑器用Notepad++, TinnR, Rstudio等,同时用等距字体(如Consolas, Courier new等)和语法高亮显示。这样便于快速寻找到其中的错误。

                                     

感谢 黄继红博士,杜彦君博士,毛岭峰博士,饶米德,冯刚对本文提出的意见和建议。

2011819 于中科院植物所

2011822日修改




https://m.sciencenet.cn/blog-255662-501317.html

上一篇:Inno setup 制作安装文件以编译FigTree为例
下一篇:6亿年来的海陆变迁

5 刘洋 况杰 杜彦君 吕利新 郭利萍

该博文允许注册用户评论 请点击登录 评论 (3 个评论)

数据加载中...
扫一扫,分享此博文

Archiver|手机版|科学网 ( 京ICP备07017567号-12 )

GMT+8, 2024-4-20 15:52

Powered by ScienceNet.cn

Copyright © 2007- 中国科学报社

返回顶部