data.table이 다른 데이터에 대한 참조 인 경우를 정확히 이해하십시오.


194

의 참조 별 속성을 이해하는 데 약간의 어려움이 data.table있습니다. 일부 작업은 참조를 '파손'하는 것처럼 보이고 무슨 일이 일어나고 있는지 정확하게 이해하고 싶습니다.

를 통해 data.table다른 테이블을 만들면 data.table(를 통해 <-새 테이블을 업데이트 :=하면 원래 테이블도 변경됩니다. 이는 다음과 같이 예상됩니다.

?data.table::copystackoverflow : 데이터 테이블 패키지 내의 오퍼레이터에 의한 전달

예를 들면 다음과 같습니다.

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의 하위 할당입니다 . 당신이 사용하는 것을 추적 할 수 있습니다 및 다음과 같습니다. 기능 과 무엇을 참조하여 할당은 전달되는 객체. 따라서 해당 객체가 이전에 (하위 할당 또는 explicit ) 복사 된 경우 참조로 수정 된 사본입니다.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더라도 벡터가 어떻게 복사 되었는지 (다른 16 진수 값은 새로운 벡터 사본을 나타냄)에 주목하십시오 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 개의 16 진수 값 (열 포인트의 벡터 및 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..

이 16 진수 값은 DT위에서 본 원래 값과 동일합니다 . example(copy)사용 tracemem및 비교에 대한 추가 예제를 입력하십시오 data.frame.

BTW, 당신이 경우 tracemem(DT)다음 DT[2,b:=600]당신은 볼 하나 개의 사본이 보도했다. 이것은 print메소드가 수행 하는 처음 10 행의 사본입니다 . 랩핑 invisible()되거나 함수 또는 스크립트 내에서 호출 될 때 print메소드가 호출되지 않습니다.

이 모든 것은 내부 함수에도 적용됩니다. 즉, :=그리고 set()심지어 기능 내에서 쓰기에 복사하지 마십시오. 로컬 사본을 수정해야하는 경우 x=copy(x)함수 시작시 호출 하십시오. 그러나 data.table큰 데이터 (작은 데이터의 프로그래밍 이점이 더 빠름)를 기억하십시오. 우리는 의도적으로 큰 객체를 복사하고 싶지 않습니다. 결과적으로 우리는 일반적인 3 * 작업 메모리 요소 규칙을 허용 할 필요가 없습니다. 하나의 열만큼 큰 작업 메모리 만 필요합니다 (즉, 작업 메모리 계수가 3이 아닌 1 / ncol).


1
이 행동은 언제 바람직합니까?
colin

흥미롭게도 전체 객체를 복사하는 동작은 data.frame 객체에 대해 발생하지 않습니다. 복사 된 data.frame에서 ->할당을 통해 직접 변경된 벡터 만 메모리 위치를 변경합니다. 변경되지 않은 벡터는 원래 data.frame의 벡터의 메모리 위치를 유지합니다. data.table여기에 설명 된 의 동작은 1.12.2 현재의 동작입니다.
lmo

105

간단히 요약하면됩니다.

<-with data.table는 기본과 같습니다. 즉, 이후에 하위 할당이 수행 될 때까지 <-(예 : 열 이름 변경 또는 같은 요소 변경) 복사가 수행되지 않습니다 DT[i,j]<-v. 그런 다음 기본처럼 전체 객체의 사본을 가져옵니다. 이를 기록 중 복사라고합니다. copy-on-subassign으로 더 잘 알려질 것입니다. 특수 :=연산자 또는에서 set*제공 하는 기능 을 사용할 때는 복사하지 않습니다 data.table. 큰 데이터가있는 경우이를 대신 사용하려고합니다. :=그리고 기능 내에서도 set*복사하지 않습니다 data.table.

이 예제 데이터가 주어지면 :

DT <- data.table(a=c(1,2), b=c(11,12))

다음은 DT2현재 이름에 바인드 된 동일한 데이터 오브젝트에 다른 이름 을 "바인드" 합니다 DT.

DT2 <- DT

이것은 결코 복사하지 않으며 기본으로 복사하지도 않습니다. R은 두 개의 다른 이름 ( DT2DT)이 동일한 객체를 가리키는 것을 알 수 있도록 데이터 객체를 표시 합니다. 따라서 나중에 하위에 할당 된 경우 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:=FUNC WITHIN FUNCTIONS 에 의해 참조로 변경됩니다 .

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큰 데이터 세트를위한 것입니다. data.table메모리 가 20GB 인 경우 이를 수행 할 방법이 필요합니다. 의 고의적 인 디자인 결정입니다 data.table.

물론 사본도 만들 수 있습니다. data.table에 copy()함수 를 사용하여 20GB 데이터 세트를 복사하고 싶다고 알려 주면 됩니다.

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))하려면 구성 요소의 메모리 주소 값을 확인하십시오 (Matthew Dowle의 답변 참조).

쓰기 :=j그 참조로 subassign 수와 같은 그룹에 의해 . 그룹별로 참조하여 새 열을 추가 할 수 있습니다. 그래서 왜 :=그런 식으로 내부에서 수행됩니까 [...]?

DT[, newcol:=mean(x), by=group]
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.