NA'ları büyük bir data.table'da değiştirmenin en hızlı yolu


151

~ 200.000 satır ve 200 sütun boyunca dağılmış birçok eksik değerle büyük bir data.table var . Bu NA değerlerini olabildiğince verimli bir şekilde sıfırlara yeniden kodlamak istiyorum.

İki seçenek görüyorum:
1: Bir data.frame'e dönüştürün ve bunun gibi bir şey kullanın
2: Bir tür harika data.table alt ayar komutu

Tip 1'in oldukça verimli bir çözümünden memnun olacağım. Bir data.frame'e ve sonra tekrar data.table'a dönüştürmek çok uzun sürmez.


5
Neden dönüştürmek istiyorsunuz data.tablea data.frame? A data.table , bir data.frame. Herhangi bir data.frame işlemi sadece çalışacaktır.
Andrie

5
@Andrie. önemli bir fark, data.tablesütun numarasını belirterek bir sütuna erişememenizdir . bu yüzden DT[,3]üçüncü sütunu vermeyecektir. Sanırım bu, bağlantıda önerilen çözümü burada yaşanmaz kılıyor. Biraz data.tablesihirbazlık kullanan zarif bir yaklaşım olduğundan eminim !
Ramnath

6
@Ramnath, AFAIK, DT[, 3, with=FALSE]üçüncü sütunu döndürür.
Andrie

2
@Andrie. ama yine mydf[is.na(mydf) == TRUE]de veri çerçevelerinde bir sorun var, mydt[is.na(mydt) == TRUE]kullansam bile bana garip bir şey veriyorwith=FALSE
Ramnath

2
@Ramnath, puan alındı. Daha önceki ifadem çok genişti, yani yanılmışım. Afedersiniz. Data.tables yalnızca data.table yöntemi olmadığında data.frames gibi davranır.
Andrie

Yanıtlar:


187

İşte kullanarak bir çözüm data.table bireyin :=ANDRIE ve Ramnath en yanıtlara bina, operatörünü.

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'ın dt1'i referans olarak güncellediğine dikkat edin. Yerel bir kopya gerekiyorsa copy, tüm veri kümesinin yerel bir kopyasını yapmak için işleve açık bir çağrı yapılması gerekir. data.table en setkey, key<-ve :=-copy on-write yoktur.

Sonra, f_dowle'ın zamanını nerede geçirdiğini görelim.

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

Orada, birkaç vektör kopyası ve vektör taramalarının olduğu na.replaceve üzerine odaklanacağım is.na. Bunlar NA, vektörde referans olarak güncellenen küçük bir na.replace C işlevi yazarak oldukça kolay bir şekilde ortadan kaldırılabilir . Bu sanırım en azından 20 saniyeyi yarıya indirir. Herhangi bir R paketinde böyle bir işlev var mı?

Nedeni f_andriebu bütün kopyaları çünkü olabilir başarısız dt1veya bütün büyüklüğünde bir mantıksal matris oluşturur dt1birkaç kez. Diğer 2 yöntem bir seferde bir sütun üzerinde çalışır (sadece kısaca bakmama rağmen NAToUnknown).

DÜZENLE (yorumlarda Ramnath tarafından talep edildiği gibi daha zarif bir çözüm):

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

Keşke başlamak için o şekilde yapsaydım!

EDIT2 (1 yıldan fazla bir süre sonra, şimdi)

Ayrıca var set(). [,:=,]Bir döngüde arama işleminin (küçük) ek yükünü önlediği için, çok sayıda sütun döngüsü varsa bu daha hızlı olabilir . setdöngülenebilir :=. Bakın ?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
+! mükemmel cevap! eval(parse)...şeylerin daha sezgisel bir eşdeğerine sahip olmak mümkün mü? Daha geniş bir çerçevede, .NET Framework'ün tüm öğeleri üzerinde çalışan işlemlere sahip olmanın yararlı olacağını düşünüyorum data.table.
Ramnath

