@evilking
2018-03-03T16:06:16.000000Z
字数 5248
阅读 1275
R基础篇
R语言是函数式编程语言,虽然也有面向对象的概念;函数(function)是R语言中最重要的部分
> my_mean <- function(x,y,z){
+ t <- (x + y + z)/3
+ return(t)
+ }
> my_mean(3,5,7) #函数调用
[1] 5
>
这里创建了一个函数,函数名为"my_mean",功能是求传入的三个参数的均值;
函数需要使用function()
来标记;其中x,y,z
为函数的参数,可以是任何的数据类型,称为形参,如果不需要传入参数可以为空;后面的{}
中的语句为函数体;函数体中最后的return(t)
语句显示表示返回对象t,在R的函数中默认都是有返回值的,如果不显示写return()语句,返回的就是最后一条语句执行的结果
语句my_mean(3,5,7)
是函数调用,my_mean
为函数名,()
中的是实际传入函数的参数,称为实参;上例求实参3,5,7
的均值结果为5
上面的代码是在R Console里面输入的,所以显示会出现
+
号,在读者输入语句时,不需要输入+
当需要使用自定义函数时,代表代码逻辑就比较复杂了,我们一般不在控制台中输入了,而是写成R脚本,然后直接运行R脚本就可以了,这样更方便
> my_mean <- function(x,y,z){
+ t <- (x + y + z)/3
+ #return(t)
+ }
> (a <- my_mean(3,5,7))
[1] 5
> my_mean(3,5,7)
>
对比这段代码与上例代码,发现其中的return(t)
语句被注释掉了,#
在R中表示注释;但是运行结果依然有返回值,同时要注意,这种方式的返回值必须用个变量来保存,不然它的值会被销毁;所以R函数是默认都有返回值的,但是我们一般都会使用return()
语句显示指明返回,这样代码看起来更清晰
> my_mean <- function(x,y = 8,z = 9){
+ t <- (x + y + z)/3
+ return(t)
+ }
> my_mean(3,5,7)
[1] 5
> my_mean(3) #使用默认参数
[1] 6.666667
> my_mean <- function(x,y = 8,z){
+ t <- (x + y + z)/3
+ return(t)
+ }
> my_mean(3,5,7)
[1] 5
> my_mean(3,4)
Error in my_mean(3, 4) : 缺少参数"z",也没有缺省值
>
还是以上面求三个参数的平均值为例,如果我想其中某两个参数有个默认值,即当调用该函数时,如果这俩实参缺省时,就用默认值,有值时就用传入的实参;这时我们就需要使用函数参数的默认值function(x,y = 8,z = 9)
,这里设置y的默认值为8,z的默认值为9
my_mean(3,5,7)
调用时由于三个参数都有值,就用这传入的实参计算,结果为5
my_mean(3)
调用时,由于缺省了后面两个参数,则缺省值就使用设置的默认值,结果为(3+8+9)/3 = 6.6666667
在最后一个例子中,我们只设置了y的默认值,当调用my_mean(3,4)
时却报错,说缺少了参数z,那是因为R中参数是从左往右依次赋值的,所以这里3赋值给x,4会赋值给y,而z缺省就会报错
函数的返回值也可以是任何对象,可以返回复杂对象,函数其实也是一种对象,所以函数符返回值也可以是一个函数,如下:
> my_mean <- function(x,y = 8,z = 9){
+ print(x)
+ t <- function(x,y,z) x + y + z
+ return(t)
+ }
> (tt <- my_mean(3)) #tt是一个函数
[1] 3
function(x,y,z) x + y + z
<environment: 0x10762ce78>
> tt(3,4,5) #函数调用
[1] 12
>
函数”my_mean“的函数体中又创建了一个函数t,并把函数t当做返回值给返回了,所以当tt <- my_mean(3)
执行后,对象tt即使函数t,功能是求三个参数的和,于是可以使用tt(3,4,5)
进行函数调用,求得的结果为3+4+5 = 12
其中t <- function(x,y,z) x+y+z
语句,当函数体只有一句时可以将{}
去掉,但是得和function
标记写在一行,这样R才能识别
如果你的函数有多个返回值,可以把它们存储在一个列表或其他容器变量中
> tt #打印出函数对象
function(x,y,z) x + y + z
<environment: 0x10762ce78>
> class(tt) #函数是function类的实例
[1] "function"
> formals(tt) #查看函数对象的形参
$x
$y
$z
> body(tt) #查看函数体
x + y + z
> page(tt) #以文件的形式打开函数
> edit(tt) #以文件的形式打开函数,还可以编辑
function(x,y,z) x + y + z
<environment: 0x10762ce78>
在R中,函数也是对象,属于第一类对象,用class(tt)函数查看属于function类,同时在控制台直接输入函数名即可打印出该函数的信息
formals()函数可以查看函数有哪些形参
body()函数可以查看函数的函数体
page()函数可以以文件的形式打开函数
edit()可以以文件的形式打开函数,同时可以编辑函数;这就非常有用了,当你使用别人的函数时,可以通过这种方式来查看别人的源码
我们在前面的篇章中已经提过,R中的一些符号也是一种函数,比如"+","-","[","("等等,举个例子:
> 3+5 #普通形式的加法
[1] 8
> "+"(3,5) #函数调用形式的加法
[1] 8
> "%a2b%" <- function(a,b) #创建二元运算符
return(a+2*b)
> 3 %a2b% 5 #使用二元运算符
[1] 13
>
这里展示了"+"作为函数的使用方式,以及创建了一个自定义的二元运算符,从创建方式可以看成,二元运算符也就是一个函数
在后面的面向对象篇章中,会再使用这种方式来创建类中自定义的运算符
在R语言的文献中,函数被正式地称为”闭包“(closure),函数不仅包括参数和函数体,也包括它的”环境“(environment);环境是由创建函数时出现的对象集构成的
> w <- 12
> f <- function(y){
+ d <- 8
+ h <- function(){
+ return(d*(w+y))
+ }
+ return(h())
+ }
> environment(f) #查看对象f所在的环境
<environment: R_GlobalEnv>
> environment(d) #当前环境中找不到对象d
Error in environment(d) : 找不到对象'd'
> ls() #查看当前环境中的所有对象
[1] "f" "w"
> ls.str() #可以获得更多的信息
f : function (y)
w : num 12
>
此例中,函数f()是在顶层(解释器的命令提示符下)构建的,于是它处于顶层环境,顶层环境在R的输出结果里表示为R_GlobalEnv
ls()函数会把某个环境中的所有对象列举出来
学过其他编程语言的读者可能对作用域的概念比较熟悉,变量的作用域就是该变量起作用的范围,还是用上面的函数f()的例子:
> f(2) #函数f调用的结果为112
[1] 112
> d #找不到对象d
错误: 找不到对象'd'
>
这个结果是如何算出来的呢?首先f(2)调用时将对象2作为实参传入函数f赋值给形参y,然后给对象d赋值为8,然后创建了一个函数h,最后return()语句返回函数h的调用结果,然后执行函数h中的return()语句,即d*(w+y),先在函数h中查找这几个变量的值,发现没有,就会向上层环境中取找,就到了函数f的环境中,这个时候能找到变量d和变量y,不过依然没找到变量w,于是再向上层环境去找,这时就到了顶层环境,能找到变量w,于是完成计算并返回,其中8*(12+2)=112
在顶层环境中查找变量d,发现找不到,那是因为函数中定义的变量为局部变量,函数调用结束后对象就被销毁了
当层次中发生命名冲突时,上层环境中的变量会被局部变量屏蔽,即优先使用最里层的变量
经过命名冲突时可以的,但是我们建议避免这种情况的发生,这会使程序难以理解
> (x <- 1:5)
[1] 1 2 3 4 5
> my_fun <- function(x){
+ print(x)
+ print("******")
+ x[2] <- x[2] + 2
+ print(x)
+ print("======")
+ } #为了方便,向量取值时并没有做索引超出边界的检验
> my_fun(x)
[1] 1 2 3 4 5
[1] "******"
[1] 1 4 3 4 5
[1] "======"
> x
[1] 1 2 3 4 5
>
此例是为了展示当使用普通方法赋值时,修改非局部变量对对象本身是没有什么影响的
此例中先生成了一个向量x,然后调用函数my_fun,该函数的功能是修改向量第二个元素的值,从打印的结果中可以看成在函数范围内的对象x确实被修改了,但是上层环境中的对象x却没有被修改,这是因为函数调用传给形参的是实参的拷贝,所以不会对原对象产生影响
更准确的说,局部变量x实际上与相应的全局变量x共享一个内存地址,直到局部变量的数值发生变化,这种情况下,会分配给局部变量x新的内存地址
那如果想修改非局部变量该怎么做呢?
> two <- function(u){
+ u <<- 2*u
+ z <- 2*z
+ } #使用了超符值运算符
> x <- 1
> z <- 3
> u #此时当前环境中没有对象u
错误: 找不到对象'u'
> two(x)
> x
[1] 1
> z
[1] 3
> u #这时创建了对象u
[1] 2
>
u <<- 2*u
中<<-
符合叫超赋值运算符,可以对非局部变量进行值修改
该例中,首先查看对象u在当前环境中不存在,然后调用了函数two,将对象x的拷贝传入,然后执行2*u,生成的结果为2使用超赋值运算符赋值给u,这时R会依次向上层环境查找对象u,直到遇到含有该变量的第一个层次,如果没有找到则就会上层环境创建对象u,由于此时的上层环境即为顶层环境,所以最后在当前环境(此时为顶层环境)中查看对象u时存在,看下面的例子:
> f <- function(){
+ inc <- function(){ x <<- x+1}
+ x <- 3
+ inc()
+ return(x)
+ }
> f()
[1] 4
> x
错误: 找不到对象'x'
>
在函数inc中调用<<-
符号后,能在上层环境即函数f中找到变量x,那么就对函数f中的变量x进行写入操作(并不是在顶层环境中对变量x进行写入),最后在顶层环境中查看变量x就会发现找不到,这是因为函数f调用完后,它的局部变量会被销毁
也可以使用assign()函数对”上层变量“进行写操作,
assign("u",2*u,pos=.GlobalEnv)
,让R把2*u(此处的u是局部变量)的值向上赋值给调用栈中的变量u,具体而言是顶层环境下的u,因为pos=.GlobalEnv
> x <- c(1,2,4)
> names(x)
NULL
> names(x) <- c("a","b","c")
> names(x)
[1] "a" "b" "c"
> x
a b c
1 2 4
> x <- "names<-"(x,value = c("aa","bb","cc")) #调用"names<-"函数
> x
aa bb cc
1 2 4
>
可能在之前的篇章中使用names()函数查看对象的名称都好理解,但是为什么names(x) <- c(*)能给对象x设置名称,这里有疑惑,那是因为R语言中的置换函数
上例中调用"names<-"函数,名字中包含特殊符号,这就是置换函数的写法;任何左边不是标识符(意味变量名)的赋值语句都可看作是”置换函数“,当我们遇到g(u) <- v
形式时,R语言会尝试用u <- "g<-"(u,value=v)
的方式来执行
比如x[3] <- 8
这个简单的赋值语句,R会解释成x <- "[<-"(x,3,value=8)
生成1~50的整数向量,然后利用自定义函数递归实现二分查找算法
x <- 1:50
#z为要查找的元素
start <- 1
end <- length(x)
binary_chop <- function(z){
binary_num <- floor((start+end)/2)
if(x[binary_num] == z){
return(binary_num)
}else if(x[binary_num] < z){
start <<- binary_num + 1
return(binary_chop(z))
}else{
end <<- binary_num - 1
return(binary_chop(z))
}
}
binary_chop(30)
二分查找的思想是不断的取向量中间的索引,然后比对要查找的元素,如果找到就返回,如果小于就取前子向量再递归调用函数,如果大于就取后子向量再递归调用函数
这里使用了start和end两个指示变量来表示子向量的范围,通过<<-
超赋值运算符在上层环境中改变变量的值
floor()函数是向下取整函数,比如floor(3.2)
的结果为3