Data.table में संदर्भ द्वारा एक पंक्ति को हटाने के लिए कैसे?


150

मेरा प्रश्न संदर्भ बनाम प्रतिलिपि में असाइनमेंट से संबंधित है data.table। मैं जानना चाहता हूं कि क्या कोई संदर्भ के अनुसार पंक्तियों को हटा सकता है

DT[ , someCol := NULL]

के बारे में जानना चाहता हूं

DT[someRow := NULL, ]

मुझे लगता है कि इस फ़ंक्शन के मौजूद नहीं होने का एक अच्छा कारण है, इसलिए शायद आप नीचे दिए गए अनुसार सामान्य प्रतिलिपि दृष्टिकोण के लिए एक अच्छा विकल्प बता सकते हैं। विशेष रूप से, उदाहरण के लिए मेरे पसंदीदा के साथ जा रहा है (data.table),

DT = data.table(x = rep(c("a", "b", "c"), each = 3), y = c(1, 3, 6), v = 1:9)
#      x y v
# [1,] a 1 1
# [2,] a 3 2
# [3,] a 6 3
# [4,] b 1 4
# [5,] b 3 5
# [6,] b 6 6
# [7,] c 1 7
# [8,] c 3 8
# [9,] c 6 9

कहते हैं कि मैं इस डेटा से पहली पंक्ति को हटाना चाहता हूं। मैं जानता हूँ मैं यह कर सकता हूँ:

DT <- DT[-1, ]

लेकिन अक्सर हम इससे बचना चाहते हैं, क्योंकि हम ऑब्जेक्ट की प्रतिलिपि बना रहे हैं (और इसके लिए लगभग 3 * N मेमोरी की आवश्यकता होती है, यदि N object.size(DT), जैसा कि यहां बताया गया है । अब मैंने पाया है set(DT, i, j, value)। मुझे पता है कि विशिष्ट मान कैसे सेट करें (जैसे यहां: सभी सेट करें) पंक्तियों में मान 1 और 2 और कॉलम 2 और 3 से शून्य)

set(DT, 1:2, 2:3, 0) 
DT
#      x y v
# [1,] a 0 0
# [2,] a 0 0
# [3,] a 6 3
# [4,] b 1 4
# [5,] b 3 5
# [6,] b 6 6
# [7,] c 1 7
# [8,] c 3 8
# [9,] c 6 9

लेकिन मैं पहली दो पंक्तियों को कैसे मिटा सकता हूं, कहो? करते हुए

set(DT, 1:2, 1:3, NULL)

पूरे DT को NULL में सेट करता है।

मेरा SQL ज्ञान बहुत सीमित है, इसलिए आप लोग मुझे बताएं: data.table SQL तकनीक का उपयोग करता है, क्या SQL कमांड के बराबर है

DELETE FROM table_name
WHERE some_column=some_value

data.table में?


17
मुझे नहीं लगता कि यह data.table()एसक्यूएल तकनीक का उपयोग करता है, क्योंकि एक एसक्यूएल में विभिन्न कार्यों और विभिन्न तर्कों के बीच एक समानांतर आकर्षित कर सकता है data.table। मेरे लिए, "प्रौद्योगिकी" का संदर्भ कुछ हद तक data.tableएक SQL डेटाबेस के शीर्ष पर कहीं बैठे है, जो AFAIK मामला नहीं है।
चेस

1
धन्यवाद का पीछा हाँ, मुझे लगता है कि sql सादृश्य एक जंगली अनुमान था।
फ्लोरियन ओसवाल्ड

1
अक्सर यह पंक्तियों को रखने के लिए एक ध्वज को परिभाषित करने के लिए पर्याप्त होना चाहिए, जैसे DT[ , keep := .I > 1], फिर बाद के कार्यों के लिए सबसेट:, DT[(keep), ...]शायद setindex(DT, keep)इस सबसेट की गति भी । रामबाण नहीं है, लेकिन अपने वर्कफ़्लो में एक डिज़ाइन विकल्प के रूप में विचार करने के लिए योग्य है - क्या आप वास्तव में उन सभी पंक्तियों को स्मृति से हटाना चाहते हैं , या आप उन्हें बाहर करना पसंद करेंगे? उत्तर उपयोग के मामले में भिन्न होता है।
माइकलक्रिको

जवाबों:


125

अच्छा प्रश्न। data.tableसंदर्भ द्वारा पंक्तियों को अभी तक हटाया नहीं जा सकता है।

