jon*_*dar 13 r data.table
好吧,这个有点奇怪......似乎通过使用:=运算符在data.table中创建一个新列,以前分配的变量(使用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!
Run Code Online (Sandbox Code Playgroud)
为了比较,我虽然看到通过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
Run Code Online (Sandbox Code Playgroud)
那么这是data.table:=运算符或预期行为的错误吗?
谢谢!
Ric*_*rta 18
简答,使用 copy
colsdt <- copy(colnames(dt))
Run Code Online (Sandbox Code Playgroud)
那你们都很好.
dt[,double_quantity:=quantity*2]
str(colsdt)
# chr [1:2] "fruit" "quantity"
Run Code Online (Sandbox Code Playgroud)
发生的事情是,一般情况下(即在基础中R),赋值运算符<-在为对象赋值时创建对象的新副本.即使分配给相同的对象名称x <- x + 1,或者更昂贵,也是如此 DF$newCol <- DF$a + DF$b.对于大型对象(想想100K +行,数十或数百列.如果更多列更糟糕),这可能会变得非常昂贵.
data.table通过纯粹的魔法(阅读:C代码)避免了这种开销.相反,它所做的是设置指向已存储对象值的相同内存位置的指针.这就是提供巨大效率和快速提升的原因.
但这也意味着你经常拥有可能看起来完全不同的对象,而且独立的对象实际上是同一个
这就是它的copy用武之地.它创建了一个对象的新副本,而不是通过引用传递.
注意:我非常松散地使用术语"源"和"目的地",它们指的是赋值关系 destination <- source
事实上,这是预期的行为,但有点混淆.
在base中R,当您分配via时<-,这两个对象指向相同的内存位置,直到其中一个更改为止.这种处理内存的方式有许多好处,即只要两个对象具有相同的确切值,就不需要复制内存.这个步骤尽可能地延长.
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
Run Code Online (Sandbox Code Playgroud)
一旦两个对象中的任何一个发生变化,那么"粘合"就会被打破.也就是说,更改"源"或"目标"对象将导致将该对象重新分配给新的内存位置.
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
Run Code Online (Sandbox Code Playgroud)
在这种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"
Run Code Online (Sandbox Code Playgroud)
但是,因为在使用时data.table,我们(几乎)总是通过引用进行修改,这可能会导致意外的结果.即,情况:
<-分配这当然会引起一个问题.
data.table是一个非常强大的包.它的力量来源是它的长发,它尽可能避免复制.
当复制并期望复制时,这将使用户的责任转移到审慎和明智.
换句话说,最佳实践是:当您希望存在副本时,请使用复制功能.