简短回答,使用copy
colsdt <- copy(colnames(dt))
那你们就都好了。
dt[,double_quantity:=quantity*2]
str(colsdt)
# chr [1:2] "fruit" "quantity"
一般情况下(即在基础上)R
),赋值运算符<-
为对象赋值时创建对象的新副本。即使分配给相同的对象名称也是如此,如x <- x + 1
,或者成本更高,DF$newCol <- DF$a + DF$b
。对于大型对象(考虑 100K+ 行、数十或数百列。如果更多列更糟糕),这可能会变得非常昂贵。
data.table
, 通过纯粹的巫术(阅读:C 代码)避免了这种开销。相反,它所做的是设置一个指向
已存储对象值的同一内存位置。这就是提供巨大效率和速度提升的原因。
但这也意味着您经常拥有可能看起来完全不同且独立的对象
实际上是一回事
这是哪里copy
它创建一个对象的新副本,而不是通过引用传递。
关于为什么会发生这种情况的更多细节。
注意:我非常宽松地使用术语“源”和“目标”,它们指的是分配关系destination <- source
这实际上是预期的行为,诚然有点令人困惑。
In base R
,当您通过分配<-
,两个对象指向同一内存位置,直到其中一个对象发生变化。
这种处理内存的方式有很多好处,即只要两个对象具有相同的精确值,就不需要复制内存。这一步要尽可能推迟。
a <- 1:5
b <- a
.Internal(inspect(a)) # @11a5e2a88 13 INTSXP g0c3 [NAM(2)] (len=5, tl=0) 1,2,3,4,5
.Internal(inspect(b)) # @11a5e2a88 13 INTSXP g0c3 [NAM(2)] (len=5, tl=0) 1,2,3,4,5
^^^^ Notice the same memory location
Once either两个物体的变化,那么“纽带”就被打破了。也就是说,更改“源”或“目标”对象将导致该对象被重新分配到新的内存位置。
a[[3]] <- a[[3]] + 1
.Internal(inspect(a)) # @11004bc38 14 REALSXP g0c4 [NAM(1)] (len=5, tl=0) 1,2,4,4,5
^^^^ New Location
.Internal(inspect(b)) # @11a5e2a88 13 INTSXP g0c3 [NAM(2)] (len=5, tl=0) 1,2,3,4,5
^^^^^ Still same as it was before;
note the actual value. This is where `a` _had_ been
问题在data.table
情况是这样的我们很少重新分配实际的 data.table 对象。
请注意,如果我们修改“目标”对象,那么它就会从该内存位置移动(复制)。
colsdt <- colnames(dt)
.Internal(inspect(colnames(dt))) # @114859280 16 STRSXP g0c7 [MARK,NAM(2)] (len=2, tl=100)
.Internal(inspect(colsdt)) # @114859280 16 STRSXP g0c7 [MARK,NAM(2)] (len=2, tl=100)
^^^^ Notice the same memory location
# insiginificant change
colsdt[] <- colsdt
.Internal(inspect(colsdt)) # @100aa4a40 16 STRSXP g0c2 [NAM(1)] (len=2, tl=100)
# we can test the original issue from the OP:
dt[, newCol := quantity*2]
str(colnames(dt)) # chr [1:3] "fruit" "quantity" "newCol"
str(colsdt) # chr [1:2] "fruit" "quantity"
应避免的情况:
然而,自从与data.table
, 我们是 (almost) 始终通过引用进行修改,这可能会导致意外结果。即,以下情况:
- 网络分配from使用标准的 data.table 对象
<-
赋值运算符
- 然后我们随后更改“源”data.table 的值
- 我们期望(并且我们的代码可能依赖于)“目标”对象仍然具有先前分配给它的值。
这当然会引起一个问题。
data.table
is an amazingly powerful package. The source of its strength is its long hair the fact that it avoids making copies whenever possible.
最佳实践:
这将责任转移给了用户,在复制和期望复制时必须深思熟虑和明智。
换句话说,最佳实践是:
当您期望存在副本时,请使用复制功能。