- 参考:
- 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()
的递归版本,每一次迭代都将函数作用到列表特定的原子向量上。
> 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/