1
2. kod bloğunuz data.tablebunu yapmanın en uygun yolu gibi görünüyor . Teşekkürler!
Zach

3
@Statwonk Sanırım bu test örneğinden farklı olarak sizin DTsütunlarınız var . Çağrının 4. bağımsız değişkenini ( sizin örneğinizde olan ve R'de double yazın) olarak değiştirin ve uyarı vermeden çalışması gerekir. logicalcreate_dt()set()0FALSE
Matt Dowle

2
@Statwonk Ve bu davayı gevşetmek ve uzunluk-1 vektörleri 0 ve 1'i mantıksal: # 996'ya zorlarken bu uyarıyı bırakmak için bir özellik isteği gönderdim . Bunu yapmayabilirsiniz, çünkü hız için, gereksiz tekrarlayan baskı konusunda uyarılmak istemezsiniz.
Matt Dowle

1
@StefanF True ve ben de tercih ediyorum seq_along(DT). Ancak okuyucu, seq_alongbunun satırlar boyunca değil, sütunlar boyunca olacağını bilmelidir . seq_len(col(DT))bu nedenle biraz daha açık.
Matt Dowle

29

İşte bulabileceğim en basit şey:

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

Etkilidir ve işlevleri ve diğer yapıştırıcı kodlarını yazmaya gerek yoktur.


büyük veri kümelerinde ve normal iş istasyonu bilgisayarlarında çalışmıyor (bellek ayırma hatası)
Jake,

3
@Jake 16GB RAM'e sahip bir makinede bunu 31 milyon satırda ~ 20 sütun üzerinde çalıştırabildim. Elbette YMMV.
Bar

Ampirik kanıtlarınıza saygı duyuyorum. Teşekkürler.
Jake

10
Maalesef data.table'ın son sürümlerinde çalışmıyor. Hata [.data.table(dt, is.na (dt)) yazıyor: i geçersiz tür (matris). Belki gelecekte 2 sütunlu bir matris, DT öğelerinin bir listesini döndürebilir (SSS 2.14'te A [B] ruhuna uygun olarak). Lütfen bunu istiyorsanız datatable-help'e bildirin veya yorumlarınızı FR # 657'ye ekleyin. >
skan

bu ilginç! Ben her zaman kullandımset
marbel

15

Bu amaç için özel işlevler ( nafillve setnafill) data.tablepakette mevcuttur (sürüm> = 1.12.4):

Sütunları paralel olarak işliyor, böylece daha önce yayınlanan karşılaştırmaları iyi bir şekilde ele alıyor, zamanlamalarının altında şimdiye kadarki en hızlı yaklaşıma göre ve ayrıca 40 çekirdekli makine kullanarak ölçeklendirilmiş.

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

Bu harika bir özellik! Karakter sütunları için destek eklemeyi mi planlıyorsunuz? O zaman burada kullanılabilir .
ismirsehregal

1
@ismirsehregal evet, bu özelliği buradan takip edebilirsiniz github.com/Rdatatable/data.table/issues/3992
jangorecki

13
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

Sadece referans için, gdata veya data.matrix'e kıyasla daha yavaştır, ancak yalnızca data.table paketini kullanır ve sayısal olmayan girişlerle başa çıkabilir.


5
Muhtemelen hem kaçınabilir hem de ifelseyaparak referans olarak güncelleme yapabilirsiniz DT[, names(DT) := lapply(.SD, function(x) {x[is.na(x)] <- "0" ; x})]. Ve bahsettiğiniz cevaplardan daha yavaş olacağından şüpheliyim.
David Arenburg

11

İşte kullanarak bir çözümdür NAToUnknowniçinde gdatapaketin. Andrie'nin çözümünü devasa bir veri tablosu oluşturmak için kullandım ve ayrıca Andrie'nin çözümüyle zaman karşılaştırmaları ekledim.

