एक बड़े data.table में NAs को बदलने का सबसे तेज़ तरीका


150

मेरे पास एक बड़ा data.table है , जिसमें कई लापता मान इसकी ~ 200k पंक्तियों और 200 स्तंभों में बिखरे हुए हैं। मैं उन NA मानों को फिर से कोड करना चाहूंगा जैसे कि कुशलता से शून्य करना।

मुझे दो विकल्प दिखाई देते हैं:
1: एक data.frame में कनवर्ट करें, और कुछ इस तरह का उपयोग करें
2: कुछ प्रकार के शांत data.table उप सेटिंग कमांड

मैं टाइप 1 के एक काफी कुशल समाधान से खुश रहूंगा। किसी डेटा.फ्रेम में बदलना और फिर डेटा में वापस आना। तब भी बहुत समय नहीं लगेगा।


5
क्यों आप परिवर्तित करना चाहते हैं data.tableएक करने के लिए data.frame? ए data.table हैdata.frame। कोई भी डेटा.फ्रेम ऑपरेशन बस काम करेगा।
एंड्री

5
@Andrie। एक महत्वपूर्ण अंतर यह है कि आप data.tableकॉलम संख्या निर्दिष्ट करके एक कॉलम तक नहीं पहुंच सकते । इसलिए DT[,3]तीसरा कॉलम नहीं देगा। मुझे लगता है कि यह समाधान लिंक में प्रस्तावित समाधान को यहाँ उपलब्ध नहीं करता है। मुझे यकीन है कि कुछ data.tableमैड्रिड का उपयोग करके एक सुंदर दृष्टिकोण है !
रामनाथ

6
@ रामनाथ, AFAIK, DT[, 3, with=FALSE]तीसरा कॉलम लौटाते हैं।
एंड्री

2
@Andrie। लेकिन अभी भी एक समस्या mydf[is.na(mydf) == TRUE]है डेटा फ्रेम पर काम करता है, जबकि mydt[is.na(mydt) == TRUE]मुझे कुछ अजीब देता है भले ही मैं उपयोग करता हूंwith=FALSE
रामनाथ

2
@ रामनाथ, बिंदु लिया गया। मेरा पहले का बयान बहुत व्यापक था, यानी मैं गलत था। माफ़ करना। Data.tables केवल डेटा की तरह व्यवहार करता है। जब कोई data.table तरीका नहीं होता है।
एंड्री

जवाबों:


184

यहाँ एक समाधान का उपयोग है data.table के :=ऑपरेटर, Andrie और रामनाथ के जवाब पर आधारित है।

require(data.table)  # v1.6.6
require(gdata)       # v2.8.2

set.seed(1)
dt1 = create_dt(2e5, 200, 0.1)
dim(dt1)
[1] 200000    200    # more columns than Ramnath's answer which had 5 not 200

f_andrie = function(dt) remove_na(dt)

f_gdata = function(dt, un = 0) gdata::NAToUnknown(dt, un)

f_dowle = function(dt) {     # see EDIT later for more elegant solution
  na.replace = function(v,value=0) { v[is.na(v)] = value; v }
  for (i in names(dt))
    eval(parse(text=paste("dt[,",i,":=na.replace(",i,")]")))
}

system.time(a_gdata = f_gdata(dt1)) 
   user  system elapsed 
 18.805  12.301 134.985 

system.time(a_andrie = f_andrie(dt1))
Error: cannot allocate vector of size 305.2 Mb
Timing stopped at: 14.541 7.764 68.285 

system.time(f_dowle(dt1))
  user  system elapsed 
 7.452   4.144  19.590     # EDIT has faster than this

identical(a_gdata, dt1)   
[1] TRUE

ध्यान दें कि f_dowle ने dt1 को संदर्भ द्वारा अद्यतन किया है। यदि एक स्थानीय प्रतिलिपि की आवश्यकता होती है, तो copyपूरे डेटासेट की एक स्थानीय प्रतिलिपि बनाने के लिए फ़ंक्शन को एक स्पष्ट कॉल की आवश्यकता होती है। data.table's setkey, key<-और :=कॉपी-ऑन-राइट न करें।

इसके बाद, देखते हैं कि f_dowle अपना समय कहां बिता रहा है।

Rprof()
f_dowle(dt1)
Rprof(NULL)
summaryRprof()
$by.self
                  self.time self.pct total.time total.pct
