使用colnames()设置变量,使用:=运算符更新data.table,变量是否以静默方式更新?

时间:2013-05-14 12:20:34

标签: 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!

为了比较,我虽然看到通过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

这是data.table:=运算符或预期行为的错误吗?

谢谢!

1 个答案:

答案 0 :(得分:18)

简答,使用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代码)避免了这种开销。相反,它所做的是设置一个指针  已存储对象值的相同内存位置。这就是提供巨大效率的原因。 spped boost。

但这也意味着您经常拥有可能看起来完全不同的对象和独立对象  实际上是同一个

这是copy的用武之地。它创建了一个对象的新副本,而不是通过引用传递。


更详细地说明为什么会发生这种情况。

注意:我使用的术语“来源”和“目的地”非常宽松,他们引用了作业关系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

一旦两个对象的发生变化,那么“债券”就会被打破。也就是说,更改“source”或“destination”对象将导致将该对象重新分配到新的内存位置。

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时,我们(几乎)总是通过引用进行修改,这可能会导致意外结果。即,情况:

  • 我们使用标准<-赋值运算符
  • 从 分配
  • 然后我们更改“source”data.table
  • 的值
  • 我们期望(并且我们的代码可能依赖于)“目标”对象仍然具有先前分配给它的值。

这当然会引起问题。

data.table是一个非常强大的软件包。它的力量来源是它的长发它避免了尽可能复制的事实。

最佳实践:

这使得用户有责任在复制和期待复制时有意识和谨慎。

换句话说,最佳做法是:   如果希望存在副本,请使用复制功能。