Розуміння, коли саме таблиця даних - це посилання на (проти копії) іншого таблицю даних


194

У мене виникають невеликі проблеми з розумінням властивостей проходження посилання data.table. Деякі операції, здається, "порушують" посилання, і я хотів би зрозуміти, що саме відбувається.

При створенні data.tableз іншої data.table(через <-, а потім оновлення нової таблиці за :=допомогою оригінальної таблиці також змінюється. Це очікується відповідно до:

?data.table::copy та stackoverflow: пройти через посилання-оператор-в-даних-таблицю-пакет

Ось приклад:

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 поводиться з цими операціями, щоб переконатися, що я не ввожу потенційні помилки у свій код.

Я дуже вдячний, якби хтось міг мені це пояснити.


1
Я щойно виявив цю "особливість", і це жахливо. В Інтернеті широко рекомендується використовувати <-замість =основного завдання в R (наприклад, Google: google.github.io/styleguide/Rguide.xml#assignment ). Але це означає, що маніпуляція з data.table не буде функціонувати так само, як маніпулювання кадрами даних, і тому далеко не заміна заміни для кадру даних.
cmo

Відповіді:


141

Так, саме підрозділ 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.

До речі, якщо ви tracemem(DT)потім DT[2,b:=600]повідомив , ви побачите один екземпляр. Це копія перших 10 рядків, яку printробить метод. Якщо обгортати invisible()функцію або сценарій або коли його викликати, printметод не викликається.

Все це стосується і внутрішніх функцій; тобто, :=і set()не копіювати на записи, навіть в межах функцій. Якщо вам потрібно змінити локальну копію, то зателефонуйте x=copy(x)на початку функції. Але пам’ятайте data.table, що для великих даних (а також більш швидкі переваги програмування для малих даних). Ми свідомо не хочемо копіювати великі об'єкти (ніколи). Як наслідок, нам не потрібно враховувати звичайне правило 3 * робочого фактора пам'яті. Ми намагаємось лише потрібну робочу пам'ять розміром, як один стовпчик (тобто коефіцієнт робочої пам'яті 1 / нкол, а не 3).


1
Коли така поведінка бажана?
Колін

Цікаво, що поведінка при копіюванні всього об’єкта не відбувається для об’єкта data.frame. У скопійованому data.frame лише місце, яке було змінено безпосередньо за допомогою ->призначення, змінює місце пам'яті. Незмінені вектори підтримують розташування пам'яті векторів вихідного data.frame. data.tableОписана тут поведінка s - це поточна поведінка станом на 1.12.2.
lmo

105

Просто швидкий підсумок.

<-з 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пам'ятайте для великих наборів даних. Якщо у вас є 20 Гб 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's 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))перегляньте значення адреси пам'яті складових (див. Відповідь Меттью Даула).

Запис :=в jподібному дозволяє subassign по посиланню групи . Ви можете додати новий стовпець за посиланням за групою. Тож тому :=все робиться саме так [...]:

DT[, newcol:=mean(x), by=group]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.