【问题标题】:Why does data.table update names(DT) by reference, even if I assign to another variable?为什么 data.table 会通过引用更新名称(DT),即使我分配给另一个变量?
【发布时间】:2018-11-03 06:31:18
【问题描述】:

我已将data.table 的名称存储为vector

library(data.table)
set.seed(42)
DT <- data.table(x = runif(100), y = runif(100))
names1 <- names(DT)

据我所知,这是一个普通的字符向量:

str(names1)
# chr [1:2] "x" "y"

class(names1)
# [1] "character"

dput(names1)
# c("x", "y")

但是,这不是普通的字符向量。这是一个神奇的字符向量!当我向我的data.table 添加新列时,此向量会更新!

DT[ , z := runif(100)]
names1
# [1] "x" "y" "z"

我知道这与 := 如何通过分配更新有关,但这对我来说仍然很神奇,因为我希望 &lt;- 能够复制 data.table'的名字。

我可以通过将名称包装在 c() 中来解决此问题:

library(data.table)
set.seed(42)
DT <- data.table(x = runif(100), y = runif(100))

names1 <- names(DT)
names2 <- c(names(DT))
all.equal(names1, names2)
# [1] TRUE

DT[ , z := runif(100)]
names1
# [1] "x" "y" "z"

names2
# [1] "x" "y"

我的问题有两个:

  1. 为什么names1 &lt;- names(DT) 不创建data.table 名称的副本?在其他情况下,我们明确警告&lt;- 创建了data.tables 和data.frames 的副本。
  2. names1 &lt;- names(DT)names2 &lt;- c(names(DT)) 有什么区别?

【问题讨论】:

  • 好问题!跟进 Arun 的回答,我已提交 #2675 以记录需要 nm &lt;- copy(names(DT))。感谢您的强调,我以前没有意识到这一点。

标签: r data.table


【解决方案1】:

更新:现在在 1.9.3 版本的 ?copy 的文档中添加了此内容。来自NEWS

  1. ?copy 移至它自己的帮助页面,并记录dt_names &lt;- copy(names(DT))dt_names 所必需的,因为通过引用更新DT(例如:通过引用添加新列)。关闭#512。感谢 Zach this SO question 和 user1971988 this SO question

您的第一个问题的一部分让我有点不清楚对我来说您对&lt;- 运算符的真正含义(至少在data.table 的上下文中),尤其是以下部分:在其他情况下,我们被明确警告

因此,在回答您的实际问题之前,我将在这里简要介绍一下。在data.table 的情况下,&lt;-(分配)仅仅不足以复制data.table。例如:

DT <- data.table(x = 1:5, y= 6:10)
# assign DT2 to DT
DT2 <- DT # assign by reference, no copy taken.
DT2[, z := 11:15]
# DT will also have the z column

如果你想创建一个copy,那么你必须使用copy 命令明确提及它。

DT2 <- copy(DT) # copied content to DT2
DT2[, z := 11:15] # only DT2 is affected

来自 CauchyDistributedRV,我理解您的意思是分配 names(dt) &lt;- . 会导致警告。我就这样吧。


现在,回答您的第一个问题:似乎names1 &lt;- names(DT) 的行为也类似。直到现在我才想到/知道这一点。 .Internal(inspect(.)) 命令在这里非常有用:

.Internal(inspect(names1))
# @7fc86a851480 16 STRSXP g0c7 [MARK,NAM(2)] (len=2, tl=100)
#   @7fc86a069f68 09 CHARSXP g1c1 [MARK,gp=0x61] [ASCII] [cached] "x"
#   @7fc86a0f96d8 09 CHARSXP g1c1 [MARK,gp=0x61] [ASCII] [cached] "y"

.Internal(inspect(names(DT)))
# @7fc86a851480 16 STRSXP g0c7 [MARK,NAM(2)] (len=2, tl=100)
#   @7fc86a069f68 09 CHARSXP g1c1 [MARK,gp=0x61] [ASCII] [cached] "x"
#   @7fc86a0f96d8 09 CHARSXP g1c1 [MARK,gp=0x61] [ASCII] [cached] "y"

在这里,您看到它们指向相同的内存位置@7fc86a851480。甚至names1truelength 也是100(默认情况下分配在data.table 中,请查看?alloc.col 以获取此信息)。

truelength(names1)
# [1] 100

所以基本上,分配names1 &lt;- names(dt) 似乎是通过引用发生的。也就是说,names1 指向与 dt 的列名指针相同的位置。

回答您的第二个问题:命令c(.) 似乎创建了一个副本因为没有检查由于连接操作导致的内容结果是否不同 .也就是说,因为c(.) 操作可以更改向量的内容,它会立即导致“复制”检查内容是否被修改。

【讨论】:

  • 感谢您的信息。因为我刚刚发现.Internal(inspect()),所以我自己开始到达那里。我想我没有意识到基本的 vectors 实际上可以在内部引用data.table——我一直认为只有data.table 可以在内部引用data.table .
  • 这很有趣。我想知道在幕后发生了什么迫使names不要在分配时复制向量?另外,我想会出现混淆是因为names&lt;-.data.table 确实强制复制,因此setnames 函数。我的猜测:这与 data.table 如何使用引用计数有关。
  • 这对我来说也是新闻,好问题。名称向量以及列指针向量被过度分配。当:= 分配一个新列时,它也通过引用更新了名称向量。这可以通过不过度分配名称向量来更改/修复。但是如果在循环中添加列会慢一些。所以也许更好地记录需要nm &lt;- copy(names(DT)),否则names(DT) 返回一个引用。
  • 啊,阿伦,这就是你指的评论! (感谢@statquant 提供链接)。马修,我同意文档应该强调使用copy,只要副本是副本。至于问题的根源,我很确定这是由于&lt;-的懒惰评估。看到这个(重复!)帖子:stackoverflow.com/a/16543387/1492421
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-28
相关资源
最近更新 更多