Bir data.table öğesinin başka bir veriye (ne kopyasına karşı) atıfta olduğunu tam olarak anlama.


194

Ben referans by özelliklerini anlamakta biraz sorun yaşıyorum data.table. Bazı operasyonlar referansı 'kırıyor' gibi görünüyor ve neler olduğunu tam olarak anlamak istiyorum.

data.tableBaşka bir tablo oluştururken data.table( <-yeni tabloyu şu yolla güncelleyerek :=, özgün tablo da değiştirilir. Bu beklenen bir durumdur:

?data.table::copy ve stackoverflow: veri-tablo-paketinde-operatör-referans-by-pass

İşte bir örnek:

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

Ancak, atama ile yukarıdaki satırlar :=arasında temel olmayan bir değişiklik <-eklersem :=, DTartık değiştirilmiyor:

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

Görünüşe göre newDT$b[2] <- 200çizgi bir şekilde referansı 'kırıyor'. Bu bir şekilde bir kopyasını çağırıyor sanırım, ama kodumda potansiyel hatalar tanıtmak için R bu işlemleri nasıl tedavi, tam olarak anlamak istiyorum.

Birisi bunu bana açıklayabilirse çok sevinirim.


1
Ben sadece bu "özelliği" keşfetti ve dehşet verici. İnternetteki temel atama <-yerine =(örneğin Google tarafından: google.github.io/styleguide/Rguide.xml#assignment ) kullanmak için İnternet'te yaygın olarak savunulmaktadır . Ancak bu, data.table manipülasyonunun veri çerçevesi manipülasyonu ile aynı şekilde işlev görmeyeceği ve bu nedenle veri çerçevesinin değiştirilmesinden çok uzak olduğu anlamına gelir.
CMO

Yanıtlar:


141

Evet, tüm nesnenin bir kopyasını oluşturan <-(veya =veya ->) kullanarak R'de alt atamadır . Bunu ve öğelerini aşağıdaki gibi kullanarak izleyebilirsiniz . Özellikleri ve ne olursa olsun referans alınarak atama onlar geçirilen nesne. Bu nesne daha önce kopyalanmışsa (bir alt atama veya açık bir şekilde ), referans ile değiştirilen kopyadı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..

aVektörün nasıl kopyalandığına (farklı onaltılık değer, vektörün yeni kopyasını gösterir), adeğiştirilmemiş olmasına rağmen dikkat edin . bSadece değiştirilmesi gereken unsurları değiştirmek yerine tamamı kopyalandı. Büyük verilerden kaçınmak ve neden :=ve set()bunlarla tanışmak önemlidir data.table.

Şimdi, kopyaladığımızla newDTreferans olarak değiştirebiliriz:

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 onaltılık değerin (sütun noktalarının vektörü ve 2 sütunun her birinin) değişmeden kaldığına dikkat edin. Böylece hiçbir kopya olmadan referans olarak gerçekten değiştirildi.

Ya da orijinali DTreferans ile değiştirebiliriz :

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..

Bu onaltılı değerler, DTyukarıda gördüğümüz orijinal değerlerle aynıdır . Tip example(copy)fazlası kullanılarak örnekler için tracememve kıyasla data.frame.

Btw, eğer tracemem(DT)o zaman DT[2,b:=600]göreceğiniz bir kopyası bildirdi. Bu, printyöntemin yaptığı ilk 10 satırın bir kopyasıdır . Bir invisible()işlev veya komut dosyası ile sarıldığında veya içinde çağrıldığında, printyöntem çağrılmaz.

Bütün bunlar iç fonksiyonlar için de geçerlidir; yani :=ve set()işlevler dahilinde bile yazma üzerine kopyalamayın. Yerel bir kopyayı değiştirmeniz gerekirse x=copy(x), işlevin başlangıcında arayın . Ancak unutmayın data.table, büyük veriler içindir (küçük veriler için daha hızlı programlama avantajlarının yanı sıra). Büyük nesneleri (şimdiye kadar) kasten kopyalamak istemiyoruz. Sonuç olarak, her zamanki 3 * çalışan bellek faktörü kuralına izin vermemiz gerekmez. Sadece bir sütun kadar büyük bir çalışma belleğine ihtiyacımız var (yani 3 yerine 1 / ncol çalışma belleği faktörü).


1
Bu davranış ne zaman istenir?
colin

İlginçtir, bir data.frame nesnesi için nesnenin tamamını kopyalama davranışı gerçekleşmez. Kopyalanan bir data.frame dosyasında, yalnızca doğrudan ->atama yoluyla değiştirilen vektör bellek konumunu değiştirir. Değişmeyen vektörler orijinal veri vektörlerinin bellek konumunu korur. Çerçeve. Davranışı data.tableBurada anlatılan s 1.12.2 itibariyle geçerli davranıştır.
lmo

105

Hızlı bir özet.

<-ile data.tabletıpkı üs gibidir; yani, daha sonra ile bir alt atama yapılıncaya kadar <-(sütun adlarının değiştirilmesi veya gibi bir öğenin değiştirilmesi gibi DT[i,j]<-v) hiçbir kopya alınmaz . Sonra tüm nesnenin bir kopyasını tıpkı bir taban gibi alır. Buna yazma üzerine kopyalama denir. Alt-atama üzerine kopya olarak daha iyi bilinirdi, sanırım! Özel :=işleci veya set*tarafından sağlanan işlevleri kullandığınızda kopyalama YAPMAZ data.table. Büyük verileriniz varsa, büyük olasılıkla bunları kullanmak istersiniz. :=ve FONKSİYONLARDA OLAN set*KOPYALAMAYACAKTIR data.table.

Bu örnek veriler göz önüne alındığında:

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

Aşağıdakiler, başka bir adı DT2şu anda ada bağlı olan aynı veri nesnesine "bağlar" DT:

DT2 <- DT

Bu asla kopyalamaz ve asla tabanda da kopyalamaz. Sadece veri nesnesini işaretler, böylece R iki farklı adın ( DT2ve DT) aynı nesneyi gösterdiğini bilir . Bu nedenle, R'nin her ikisinden sonra atanmışsa nesneyi kopyalaması gerekir .

Bu da mükemmel data.table. Bunu yapmak :=için değil. Dolayısıyla, aşağıdaki :=nesne adları sadece bağlayıcı nesne adları için değil, kasıtlı bir hatadır :

DT2 := DT    # not what := is for, not defined, gives a nice error

:=için subassigning referans ile içerilmektedir. Ama bunu temelde kullandığınız gibi kullanmıyorsunuz:

DT[3,"foo"] := newvalue    # not like this

şöyle kullanın:

DT[3,foo:=newvalue]    # like this

DTReferans olarak değişti . newVeri nesnesine referans olarak yeni bir sütun eklediğinizi varsayalım, buna gerek yok:

DT <- DT[,new:=1L]

çünkü RHS DTreferans olarak zaten değişti . Ekstra DT <-, ne yaptığını yanlış anlamaktır :=. Oraya yazabilirsin, ama gereksiz.

DTreferans olarak, :=FONKSİYONLARDA OLAN olarak değiştirilir :

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.tablebüyük veri kümeleri içindir, unutmayın. data.tableBelleğinizde 20GB varsa, bunu yapmanın bir yoluna ihtiyacınız var. Çok kasıtlı bir tasarım kararı data.table.

Elbette kopyalar yapılabilir. Sadece data.table öğesini 20 GB veri kümenizi kopyalamak istediğinizden emin olduğunuzu söylemeniz gerekir copy():

DT3 <- copy(DT)   # rather than DT3 <- DT
DT3[,new3:=3L]     # now, this just changes DT3 because it's a copy, not DT too.

Kopyaları önlemek için, temel tür atamasını veya güncellemesini kullanmayın:

DT$new4 <- 1L                 # will make a copy so use :=
attr(DT,"sorted") <- "a"      # will make a copy use setattr() 

Referans kullanım ile güncellediğinizden emin olmak istiyorsanız .Internal(inspect(x))ve bileşenlerin bellek adresi değerlerine bakın (bkz. Matthew Dowle'ın cevabı).

Bu şekilde yazmak :=, jgruba göre referansa göre alt atama yapmanızı sağlar . Gruba göre başvuru ile yeni bir sütun ekleyebilirsiniz. Bu yüzden :=içeride bu şekilde yapılır [...]:

DT[, newcol:=mean(x), by=group]
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.