13

これは少し奇妙です... := 演算子を使用して 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 := 演算子のバグですか、それとも予想される動作ですか?

ありがとう!

4

1 に答える 1

18

簡単な回答、使用copy

colsdt <- copy(colnames(dt))

それでは皆様お元気で。

dt[,double_quantity:=quantity*2]
str(colsdt)
# chr [1:2] "fruit" "quantity"

一般に (つまり、 base でR)、オブジェクトに値を代入するときに代入演算子<-がオブジェクトの新しいコピーを作成します。これは、 のように同じオブジェクト名に割り当てる場合x <- x + 1や、よりコストのかかる DF$newCol <- DF$a + DF$b. 大きなオブジェクト (100K 以上の行、数十または数百の列を考えてください。列が増えるとさらに悪いことです) では、これは非常にコストがかかる可能性があります。

data.table純粋な魔法(読み取り:Cコード)により、このオーバーヘッドを回避します。代わりに、オブジェクト値が既に格納されている同じメモリ位置へのポインターを設定します。これは、巨大な効率と高速ブーストを提供するものです。

しかし、そうでなければ完全に異なるように見えるオブジェクトがしばしばあり、独立したオブジェクトが実際にはまったく同じであることも意味します。

ここでcopy出番です。参照渡しとは対照的に、オブジェクトの新しいコピーを作成します。


なぜこれが起こっているのかについての詳細。

注:「ソース」と「宛先」という用語を非常に大まかに使用しており、割り当て関係を指しています destination <- source

これは実際には予期された動作であり、確かに少し難読化されています。

baseRでは、 via を割り当てる<-と、2 つのオブジェクトはいずれかが変更されるまで同じメモリ位置を指します。このメモリ処理方法には多くの利点があります。つまり、2 つのオブジェクトが正確に同じ値を持っている限り、メモリを複製する必要はありません。このステップは可能な限り延期されます。

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

2 つのオブジェクトのいずれかが変更されると、その「結合」は解除されます。つまり、「ソース」または「宛先」オブジェクトのいずれかを変更すると、そのオブジェクトが新しいメモリ位置に再割り当てされます。

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、(ほとんど) 常に参照によって変更しているため、予期しない結果が生じる可能性があります。つまり、次のような状況です。

  • 標準の代入演算子を使用して data.table オブジェクトから代入します<-
  • 次に、「ソース」data.table の値を変更します。
  • 「宛先」オブジェクトには、以前に割り当てられた値が残っていることが期待されます (そしてコードが依存する可能性があります)。

これはもちろん問題を引き起こします。

data.table驚くほど強力なパッケージです。その強さの源はその長い髪であり、可能な限りコピーを作らないという事実.

ベスト プラクティス:

これにより、ユーザーは、コピーを作成したり、コピーが作成されることを期待したりするときに、慎重かつ賢明であるという責任を負うことになります。

つまり、ベスト プラクティスは次のとおりです。コピーが存在することが予想される場合は、コピー機能を使用します。

于 2013-05-14T12:39:10.873 に答える