"na.replace"           5.10    49.71       6.62     64.52
"[.data.table"         2.48    24.17       9.86     96.10
"is.na"                1.52    14.81       1.52     14.81
"gc"                   0.22     2.14       0.22      2.14
"unique"               0.14     1.36       0.16      1.56
... snip ...

वहाँ, मैं na.replaceऔर is.naजहाँ कुछ सदिश प्रतियां और वेक्टर स्कैन हैं, पर ध्यान केंद्रित करूँगा । NAवेक्टर में संदर्भ द्वारा अद्यतन होने वाले छोटे na.replace C फ़ंक्शन को लिखकर उन्हें आसानी से समाप्त किया जा सकता है । मुझे लगता है कि कम से कम 20 सेकंड आधा कर दिया। क्या इस तरह के फ़ंक्शन किसी भी आर पैकेज में मौजूद हैं?

कारण f_andrieविफल हो सकता है क्योंकि यह संपूर्ण की प्रतिलिपि dt1बनाता है, या तार्किक मैट्रिक्स को पूरे के बड़े dt1, कुछ समय में बनाता है । अन्य 2 विधियाँ एक समय में एक कॉलम पर काम करती हैं (हालाँकि मैंने केवल संक्षेप में देखा था NAToUnknown)।

EDIT (टिप्पणियों में रामनाथ द्वारा अनुरोध के रूप में अधिक सुरुचिपूर्ण समाधान):

f_dowle2 = function(DT) {
  for (i in names(DT))
    DT[is.na(get(i)), (i):=0]
}

system.time(f_dowle2(dt1))
  user  system elapsed 
 6.468   0.760   7.250   # faster, too

identical(a_gdata, dt1)   
[1] TRUE

काश मैंने इसे शुरू करने के तरीके से किया होता!

EDIT2 (1 साल बाद, अब)

भी है set()। यह तेजी से हो सकता है अगर बहुत सारे स्तंभों के माध्यम से लूप किया जा रहा है, क्योंकि यह [,:=,]लूप में कॉलिंग के (छोटे) ओवरहेड से बचा जाता है । setएक पाशनीय है :=। देख लो ?set

f_dowle3 = function(DT) {
  # either of the following for loops

  # by name :
  for (j in names(DT))
    set(DT,which(is.na(DT[[j]])),j,0)

  # or by number (slightly faster than by name) :
  for (j in seq_len(ncol(DT)))
    set(DT,which(is.na(DT[[j]])),j,0)
}

5
+! बहुत बढ़िया जवाब! क्या eval(parse)...सामान का अधिक सहज समकक्ष होना संभव है । एक व्यापक नोट पर, मुझे लगता है कि उन सभी कार्यों पर काम करना उपयोगी होगा data.table
रामनाथ 20

1
आपका दूसरा ब्लॉक कोड ऐसा करने का सबसे data.tableउपयुक्त तरीका लगता है । धन्यवाद!
Zach

3
@Statwonk मुझे लगता है कि आपके DTपास इस परीक्षण logicalके create_dt()उदाहरण के विपरीत, प्रकार के कॉलम हैं । set()कॉल के चौथे तर्क को बदलें (जो 0आपके उदाहरण में है और आर में डबल टाइप करें) FALSEऔर इसे बिना चेतावनी के काम करना चाहिए।
मैट डोले

2
@Statwonk और मैंने इस केस को रिलैक्स करने के लिए एक फीचर रिक्वेस्ट दायर की है और उस चेतावनी को छोड़ने के लिए जब लम्बाई -१ वैक्टर ० और १ को तार्किक: # ९९ ६ पर जोर दे रहा हो । हो सकता है कि ऐसा न हो, गति के लिए, आप अनावश्यक पुनरावृत्ति के बारे में चेतावनी देना चाहते हैं।
मैट डोले

1
@StefanF सच है और मुझे seq_along(DT)भी पसंद है। लेकिन तब पाठक को यह जानना seq_alongहोगा कि यह कॉलम के साथ होगा और पंक्तियों के नीचे नहीं। seq_len(col(DT))उस कारण से थोड़ा अधिक स्पष्ट।
मैट डोले

28

यहाँ सबसे सरल एक मैं आ सकता है:

dt[is.na(dt)] <- 0

यह कुशल है और फ़ंक्शन और अन्य गोंद कोड लिखने की आवश्यकता नहीं है।


बड़े डेटासेट और सामान्य वर्कस्टेशन कंप्यूटर (मेमोरी आवंटन त्रुटि) पर काम नहीं करता है
जेक

