115-R编程17-赋值运算符

2022-04-05 15:35:50 浏览数 (1)

  • 参考:
    • R 语言赋值运算符:`<-` , `=`, `<<-` - Loca - 博客园 (cnblogs.com)[1]
    • 19 函数进阶 | R语言教程 (pku.edu.cn)[2]

前言

这次不说别的,补充一下R编程的赋值运算符。

1.=和<-

R语言中,<- 与 = 这两个赋值运算符最主要的区别在于两者的作用域不同。大家可以从下面的例子感受一下。

先说结论:= 一般用于参数传递, <- 用于赋值。

代码语言:javascript复制
> rm(x)       ## 如果变量 x 存在的话,删除此变量
> mean(x = 1:10)
[1] 5.5
> x
Error: object 'x' not found

在以上范例里,变量 x 是在函数的作用域里进行声明的,所以它只存在于此函数中,一旦运算完成便“消失”。关于作用域部分的详细说明,可以参见:[[113-R编程16-R的内部机制1]]

里面提到了句法作用域的概念。

代码语言:javascript复制
> rm(x)       ## 如果变量 x 存在的话,删除此变量
> mean(x <- 1:10)
[1] 5.5
> x
[1]  1  2  3  4  5  6  7  8  9 10

但如果是<- 的话,则变量则出现在我们的全局环境中了。

2. <- 作用域赋值成立条件

再看一个例子:

代码语言:javascript复制
> a <- 1
> f <- function(a) return(TRUE)
> f <- f(a <- a   1)

你觉得a 的返回值是多少?等于2?

代码语言:javascript复制
> a <- 1
> f <- function(a) return(TRUE)
> f <- f(a <- a   1)
> a
[1] 1

惊了,为什么这里又变成了1 了呢?

还是可以参见:[[113-R编程16-R的内部机制1]] 内容,这也和R 机制中的懒惰求值有关。

如果我们修改一下函数,在函数运行时引用这个形参:

代码语言:javascript复制
> f <- function(a) {a;return(TRUE)}
> tmp <- f(a <- a   1)
> a
[1] 2

这也体现了R “懒惰求值”的特性,只有用到某个形式变量的值才求出其对应实参的值。只有使用到a 时,才会求出a 实参结果 a 1,并把其赋值给a,否则a 还是a。

代码语言:javascript复制
> a <- 1
> f <- function(a) {
          if(runif(1)>0.5) TRUE 
          else a
  }

> f(a <- a 1);a
[1] TRUE
[1] 1
> f(a <- a 1);a
[1] TRUE
[1] 1
> f(a <- a 1);a
[1] TRUE
[1] 1
> f(a <- a 1);a
[1] 2
> f(a <- a 1);a
[1] TRUE
[1] 2
> f(a <- a 1);a
[1] 3

3.<- 为何出现

通过上面的案例,我们可以很明显的感觉到,在形参中使用<- 赋值的复杂。因此,并不推荐大家在调用函数时输出参数的同时进行赋值。

= 用于参数传递, <- 用于赋值。

所以,为了避免上面的作用域、懒惰求值等等复杂的问题,做到该赋值时赋值,该传参时传参,两不干预,基本99% 的上述相关问题,全部都解决了。

但这并不意味着<- 不是个好东西。最开始学习R 时,我也时常会图省事用= 进行替代。

但其实,这个赋值号的意义本身是很清晰的。这个箭头,它是有方向的。

箭头的指向,比如 <-,也就是右边指向左边,表示的便是将右边的值赋值给左边,如果你喜欢,用 -> 甚至都是可以的。

这也就意味着,通过<- 的符号,可以非常直观的观察函数的赋值方向:

代码语言:javascript复制
> 5 -> s
> s
[1] 5

但通常习惯,我们还是从右往左赋值的。

4.超赋值运算符<<-

在 R 语言中,处在某一个环境层的代码都拥有读入上一环境层的变量的权限,但相反地,若只通过标准的赋值运算符 <- ,是无法向上一环境层写入变量的。若想在上一环境层进行赋值行为,即向上一层次写入变量,则需要用到 <<- (superassignment)运算符啦~

比如在闭包中使用超赋值:

代码语言:javascript复制
f.gen <- function(){
  runTimes <- 0

  function(){
    runTimes <<- runTimes   1
    print(runTimes)
  }
}
f <- f.gen()
f()

因为在内部函数function() 外部,即f.gen函数作用域中存在变量runTimes,此时每调用一次函数f,其内部的次数结果都会向上复制给外部的runTimes。

ps:关于闭包的参见下期[[123-R编程20-函数式编程和函数工厂]]

一般说来, <<- 多用于在顶层环境中写入变量。然而需要注意的是,以 <<- 执行赋值时,会一直向上直至顶层进行变量查找。若在查找过程中寻找到该名称的变量,就会进行赋值操作。否则,将在顶层环境中创建变量并赋值。

