这个有点奇怪…似乎通过在数据中创建一个新列。
使用:=操作符,先前分配的变量(使用colnames创建)将静默更改。这是预期的行为吗?如果不是,是什么错?
# Lets make a simple data table
require(data.table)
dt <- data.table(fruit=c("apple","banana","cherry"),quantity=c(5,8,23))
dt
fruit quantity
1: apple 5
2: banana 8
3: cherry 23
# and assign the column names to a variable
colsdt <- colnames(dt)
str(colsdt)
chr [1:2] "fruit" "quantity"
# Now let's add a column to the data table using the := operator
dt[,double_quantity:=quantity*2]
dt
fruit quantity double_quantity
1: apple 5 10
2: banana 8 16
3: cherry 23 46
# ... and WITHOUT explicitly changing 'colsdt', let's take another look:
str(colsdt)
chr [1:3] "fruit" "quantity" "double_quantity"
# ... colsdt has been silently updated!
为了比较,我想我会看看通过data.frame方法添加新列是否有同样的问题。它不:
dt$triple_quantity=dt$quantity*3
dt
fruit quantity double_quantity triple_quantity
1: apple 5 10 15
2: banana 8 16 24
3: cherry 23 46 69
# ... again I make no explicit changes to colsdt, so let's take a look:
str(colsdt)
chr [1:3] "fruit" "quantity" "double_quantity"
# ... and this time it is NOT silently updated
所以这是数据的错误。表:=操作符,还是预期行为?
谢谢!
简答,使用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代码)避免了这个开销。相反,它所做的是设置指向已经存储对象值的相同内存位置。这提供了巨大的效率& &;sp提振。
但这也意味着你经常有一些对象,否则它们可能看起来是完全不同和独立的对象实际上是同一个
这就是copy
的由来。它创建对象的新副本,而不是通过引用传递。
关于为什么会发生这种情况的更多细节。
注意:我使用术语"源"one_answers"目标"非常宽松,它们指的是赋值关系destination <- source
这实际上是预期的行为,承认有点混乱。
在基数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
一旦两个对象中的中的发生变化,那么这个"键"就被打破了。也就是说,改变"源"或"目标"对象都会导致该对象被重新分配到一个新的内存位置。
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
的问题在于我们很少重新分配实际数据。表对象。注意,如果我们修改"destination"对象,那么它就会被移出(复制)那个内存位置。
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
时,我们(几乎)总是通过引用进行修改,这可能会导致意想不到的结果。即:
- 我们从赋值一个数据。表对象使用标准
<-
赋值操作符 - 然后我们改变"source" data的值。table
- 我们期望(并且我们的代码可能依赖于)"目标"对象仍然具有先前分配给它的值。
这当然会引起问题。
data.table
是一个非常强大的包。它的力量来源是它的长发,它尽可能避免复制的事实。
最佳实践:
这就把责任转移给了用户,在复制和期望复制时要深思熟虑和明智。
换句话说,最佳实践是:当您希望副本存在时,请使用copy函数。