जब एक data.table किसी अन्य data.table (बनाम की एक प्रति) का संदर्भ है, तो ठीक से समझना


194

मुझे पास-पास के संदर्भ गुणों को समझने में थोड़ी परेशानी हो रही है data.table। कुछ ऑपरेशन संदर्भ को 'ब्रेक' करने लगते हैं, और मैं यह समझना चाहता हूं कि वास्तव में क्या हो रहा है।

data.tableदूसरे से बनाने पर data.table(द्वारा <-, फिर नई तालिका को अपडेट करके :=, मूल तालिका भी बदल दी जाती है। यह अपेक्षित है, निम्नानुसार है:

?data.table::copy और स्टैकओवरफ़्लो: पास-बाय-रेफरेंस-ऑफ़-द-ऑपरेटर-इन-द-डेटा-टेबल-पैकेज

यहाँ एक उदाहरण है:

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रेखा किसी भी तरह 'संदर्भ' को तोड़ती है। मुझे लगता है कि यह किसी भी तरह से एक कॉपी को आमंत्रित करता है, लेकिन मैं पूरी तरह से समझना चाहता हूं कि आर कैसे इन ऑपरेशनों का इलाज कर रहा है, यह सुनिश्चित करने के लिए कि मैं अपने कोड में संभावित बगों का परिचय नहीं देता हूं।

अगर कोई मुझे यह समझा सकता है तो मैं बहुत सराहना करूँगा।


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

Btw, अगर तुम tracemem(DT)तो DT[2,b:=600]तुम एक प्रति रिपोर्ट देखेंगे। यह पहली 10 पंक्तियों की एक प्रति है जो printविधि करती है। जब invisible()किसी फ़ंक्शन या स्क्रिप्ट में printकॉल किया जाता है , तो विधि को कॉल नहीं किया जाता है।

यह सब कार्यों के अंदर भी लागू होता है; यानी, :=और set()कार्यों के भीतर भी, लेखन पर कॉपी न करें। यदि आपको एक स्थानीय प्रतिलिपि को संशोधित करने की आवश्यकता है, तो x=copy(x)फ़ंक्शन की शुरुआत में कॉल करें । लेकिन, याद रखें data.tableबड़े डेटा (छोटे डेटा के लिए तेज प्रोग्रामिंग फायदे) के लिए है। हम जानबूझकर बड़ी वस्तुओं (कभी) की नकल नहीं करना चाहते हैं। परिणामस्वरूप हमें अंगूठे के सामान्य 3 * वर्किंग मेमोरी फैक्टर नियम के लिए अनुमति देने की आवश्यकता नहीं है। हमें केवल एक कॉलम जितना बड़ा (यानी 3 के बजाय 1 / ncol का वर्किंग मेमोरी फैक्टर) के रूप में कार्यशील मेमोरी की आवश्यकता है।


1
यह व्यवहार कब वांछनीय है?
कॉलिन

दिलचस्प बात यह है कि पूरी वस्तु को कॉपी करने का व्यवहार डेटा.फ्रेम ऑब्जेक्ट के लिए नहीं होता है। कॉपी किए गए डेटा.फ्रेम में, केवल वेक्टर जो ->असाइनमेंट के माध्यम से सीधे बदल दिया गया था स्मृति स्थान बदल जाता है । अपरिवर्तित वैक्टर मूल data.frame के वैक्टर की मेमोरी लोकेशन को बनाए रखता है। data.tableयहाँ वर्णित s का व्यवहार वर्तमान व्यवहार 1.12.2 है।
लोमो

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संदर्भ द्वारा बदल दिया गया है, द्वारा :=, EVEN 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बड़े डेटासेट के लिए है, याद रखें। अगर आपके पास 20GB data.tableमेमोरी है तो आपको इसे करने का तरीका चाहिए। यह एक बहुत ही जानबूझकर डिजाइन का निर्णय है data.table

निश्चित रूप से प्रतियां बनाई जा सकती हैं। आपको केवल copy()फ़ंक्शन का उपयोग करके, यह सुनिश्चित करने के लिए data.table की आवश्यकता है कि आप अपने 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))और घटकों के मेमोरी एड्रेस मूल्यों को देखें (मैथ्यू डॉवेल का उत्तर देखें)।

लेखन :=में jहै कि आप संदर्भ द्वारा subassign की अनुमति देता है की तरह समूह द्वारा । आप समूह द्वारा संदर्भ द्वारा एक नया कॉलम जोड़ सकते हैं। इसलिए इस :=तरह से अंदर किया जाता है [...]:

DT[, newcol:=mean(x), by=group]
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.