library(lobstr)对象的变量名是一个常量指针,指向一个对象所在的地址。
x <- 1:3
y <- x # x, y 为指向同一个向量的指针
z <- 1:3
obj_addr(x) # 对象的唯一标识符,并非内存中真正的地址#> [1] "0x1daa7956720"
obj_addr(y)#> [1] "0x1daa7956720"
obj_addr(z) # 可见,z 指向的是另一个对象#> [1] "0x1daa79fb1d0"
# 几种访问函数的方式,第二个指向的不是同一个潜在函数对象
# 可能是因为某个包的 mean() 覆盖了 base::mean()
mean %>% obj_addr()#> [1] "0x1daa79f6c38"
base::mean %>% obj_addr()#> [1] "0x1da9cf7eff0"
get("mean") %>% obj_addr()#> [1] "0x1daa79f6c38"
evalq(mean) %>% obj_addr()#> [1] "0x1daa79f6c38"
match.fun("mean") %>% obj_addr()#> [1] "0x1da9cf7eff0"
R 中变量的命名规则:名称只能包含 a~z,
A~Z,
下划线_和句点.,不能有空格,也不能以下划线_开头,而且不能是
R 中的 reserved word
违反命名规则的变量名,要用反引号括起来,才能被识别
c %>% class() # c是一个内置函数,所以一般变量不能以'c'命名#> [1] "function"
c#> function (...) .Primitive("c")
`x+1` <- function(n) n + 1
`x+1`#> function(n) n + 1
`x+1`(3)#> [1] 4
R 中的对象一般不允许被修改(就像 JS
中的const常量),这对数据安全有好处。
一旦涉及数据修改,R 常常会 copy 一份数据的副本,然后在副本上修改数据——这是 R 速度慢的一个重要原因。
特别是 for 循环,每次迭代都会对数据产生一次或多次拷贝。purrr 包的高阶函数比较快就是因为避免了这一点。
有多个指针指向同一个对象,且通过其中一个指针修改该对象时,会生成该对象的一个副本(一个新对象)。修改作用在这个副本上,然后使被修改的指针指向这个副本。
x <- c(1, 2, 3)
cat(tracemem(x), "\n") # tracemen()跟踪指针指向的对象,每当这个对象被复制,会打印一条说明#> <000001DAAE36C0B8>
y <- x
y[3] <- 4L # x, y 均指向对象,通过 y 修改对象时,发生 copy#> tracemem[0x000001daae36c0b8 -> 0x000001daae3843f8]: eval eval eval_with_user_handlers withVisible withCallingHandlers handle timing_fn evaluate_call <Anonymous> evaluate in_dir in_input_dir eng_r block_exec call_block process_group.block process_group withCallingHandlers process_file <Anonymous> <Anonymous> do.call eval eval eval eval eval.parent local
obj_addr(x)#> [1] "0x1daae36c0b8"
obj_addr(y)#> [1] "0x1daae3843f8"
y[3] <- 5L # 不会 copy,因为只有唯一指针 y 指向对象
obj_addr(y)#> [1] "0x1daae3843f8"
untracemem(y) # 关闭跟踪对象作为参数传递给函数,并在函数内部修改对象时,也会创建对象的副本。函数外部的变量名仍指向未被修改的原对象
v1 <- c(1, 2, 3)
cat(tracemem(v1), "\n")#> <000001DAAE624AE8>
modify1 <- function(x) {
x[1] <- 0
x
}
modify1(v1) %>% obj_addr()#> tracemem[0x000001daae624ae8 -> 0x000001daae640e48]: modify1 obj_addr_ obj_addr %>% eval eval eval_with_user_handlers withVisible withCallingHandlers handle timing_fn evaluate_call <Anonymous> evaluate in_dir in_input_dir eng_r block_exec call_block process_group.block process_group withCallingHandlers process_file <Anonymous> <Anonymous> do.call eval eval eval eval eval.parent local
#> [1] "0x1daae640e48"
v1 # 外部的 v1 未被修改#> [1] 1 2 3
tracemem(v1)#> [1] "<000001DAAE624AE8>"为了节省空间,列表储存的不是对象,而是指针。修改列表时,只有变化的元素会复制。
l1 <- list(1, 2, 3)
l2 <- l1
l2[[3]] <- 4 # 拷贝机制启动,但拷贝的是指针,而不是 value,这是浅拷贝
lobstr::ref(l1, l2)#> █ [1:0x1daa6c99b68] <list>
#> ├─[2:0x1daa56f8ce0] <dbl>
#> ├─[3:0x1daa56f8d18] <dbl>
#> └─[4:0x1daa56f8d50] <dbl>
#>
#> █ [5:0x1daa6db2de8] <list>
#> ├─[2:0x1daa56f8ce0]
#> ├─[3:0x1daa56f8d18]
#> └─[6:0x1daa56f8e30] <dbl>


a <- 1:10
b <- list(a, a)
c <- list(b, a, 1:10)
lobstr::ref(a)#> [1:0x1daa2978200] <int>
# 第一行是指向列表的指针,2、3行是指向列表元素的指针
# b的两个元素,指向同一个向量
lobstr::ref(b) #> █ [1:0x1daab074838] <list>
#> ├─[2:0x1daa2978200] <int>
#> └─[2:0x1daa2978200]
lobstr::ref(c) # c的第三个元素不同于a指向的向量#> █ [1:0x1daa9c9cbf8] <list>
#> ├─█ [2:0x1daab074838] <list>
#> │ ├─[3:0x1daa2978200] <int>
#> │ └─[3:0x1daa2978200]
#> ├─[3:0x1daa2978200]
#> └─[4:0x1daa2724f90] <int>