# 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 İyi bul. İlginç - benzer userzamana sahip zamanlamaları ilk kez görüyorum ama zaman açısından gerçekten büyük farklar elapsed.
Andrie

@Andrie Daha rbenchmarkfazla çoğaltma kullanarak çözümleri karşılaştırmak için kullanmayı denedim , ancak muhtemelen veri çerçevesinin boyutundan dolayı yetersiz bellek hatası aldım. Çalıştırmak eğer benchmarkher ikisi birden tekrarlamalı bu çözümleri gerçekten emin niçin i 3x hızlanma alıyorum değilim olarak, bu sonuçlar ilginç olacaktır
Ramnath

@Ramnath İşleri doğru yapmak için, bu cevabın zamanlamaları ncol=5bence (çok daha uzun sürmeli ) hata nedeniyle create_dt.
Matt Dowle

5

Tamlık adına, NA'ları 0 ile değiştirmenin başka bir yolu kullanmaktır

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

Sonuçları ve süreleri karşılaştırmak için şimdiye kadar bahsedilen tüm yaklaşımları dahil ettim.

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

Bu nedenle, yeni yaklaşım f_dowle3diğer tüm yaklaşımlardan biraz daha yavaş ama daha hızlıdır. Ama dürüst olmak gerekirse, bu benim sezgime aykırıdır. Tablo Sözdizimi ve bunun neden işe yaradığına dair hiçbir fikrim yok. Biri beni aydınlatabilir mi?


1
Evet onları kontrol ettim, bu yüzden ikili özdeşleri dahil ettim.
bratwoorst711

1
Bunun deyimsel yol olmadığının bir nedeni: stackoverflow.com/a/20545629
Naumz

4

Anladığım kadarıyla, R'deki hızlı işlemlerin sırrı vektörü (veya başlık altındaki vektörler olan dizileri) kullanmaktır.

Bu çözümde, a data.matrixolan arraya'dan yararlanıyorum ama biraz a data.frame. Bir dizi olduğu için, NAs'yi değiştirmek için çok basit bir vektör ikamesi kullanabilirsiniz :

E'leri kaldırmak için küçük bir yardımcı işlev NA. Öz, tek bir kod satırıdır. Bunu sadece yürütme süresini ölçmek için yapıyorum.

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

data.tableBelirli bir boyutta bir oluşturmak için küçük bir yardımcı işlev .

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))
}

Küçük bir örnek üzerinde gösteri:

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

Bu çok güzel bir örnek veri kümesi. Deneyeceğim ve geliştireceğim remove_na. 21.57s arasında bu zamanlama atımını içerir, create_dt(dahil runifve samplebirlikte) remove_na. 2 kez bölmek için düzenleme yapma şansın var mı?
Matt Dowle

İçinde küçük bir hata var create_dtmı? Görünüşe göre her zaman 5 sütunlu bir veri tablosu oluşturuyor ncol.
Matt Dowle

@MatthewDowle İyi görüldü. Hata kaldırıldı (zamanlamaların yanı sıra)
Andrie

Matrise dönüştürme, yalnızca tüm sütunlar aynı türse düzgün çalışacaktır.
Skan

2

Birçok sütunu genellemek için bu yaklaşımı kullanabilirsiniz (önceki örnek verileri kullanarak ancak bir sütun ekleyerek):

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))]

Yine de hızı test etmedim


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
Peki bunu birden fazla sütuna nasıl genellersiniz?
David Arenburg

@DavidArenburg sadece bir for döngüsü yazın. Kabul edilen cevap bu olmalı: en basit olanı!
baibo

1

Kullanımı fifelseen yeniden işlevini data.tablesürümleri 1.12.6, hatta 10 kat daha hızlı olduğu NAToUnknowniçinde gdatapaketin:

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 

Bu yanıta bazı zamanlama karşılaştırmaları ekleyebilir misiniz? Sanırım f_dowle3daha hızlı olacak: stackoverflow.com/a/7249454/345660
Zach
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.