data.tableकॉलम संदर्भों के वेक्टर को ओवर-आवंटित करने के बाद से आप संदर्भ द्वारा कॉलम को जोड़ और हटा सकते हैं , जैसा कि आप जानते हैं। योजना यह है कि पंक्तियों के लिए कुछ ऐसा ही किया जाए insertऔर तेजी से अनुमति दी जाए deletememmoveहटाए गए पंक्तियों के बाद आइटम (प्रत्येक और प्रत्येक स्तंभ में) को बदलने के लिए C में एक पंक्ति हटाने का उपयोग किया जाएगा । तालिका के मध्य में एक पंक्ति स्टोर डेटाबेस की तुलना में एक पंक्ति को हटाना अभी भी काफी अक्षम होगा, जो कि उन पंक्तियों को तालिका में शामिल करने के लिए तेजी से सम्मिलित करने और पंक्तियों को हटाने के लिए अधिक अनुकूल है। लेकिन फिर भी, हटाए गए पंक्तियों के बिना एक नई बड़ी वस्तु की नकल करने की तुलना में यह बहुत तेज़ होगा।

दूसरी ओर, चूंकि स्तंभ वैक्टर अति-आबंटित होंगे, इसलिए अंत में पंक्तियों को डाला जा सकता है (और हटाया जा सकता है) , तुरंत; उदाहरण के लिए, एक बढ़ती समय श्रृंखला।


इसे एक समस्या के रूप में दर्ज किया गया है: संदर्भ द्वारा पंक्तियों को हटाएं


1
@ मट्टू डोले क्या इस पर कुछ खबर है?
स्टेटकैंट

15
@statquant मुझे लगता है कि मुझे 37 बग्स को ठीक करना चाहिए, और freadपहले खत्म करना चाहिए । उसके बाद यह बहुत अधिक है।
मैट डाउल

15
@MatthewDowle यकीन है, जो कुछ भी आप कर रहे हैं उसके लिए फिर से धन्यवाद।
मूर्तिमान

1
@rbatt सही। DT[b<8 & a>3]एक नया डेटा देता है। हम जोड़ना चाहते हैं delete(DT, b>=8 | a<=3)और DT[b>=8 | a<=8, .ROW:=NULL]। उत्तरार्द्ध का लाभ []पंक्ति संख्याओं की अन्य विशेषताओं के साथ संयोजन करना i, इसमें शामिल होना iऔर अनुकूलन rollसे लाभान्वित करना होगा [i,j,by]
मैट डाउल

2
@charliealpha कोई अद्यतन नहीं। योगदान का स्वागत करते हैं। मैं मार्गदर्शन करने को तैयार हूं। इसे सी कौशल की आवश्यकता है - फिर से, मैं मार्गदर्शन करने के लिए तैयार हूं।
मैट डोले

29

मेमोरी उपयोग करने के लिए मैंने जो तरीका अपनाया है वह इन-प्लेस डिलीट के समान है, एक समय में एक कॉलम को सब्मिट करना और डिलीट करना है। एक उचित सी मेमोवेट समाधान के रूप में उपवास के रूप में नहीं, लेकिन स्मृति उपयोग सभी मैं यहाँ के बारे में परवाह है। कुछ इस तरह:

DT = data.table(col1 = 1:1e6)
cols = paste0('col', 2:100)
for (col in cols){ DT[, (col) := 1:1e6] }
keep.idxs = sample(1e6, 9e5, FALSE) # keep 90% of entries
DT.subset = data.table(col1 = DT[['col1']][keep.idxs]) # this is the subsetted table
for (col in cols){
  DT.subset[, (col) := DT[[col]][keep.idxs]]
  DT[, (col) := NULL] #delete
}

5
+1 अच्छा स्मृति कुशल दृष्टिकोण। तो आदर्श रूप से हमें संदर्भों द्वारा पंक्तियों के एक सेट को हटाने की आवश्यकता है वास्तव में हम नहीं, मैंने ऐसा नहीं सोचा था। यह memmoveअंतराल की चपेट में आने के लिए एस की एक श्रृंखला होगी, लेकिन यह ठीक है।
मैट डोले

क्या यह एक फ़ंक्शन के रूप में काम करेगा, या किसी फ़ंक्शन में उपयोग करता है और इसे मेमोरी कॉपी बनाने के लिए मजबूर करता है?
रुसलपिएर्स

1
यह एक फ़ंक्शन में काम करेगा, क्योंकि data.tables हमेशा संदर्भ होते हैं।
vc273

