Advanced R学习笔记(一)Names and value
Introduction
在R语言里面理解对象和它的名字之间的区别是非常重要的.可以帮助你:
-
更精确的判断代码的性能和内存的使用
-
避免意外的复制来写出更快的代码
-
更好地理解R的功能编程工具
Binding basics
x <- c(1, 2, 3)
准确的说这段代码做了两件事情
-
创建了一个对象,一个向量c(1,2,3)
-
将这个对象绑定给一个名字x
换句话说,这个对象或值没有名字,这是一个有值的名字
为了进一步探查这之中的不同,可以画出下面这样的图表
![](https://img.haomeiwen.com/i10426739/c59b8c7a7329e0a1.png)
名字x使用圆角矩形绘制的,它有一个箭头指向一个值c(1,2,3).这个箭头指向与赋值箭头<- 相反的方向,创建从左侧名称到右侧对象的绑定.
因此可以将一个名字试做对一个值的引用.例如,下面这个代码,不会得到一个拷贝,而是将另一个名字绑定到现在的值上.
y <- x
![](https://img.haomeiwen.com/i10426739/60e07167faca193f.png)
这个值c(1,2,3,)拥有一个标签0x74b.虽然这个向量没有名字,但有的时候需要引用一个对象而不依赖它所绑定的,因此有了一个唯一的标识符.这些标识符有一种特殊的形式,看起来像对象的内存地址.但是因为每次运行代码时实际的内存地址都会发生变化,所以我们使用这些标识符.
可以使用lobstr::obj_addr()来得到这些对象的标识.可以看到x和y指向相同的标识
obj_addr(x)
#> [1] "0x3000ed8"
obj_addr(y)
#> [1] "0x3000ed8"
这些标识符很长,在每次重启R的时候都会发生变化
理解名称和值之间的区别可能需要一些时间,但是理解这一点对于函数编程非常有帮助,因为函数在不同的上下文中可以有不同的名称.
Non-syntactic names
R对于有效的名称组成有着严格的规定.一个符合语法的名字必须由数字,字母和_组成,并且以字母开头.
此外不能使用一些保留单词,如TRUE,NULL,if和function(可以在?Reserved看到完整的列表)不遵守这些规则的名称是非句法名称;如果您尝试使用它们,将会报错
_abc <- 1
#> Error: unexpected input in "_"
if <- 10
#> Error: unexpected assignment in "if<-"
可以重写这个规则并且使用人和名字,通过反引号括起来
`_abc` <- 1
`_abc`
#> [1] 1
`if` <- 10
`if`
#> [1] 10
虽然不太可能去创建这些,但是需要了解这些名称是如何工作的,因为会经常遇到它们,例如在读取R之外的数据的时候
虽然可以使用双引号和单引号去办到这一点,但是最好不要这样做,因为这样就需要使用不同的语法去检索值.
Copy-on-modify
x <- c(1, 2, 3)
y <- x
y[[3]] <- 4
x
#> [1] 1 2 3
这是原始对象并没有改变,而是创建了一个新的对象,原对象的拷贝,并带着一个值的改变.
![](https://img.haomeiwen.com/i10426739/89178dec06e50e37.png)
理解这些将会提高对于一个R代码效率的理解.有种说法是R的对象是无法改变的,但是要避免这种说法,因为有几种重要的例外.
![](https://img.haomeiwen.com/i10426739/549c3ba2c0783df0.png)
tracemem()
可以看到在base::tracemem()的帮助下,对象何时被复制。一旦你用一个对象调用那个函数,你就会得到这个对象的当前地址
从那时起,每当复制该对象时,tracemem()将打印一条消息,告诉您复制了哪个对象、它的新地址以及导致复制的调用序列
y <- x
y[[3]] <- 4L
#> tracemem[0x7f80c0e0ffc8 ->0x7f80c4427f40]:
如果再次修改y,它就不会被复制了。这是因为新对象现在只绑定了一个名称,所以R应用就地修改优化.
y[[3]] <- 5L
untracemem(y)
Function calls
同样的规则也被应用在函数命名上面.
f <- function(a) {
a
}
x <- c(1, 2, 3)
cat(tracemem(x), "\n")
#> <0x55f3108>
z <- f(x)
# there's no copy here!
untracemem(x)
当一个函数运行的时候,函数内部的a与函数外部的x指向相同
![](https://img.haomeiwen.com/i10426739/ab1771316042d2a9.png)
简而言之:函数f()由右边的黄色对象表示。它有一个正式的参数a,当函数运行时,它成为执行环境(灰色框)中的绑定(用黑点线表示)。
一旦函数运行完毕,x和z就都会指向同一个对象.而没有拷贝,因为并没有做什么修饰.如果函数修饰了x,则R会创建一个新的拷贝,z将会绑定到新的对象.
列表
不仅是一个名字(例如向量)指向值,列表中的元素也是如此.列表更为更复杂,因为它不是储存值本身,而是储存对它们的引用
l1 <- list(1, 2, 3)
![](https://img.haomeiwen.com/i10426739/7f857bafca500bcb.png)
当修改列表时尤为重要.
![](https://img.haomeiwen.com/i10426739/b0719441f3d419cf.png)
列表对象和它的绑定被拷贝,但是被绑定的值并没有被拷贝.
若要查看跨列表共享的值,请使用lobstr::ref().ref()打印每个对象的内存地址和一个本地ID,以便轻松地交叉引用共享组件。
ref(l1, l2)
#> █ [1:0x2fa8198]
#> ├─[2:0x83b94b0]
#> ├─[3:0x83b9478]
#> └─[4:0x83b9440]
#>
#> █ [5:0x88a0b88]
#> ├─[2:0x83b94b0]
#> ├─[3:0x83b9478]
#> └─[6:0x88bb5a0]
数据框
数据框是向量的列表,
d1 <- data.frame(x = c(1, 5, 6), y = c(2, 4, 3))
![](https://img.haomeiwen.com/i10426739/20bac003c09751e0.png)
如果只修饰了一列,那么其他的则会继续指向它们原来的引用
d2 <- d1
d2[, 2] <- d2[, 2] * 2
![](https://img.haomeiwen.com/i10426739/04a79b9314c81716.png)
如果修饰了一行,那么每一列都将生成一个副本
d3 <- d1
d3[1, ] <- d3[1, ] * 3
![](https://img.haomeiwen.com/i10426739/f0255ef48dc6480d.png)
字符向量
![](https://img.haomeiwen.com/i10426739/901d35fcafb2e0e7.png)
R实际上使用了一个全局字符串池,其中字符向量的每个元素都是池中唯一字符串的指针
![](https://img.haomeiwen.com/i10426739/33b43186a8b45a4a.png)
可以通过设置ref()函数中character参数为TRUE来获得这些引用
ref(x, character = TRUE)
#> █ [1:0x552d798]
#> ├─[2:0x264d758]
#> ├─[2:0x264d758]
#> ├─[3:0x411e430]
#> └─[4:0x2b03af0]
Object size
可以通过lobstr::obj_size()了解一个对象占用了多少内存
obj_size(letters)
#> 1,712 B
obj_size(ggplot2::diamonds)
#> 3,456,344 B
由于列表是对值的引用,所以它的大小一般都比较小
x <- runif(1e6)
obj_size(x)
#> 8,000,048 B
y <- list(x, x, x)
obj_size(y)
#> 8,000,128 B
同样的 字符串也是引用,重复一千次并不会使得它们占用一千倍的内存
banana <- "bananas bananas bananas"
obj_size(banana)
#> 136 B
obj_size(rep(banana, 100))
#> 928 B
在R3.5.0之后 R之储存前后两个数字,所以大小都是一样的
obj_size(1:3)
#> 680 B
obj_size(1:1e3)
#> 680 B
obj_size(1:1e6)
#> 680 B
obj_size(1:1e9)
#> 680 B
Modify-in-place
正如上面所述,R在修改一个对象的时候通常是去创建一个拷贝,但是有两个例外
-
只有一个绑定对象时可以获得特殊的性能优化
-
环境是一种特殊类型的对象,总是在当前位置进行修改
只有一个绑定的对象
![](https://img.haomeiwen.com/i10426739/6349461eca0c7da4.png)
for循环在R中是出了名的慢,这种慢是由于在循环的时候迭代创建拷贝导致的.
x <- data.frame(matrix(runif(5 * 1e4), ncol = 5))
medians <- vapply(x, median, numeric(1))
for (i in seq_along(medians)) {
x[[i]] <- x[[i]] - medians[[i]]
}
这个循环非常慢,每次循环都会创建拷贝,可以用tracemem()看到
cat(tracemem(x), "\n")
#> <0x7f80c429e020>
for (i in 1:5) {
x[[i]] <- x[[i]] - medians[[i]]
}
#> tracemem[0x7f80c429e020 ->0x7f80c0c144d8]:
#> tracemem[0x7f80c0c144d8 -> 0x7f80c0c14540]:[[<-.data.frame [[<-
#> tracemem[0x7f80c0c14540 -> 0x7f80c0c145a8]:[[<-.data.frame [[<-
#> tracemem[0x7f80c0c145a8 ->0x7f80c0c14610]:
#> tracemem[0x7f80c0c14610 -> 0x7f80c0c14678]:[[<-.data.frame [[<-
#> tracemem[0x7f80c0c14678 -> 0x7f80c0c146e0]:[[<-.data.frame [[<-
#> tracemem[0x7f80c0c146e0 -> 0x7f80c0c14748]:
#> tracemem[0x7f80c0c14748 -> 0x7f80c0c147b0]:[[<-.data.frame [[<-
#> tracemem[0x7f80c0c147b0 -> 0x7f80c0c14818]:[[<-.data.frame [[<-
#> tracemem[0x7f80c0c14818 ->0x7f80c0c14880]:
#> tracemem[0x7f80c0c14880 -> 0x7f80c0c148e8]:[[<-.data.frame [[<-
#> tracemem[0x7f80c0c148e8 -> 0x7f80c0c14950]:[[<-.data.frame [[<-
#> tracemem[0x7f80c0c14950 ->0x7f80c0c149b8]:
#> tracemem[0x7f80c0c149b8 -> 0x7f80c0c14a20]:[[<-.data.frame [[<-
#> tracemem[0x7f80c0c14a20 -> 0x7f80c0c14a88]:[[<-.data.frame [[<-
untracemem(x)
可以使用下列方法来修改,使用列表
y <- as.list(x)
cat(tracemem(y), "\n")
#> <0x7f80c5c3de20>
for (i in 1:5) {
y[[i]] <- y[[i]] - medians[[i]]
}
#> tracemem[0x7f80c5c3de20 ->0x7f80c48de210]:
环境
必须要说的是它们的行为和其他对象不一样
![](https://img.haomeiwen.com/i10426739/9b3463a5caec8c8c.png)
下面这个环境绑定了e1和e2
e1 <- rlang::env(a = 1, b = 2, c = 3)
e2 <- e1
![](https://img.haomeiwen.com/i10426739/d739109c081b981e.png)
如果改变了绑定,环境将会被就地改变
e1$c <- 4
e2$c
#> [1] 4
![](https://img.haomeiwen.com/i10426739/475f54c384ff641a.png)
环境还可以包含他们自己,这是它们独有的性质
e <- rlang::env()
e$self <- e
ref(e)
#> █ [1:0x300b2f0]
#> └─self = [1:0x300b2f0]
![](https://img.haomeiwen.com/i10426739/ca23ec076ced6bf4.png)
Unbinding and the garbage collector
![](https://img.haomeiwen.com/i10426739/ee616f951a90f3e9.png)
最后这两个对象如何删除,这是垃圾收集器(缩写GC)的功能.
可以使用gc()来回收内存,强制使其工作,其他情况下不需要对其工作机理做过多的研究