У меня возникли проблемы с пониманием свойств pass-by-reference data.table
. Некоторые операции, похоже, "ломают" ссылку, и я хотел бы точно понять, что происходит.
При создании data.table
из другого data.table
(через <-
, а затем обновив новую таблицу на :=
, также изменится исходная таблица. Ожидается, что:
?data.table::copy
и https://stackoverflow.com/questions/8030452/pass-by-reference-operator-in-the-data-table-package-modifies-another-data
Вот пример:
library(data.table)
DT <- data.table(a=c(1,2), b=c(11,12))
print(DT)
# a b
# [1,] 1 11
# [2,] 2 12
newDT <- DT # reference, not copy
newDT[1, a := 100] # modify new DT
print(DT) # DT is modified too.
# a b
# [1,] 100 11
# [2,] 2 12
Однако, если я вставляю модификацию на основе не :=
между назначением <-
и строками :=
выше, DT
теперь больше не изменяется:
DT = data.table(a=c(1,2), b=c(11,12))
newDT <- DT
newDT$b[2] <- 200 # new operation
newDT[1, a := 100]
print(DT)
# a b
# [1,] 1 11
# [2,] 2 12
Итак, кажется, что строка newDT$b[2] <- 200
как-то "ломает" ссылку. Я бы предположил, что это каким-то образом вызывает копию, но я хотел бы полностью понять, как R обрабатывает эти операции, чтобы я не вводил потенциальные ошибки в свой код.
Я был бы очень признателен, если бы кто-нибудь мог мне это объяснить.
Да, это переназначение в R с помощью <-
(или =
или ->
), которое делает копию всего объекта. Вы можете проследить это с помощью tracemem(DT)
и .Internal(inspect(DT))
, как показано ниже. Элементы data.table
:=
и set()
назначаются ссылкой на любой объект, который они передают. Поэтому, если этот объект был ранее скопирован (путем выделения массива <-
или явного copy(DT)
), то это копия, которая изменяется по ссылке.
DT <- data.table(a = c(1, 2), b = c(11, 12))
newDT <- DT
.Internal(inspect(DT))
# @0000000003B7E2A0 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)
# @00000000040C2288 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
# @00000000040C2250 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,12
# ATTRIB: # ..snip..
.Internal(inspect(newDT)) # precisely the same object at this point
# @0000000003B7E2A0 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)
# @00000000040C2288 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
# @00000000040C2250 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,12
# ATTRIB: # ..snip..
tracemem(newDT)
# [1] "<0x0000000003b7e2a0"
newDT$b[2] <- 200
# tracemem[0000000003B7E2A0 -> 00000000040ED948]:
# tracemem[00000000040ED948 -> 00000000040ED830]: .Call copy $<-.data.table $<-
.Internal(inspect(DT))
# @0000000003B7E2A0 19 VECSXP g0c7 [OBJ,NAM(2),TR,ATT] (len=2, tl=100)
# @00000000040C2288 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
# @00000000040C2250 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,12
# ATTRIB: # ..snip..
.Internal(inspect(newDT))
# @0000000003D97A58 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)
# @00000000040ED7F8 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
# @00000000040ED8D8 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,200
# ATTRIB: # ..snip..
Обратите внимание, что даже скопирован вектор a
(другое шестнадцатеричное значение указывает на новую копию вектора), хотя a
не был изменен. Было скопировано даже все b
, а не просто изменение элементов, которые необходимо изменить. Это важно избегать для больших данных и почему :=
и set()
были введены в data.table
.
Теперь, с нашей скопированной newDT
, мы можем изменить ее по ссылке:
newDT
# a b
# [1,] 1 11
# [2,] 2 200
newDT[2, b := 400]
# a b # See FAQ 2.21 for why this prints newDT
# [1,] 1 11
# [2,] 2 400
.Internal(inspect(newDT))
# @0000000003D97A58 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)
# @00000000040ED7F8 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
# @00000000040ED8D8 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,400
# ATTRIB: # ..snip ..
Обратите внимание, что все 3 шестнадцатеричных значения (вектор столбчатых точек и каждый из 2 столбцов) остаются неизменными. Поэтому он был действительно изменен ссылкой без каких-либо копий.
Или мы можем изменить оригинал DT
по ссылке:
DT[2, b := 600]
# a b
# [1,] 1 11
# [2,] 2 600
.Internal(inspect(DT))
# @0000000003B7E2A0 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)
# @00000000040C2288 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
# @00000000040C2250 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,600
# ATTRIB: # ..snip..
Эти шестнадцатеричные значения совпадают с исходными значениями, которые мы видели для DT
выше. Введите example(copy)
для получения дополнительных примеров с использованием tracemem
и сравнения с data.frame
.
Btw, если вы tracemem(DT)
, затем DT[2,b:=600]
, вы увидите один экземпляр. Это копия первых 10 строк, которые выполняет метод print
. При завершении invisible()
или при вызове внутри функции или script метод print
не вызывается.
Все это относится и к внутренним функциям; т.е. :=
и set()
не копируются при записи даже внутри функций. Если вам нужно изменить локальную копию, тогда вызовите x=copy(x)
в начале функции. Но помните, что data.table
- для больших данных (а также более быстрые преимущества программирования для небольших данных). Мы намеренно не хотим копировать большие объекты (когда-либо). В результате нам не нужно учитывать обычное правило 3 * рабочей памяти. Мы стараемся, чтобы рабочая память имела только одну колонку (т.е. Коэффициент рабочей памяти 1/ncol, а не 3).
<-
с data.table
- это как основание; то есть копирование не выполняется до тех пор, пока субсайт не будет выполнен после <-
(например, изменение имен столбцов или изменение элемента, такого как DT[i,j]<-v
). Затем он берет копию всего объекта, как базу. Это называется копированием на запись. Думаю, это будет лучше известно как копирование на субассигнат! Он НЕ копируется при использовании специального оператора :=
или функций set*
, предоставляемых data.table
. Если у вас большие данные, вы, вероятно, захотите их использовать. :=
и set*
НЕ КОПИРУЕТСЯ data.table
, ДАЖЕ НЕ РАБОТАЕТ.
Учитывая этот пример данных:
DT <- data.table(a=c(1,2), b=c(11,12))
Следующее просто "связывает" другое имя DT2
с той же привязкой объекта данных, привязанной в настоящее время к имени DT
:
DT2 <- DT
Это никогда не копирует и никогда не копирует в базе. Он просто отмечает объект данных, так что R знает, что два разных имени (DT2
и DT
) указывают на один и тот же объект. И поэтому R необходимо будет скопировать объект, если он впоследствии назначен.
Это тоже идеально подходит для data.table
. :=
не для этого. Итак, следующая преднамеренная ошибка: :=
не только для привязки имен объектов:
DT2 := DT # not what := is for, not defined, gives a nice error
:=
предназначен для переназначения по ссылке. Но вы не используете его, как в базе:
DT[3,"foo"] := newvalue # not like this
вы используете его следующим образом:
DT[3,foo:=newvalue] # like this
Это изменило DT
по ссылке. Скажем, вы добавляете новый столбец new
по ссылке на объект данных, нет необходимости делать это:
DT <- DT[,new:=1L]
потому что RHS уже изменил DT
по ссылке. Дополнительный DT <-
заключается в неправильном понимании того, что делает :=
. Вы можете написать его там, но это лишнее.
DT
изменяется с помощью ссылки, :=
, ДАЖЕ В ФУНКЦИЯХ:
f <- function(X){
X[,new2:=2L]
return("something else")
}
f(DT) # will change DT
DT2 <- DT
f(DT) # will change both DT and DT2 (they're the same data object)
data.table
для больших наборов данных, помните. Если у вас 20GB data.table
в памяти, вам нужен способ сделать это. Это очень продуманное дизайнерское решение data.table
.
Конечно, можно сделать копии. Вам просто нужно сообщить data.table, что вы уверены, что хотите скопировать 20-гигабайтный набор данных, используя функцию copy()
:
DT3 <- copy(DT) # rather than DT3 <- DT
DT3[,new3:=3L] # now, this just changes DT3 because it a copy, not DT too.
Чтобы избежать копирования, не используйте назначение базового типа или обновление:
DT$new4 <- 1L # will make a copy so use :=
attr(DT,"sorted") <- "a" # will make a copy use setattr()
Если вы хотите быть уверенным, что вы обновляете по ссылке, используйте .Internal(inspect(x))
и посмотрите на значения адресов памяти для составляющих (см. ответ Matthew Dowle).
Запись :=
в j
, как это позволяет вам переназначить по ссылке группой. Вы можете добавить новый столбец по ссылке группы. Итак, почему :=
выполняется таким образом внутри [...]
:
DT[, newcol:=mean(x), by=group]
DT3
вместо DT
в DT[,new3:=3L]
. Невозможно изменить только один символ в вашем ответе.
<-
вместо=
для базового назначения в R (например, Google: google.github.io/styleguide/Rguide.xml#assignment ). Но это означает, что манипулирование данными.таблицы не будет функционировать так же, как манипулирование фреймами данных, и, следовательно, это далеко не полная замена фрейма данных.