3
@ 16GB RAM वाली मशीन पर जाके मैं इसे 31M पंक्तियों, ~ 20 कॉलमों पर चलाने में सक्षम था। YMMV बेशक।
बार

मैं आपके अनुभवजन्य साक्ष्यों को टालता हूं। धन्यवाद।
जेक

10
डेटा के नवीनतम संस्करणों में Unfortunatelly। यह काम नहीं करता है। यह कहता है कि त्रुटि [.data.table(dt, is.na (dt)): मैं अमान्य प्रकार (मैट्रिक्स) है। शायद भविष्य में एक 2 कॉलम मैट्रिक्स डीटी के तत्वों की एक सूची (एफए 2.14 में ए [बी] की भावना में) वापस कर सकता है। यदि आप इसे पसंद करते हैं, तो कृपया पता करने में मदद करें, या अपनी टिप्पणी FR # 657 में जोड़ें। >
स्कान

यह दिलचस्प है! मैंने हमेशा इस्तेमाल कियाset
marbel

15

उस प्रयोजन के लिए समर्पित कार्य ( nafillऔर setnafill) data.tableपैकेज में उपलब्ध हैं (संस्करण> = 1.12.4):

यह अब तक पहले से पोस्ट किए गए बेंचमार्क के समानांतर समानांतर और अच्छी तरह से पता लगाने वाले स्तंभों को संसाधित करता है, अब तक के सबसे तेज़ दृष्टिकोण के नीचे, और 40 कोर मशीन का उपयोग करके भी स्केल किया गया है।

library(data.table)
create_dt <- function(nrow=5, ncol=5, propNA = 0.5){
  v <- runif(nrow * ncol)
  v[sample(seq_len(nrow*ncol), propNA * nrow*ncol)] <- NA
  data.table(matrix(v, ncol=ncol))
}
f_dowle3 = function(DT) {
  for (j in seq_len(ncol(DT)))
    set(DT,which(is.na(DT[[j]])),j,0)
}

set.seed(1)
dt1 = create_dt(2e5, 200, 0.1)
dim(dt1)
#[1] 200000    200
dt2 = copy(dt1)
system.time(f_dowle3(dt1))
#   user  system elapsed 
#  0.193   0.062   0.254 
system.time(setnafill(dt2, fill=0))
#   user  system elapsed 
#  0.633   0.000   0.020   ## setDTthreads(1) elapsed: 0.149
all.equal(dt1, dt2)
#[1] TRUE

set.seed(1)
dt1 = create_dt(2e7, 200, 0.1)
dim(dt1)
#[1] 20000000    200
dt2 = copy(dt1)
system.time(f_dowle3(dt1))
#   user  system elapsed 
# 22.997  18.179  41.496
system.time(setnafill(dt2, fill=0))
#   user  system elapsed 
# 39.604  36.805   3.798 
all.equal(dt1, dt2)
#[1] TRUE

यह एक महान विशेषता है! क्या आप वर्ण स्तंभों के लिए समर्थन जोड़ने की योजना बना रहे हैं? तब इसका उपयोग यहां किया जा सकता था ।
ismirsehregal

1
@ismirsehregal हाँ, आप इस सुविधा का यहाँ ट्रैक कर सकते हैं github.com/Rdatatable/data.table/issues/3992
jangorecki

12
library(data.table)

DT = data.table(a=c(1,"A",NA),b=c(4,NA,"B"))

DT
    a  b
1:  1  4
2:  A NA
3: NA  B

DT[,lapply(.SD,function(x){ifelse(is.na(x),0,x)})]
   a b
1: 1 4
2: A 0
3: 0 B

सिर्फ संदर्भ के लिए, gdata या data.matrix की तुलना में धीमी, लेकिन केवल डेटा का उपयोग करता है। पैकेज और गैर संख्यात्मक प्रविष्टियों के साथ सौदा कर सकते हैं।


5
आप शायद कर ifelseऔर संदर्भ द्वारा अद्यतन दोनों से बच सकते हैं DT[, names(DT) := lapply(.SD, function(x) {x[is.na(x)] <- "0" ; x})]। और मुझे संदेह है कि यह आपके द्वारा उल्लिखित उत्तरों की तुलना में धीमा होगा।
डेविड अरेनबर्ग

11

यहां पैकेज NAToUnknownमें एक समाधान का उपयोग किया गया gdataहै। मैंने एक विशाल डेटा टेबल बनाने के लिए एंड्री के समाधान का उपयोग किया है और एंड्री के समाधान के साथ समय की तुलना भी शामिल है।

# CREATE DATA TABLE
dt1 = create_dt(2e5, 200, 0.1)

# FUNCTIONS TO SET NA TO ZERO   
f_gdata  = function(dt, un = 0) gdata::NAToUnknown(dt, un)
f_Andrie = function(dt) remove_na(dt)

# COMPARE SOLUTIONS AND TIMES
system.time(a_gdata  <- f_gdata(dt1))

user  system elapsed 
4.224   2.962   7.388 

system.time(a_andrie <- f_Andrie(dt1))

 user  system elapsed 
4.635   4.730  20.060 

identical(a_gdata, g_andrie)  

TRUE

+1 अच्छी खोज। दिलचस्प है - यह पहली बार है जब मैं समान userसमय के साथ समय देखता हूं लेकिन समय में वास्तव में बड़ा अंतर है elapsed
एंड्री

@ और्री मैंने rbenchmarkअधिक प्रतिकृति का उपयोग करके बेंचमार्क समाधानों का उपयोग करने की कोशिश की , लेकिन संभवतः डेटा फ़्रेम के आकार के कारण मेमोरी त्रुटि से बाहर निकल गया। यदि आप benchmarkइन दोनों समाधानों पर कई प्रतिकृति के साथ चल सकते हैं, तो वे परिणाम दिलचस्प होंगे क्योंकि मुझे वास्तव में यकीन नहीं है कि मुझे 3x स्पीड क्यों मिल रही है
रामनाथ

@ रामनाथ चीजों को सही करने के लिए, इस उत्तर में समय के लिए ncol=5मुझे लगता है (बहुत अधिक समय लेना चाहिए) बग के कारण है create_dt
मैट डॉवेल

5

पूर्णता के लिए, NA को 0 से बदलने का दूसरा तरीका उपयोग करना है

f_rep <- function(dt) {
dt[is.na(dt)] <- 0
return(dt)
}

परिणामों और समय की तुलना करने के लिए मैंने अब तक उल्लिखित सभी दृष्टिकोणों को शामिल किया है।

set.seed(1)
dt1 <- create_dt(2e5, 200, 0.1)
dt2 <- dt1
dt3 <- dt1

system.time(res1 <- f_gdata(dt1))
   User      System verstrichen 
   3.62        0.22        3.84 
system.time(res2 <- f_andrie(dt1))
   User      System verstrichen 
   2.95        0.33        3.28 
system.time(f_dowle2(dt2))
   User      System verstrichen 
   0.78        0.00        0.78 
system.time(f_dowle3(dt3))
   User      System verstrichen 
   0.17        0.00        0.17 
system.time(res3 <- f_unknown(dt1))
   User      System verstrichen 
   6.71        0.84        7.55 
system.time(res4 <- f_rep(dt1))
   User      System verstrichen 
   0.32        0.00        0.32 

identical(res1, res2) & identical(res2, res3) & identical(res3, res4) & identical(res4, dt2) & identical(dt2, dt3)
[1] TRUE

इसलिए नया दृष्टिकोण f_dowle3अन्य सभी दृष्टिकोणों की तुलना में थोड़ा धीमा लेकिन तेज है। लेकिन ईमानदार होने के लिए, यह डेटा के मेरे अंतर्ज्ञान के खिलाफ है। सिंटेक्स और मुझे नहीं पता कि यह क्यों काम करता है। क्या कोई मुझे बता सकता है?


1
हां, मैंने उनकी जाँच की, इसीलिए मैंने जोड़ीदार समरूपों को शामिल किया है।
bratwoorst711

1
यहाँ एक कारण है कि यह मुहावरेदार तरीका क्यों नहीं है - stackoverflow.com/a/20545629
Naumz

4

मेरी समझ यह है कि R में तेज संचालन का रहस्य वेक्टर (या सरणियों, जो हुड के नीचे वैक्टर हैं) का उपयोग करना है।)

