R_Advance

Advanced R学习笔记(一)Names and value

2019-03-09  本文已影响27人  ihtguy

Introduction

在R语言里面理解对象和它的名字之间的区别是非常重要的.可以帮助你:

Binding basics


x <- c(1, 2, 3)

准确的说这段代码做了两件事情

换句话说,这个对象或值没有名字,这是一个有值的名字

为了进一步探查这之中的不同,可以画出下面这样的图表

image

名字x使用圆角矩形绘制的,它有一个箭头指向一个值c(1,2,3).这个箭头指向与赋值箭头<- 相反的方向,创建从左侧名称到右侧对象的绑定.

因此可以将一个名字试做对一个值的引用.例如,下面这个代码,不会得到一个拷贝,而是将另一个名字绑定到现在的值上.


y <- x

image

这个值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

这是原始对象并没有改变,而是创建了一个新的对象,原对象的拷贝,并带着一个值的改变.

image

理解这些将会提高对于一个R代码效率的理解.有种说法是R的对象是无法改变的,但是要避免这种说法,因为有几种重要的例外.

image

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指向相同

image

简而言之:函数f()由右边的黄色对象表示。它有一个正式的参数a,当函数运行时,它成为执行环境(灰色框)中的绑定(用黑点线表示)。

一旦函数运行完毕,x和z就都会指向同一个对象.而没有拷贝,因为并没有做什么修饰.如果函数修饰了x,则R会创建一个新的拷贝,z将会绑定到新的对象.

列表

不仅是一个名字(例如向量)指向值,列表中的元素也是如此.列表更为更复杂,因为它不是储存值本身,而是储存对它们的引用


l1 <- list(1, 2, 3)

image

当修改列表时尤为重要.

image

列表对象和它的绑定被拷贝,但是被绑定的值并没有被拷贝.

若要查看跨列表共享的值,请使用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))

image

如果只修饰了一列,那么其他的则会继续指向它们原来的引用


d2 <- d1

d2[, 2] <- d2[, 2] * 2

image

如果修饰了一行,那么每一列都将生成一个副本


d3 <- d1

d3[1, ] <- d3[1, ] * 3

image

字符向量

image

R实际上使用了一个全局字符串池,其中字符向量的每个元素都是池中唯一字符串的指针

image

可以通过设置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在修改一个对象的时候通常是去创建一个拷贝,但是有两个例外

只有一个绑定的对象

image

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]:

环境

必须要说的是它们的行为和其他对象不一样

image

下面这个环境绑定了e1和e2


e1 <- rlang::env(a = 1, b = 2, c = 3)

e2 <- e1

image

如果改变了绑定,环境将会被就地改变


e1$c <- 4

e2$c

#> [1] 4

image

环境还可以包含他们自己,这是它们独有的性质


e <- rlang::env()

e$self <- e

ref(e)

#> █ [1:0x300b2f0]

#> └─self = [1:0x300b2f0]

image

Unbinding and the garbage collector

image

最后这两个对象如何删除,这是垃圾收集器(缩写GC)的功能.

可以使用gc()来回收内存,强制使其工作,其他情况下不需要对其工作机理做过多的研究

上一篇 下一篇

猜你喜欢

热点阅读