1
धन्यवाद, एक अच्छा। थोड़ा-थोड़ा गति करने के लिए (विशेषकर कई स्तंभों के साथ) आप बदल DT[, col:= NULL, with = F]जाते हैंset(DT, NULL, col, NULL)
मिशेल

2
मुहावरे और चेतावनी को बदलने के प्रकाश में "के साथ = FALSE को एक साथ: = v1.9.4 में जारी किया गया था 2014 में जारी किया गया अद्यतन। कृपया के LHS लपेटें: = कोष्ठक के साथ; उदाहरण के लिए, डीटी [, (myVar: = sum (b)) द्वारा, myAar में आयोजित कॉलम नाम (ओं) को असाइन करने के लिए a। = अन्य उदाहरणों के लिए देखें? ': =' अन्य उदाहरणों के लिए। 2014 में चेतावनी दी गई थी, यह अब एक चेतावनी है। "
फ्रैंक

6

यहाँ @ vc273 के उत्तर और @ फ्रैंक की प्रतिक्रिया के आधार पर एक कामकाजी समारोह है।

delete <- function(DT, del.idxs) {           # pls note 'del.idxs' vs. 'keep.idxs'
  keep.idxs <- setdiff(DT[, .I], del.idxs);  # select row indexes to keep
  cols = names(DT);
  DT.subset <- data.table(DT[[1]][keep.idxs]); # this is the subsetted table
  setnames(DT.subset, cols[1]);
  for (col in cols[2:length(cols)]) {
    DT.subset[, (col) := DT[[col]][keep.idxs]];
    DT[, (col) := NULL];  # delete
  }
   return(DT.subset);
}

और इसके उपयोग का उदाहरण:

dat <- delete(dat,del.idxs)   ## Pls note 'del.idxs' instead of 'keep.idxs'

जहाँ "dat" एक data.table है। 1.4M पंक्तियों से 14k पंक्तियों को हटाने से मेरे लैपटॉप पर 0.25 सेकंड लगते हैं।

> dim(dat)
[1] 1419393      25
> system.time(dat <- delete(dat,del.idxs))
   user  system elapsed 
   0.23    0.02    0.25 
> dim(dat)
[1] 1404715      25
> 

