294 阅读 2020-10-20 10:35:03 上传
以下文章来源于 R语言数据科学
函数的概念
函数是指一段可以直接被另一段程序或代码引用的程序或代码。也叫做子程序、(OOP中)方法——百度百科。要想成为一名数据科学家,编写函数绝对是必经之路。函数使得程序更加简洁、便于复用、维护。Hadley认为,假如同一段代码copy&paste 超过2次,就可以利用函数,进行处理了。类似于其它计算机语言,R也支持UDF形式的函数。以下主要介绍,在R中构建函数,将会遇到的问题。
如何创建函数
一般情况下,函数包含3部分组件:"函数名"、"参数"、"表达式"(函数正文)
函数格式如下:
函数名 <- function(参数...) { # "{"与function同一行,代表起始
表达式 #空2格,再写表达式
} # "}"独自一行,代表结束

函数名
建议把函数名的每个单词首字母大写(遵循google规范),按照"动宾"形式设计原则,清楚表达函数功能。避免定义类似"f()"、"fun()"、"my_fun()",使人无法理解函数功能。
在某些情形下,可以省略函数名,比如map函数中。
举个栗子:x <- c(1,2,3) #初始化一个向量
map_dbl(x, function(x){x + 1} )#函数并没有设置函数名
[1] 2 3 4

参数
参数一般是提供计算的数据,亦或是控制计算过程的细节需要理解以下几个方面:1、参数的调用形式2、形参与实参
3、2种特殊参数
1、调用的3种形式:"全部名称调用" 、 "部分名称调用" 、"位置调用"优先级:"全部名称调用" > "部分名称调用" >"位置调用"举个栗子: f <- function(abcdef = 1, bcde1 = 20, bcde2){
#构造一个函数f, 有3个参数abcdef、bcde1、bcde2
#其中abcdef与bcde1,设置了默认参数1和20
abcdef <- abcdef + 1
bcde1 <- bcde1 + 10
bcde2 <- 20
c(abcdef, bcde1, bcde2)
}
#直接调用f函数,不添加任何参数
f()
[1] 2 30 20
#根据"位置",参数abcdef被设置成2
f(2,,)
[1] 3 30 20
#根据"全部参数名称",参数bcde1被设置成3,默认的abcdef还是1
f(bcde1 = 3)
[1] 2 13 20
#根据"部分名称",参数abcdef被设置成12。注意根据"ab"直接能唯一定位到参数"abcdef"
#假如部分参数名是"bcd",则函数无法正常运行
f(ab = 12)
[1] 13 30 20
参数的调用遵循"惰性"原则,可能会出现传递了参数,但在函数的表达式中并没有使用。
2、形参与实参"形参"是指形式参数,类似于一个占位符,有时候也会给形参赋一个默认值。上例的f函数,其中形式参数"abcdef"和"bcde1"就分别设置了默认值,假如在调用时没有传递数值,那就以1和20代入表达式计算。"实参"就是指代入表达式,实际计算的值了。比如f(bcde1 = 3)中的"3",就是"实参","3"会代替默认参数"20",代入表达式计算。
3、2种特殊参数:"缺失参数"与"..."缺失参数:在使用其计算之前,不知道具体的数值(遵循惰性原则)举个栗子:#参数x是根据参数a定义的
#x在函数结束之时,才会求值(惰性原则)
#x的取值也支持在函数body中的参数
#假如在"a <- 1"之前有使用到x, 则函数报错。
#因为当时的"环境"中,并不存在a,因此也无法求值x
myfun <- function(a, x = a + 1){
a <- 1
c(a, x)
}
myfun()
[1] 1 2
"...":将所有没有匹配的参数进行匹配 sum就属于"..."类型,查看sum函数的"Usage"发现sum(..., na.rm = FALSE)举个栗子: #向量x,y,z相加, 在z中存在NA
x <- c(1,2,3)
y <- c(2,3,4)
z <- c(3,4,NA)
sum(x,y,z, na.rm = TRUE)
[1] 22
#假如把sum嵌套于函数结构中, 注意在调用参数op的时候,需要全名称调用
#这个用例,有点画蛇添足!!!
fun <- function(..., op = FALSE) sum(..., na.rm = op)
fun(x,y,z, op = TRUE)
[1] 22假如希望知道"..."传递了什么参数,可以在函数中通过list(...)返回相应的参数值
表达式:函数的正文,负责具体功能实现,本篇不做详细介绍

环境空间
虽然在函数定义中,并没有提及"环境空间",但却至关重要。理解了环境空间,能更好的理解函数内在的执行逻辑我更偏向于称作函数的作用域(在R-Advanced书中,用environment),支持函数在不同的环境中,能通过对象名称找到相应的值Hadley是这么描述的:The data structure that determines how the function find the values associated with the name."作用域"具有以下2种特性
1、对象屏蔽2、动态查找
对象屏蔽:R会筛选"环境空间"最相近的对象代入函数,而屏蔽其他对象在R中,无论"变量"或者"函数",都是对象!!!举个栗子:#存在2个y变量, 存在于不同的环境
#函数调用过程中,会优先调用"环境空间"最接近的y
y <- 1 #在R_GlobalEnv空间中,创建y
test <- function(x){
y <- 101 #在函数内部空间中,创建y
c(x, y)
}
test(2) #函数调用了101,而非1
[1] 2 101
#存在2个y函数, 存在于不同的环境
#函数调用过程中,会优先调用"环境空间"最接近的y
y <- function(x){x + 1}
test <- function(x){
y <- function(x){x + 100} #在函数内部,创建了y函数
y(x)
}
test(1) #优先调用函数内部的y函数
[1] 101
动态查找:R只有在执行函数的时候,才会去查询函数中对象具体的值;创建函数的时候,并不会去查找假如函数引用了上层环境空间的对象,可能会出现调用同一个函数,结果不一致举个栗子:#函数test,调用了上层环境空间的x对象
#x对象不受函数控制,随时可能变动
#虽然调用的都是test(),但是返回的结果不同
x <- 101
test <- function(){x}
test()
[1] 101
x <- 102
test()
[1] 102
通常情况下,应该避免这种形式的函数,因为这样写,让函数失去了"独立性"。好比原来是一条管道,只有头尾开了口,现在中间段又凿了个洞。
建议通过codetools包中的findglobals(),检验自定义的函数是否引用了外在对象或者通过参数调用的形式,把上层对象传递到函数中。
本来Hadley定义了4中Lexical Scoping,但我更偏向于把"Name Masking"、"Functions versus variables"合并成了"对象屏蔽"、删除了"A fresh start"
以上规则,同样适用于其它计算机语言。








