首页 > 解决方案 > data.table 的 `:=` 操作符真的是通过引用来操作的吗?

问题描述

data.table 的:=运算符记录为

...通过引用添加或更新或删除列。它根本不复制内存的任何部分。

那么这里会发生什么?

dt <- data.table(a = 1:5, b = 6:10)
address(dt$b)
# [1] "0000021cca78db58"

dt[, b := 2*a]
address(dt$b)
# [1] "0000021cc77ade10"

列的地址怎么b变了?

我正在使用 R 3.6.1 和 data.table 1.12.8。

标签: rdata.table

解决方案


您(或者也许是专栏)刚刚被打断了;)帮助文本()中对打断行为进行了相当详尽的描述?`:=`

不像<-_data.frame,(可能很大)LHS 不会被强制匹配(通常很小)RHS 的类型。相反,如果需要,RHS 会被强制匹配 LHS 的类型。如果这涉及将双精度值强制转换为整数列,则会给出警告(无论小数数据是否被截断)。这样做的动机是效率。最好在前面正确地获取列类型并坚持使用它们。更改列类型是可能的,但故意更难:提供一整列作为 RHS。然后将此 RHS 插入该列槽中,我们称之为 plonk 语法,或者如果您愿意,可以替换列语法。通过需要构造一个新类型的全长向量,作为用户的您更加了解正在发生的事情,并且您的代码的读者更清楚您确实打算更改列类型。

但是,文档中目前没有明确说明plonking 和内存之间的关系(但请参见下文)。因此,像您和其他人这样的问题(在 github 上::= 如果 i 丢失,则不会通过引用现有列更新,并不总是就地分配)。:=

github 帖子中有很多有趣的点,但我不要重复它们,请去那里享受吧!不过,我相信Matt Dowle 的一句话很好地证明了这种笨拙的行为:

而不是 5 列分配,现在只有一个用于a+a表达式(RHS,无论如何都会创建),然后通过引用将其插入列槽,即address(DT)不会改变但address(DT$a)会改变。这是正确的行为,也是最有效的,可以保存将整个 RHS 复制到现有列中(这只有在它们是相同类型的情况下才有可能)。由于 RHS 与行数一样长,所以它只是被插入。

(免责声明:自那篇文章以来,事情可能已经发生了变化data.tableR但我认为主要信息仍然有效。)


关于文档,有一个开放的 PR(更新和澄清 := docs),其中建议对 plonk 和内存进行更明确的描述:

当一列被plonked时,原始列不会通过引用更新,因为这需要更新该列的每个元素。


我被撞了吗?是的!对我来说,这不是内存,而是列类引起了一些头疼,我最终来到这里:Why is data.table cast column classes when I assign all columns by reference。在阅读了您的问题后,我回到了那篇文章,并意识到马特的非常好的回答不仅“”解决了课堂问题,而且还解决了记忆问题。我认为值得在这里重复(我的粗体和评论[]):

如果然后将RHSlength(RHS) == nrow(DT)(以及任何类型) 插入该列槽。即使这些长度为 1。如果,列(及其类型)的内存保持在原位[隐含内存没有保持原位,我假设]但 RHS 被强制并回收以替换(子集)项目在那一栏中。length(RHS) < nrow(DT)length(RHS) == nrow(DT)

如果我需要在大表中更改列的类型,我会写:

DT[, col := as.numeric(col)]

这里as.numeric分配一个新向量,将“col”强制到那个新内存中,然后将它插入到列 slot中。它尽可能高效。这是一个笨拙的原因是因为 length(RHS) == nrow(DT)


推荐阅读