의 참조 별 속성을 이해하는 데 약간의 어려움이 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이 이러한 작업을 처리하는 방법을 완전히 이해하여 코드에 잠재적 인 버그가 발생하지 않도록하고 싶습니다.
누군가 나에게 이것을 설명 할 수 있다면 대단히 감사하겠습니다.
답변
예, 전체 객체 의 복사본을 만드는 <-
(또는 =
또는 ->
)를 사용하는 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).
답변
간단히 요약하면됩니다.
<-
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은 두 개의 다른 이름 ( 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
:=
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]