18. R编程(四:函数详述、匿名函数、变量作用域)

您所在的位置:网站首页 function在r语言 18. R编程(四:函数详述、匿名函数、变量作用域)

18. R编程(四:函数详述、匿名函数、变量作用域)

#18. R编程(四:函数详述、匿名函数、变量作用域)| 来源: 网络整理| 查看: 265

部分内容参见:生信技能树 课程

1. 定义函数

函数定义使用function关键字,一般格式为:

★函数名 my_f(c(1,2,3,4,5)) [1] 3 6 9 12 15 R 的默认参数

和py 一样,R 也可以指定缺省值,有缺省值的形式参数在调用时可以省略对应的实参, 省略时取缺省值。

R 的参数顺序

R函数调用时全部或部分形参对应的实参可以用“形式参数名=实参”的格式给出, 这样格式给出的实参不用考虑次序, 不带形式参数名的则按先后位置对准。

function(x, y){ c("The first one is", x, "The second one is", y)} > my_f(3,4) [1] "The first one is" "3" "The second one is" [4] "4" > my_f(y = 3, x = 4) [1] "The first one is" "4" "The second one is" [4] "3" > my_f(y = 3, 4) [1] "The first one is" "4" "The second one is" [4] "3"

作为好的程序习惯, 调用R函数时, 如果既有按位置对应的参数又有带名参数, 应仅有一个或两个是按位置对应的, 按位置对应的参数都写在前面, 带名参数写在后面, 按位置对应的参数在参数表中的位置应与定义时的位置一致。在定义函数时,没有缺省值的参数写在前面, 有缺省值的参数写在后面。不遵守这样的约定容易使得程序被误读, 有时会在运行时匹配错位。

函数的递归调用

最经典的斐波那契数列,在python 中我们通过在函数中引用函数自身来表示递归调用,R 也同样可以实现:

fib1 =2 ) { fib1(n-1) + fib1(n-2) } } > for (i in 1:5) print(fib1(i)) [1] 1 [1] 1 [1] 2 [1] 3 [1] 5

但这样定义有个问题,当我们的原先的函数改名了,使用新被定义的函数执行代码,就会发生报错了:

> tmp = fib1 > fib1 = 1 > tmp(3) Error in fib1(n - 1) : 没有"fib1"这个函数

因此对于递归的时候,除非为了装X,否则谨慎使用。(ps:装X 还不够吸引人吗?)

3. 函数的返回值函数体中最后的表达式为函数返回值> my_f = function(x){ x+1;x*3 } > my_f(3) [1] 9

如果需要指定,可以使用return(y)的方式在函数体的任何位置退出函数并返回y的值。

> my_f = function(x){ return(x+5); print("This won't be printed")} > my_f(5) [1] 10 invisible 可以让R命令行直接调用此函数时不自动显示返回值> my_f = function(x){if (x > 0) x else invisible(x)} > my_f(5) [1] 5 > my_f(-2)

当预期返回的结果显示无意义或者显示在命令行会产生大量的输出时可以用此方法。

4. 函数的组成部分

一个自定义R函数由三个部分组成:

函数体body(),即要函数定义内部要执行的代码;formals(),即函数的形式参数表以及可能存在的缺省值;environment(),是函数定义时所处的环境, 这会影响到参数表中缺省值与函数体中非局部变量的的查找。> my_fn = function(x,y=100) x+y > environment(my_fn) > body(my_f) { x * 3 } > body(my_fn) x + y > formals(my_fn) $x $y [1] 100

注意,函数名并不是函数对象的必要组成部分。

R提供了一个非常方便的函数alist用来构建参数列表。我们可以像定义函数一样很简单地指定参数列表。

> f formals(f) args(f) function (x, y = 100, z = 200) NULL 5. 函数的使用技巧向量化与效率

关于程序效率,请比较如下两个表达式:

n/(n-1)/(n-2)*sum( (x - xbar)^3 ) / S^3 n/(n-1)/(n-2)*sum( ((x - xbar)/S)^3 )

ps:虽然能够理解这种S 表达式先后处理的差异,可是这个除了n 次还是无法理解啊。

