[关闭]
@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

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注