2017-08-25 11 views
10

ऐसा प्रतीत होता है कि [.data.table के साथ डेटा.table से स्तंभ (ओं) का चयन अंतर्निहित वेक्टर की एक प्रति में होता है। मैं नाम से बहुत सरल कॉलम चयन के बारे में बात कर रहा हूं, j में गणना करने के लिए कोई अभिव्यक्ति नहीं है और i में सबसेट करने के लिए कोई पंक्ति नहीं है। और भी अजीब बात यह है कि डेटा.फ्रेम में कॉलम सबसेटिंग कोई प्रतिलिपि नहीं दिखती है। मैं डेटा.table संस्करण डेटाटेबल 1.10.4 का उपयोग कर रहा हूँ। विवरण और बेंचमार्क के साथ एक सरल उदाहरण नीचे प्रदान किया गया है। मेरे प्रश्न हैं:डेटाटेबल परिणामों से कॉलम (ओं) का चयन प्रतिलिपि में क्यों करता है?

  • क्या मैं कुछ गलत कर रहा हूं?
  • क्या यह एक बग है या यह इरादा व्यवहार है?
  • यदि इसका इरादा है, तो कॉलम द्वारा डेटाटेबल को सबसेट करने और अतिरिक्त प्रति से बचने का सबसे अच्छा तरीका क्या है?

इच्छित उपयोग-मामले में बड़े डेटासेट शामिल हैं, इसलिए अतिरिक्त प्रतियों से परहेज करना आवश्यक है (विशेष रूप से बेस आर पहले से ही इसका समर्थन करता है)।

library(data.table) 
set.seed(12345) 
cpp_dt <- data.table(a = runif(1e6), b = rnorm(1e6), c = runif(1e6)) 
cols=c("a","c") 

## naive/data.frame style of column selection 
## leads to a copy of the column vectors in cols 
subset_cols_1=function(dt,cols){ 
    return(dt[,cols,with=F]) 
} 

## alternative syntax, still results in a copy 
subset_cols_2=function(dt,cols){ 
    return(dt[,..cols]) 
} 

## work-around that uses data.frame column selection, 
## appears to avoid the copy 
subset_cols_3=function(dt,cols){ 
    setDF(dt) 
    subset=dt[,cols] 
    setDT(subset) 
    setDT(dt) 
    return(subset) 
} 

## another approach that makes a "shallow" copy of the data.table 
## then NULLs the not needed columns by reference 
## appears to also avoid the copy 
subset_cols_4=function(dt,cols){ 
    subset=dt[TRUE] 
    other_cols=setdiff(names(subset),cols) 
    set(subset,j=other_cols,value=NULL) 
    return(subset) 
} 

subset_1=subset_cols_1(cpp_dt,cols) 
subset_2=subset_cols_2(cpp_dt,cols) 
subset_3=subset_cols_3(cpp_dt,cols) 
subset_4=subset_cols_4(cpp_dt,cols) 

अब स्मृति आवंटन को देखें और मूल डेटा की तुलना करें।

.Internal(inspect(cpp_dt)) # original data, keep an eye on 1st and 3d vector 
# @7fe8ba278800 19 VECSXP g1c7 [OBJ,MARK,NAM(2),ATT] (len=3, tl=1027) 
# @10e2ce000 14 REALSXP g1c7 [MARK,NAM(2)] (len=1000000, tl=0) 0.720904,0.875773,0.760982,0.886125,0.456481,... 
# @10f1a3000 14 REALSXP g1c7 [MARK,NAM(2)] (len=1000000, tl=0) -0.947317,-0.636669,0.167872,-0.206986,0.411445,... 
# @10f945000 14 REALSXP g1c7 [MARK,NAM(2)] (len=1000000, tl=0) 0.717611,0.95416,0.191546,0.48525,0.539878,... 
# ATTRIB: [removed] 

[.data.table पद्धति का उपयोग करना कॉलम सबसेट तक:

.Internal(inspect(subset_2)) # same, still copy 
# @7fe8b6402600 19 VECSXP g0c7 [OBJ,NAM(1),ATT] (len=2, tl=1026) 
# @115452000 14 REALSXP g0c7 [NAM(2)] (len=1000000, tl=0) 0.720904,0.875773,0.760982,0.886125,0.456481,... 
# @1100e7000 14 REALSXP g0c7 [NAM(2)] (len=1000000, tl=0) 0.717611,0.95416,0.191546,0.48525,0.539878,... 
# ATTRIB: [removed] 

setDF के अनुक्रम का उपयोग करना, के बाद: कि अभी भी [.data.table उपयोग करता है और अभी भी एक प्रतिलिपि बनाने

.Internal(inspect(subset_1)) # looks like data.table is making a copy 
# @7fe8b9f3b800 19 VECSXP g0c7 [OBJ,NAM(1),ATT] (len=2, tl=1026) 
# @114cb0000 14 REALSXP g0c7 [MARK,NAM(2)] (len=1000000, tl=0) 0.720904,0.875773,0.760982,0.886125,0.456481,... 
# @1121ca000 14 REALSXP g0c7 [NAM(2)] (len=1000000, tl=0) 0.717611,0.95416,0.191546,0.48525,0.539878,... 
# ATTRIB: [removed] 

एक और वाक्य रचना संस्करण [.data.frame और setDT। देखो, वेक्टर a और c अब कॉपी नहीं किए गए हैं! ऐसा प्रतीत होता है कि आधार आर विधि अधिक कुशल/छोटी स्मृति पदचिह्न है?

.Internal(inspect(subset_3)) # "[.data.frame" is not making a copy!! 
# @7fe8b633f400 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=1026) 
# @10e2ce000 14 REALSXP g1c7 [MARK,NAM(2)] (len=1000000, tl=0) 0.720904,0.875773,0.760982,0.886125,0.456481,... 
# @10f945000 14 REALSXP g1c7 [MARK,NAM(2)] (len=1000000, tl=0) 0.717611,0.95416,0.191546,0.48525,0.539878,... 
# ATTRIB: [removed] 

एक और दृष्टिकोण data.table की एक उथले प्रतिलिपि बनाने के लिए, तो नए data.table में संदर्भ द्वारा सभी अतिरिक्त कॉलम रिक्त है। फिर कोई प्रतियां नहीं बनाई जाती हैं।

.Internal(inspect(subset_4)) # 4th approach seems to also avoid the copy 
# @7fe8b924d800 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=1027) 
# @10e2ce000 14 REALSXP g1c7 [MARK,NAM(2)] (len=1000000, tl=0) 0.720904,0.875773,0.760982,0.886125,0.456481,... 
# @10f945000 14 REALSXP g1c7 [MARK,NAM(2)] (len=1000000, tl=0) 0.717611,0.95416,0.191546,0.48525,0.539878,... 
# ATTRIB: [removed] 

अब इन चार दृष्टिकोणों के बेंचमार्क देखें। ऐसा लगता है कि "[.data.frame" (subset_cols_3) एक स्पष्ट विजेता है।

microbenchmark({subset_cols_1(cpp_dt,cols)}, 
       {subset_cols_2(cpp_dt,cols)}, 
       {subset_cols_3(cpp_dt,cols)}, 
       {subset_cols_4(cpp_dt,cols)}, 
       times=100) 

# Unit: microseconds 
#         expr  min  lq  mean median  uq  max neval 
# {  subset_cols_1(cpp_dt, cols) } 4772.092 5128.7395 8956.7398 7149.447 10189.397 53117.358 100 
# {  subset_cols_2(cpp_dt, cols) } 4705.383 5107.1690 8977.1816 6680.666 9206.164 53523.191 100 
# {  subset_cols_3(cpp_dt, cols) } 148.659 177.9595 285.4926 250.620 283.414 4422.968 100 
# {  subset_cols_4(cpp_dt, cols) } 193.912 241.9010 531.8308 336.467 384.844 20061.864 100 
+1

शायद यहां अपडेट के लिए प्रतीक्षा करें: https://stackoverflow.com/a/26481429/ 'उथला 'फ़ंक्शन अभी तक निर्यात नहीं किया गया है, लेकिन शायद इसके साथ मदद करेगा। – Frank

उत्तर

4

यह कुछ समय हो गया है क्योंकि मैंने इस बारे में सोचा था, लेकिन यहां जाता है।

अच्छा सवाल। लेकिन आपको data.table को इस तरह क्यों सब्सक्राइब करने की आवश्यकता है? हमें वास्तव में यह देखने की ज़रूरत है कि आप क्या कर रहे हैं अगला: बड़ी तस्वीर। यह वह बड़ी तस्वीर है जो हमारे पास बेस आर idiom की तुलना में डेटा.table में शायद एक अलग तरीका है।

मोटे तौर पर शायद एक बुरा उदाहरण के साथ illustrating:

DT[region=="EU", lapply(.SD, sum), .SDcols=10:20] 
बजाय बाहर परिणाम पर एक सबसेट लेने और फिर कुछ अगले करने का आधार आर मुहावरा (यहाँ, apply)

:

apply(DT[DT$region=="EU", 10:20], 2, sum) 

सामान्य रूप से, हम एक [...] के अंदर जितना संभव हो सके प्रोत्साहित करना चाहते हैं ताकि डेटाटेबल i, j और by को एक साथ​​में देख सकेऑपरेशन और संयोजन अनुकूलित कर सकते हैं। जब आप कॉलम को सब्सक्राइब करते हैं और फिर अगली चीज़ को बाद में करते हैं तो इसे अनुकूलित करने के लिए अधिक सॉफ़्टवेयर जटिलता की आवश्यकता होती है। ज्यादातर मामलों में, अधिकांश कम्प्यूटेशनल लागत पहले [...] के अंदर होती है जो अपेक्षाकृत महत्वहीन आकार तक कम हो जाती है।

इसके साथ ही, shallow के बारे में फ्रैंक की टिप्पणी के अलावा, हम यह भी देखने का इंतजार कर रहे हैं कि ALTREP project कैसे बाहर निकलता है। इससे बेस आर में संदर्भ गिनती में सुधार होता है और := को विश्वसनीय रूप से जानने के लिए सक्षम किया जा सकता है कि यह जिस कॉलम पर चल रहा है, उसे कॉपी-ऑन-राइट पहले या नहीं होना चाहिए। वर्तमान में, := हमेशा संदर्भ द्वारा अद्यतन करता है, इसलिए यह डेटा.table दोनों को अद्यतन करेगा यदि चयन-कुछ-पूर्ण-स्तंभों ने गहरी प्रतिलिपि नहीं ली है (यह जानबूझकर है कि यह प्रतिलिपि बनाता है, इसी कारण से)। := का उपयोग [...] के अंदर नहीं किया जाता है तो [...] हमेशा एक नया परिणाम देता है जो := पर उपयोग करने के लिए सुरक्षित है, जो वर्तमान में काफी सरल नियम है। यहां तक ​​कि यदि आप कुछ भी कर रहे हैं तो कुछ कारणों से कुछ पूरे कॉलम का चयन कर रहे हैं।

हमें वास्तव में बड़ी तस्वीर देखने की ज़रूरत है: आप कॉलम के सबसेट पर बाद में क्या कर रहे हैं। यह स्पष्ट होने से या तो एएलटीआरईपी की जांच में प्राथमिकता बढ़ाने में मदद मिलेगी या शायद इस मामले के लिए हमारी खुद की संदर्भ संख्या करनी होगी।

संबंधित मुद्दे