पुनश्च। जब से मैं SO के लिए नया हूं, मैं @ vc273 के धागे में टिप्पणी नहीं जोड़ सका :-(


मैंने vc के उत्तर में टिप्पणी की कि (कर्नल) के बदले हुए वाक्यविन्यास को स्पष्ट करते हुए: =। "डिलीट" नाम का एक फंक्शन होने के लिए अजीब तरह का लेकिन क्या रखने के लिए संबंधित एक arg। Btw, आम तौर पर अपने स्वयं के डेटा के लिए मंद दिखाने के बजाय एक प्रतिलिपि प्रस्तुत करने योग्य उदाहरण का उपयोग करना पसंद किया जाता है। उदाहरण के लिए, आप प्रश्न से डीटी का पुन: उपयोग कर सकते हैं।
फ्रैंक

मुझे समझ में नहीं आता कि आप इसे संदर्भ के द्वारा क्यों करते हैं, लेकिन बाद में असाइनमेंट
डेट

1
@skan, यह असाइनमेंट संशोधित डेटा की ओर संकेत करने के लिए "डेट" असाइन करता है। मूल डेटाटेबल को सब्मिट करके स्वयं बनाया गया है। <- वर्गीकरण रिटर्न डेटा की प्रतिलिपि नहीं करता है, बस इसके लिए नया नाम असाइन करता है। लिंक
जर्नो पी।

@ फ्रेंक, मैंने आपके द्वारा इंगित विषमता के लिए फ़ंक्शन को अपडेट किया है।
जर्नो पी।

ठीक है धन्यवाद। मैं टिप्पणी छोड़ रहा हूं क्योंकि मुझे अभी भी लगता है कि यह ध्यान देने योग्य है कि एक प्रतिलिपि प्रस्तुत करने योग्य उदाहरण के बजाय कंसोल आउटपुट दिखाना यहां प्रोत्साहित नहीं किया गया है। इसके अलावा, एक भी बेंचमार्क इतना जानकारीपूर्ण नहीं है। यदि आपने सबसेट के लिए लिया गया समय भी मापा है, तो यह अधिक जानकारीपूर्ण होगा (क्योंकि हम में से अधिकांश को सहज ज्ञान नहीं है कि कितना समय लगता है, यह आपके COMP पर कितना कम समय लेता है)। वैसे भी, मेरा मतलब यह नहीं है कि यह एक बुरा जवाब है; मैं इसके उत्थानकर्ताओं में से एक हूं।
फ्रैंक

4

इसके बजाय या NULL पर सेट होने का प्रयास करते हुए, NA पर सेट करने का प्रयास करें (पहले कॉलम के लिए NA-प्रकार से मेल खाते हुए)

set(DT,1:2, 1:3 ,NA_character_)

3
हाँ, मुझे लगता है कि काम करता है। मेरी समस्या यह है कि मेरे पास बहुत अधिक डेटा है और मैं उन पंक्तियों को NA से हटाना चाहता हूं, संभवतः उन पंक्तियों से छुटकारा पाने के लिए डीटी की प्रतिलिपि बनाए बिना। वैसे भी आपकी टिप्पणी के लिए धन्यवाद!
फ्लोरियन ओसवाल्ड

4

विषय अभी भी कई लोगों (मुझे शामिल किया गया) दिलचस्प है।

उस बारे में क्या? मैं पहले वर्णित कोड और कोड assignको प्रतिस्थापित glovalenvकरता था। मूल वातावरण पर कब्जा करना बेहतर होगा लेकिन कम से कम globalenvयह स्मृति कुशल है और रेफ द्वारा परिवर्तन की तरह कार्य करता है।

delete <- function(DT, del.idxs) 
{ 
  varname = deparse(substitute(DT))

  keep.idxs <- setdiff(DT[, .I], del.idxs)
  cols = names(DT);
  DT.subset <- data.table(DT[[1]][keep.idxs])
  setnames(DT.subset, cols[1])

  for (col in cols[2:length(cols)]) 
  {
    DT.subset[, (col) := DT[[col]][keep.idxs]]
    DT[, (col) := NULL];  # delete
  }

  assign(varname, DT.subset, envir = globalenv())
  return(invisible())
}

DT = data.table(x = rep(c("a", "b", "c"), each = 3), y = c(1, 3, 6), v = 1:9)
delete(DT, 3)

बस स्पष्ट होने के लिए, यह संदर्भ (आधार पर address(DT); delete(DT, 3); address(DT)) से नहीं हटता है , हालांकि यह कुछ अर्थों में कुशल हो सकता है।
फ्रैंक

1
नहीं, यह नहीं है। यह व्यवहार का अनुकरण करता है और स्मृति कुशल है। इसलिए मैंने कहा: यह काम करता है । लेकिन कड़ाई से बोलते हुए आप सही हैं कि पता बदल गया है।
JRR

3

यहां कुछ रणनीतियों का उपयोग किया गया है। मेरा मानना ​​है कि एक .ROW फ़ंक्शन आ सकता है। नीचे दिए गए इन तरीकों में से कोई भी तेज़ नहीं है। ये कुछ रणनीतियाँ हैं, जो सबसे छोटे से परे या फ़िल्टरिंग से परे हैं। मैंने डीबीए की तरह सोचने की कोशिश की बस डेटा को साफ करने की कोशिश कर रहा हूं। जैसा कि ऊपर उल्लेख किया गया है, आप डेटा में पंक्तियों को चुन सकते हैं या हटा सकते हैं।

data(iris)
iris <- data.table(iris)

iris[3] # Select row three

iris[-3] # Remove row three

You can also use .SD to select or remove rows:

iris[,.SD[3]] # Select row three

iris[,.SD[3:6],by=,.(Species)] # Select row 3 - 6 for each Species

iris[,.SD[-3]] # Remove row three

iris[,.SD[-3:-6],by=,.(Species)] # Remove row 3 - 6 for each Species

नोट: .SD मूल डेटा का एक सबसेट बनाता है और आपको j या बाद के डेटा में काफी काम करने की अनुमति देता है। Https://stackoverflow.com/a/47406952/305675 देखें । यहाँ मैंने सेपल लेंथ द्वारा अपने इरेज़र्स का आदेश दिया, एक निर्दिष्ट सेपल ले लें। न्यूनतम के रूप में, सभी प्रजाति के टॉप थ्री (सेपल लेंथ) का चयन करें और सभी डेटा के साथ वापस लौटें:

iris[order(-Sepal.Length)][Sepal.Length > 3,.SD[1:3],by=,.(Species)]

पंक्तियों को हटाते समय सभी उपर्युक्त दृष्टिकोण डेटा को प्रदर्शित करते हैं। आप एक data.table को स्थानांतरित कर सकते हैं और पुरानी पंक्तियों को हटा सकते हैं या बदल सकते हैं जो अब ट्रांसपोज़्ड कॉलम हैं। ट्रांसपोज़्ड रो को हटाने के लिए ': = NULL' का उपयोग करते समय, बाद के कॉलम का नाम भी हटा दिया जाता है:

m_iris <- data.table(t(iris))[,V3:=NULL] # V3 column removed

d_iris <- data.table(t(iris))[,V3:=V2] # V3 column replaced with V2

जब आप data.frame को data.table में वापस ट्रांसफर करते हैं, तो आप मूल डेटा से नाम बदलना चाह सकते हैं। अब लागू किए गए data.table के लिए ": = NULL" को लागू करना सभी वर्ण वर्गों को बनाता है।

m_iris <- data.table(t(d_iris));
setnames(d_iris,names(iris))

d_iris <- data.table(t(m_iris));
setnames(m_iris,names(iris))

आप केवल उन डुप्लिकेट पंक्तियों को हटाना चाहते हैं जिन्हें आप कुंजी के साथ या उसके बिना कर सकते हैं:

d_iris[,Key:=paste0(Sepal.Length,Sepal.Width,Petal.Length,Petal.Width,Species)]     

d_iris[!duplicated(Key),]

d_iris[!duplicated(paste0(Sepal.Length,Sepal.Width,Petal.Length,Petal.Width,Species)),]  

'.I' के साथ एक वृद्धिशील काउंटर जोड़ना भी संभव है। फिर आप डुप्लिकेट कीज़ या फ़ील्ड्स को खोज सकते हैं और काउंटर के साथ रिकॉर्ड को हटाकर उन्हें हटा सकते हैं। यह कम्प्यूटेशनल रूप से महंगा है, लेकिन कुछ फायदे हैं क्योंकि आप हटाए जाने वाली लाइनों को प्रिंट कर सकते हैं।

d_iris[,I:=.I,] # add a counter field

d_iris[,Key:=paste0(Sepal.Length,Sepal.Width,Petal.Length,Petal.Width,Species)]

for(i in d_iris[duplicated(Key),I]) {print(i)} # See lines with duplicated Key or Field

for(i in d_iris[duplicated(Key),I]) {d_iris <- d_iris[!I == i,]} # Remove lines with duplicated Key or any particular field.

आप केवल 0 या NA के साथ एक पंक्ति भर सकते हैं और फिर उन्हें हटाने के लिए i क्वेरी का उपयोग कर सकते हैं:

 X 
   x v foo
1: c 8   4
2: b 7   2

X[1] <- c(0)

X
   x v foo
1: 0 0   0
2: b 7   2

X[2] <- c(NA)
X
    x  v foo
1:  0  0   0
2: NA NA  NA

X <- X[x != 0,]
X <- X[!is.na(x),]

यह वास्तव में सवाल का जवाब नहीं देता है (संदर्भ से हटाने के बारे में) और tडेटा पर उपयोग करना। फ़फ़र आमतौर पर एक अच्छा विचार नहीं है; यह देखने के str(m_iris)लिए जांचें कि सभी डेटा स्ट्रिंग / वर्ण बन गए हैं। Btw, आप d_iris[duplicated(Key), which = TRUE]बिना काउंटर कॉलम बनाए भी पंक्ति संख्याएँ प्राप्त कर सकते हैं ।
फ्रैंक

1
हाँ आप सही है। मैं विशेष रूप से इस सवाल का जवाब नहीं देता। लेकिन संदर्भ द्वारा एक पंक्ति को हटाने के लिए आधिकारिक कार्यक्षमता या प्रलेखन अभी तक नहीं है और बहुत से लोग इस पोस्ट पर आने वाले हैं जो वास्तव में ऐसा करने के लिए सामान्य कार्यक्षमता की तलाश कर रहे हैं। हम केवल एक पंक्ति को हटाने के बारे में प्रश्न का उत्तर देने के लिए एक पोस्ट बना सकते हैं। स्टैक ओवरफ्लो बहुत उपयोगी है और मैं वास्तव में प्रश्न का उत्तर सटीक रखने के लिए आवश्यक समझता हूं। हालांकि कभी-कभी, मुझे लगता है कि एसओ इस संबंध में एक छोटा सा फासीवादी हो सकता है ... लेकिन शायद इसके लिए एक अच्छा कारण है।
राफ्रीक्स

ठीक है, समझाने के लिए धन्यवाद। मुझे लगता है कि अभी के लिए हमारी चर्चा यहाँ इस मामले में भ्रमित होने वाले किसी भी व्यक्ति के लिए पर्याप्त है।
फ्रैंक
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.