这两个表达式的值相同。表面上看,第二个表达式更贴近原始数学公式, 但是在编程时, 需要考虑计算效率问题, 第一个表达式关于S只需要除一次, 而第二个表达关于S除了n次, 所以第一个表达式效率更高。一个函数如果仅仅用几次, 这些细微的效率问题不重要, 但是如果要编写一个R扩展包提供给许多人使用, 程序效率就是重要的问题。

部分匹配

在调用函数时, 如果以“形参名=实参值”的格式输入参数, 则“形参名”与定义时的形参名完全匹配时最优先采用;如果“形参名”是定义时的形参名的前一部分子串, 即部分匹配, 这时调用表中如果没有其它部分匹配, 也可以输入到对应的完整形参名的参数中;按位置匹配是最后才进行的。有缺省值的形参在调用时可省略。

部分匹配允许我们节省一丁点儿键入工作量(狂喜),但确实实在不严谨,虽然R 默认是纵容我们这种行为的,但我们可以选择设置发出警告:

options(warnPartialMatchArgs = TRUE) > my_f = function(asd){asd} > my_f(a = 3) [1] 3 Warning message: In my_f(a = 3) : 'a'部分匹配为'asd' do.call 与管道符号

do.call 可以对列表对象进行处理,相当于将列表中的所有元素作为参数进行处理:

> do.call(mean, list(3,4,5)) [1] 3

而magrittr包中的%>% 管道符号,则可以很方便的表现出步骤执行的顺序,可以参见:比如说对数据框类型的数据处理时使用。

6. 匿名函数

由于R 的语法限制,其并没有py 中通过lambda 创建匿名函数的功能。

但也可以通过其他函数来使用匿名函数,比如apply 家族。但由于R 的函数声明比py 相对复杂(需要声明function 来创建),因此语句上也复杂一些:

> sapply(c(-2, -0.5, 0, 0.5, 1, 1.5), function(x) if(abs(x) Filter(function(x) is.numeric(x), list(1,2,'sd')) [[1]] [1] 1 [[2]] [1] 2

此外函数integrate 也接受函数作为参数的。

简单理解来说,任何可以接受函数作为参数的函数,都可以使用匿名函数。

7. 变量作用域全局变量与工作空间

在所有函数外面(如R命令行)定义的变量是全局变量。在命令行定义的所有变量都保存在工作空间 (workspace), 也称为全局环境中。在RStudio的Environment窗格可以看到“Global Environment”的内容, 分为数据框(Data)、其它变量(Values)和函数(Functions)三类。

在命令行可以用ls()查看工作空间内容。

> ls() [1] "a" "fib1" "fib2" "g" "gv" "i" "my_f" "tmp"

ls() 函数还支持正则匹配,我们可以使用pattern 参数匹配指定的变量。

此外,object.size 函数可以用来计算变量所占的存储大小,其也可以接受多个变量,计算其大小总和:

> object.size(ls()) 560 bytes 局部变量

在一般计算机语言中, “变量”实际是计算机内存中的一段存储空间, 但是R中略微复杂一些, R的变量实际是指向R对象的引用, 称为“绑定” (这点和py 类似?)。在较简单的函数定义中大体上可以将R 变量看成是对应的存储空间。

函数的参数(自变量)在定义时并没有对应的存储空间, 所以也称函数定义中的参数为“形式参数”。函数的形式参数在调用时被赋值为实参值(这是一般情形), 形参变量和函数体内被赋值的变量都是局部的。这一点符合函数式编程(functional programming)的要求。所谓局部变量, 就是仅在函数运行时才存在, 一旦退出函数就不存在的变量。

tmp = function(){b = 10; print(b)} > b 错误: 找不到对象'b'

在函数调用时,行参被赋值为实参,在函数内部对形式参数作任何修改在函数运行完成后都不影响原来的实参变量, 而且函数运行完毕后形式参数不再与实际的存储空间联系。

a = 5 > tmp = function(){a = 10; print(a)} > tmp() [1] 10 > a [1] 5

如果我们希望在函数内对变量进行修改,可以将其返回值进行赋值。

f


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3