RStudio和R中的运算符"[<-"

45

我偶然遇到了 "[<-" 运算符的奇怪行为。它的行为取决于调用的顺序以及我是在使用 RStudio 还是普通的 RGui。以下是一个例子来说明清楚。

x <- 1:10
"[<-"(x, 1, 111)
x[5] <- 123

据我所知,第一个分配应该不会改变x(或者我错了?),而第二个分配应该会改变x。实际上,上述操作的结果是:
x
[1]  1  2  3  4  123  6  7  8  9 10

然而,当我们以不同的顺序执行这些操作时,结果是不同的,并且x已经改变了!具体地说:
x <- 1:10
x[5] <- 123
"[<-"(x, 1, 111)
x
[1] 111   2   3   4   123   6   7   8   9  10

但这只发生在我使用纯 R 时!在 RStudio 中,两种选项的行为都是相同的。我已经在两台机器上检查过了(一台是 Fedora,一台是 Win7),情况看起来完全一样。我知道“函数式”版本("[<-"(x..))可能永远不会被使用,但我非常好奇为什么会发生这种情况。是否有人能解释一下?
==========================
编辑: 好吧,从评论中我知道原因是 x <- 1:10 的类型是 'integer',并且在替换 x[5] <- 123 后变成了 'double'。 但仍然存在一个问题,为什么在 RStudio 中的行为不同?我重新启动 R 会话,但它没有改变任何东西。

3
或许阅读一下这个链接 https://dev59.com/CW_Xa4cB1Zd3GeqPxiOm#15179065 可能有帮助 -- 不过这不是同一个问题。 - mnel
3
这只是一个猜测,我不确定如何进行测试,但在mnel提供的问题中,有人指出如果对象有第二个引用,则替换不会就地进行,而是会产生一个副本(因此不会修改原始变量)。也许RStudio作为其GUI的一部分有对象的引用。这是可能的,因为它有一个对象浏览器。或者是其他某个方面触发了复制机制,而不是就地替换行为。 - Brian Diggs
1
我无法重现这个问题,在RStudio和RGUI中表现相同(顺序很重要),Eclipse也是如此,因为它使用Rterm。 - Roman Luštrik
1
@Arun -- 我认为最初的评论是一个误导。这不是R的问题,所以如果它自2月20日版的R-devel以来没有改变,我会感到震惊(而且不是一件好事)。 - Josh O'Brien
2
@Arun -- 正如Matt所说,当您一次性粘贴所有命令时,Rstudio的对象浏览器没有机会以重置其named字段为2的方式“触摸” x,直到子分配发生之后。 您(或拥有Rstudio的其他人)可以通过一次性或逐个粘贴x <- 1:10; .Internal(inspect(x))来测试此功能。 在第一种情况下,我希望看到[MARK,NAM(1)],在第二种情况下,我希望看到[MARK,NAM(2)]。 如果是这样,我认为谜团基本上已经解决了。 - Josh O'Brien
显示剩余15条评论
1个回答

39

Rstudio的行为

Rstudio的对象浏览器在检查对象时会以一种方式修改对象,这迫使修改时进行复制。具体而言,对象浏览器使用至少一个R函数,其调用内部强制评估对象,在此过程中重置对象的命名字段的值从1变为2。来自R-Internals手册

当要更改对象时,将查询名称字段。值2表示必须在更改之前复制对象。[...]值1用于情况[...]其中原则上存在两个a的副本在计算期间[...]但不再存在,因此某些原始函数可以优化以避免在这种情况下进行复制。

为了看到对象浏览器如何修改已命名字段(下一个代码块中的[NAM()]),请比较运行以下行的结果。在第一行中,同时运行'lines',因此Rstudio没有时间在查询其结构之前“接触”X。在第二行中,每行单独粘贴,因此在检查它之前就修改了X

## Pasted in together
x <- 1:10; .Internal(inspect(x))
# @46b47b8 13 INTSXP g0c4 [NAM(1)] (len=10, tl=0) 1,2,3,4,5,...

## Pasted in with some delay between lines
x <- 1:10
.Internal(inspect(x))
# @42111b8 13 INTSXP g0c4 [NAM(2)] (len=10, tl=0) 1,2,3,4,5,... 

一旦named字段设置为2,[<-(X, ...)不会修改原始对象。将以下内容一次性粘贴到Rstudio中会修改X,而逐行粘贴则不会:

x <- 1:10
"[<-"(x, 1, 111)

所有这些的另一个后果是,Rstudio的对象浏览器实际上使一些操作比它们本来应该更慢。再次比较首先粘贴在一起的相同两个命令,然后逐个执行:

## Pasted in together
x <- 1:5e7
system.time(x[1] <- 9L)
#    user  system elapsed 
#       0       0       0 

## Pasted in one at a time
x <- 1:5e7
system.time(x[1] <- 9L)
#    user  system elapsed 
#    0.11    0.04    0.16 

[<-在R中的变量行为

[<- 对向量 X 进行修改的行为取决于 X 的存储类型和被分配到其中的元素的类型。这解释了 R 的行为,但不是 Rstudio 的。

在R中,当[<- 要么向向量 X 追加内容,要么执行子赋值需要修改 X 类型时,X 就会被复制,函数返回的值不会覆盖已存在的 X 变量。(要实现这一点,你需要进行类似于 X <- "[<-(X, 2, 100) 的操作)。

所以,以下两种情况都不会修改 X:

X <- 1:2         ## Note: typeof(X) --> "integer"

## Subassignment that requires that X be coerced to "numeric" type
"[<-"(X, 2, 100) ## Note: typeof(100) --> "numeric"
X 
# [1]   1   2

## Appending to X
"[<-"(X, 3, 100L)
X
# [1]   1   2

尽可能地,R允许[<-函数通过引用直接修改X(即不复制)。在这里,“可能”包括不需要修改X类型的子分配的情况。

因此,以下所有内容都会修改X

X <- c(0i, 0i, 0i, 0i)
"[<-"(X, 1, TRUE)
"[<-"(X, 2, 20L)
"[<-"(X, 3, 3.14)
"[<-"(X, 4, 5+5i)
X
# [1]  1.00+0i 20.00+0i  3.14+0i  5.00+5i

2
我已经复制了您的最后一个示例并得到了相同的结果,但是在替换 "[<-"(X, 2, 20L) 之前运行命令 typeof(X) 后,替换就无法工作(在 RGui 中)。很奇怪。 - BartekCh
1
@JoshO'Brien:关于层次结构的参考,请使用:?Comparison - IRTFM
1
命名字段用于引用计数,表示有多少个变量指向内存中的该对象。它可以是1或2,这意味着超过1个。这是一种性能优化,允许简单的分配和删除避免需要垃圾收集器。我怀疑在RStudio中将其设置为2,因为该对象有两个引用:一个在控制台中,另一个在对象浏览器中。 - hadley
3
我在R-Devel上给出了稍微详细一些的答案,但你不需要了解C语言就能理解class()typeof()之间的区别 - 只需在R提示符下键入它们两个并注意所打印内容的差异即可。 - mweylandt
1
@JoshO'Brien 你知道RStudio页面上有关这个“特性”的相关文档/问题吗?我原本计划通过阅读Confused about NAMED的答案来解决问题,但是在问题的第二行就已经卡住了:y = 1:10 "NAM(1) as expected"。然而,在Rstudio中并没有出现...感谢你的出色回答,救了我的一天。 - Henrik
显示剩余21条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接