即如果想在多层函数的内部使用 <<- 改变全局环境中内容的话,需要注意中间函数是否存在相同名称的变量。

比如:

代码语言:javascript复制
> glob <- function(){
          d <- 5
          nxt1 <- function(){d <<- e   1}
          e <- 2
          nxt1()
          d        
  }

> glob()
[1] 3
> d
Error: object 'd' not found

## 对比以下
> glob <- function(){
> 
          ##区别在这里,少了一行' d <- 5' 
           
          nxt1 <- function(){d <<- e   1}
          e <- 2
          nxt1()
          d        
  }

> glob()
[1] 3
> d
[1] 3

因为<<- 会将函数作用域中的变量带到全局,所以在使用以及变量的命名选择上,一定要慎重考虑。

5.创建数据框需要=

代码语言:javascript复制
> tmp <- data.frame(
      a1 = 1:3,
      a2 = 1:3
  )
> tmp <- data.frame(
      a1 = 1:3,
      a2 = 1:3
  )
> 
> tmp2 <- data.frame(
      a3 <- 1:5,
      b3 <- 2:6
  )
> tmp
  a1 a2
1  1  1
2  2  2
3  3  3
> tmp2
  a3....1.5 b3....2.6
1         1         2
2         2         3
3         3         4
4         4         5
5         5         6

大家自行感受,data.frame 不懂<-,就像你不懂蓝色妖姬代表清纯的爱和敦厚善良一样。

6-“<<-”的骚操作

参见:R的内部机制 - 王诗翔 (shixiangwang.github.io)[3]

运算符<<-的一个用法是“拉平”一个嵌套列表。

ps:这里我也不是很能明白其递归的内涵,主要也是我懒,以后有时间再深究其操作。

比如如下这个嵌套列表:

代码语言:javascript复制
nested_list = list(
    a = c(1, 2, 3),
    b = list(
        x = c("a", "b", "c"),
        y = list(
            z = c(TRUE, FALSE),
            w = c(2, 3, 4)
        )
    )
)
str(nested_list)
#> List of 2
#>  $ a: num [1:3] 1 2 3
#>  $ b:List of 2
#>   ..$ x: chr [1:3] "a" "b" "c"
#>   ..$ y:List of 2
#>   .. ..$ z: logi [1:2] TRUE FALSE
#>   .. ..$ w: num [1:3] 2 3 4

我们可以尝试将其拉平——即将所有的嵌套部分放在最外层。rapply()lapply()的递归版本,每一次迭代都将函数作用到列表特定的原子向量上。

代码语言:javascript复制
> X <- list(list(a = pi, b = list(c = 1L, d = 32)))
> rapply(X, log)
       a      b.c      b.d 
1.144730 0.000000 3.465736 

可以简单的理解为,rapply 会直逼列表及嵌套列表中最小元素,并对最小元素逐一处理。

如果想要实现拉平呢:

代码语言:javascript复制
flat_list = list()
i = 1

res = rapply(nested_list, function(x) {
    flat_list[[i]] <<- x
    i <<- i   1
})
res
#>     a   b.x b.y.z b.y.w 
#>     2     3     4     5

names(flat_list) = names(res)
str(flat_list)
#> List of 4
#>  $ a    : num [1:3] 1 2 3
#>  $ b.x  : chr [1:3] "a" "b" "c"
#>  $ b.y.z: logi [1:2] TRUE FALSE
#>  $ b.y.w: num [1:3] 2 3 4

也就是利用rapply()将一个函数递归应用到nested_list()列表上,每一次迭代,函数通过x获得一个该列表的原子向量,然后将flat_list的第i个元素设为x,并将i加1。

番外,令人羡慕的py语法糖

单纯的嫉妒一下py。

比如这个:

代码语言:javascript复制
In [4]: a,b                                                                     
Out[4]: (2, 3)

In [5]: a,b =b,a                                                                

In [6]: a,b                                                                     
Out[6]: (3, 2)

而R 呢:

代码语言:javascript复制
> a <- 1

> 2 -> b

> a,b <- b,a

错误: 意外的',' in "a,"

所以总是整一些花里胡哨的有什么用呢?

参考资料

[1]

R 语言赋值运算符:<- , =, <<- - Loca - 博客园 (cnblogs.com): https://www.cnblogs.com/loca/p/4301344.html

[2]

19 函数进阶 | R语言教程 (pku.edu.cn): https://www.math.pku.edu.cn/teachers/lidf/docs/Rbook/html/_Rbook/p-advfunc.html#p-advfunc-inner

[3]

R的内部机制 - 王诗翔 (shixiangwang.github.io): https://shixiangwang.github.io/home/cn/post/2019-11-20-r-mechanism/

0 人点赞