x <- list(1:10)
ref(x)#> █ [1:0x1daa5fa5d68] <list>
#> └─[2:0x1daa57534c8] <int>
x[[2]] <- x
ref(x)#> █ [1:0x1daaca3e858] <list>
#> ├─[2:0x1daa57534c8] <int>
#> └─█ [3:0x1daa5fa5d68] <list>
#> └─[2:0x1daa57534c8]

数据框本质上是一个列表,储存的都是指向列向量的指针。

d1 <- data.frame(x = c(1, 5, 6), y = c(2, 4, 3))
d2 <- d1
d3 <- d1
ref(d1, d2, d3)#> █ [1:0x1daa7c31cd8] <df[,2]>
#> ├─x = [2:0x1daad9f05c8] <dbl>
#> └─y = [3:0x1daad9f0578] <dbl>
#>
#> [1:0x1daa7c31cd8]
#>
#> [1:0x1daa7c31cd8]
# 修改列,该列复制;其他列扔指向原对象
d2[, 2] <- d2[, 2] * 2
ref(d1, d2)#> █ [1:0x1daa7c31cd8] <df[,2]>
#> ├─x = [2:0x1daad9f05c8] <dbl>
#> └─y = [3:0x1daad9f0578] <dbl>
#>
#> █ [4:0x1daa7c518d8] <df[,2]>
#> ├─x = [2:0x1daad9f05c8]
#> └─y = [5:0x1daadb21b68] <dbl>

# 修改行,必须复制每一列
d3[1, ] <- d3[1, ] * 3
ref(d1, d3)#> █ [1:0x1daa7c31cd8] <df[,2]>
#> ├─x = [2:0x1daad9f05c8] <dbl>
#> └─y = [3:0x1daad9f0578] <dbl>
#>
#> █ [4:0x1daad6bf358] <df[,2]>
#> ├─x = [5:0x1daadc39218] <dbl>
#> └─y = [6:0x1daadc39178] <dbl>

也是指针的向量。每个指针指向一个字符串,这些字符串位于全局字符串池中。
x <- c("a", "a", "abc", "d")
ref(x, character = TRUE)#> █ [1:0x1daadcf9028] <chr>
#> ├─[2:0x1da9a6ebb08] <string: "a">
#> ├─[2:0x1da9a6ebb08]
#> ├─[3:0x1daa8c4ef08] <string: "abc">
#> └─[4:0x1da9a9402e8] <string: "d">

只有一个指针指向对象时,修改对象不会发生复制。
但 Rstudio 中与 Console 中不同,Rstudio 中仍要复制。本文件是在 Rstudio 中生成的,因此下面的代码段仍然含有复制。
v <- c(1,2)
obj_addr(v)#> [1] "0x1daadbd5f08"
v[[2]] <- 5
obj_addr(v)#> [1] "0x1daadbf5648"
但是,R 对指针数量的判断不够智能。如两个指针指向一个对象,其中一个被删除后,R 却不知道,仍然以为有两个指针。因此会产生很多不必要的对象拷贝。
总是被就地修改,不会 copy
此功能非常有用,可以实现闭包、R6类型系统,等等
zeallot 包提供的 %<-% 和 %>-%
函数
library(zeallot)
# unpack vector
c(lat, lng) %<-% c(38.061944, -122.643889)
lat#> [1] 38.06194
lng#> [1] -122.6439
# unpack list
coords_list <- function() {
list(38.061944, -122.643889)
}
c(lat, lng) %<-% coords_list()
lat#> [1] 38.06194
lng#> [1] -122.6439
# unpack regression result
c(inter, slope) %<-% coef(lm(mpg ~ cyl, data = mtcars))
inter#> [1] 37.88458
slope#> [1] -2.87579
# unpack data.frame
c(mpg, cyl, disp, hp) %<-% mtcars[, 1:4]
head(mpg)#> [1] 21.0 21.0 22.8 21.4 18.7 18.1
# unpack nested values
c(a, c(b, d), e) %<-% list("begin", list("middle1", "middle2"), "end")
a#> [1] "begin"
b#> [1] "middle1"
d#> [1] "middle2"
e#> [1] "end"
# unpack character string
c(ch1, ch2, ch3) %<-% "abc"
ch1#> [1] "a"
ch2#> [1] "b"
ch3#> [1] "c"
# unpack Date
c(y, m, d) %<-% Sys.Date()
y#> [1] 2022
m#> [1] 5
d#> [1] 2
# ... rest of
c(begin, ...middle, end) %<-% list(1, 2, 3, 4, 5)
begin#> [1] 1
middle#> [[1]]
#> [1] 2
#>
#> [[2]]
#> [1] 3
#>
#> [[3]]
#> [1] 4
# place holder
c(min_wt, ., ., mean_wt, ., max_wt) %<-% summary(mtcars$wt)
min_wt#> [1] 1.513
mean_wt#> [1] 3.21725
max_wt#> [1] 5.424
# 向右的解构赋值符号 %->%
mtcars %>%
subset(hp > 100) %>%
aggregate(. ~ cyl, data = ., FUN = . %>% mean() %>% round(2)) %>%
transform(kpl = mpg %>% multiply_by(0.4251)) %->%
c(cyl, mpg, ...rest)
cyl#> [1] 4 6 8