变量用于临时存储数据,而函数用于操作数据,实现代码的重复使用。在R中,函数只是另一种数据类型的变量,可以被分配,操作,甚至把函数作为参数传递给其他函数。分支控制和循环控制,和通用编程语言的风格很相似,但是,不要因为R具有这些元素,就把R作为通用编程语言来看待,R的最小变量是向量,是一种面向数组(Array-Oriented)的语言。在编程时,尽量用array的方式思考,避免使用循环(for,while,repeat)控制,而使用apply函数家族实现计算的迭代,这是R语言的特色,把特定的函数应用于向量,列表或数组中的每个元素上。
一,R风格的函数
R通过function关键字定义函数,函数主要由函数名称,参数,运行的代码块和返回值组成,函数名称是变量,参数是调用函数时需要传递的形式参数;代码块是由由大括号构成,是调用函数时需要执行的代码逻辑;R的函数不需要显式地使用return关键字明确返回值,R函数的计算的最后一个值将自动作为返回值。
1,创建函数
例如,创建一个函数,并把函数赋值给avg变量,可以认为函数的名称是avg,其有两个参数a和b,大括号内的代码是函数体,实现的功能是求两个数的平均值。在R中创建函数时,如果函数体只有一行代码,可以省略大括号。
avg=function(a,b) { mead(c(a,b)) }
该函数没有显式调用return关键字,函数代码块种计算的最后一次自动最为返回值,在avg函数种,代码块调用系统函数mean,返回参数a和b的平均值,调用格式是函数名称(形式参数):
> avg(2,3) [1] 2.5
调用函数时,如果不命名参数,则R按照位置匹配参数,因此,2对应形式参数a,3对应形式参数b。如果要改变传递参数的顺序,则可以传入命名参数:
> avg(b=2,a=3) [1] 2.5
可以为函数的参数设置默认值,当调用函数时,如果没有为参数传递相应的值,那么函数将自动使用默认值作为函数代码块执行的当前值。
> avg=function(a=1,b=1)mean(c(a,b)) > avg() [1] 1
虽然函数中最后一行代码的值是自动返回的,但是,为了便于阅读代码,建议使用return函数,清晰地指定要返回的值,return函数除了指定函数的返回值之外,还能使函数退出,不再继续执行后面的代码。
> avg=function(a=1,b=1) return(mean(c(a,b)))
2,把函数作为参数传递给其他函数
函数本质上是一个函数类型的变量,和其他类型的变量一样,能够作为函数的形式参数,传递给其他函数,例如:
> fn_call=function(x,a,b)x(a,b) > fn_call(avg,1,2) [1] 1.5
R提供一个系统函数do.call(fun,args),用于调用其他函数,第一个参数是函数变量,第二个参数是有参数构成的列表:
> do.call(avg,args=list(1,2)) [1] 1.5
3,查看函数的定义
通过函数args用于显示函数的头部文本,用于显示函数的头部定义,例如:
> args(avg) function (a = 1, b = 1) NULL
body(fun)函数用于返回函数的代码块,例如:
> body(avg) mean(c(a, b))
除了这两个函数,还有函数formals(fun),用于查看函数的参数及其默认值,如果函数没有定义参数的默认值,那么只显示参数名,formals显示的参数名格式是$参数名。函数deparse(fun)用于查看函数调用的函数。
4,特殊的参数
R提供了一个特殊的运算符(...),该运算符允许函数具有任意多个的参数,并且不需要在函数定义中指定。
avg=function(a,b, ...) mead(c(a,b))
二,分支和循环
分支和循环是通用编程必不可少的两大流程控制元素,分支语句根据条件,执行不同的代码,而循环语句重复执行代码块,可以根据条件结束循环。
1,分支控制
经典流程控制关键字是if-else,特殊的关键字ifelse(test, yes, no),当test条件为TRUE时,返回yes值,当test条件为FALSE时,返回no值。
> ifelse(c(1,2,3)>=2,'Greater','Less') [1] "Less" "Greater" "Greater"
如果分支较多,可以使用switch函数实现分支的选择,switch函数的第一个参数是表达式(exp),通常是一个字符串;当表达式(exp)匹配后续的参数名(即变量名)时,返回参数的值:
> color=function(t) switch(t,r='red',g='green',b='blue') > color('r') [1] "red"
如果不匹配任何参数名,switch函数不返回任何值,可以添加一个匿名的参数,当表达式(exp)匹配不上任意一个命名参数时,switch函数将返回匿名参数的值:
> color=function(t) switch(t,r='red',g='green',b='blue', 'error') > color('d') [1] "error"
2,循环控制
循环控制语句是repeat、while和for,R的向量化自带循环特性,减少了循环语句使用的场景,但是在重复执行代码时,循环控制还是非常有用。
repeat 循环:先执行代码,遇到break关键字,结束循环,也可以在break关键字前增减if(test)语句,当指定的条件成立(为TRUE)时,执行break关键字,结束循环:
repeat {
code
if(test) break
}
while 循环:先检测条件,如果条件为TRUE,执行code;如果条件为FALSE,结束循环:
while(test)
{
code
}
for 循环:使用迭代器和一个向量参数,在每个循环中,迭代器变量从向量中取得一个值,直到迭代所有得向量:
for(i in c(1:5)) { code }
三,高级循环控制
高级循环控制主要是指编写向量化的代码,apply系列的函数使得循环更加向量化,更加高效。
1,重复调用表达式
rep函数把输入的参数重复多次,如果参数是表达式,rep函数会把表达式的结果重复多次;而replicate函数是重复调用表达式,每次调用表达式的过程都是独立的,这意味着,如果产生随机数,rep函数产生的随机数是”伪随机的“,重复第一次产生的随机数,而replicate产生的随机数是真正随机的,每次都不相同。
> rep(runif(1),5) [1] 0.8721105 0.8721105 0.8721105 0.8721105 0.8721105 > replicate(5,runif(1)) [1] 0.9426709 0.1280271 0.1926333 0.7091503 0.5404846
2,遍历数组
R 文档中,apply函数返作用于数组或矩阵,按照相应的维度(边,margin)提取参数,调用函数执行计算,并返回的结果,结果的类型是向量,数组,或列表。
Returns a vector or array or list of values obtained by applying a function to margins of an array or matrix.
apply函数的最大特色是用于调用函数,第一个参数是数组或矩阵,第二个参数是边,即维度的序号或维度名称,1是指按照第一个维度(row),2是指按照第二个维度(column),3是指按照第三个维度(high),以此类推,也可以是维度的向量,例如,c(1,2);第三个参数是函数变量,函数变量必须能够接收相应维度的所有项:
apply(X, MARGIN, FUN, ...)
例如,对矩阵按照行(row)调用求和函数,统计每行的加和,即把同一行中的所有列的值相加求和:
> a_matrix=matrix(data=1:12,nrow=4) > a_matrix [,1] [,2] [,3] [1,] 1 5 9 [2,] 2 6 10 [3,] 3 7 11 [4,] 4 8 12 > apply(a_matrix,1,sum) [1] 15 18 21 24
2,遍历列表
遍历列表是指遍历列表的所有列表项,最常用的apply函数是lapply,该函数名是”list apply“的缩写,该函数的定义是:lapply(x, FUN, ...),参数x是列表变量,参数FUN是应用在列表项上的函数,lapply函数返回的结果是一个列表,其长度和参数x(参数是一个列表)的长度相同,每一个列表项对应参数x的列表项,只不过,lapply函数把参数FUN指定的函数,作用于参数x的每个列表项,把FUN处理的结果,作为新的列表项返回。
例如,定义一个列表,该列表有两个列表项,每个列表项中都有重复的元素值:
> a_list=list(c('a','b','a'),rep(c(1:2),5)) > a_list [[1]] [1] "a" "b" "a" [[2]] [1] 1 2 1 2 1 2 1 2 1 2
方法1,通过for循环,逐个遍历列表项,使用unique函数,去除列表项中元素的重复值:
> b_list=list() > for(i in seq_along(a_list)){ + b_list[[i]]=unique(a_list[[i]]); + }
方法2,使用lapply函数,应用unique函数,去除列表项中元素的重复值:
> lapply(a_list,unique)
3,多参数列表应用
mapply是多参数列表应用(multiple argument list apply)的简称,mapply函数的第一个参数是函数变量(fun),函数变量可以接收多个向量作为参数。mapply函数的作用是把函数变量应用于参数的各个元素。
> va=c(1:5) > vb=c(6:10) > mapply(sum,va,vb) [1] 7 9 11 13 15
4,分组聚合
apply函数家族,最重要的函数是tapply,在研究数据时,有时需要对数据按照特定的字段进行分组,然后统计各个分组的数据,这就是SQL语法中的分组聚合。在R语言中,可以通过三步实现:拆分-应用-合并(Split-Apply-Combine)
对玩家的游戏成绩进行统计和分析,创建示例数据:
> players_scores=data.frame( + player=rep(c('Tom','Dick','Jim'),times=c(2,5,3)), + score=round(runif(10,1,100),-1) + )
计算每个玩家的平均得分,首先对玩家分组,split函数的作用是按照特定的字段对数据框进行分组,第一个参数是数据框对象,第二个参数是分组字段,split函数返回的结果是列表对象。
例如,split(score,player)函数的作用是按照player字段把数据框中的score拆分成一组,也就是说,player 相同的score是同一个分组,填充到同一个列表项中:
> (scores_by_player=with(players_scores,split(score,player))) $Dick [1] 70 20 30 70 70 $Jim [1] 80 90 50 $Tom [1] 80 90
第二步是对每个分组计算平均分,利用lapply函数,把函数引用于列表的每个列表项中:
list_mean_by_player=lapply(scores_by_player,mean)
第三步是把结果合并到单个向量中,也就是把列表转换成向量,
> unlist(list_mean_by_player) Dick Jim Tom 52.00000 73.33333 85.00000
在数据分析中,”拆分-应用-合并“ 显示十分繁琐,tapply函数一次完成所有的三个步骤,一气呵成:
with(players_scores,tapply(score,player,mean))
tapply函数常用的参数共有三个,第一个参数是:数据框对象或向量,第二个参数是因子列表,也就是分组字段,第三个参数是指对单个分组应用的函数变量:
tapply(X, INDEX, FUN = NULL, ...)
by函数和aggregate函数是tapply函数的包装函数,功能相同,接口稍微不同。
参考文档:
R Document