Подтвердить что ты не робот

Установите переменную с помощью colnames(), update data.table, используя: = operator, переменная тихо обновляется?

Ну, это немного странно... Кажется, создавая новый столбец в 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 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 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 take a look:
str(colsdt)
 chr [1:3] "fruit" "quantity" "double_quantity"

# ... and this time it is NOT silently updated

Итак, это ошибка с data.table: = operator или ожидаемое поведение?

Спасибо!

4b9b3361

Ответ 1

Короткий ответ, используйте 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

Это на самом деле ожидаемое поведение, возможно, немного запутанное.

В базе 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 заключается в том, что мы редко переназначаем фактический объект 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, мы (почти) всегда изменяем по ссылке, это может привести к неожиданным результатам. А именно, ситуация, когда:

  • мы назначаем из объекта data.table с помощью стандартного оператора присваивания <-
  • то впоследствии мы изменим значение "исходных" данных. table
  • мы ожидаем (и наш код может зависеть от) объекта-адресата, который все еще имеет ранее присвоенное ему значение.

Это, конечно, вызовет проблему.

data.table - удивительно мощный пакет. Источником его силы является его длинные волосы, что он избегает делать копии, когда это возможно.

Лучшая практика:

Это смещает бремя на пользователя, чтобы быть преднамеренным и разумным при копировании и ожидании копирования.

Другими словами, лучшие практики: Когда вы ожидаете существования копии, используйте функцию копирования.