इस समाधान में मैं एक का उपयोग करता हूं data.matrix, arrayलेकिन एक सा व्यवहार करता है data.frame। क्योंकि यह एक सरणी है, आप NAs को बदलने के लिए एक बहुत ही सरल वेक्टर प्रतिस्थापन का उपयोग कर सकते हैं :

NAएस को हटाने के लिए थोड़ा सहायक कार्य करता है । सार कोड की एक एकल पंक्ति है। मैं केवल निष्पादन समय को मापने के लिए ऐसा करता हूं।

remove_na <- function(x){
  dm <- data.matrix(x)
  dm[is.na(dm)] <- 0
  data.table(dm)
}

एक data.tableदिया गया आकार बनाने के लिए थोड़ा सहायक कार्य ।

create_dt <- function(nrow=5, ncol=5, propNA = 0.5){
  v <- runif(nrow * ncol)
  v[sample(seq_len(nrow*ncol), propNA * nrow*ncol)] <- NA
  data.table(matrix(v, ncol=ncol))
}

एक छोटे नमूने पर प्रदर्शन:

library(data.table)
set.seed(1)
dt <- create_dt(5, 5, 0.5)

dt
            V1        V2        V3        V4        V5
[1,]        NA 0.8983897        NA 0.4976992 0.9347052
[2,] 0.3721239 0.9446753        NA 0.7176185 0.2121425
[3,] 0.5728534        NA 0.6870228 0.9919061        NA
[4,]        NA        NA        NA        NA 0.1255551
[5,] 0.2016819        NA 0.7698414        NA        NA

remove_na(dt)
            V1        V2        V3        V4        V5
[1,] 0.0000000 0.8983897 0.0000000 0.4976992 0.9347052
[2,] 0.3721239 0.9446753 0.0000000 0.7176185 0.2121425
[3,] 0.5728534 0.0000000 0.6870228 0.9919061 0.0000000
[4,] 0.0000000 0.0000000 0.0000000 0.0000000 0.1255551
[5,] 0.2016819 0.0000000 0.7698414 0.0000000 0.0000000

यह एक बहुत अच्छा उदाहरण डेटासेट है। मैं कोशिश करूंगा और सुधार करूंगा remove_na। 21.57 के उस समय में create_dt(के साथ runifऔर sample) शामिल हैं remove_na। कोई भी मौका जिसे आप 2 बार विभाजित करने के लिए संपादित कर सकते हैं?
मैट डॉवेल

क्या कोई छोटा बग है create_dt? यह हमेशा एक 5 स्तंभ डेटा बनाने के लिए लगता है ncol। पारित किए बिना परवाह किए बिना ।
मैट डाउल

@MatthewDowle वेल स्पॉट किया गया। त्रुटि (साथ ही समय
समाप्त

यदि सभी स्तंभ एक ही प्रकार के हैं, तो मैट्रिक्स में कनवर्ट करना ठीक से काम करेगा।
13

2

कई कॉलमों को सामान्य करने के लिए आप इस दृष्टिकोण का उपयोग कर सकते हैं (पिछले नमूना डेटा का उपयोग करके, लेकिन एक कॉलम जोड़कर):

z = data.table(x = sample(c(NA_integer_, 1), 2e7, TRUE), y = sample(c(NA_integer_, 1), 2e7, TRUE))

z[, names(z) := lapply(.SD, function(x) fifelse(is.na(x), 0, x))]

हालांकि गति के लिए परीक्षण नहीं किया


1
> DT = data.table(a=LETTERS[c(1,1:3,4:7)],b=sample(c(15,51,NA,12,21),8,T),key="a")
> DT
   a  b
1: A 12
2: A NA
3: B 15
4: C NA
5: D 51
6: E NA
7: F 15
8: G 51
> DT[is.na(b),b:=0]
> DT
   a  b
1: A 12
2: A  0
3: B 15
4: C  0
5: D 51
6: E  0
7: F 15
8: G 51
> 

3
और यद्यपि आप इसे एक से अधिक स्तंभों के लिए सामान्यीकृत कैसे करेंगे?
डेविड ऐरनबर्ग

@DavidArenburg सिर्फ लूप के लिए लिखें। यह स्वीकृत उत्तर होना चाहिए: यह सबसे सरल है!
baibo

1

1.12.6 fifelseनवीनतम data.tableसंस्करणों से फ़ंक्शन का उपयोग करना , यह पैकेज की तुलना NAToUnknownमें 10 गुना तेज है gdata:

z = data.table(x = sample(c(NA_integer_, 1), 2e7, TRUE))
system.time(z[,x1 := gdata::NAToUnknown(x, 0)])

#   user  system elapsed 
#  0.798   0.323   1.173 
system.time(z[,x2:= fifelse(is.na(x), 0, x)])

#   user  system elapsed 
#  0.172   0.093   0.113 

क्या आप इस उत्तर में कुछ समय की तुलना जोड़ सकते हैं? मुझे लगता है कि f_dowle3अभी भी तेज होगा: stackoverflow.com/a/7249454/345660
Zach
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.