dplyr on data.table, gerçekten data.table kullanıyorum?


91

Ben kullanırsanız dplyr bir üstündeki sözdizimi datatable hala dplyr sözdizimini kullanırken, ben Datatable tüm hızı faydaları alabilirim? Başka bir deyişle, datatable'ı dplyr sözdizimi ile sorgularsam yanlış kullanır mıyım? Yoksa tüm gücünden yararlanmak için saf verilebilir sözdizimi kullanmam gerekiyor mu?

Herhangi bir tavsiye için şimdiden teşekkürler. Kod Örneği:

library(data.table)
library(dplyr)

diamondsDT <- data.table(ggplot2::diamonds)
setkey(diamondsDT, cut) 

diamondsDT %>%
    filter(cut != "Fair") %>%
    group_by(cut) %>%
    summarize(AvgPrice = mean(price),
                 MedianPrice = as.numeric(median(price)),
                 Count = n()) %>%
    arrange(desc(Count))

Sonuçlar:

#         cut AvgPrice MedianPrice Count
# 1     Ideal 3457.542      1810.0 21551
# 2   Premium 4584.258      3185.0 13791
# 3 Very Good 3981.760      2648.0 12082
# 4      Good 3928.864      3050.5  4906

İşte bulduğum datatable denklik. DT iyi uygulamasına uygun olup olmadığından emin değilim. Ancak, kodun sahne arkasındaki dplyr sözdiziminden gerçekten daha verimli olup olmadığını merak ediyorum:

diamondsDT [cut != "Fair"
        ] [, .(AvgPrice = mean(price),
                 MedianPrice = as.numeric(median(price)),
                 Count = .N), by=cut
        ] [ order(-Count) ]

7
Veri tablosu sözdizimini neden kullanmazsınız? Aynı zamanda zarif ve verimli. Soru çok geniş olduğu için gerçekten cevaplanamaz. Evet, dplyrveri tabloları için yöntemler var , ancak veri tablosunun kendi karşılaştırılabilir yöntemleri de var
Rich Scriven

7
Verilebilir sözdizimi veya ders kullanabilirim. Ama bir şekilde dplyr sözdizimini daha zarif buluyorum. Sözdizimi için tercihim ne olursa olsun. Gerçekten bilmek istediğim şey şu: Verilebilir gücün% 100 avantajlarından yararlanmak için saf verilebilir sözdizimi kullanmam gerekiyor mu?
Polymerase

3
'Lerde ve karşılık gelen' lerde dplyrkullanıldığı yeni bir kıyaslama için buraya (ve oradaki referanslara) bakın. data.framedata.table
Henrik

2
@Polymerase - Bu sorunun cevabının kesinlikle "Evet" olduğunu düşünüyorum
Rich Scriven

1
@Henrik: Daha sonra o sayfayı yanlış yorumladığımı fark ettim çünkü onlar sadece dataframe inşası için kodu gösterdiler, data.table inşası için kullandıkları kodu göstermediler. Bunu anladığımda, yorumumu sildim (görmemişsinizdir umarım).
IRTFM

Yanıtlar:


77

Açık / basit bir cevap yok çünkü bu iki paketin de felsefeleri belirli açılardan farklılık gösteriyor. Yani bazı tavizler kaçınılmazdır. İşte ele almanız / dikkate almanız gerekebilecek endişelerden bazıları.

i(== filter()ve slice()dplyr'de) içeren işlemler

DT10 sütun ile varsayalım . Şu data.table ifadelerini göz önünde bulundurun:

DT[a > 1, .N]                    ## --- (1)
DT[a > 1, mean(b), by=.(c, d)]   ## --- (2)

(1) DTsütundaki satırların sayısını verir a > 1. (2) , (1) ile aynı ifade için mean(b)gruplanmış olarak döndürür .c,di

Yaygın olarak kullanılan dplyrifadeler şöyle olacaktır:

DT %>% filter(a > 1) %>% summarise(n())                        ## --- (3) 
DT %>% filter(a > 1) %>% group_by(c, d) %>% summarise(mean(b)) ## --- (4)

Açıkça, data.table kodları daha kısadır. Ayrıca bellek açısından daha verimli 1 . Neden? Çünkü hem (3) hem de (4) 'te, önce 10 sütunun tümü için satırlarıfilter() döndürür , (3)' te sadece satır sayısına ihtiyacımız olduğunda ve (4) 'te sadece ardışık işlemler için sütunlara ihtiyacımız var . Bunu aşmak için apriori sütunlarına ihtiyacımız var :b, c, dselect()

DT %>% select(a) %>% filter(a > 1) %>% summarise(n()) ## --- (5)
DT %>% select(a,b,c,d) %>% filter(a > 1) %>% group_by(c,d) %>% summarise(mean(b)) ## --- (6)

İki paket arasındaki önemli bir felsefi farkı vurgulamak önemlidir:

  • İçinde data.table, bu ilgili işlemleri bir arada tutmayı seviyoruz ve bu j-expression(aynı işlev çağrısından) bakmaya ve (1) 'de herhangi bir sütuna gerek olmadığını anlamaya izin veriyor . İçindeki ifade ihesaplanır ve .Nsatır sayısını veren mantıksal vektörün sadece toplamıdır; tüm alt küme asla gerçekleştirilmez. (2) 'de, sadece sütun b,c,dalt kümede gerçekleşir, diğer sütunlar yok sayılır.

  • Ama dplyrfelsefe, bir işleve tam olarak bir şeyi iyi yapmaktır . filter()Filtrelediğimiz tüm bu sütunlara sonraki işlemin gerekip gerekmediğini söylemenin (en azından şu anda) bir yolu yok . Bu tür görevleri verimli bir şekilde gerçekleştirmek istiyorsanız ileriyi düşünmeniz gerekir. Ben şahsen bunu bu durumda mantıksız buluyorum.

(5) ve (6) 'da, aihtiyaç duymadığımız sütunu hala alt küme olarak belirlediğimizi unutmayın. Ama bundan nasıl kaçınacağımdan emin değilim. filter()İşlevin döndürülecek sütunları seçmek için bir argümanı varsa , bu sorunu önleyebilirdik, ancak bu durumda işlev yalnızca bir görevi yapmayacaktır (bu aynı zamanda bir dplyr tasarım seçimidir).

Referansla alt atama

dplyr asla referans olarak güncellenmez. Bu, iki paket arasındaki başka bir büyük (felsefi) farktır.

Örneğin, data.table'da şunları yapabilirsiniz:

DT[a %in% some_vals, a := NA]

Sütunu , yalnızca koşulu sağlayan satırlarda a referans olarak günceller . Şu anda dplyr, yeni bir sütun eklemek için tüm data.table'ı dahili olarak kopyalar. @BrodieG cevabında zaten bundan bahsetmişti.

Ancak derin kopya, FR # 617 uygulandığında sığ bir kopya ile değiştirilebilir . Ayrıca ilgili: dplyr: FR # 614 . Yine de, değiştirdiğiniz sütunun her zaman kopyalanacağını unutmayın (bu nedenle biraz daha yavaş / daha az bellek verimli). Sütunları referansa göre güncellemenin bir yolu olmayacaktır.

Diğer işlevler

  • Data.table'da, birleştirme sırasında birleştirme yapabilirsiniz ve bunu anlamak daha basittir ve ara birleştirme sonucu hiçbir zaman gerçekleşmediğinden bellek verimlidir. Örnek için bu gönderiye göz atın . Bunu (şu anda?) Dplyr'ın data.table / data.frame sözdizimini kullanarak yapamazsınız.

  • data.table'ın yuvarlanan birleştirme özelliği, dplyr'ın sözdiziminde de desteklenmez.

  • Son zamanlarda data.table'da , şu anda ayrı bir işlev olan ve bu nedenle boru operatörleriyle (magrittr / pipeR? - kendim hiç denemedim) aralık aralıkları üzerinden birleştirmek için örtüşme birleştirmeleri uyguladık ( burada bir örnekfoverlaps() ).

    Ama nihayetinde amacımız, [.data.tablegruplama, birleştirme sırasında toplama vb. Gibi diğer özellikleri toplayabilmemiz için onu entegre etmektir . Bu sınırlamalar yukarıda belirtilen aynı sınırlamalara sahiptir.

  • 1.9.4'ten beri data.table, normal R sözdiziminde hızlı ikili aramaya dayalı alt kümeler için ikincil anahtarları kullanarak otomatik indeksleme uygular. Örn: DT[x == 1]ve DT[x %in% some_vals]ilk çalıştırmada otomatik olarak bir indeks oluşturur, bu daha sonra aynı sütundan birbirini takip eden alt kümelerde ikili aramayı kullanarak hızlı alt kümeye kadar kullanılır. Bu özellik gelişmeye devam edecek. Bu özelliğe kısa bir genel bakış için bu özü kontrol edin .

    Yolu itibaren filter()data.tables için uygulanan, bu özellikten yararlanmak almaz.

  • Bir dplyr özelliği, şu anda data.table'ın sağlamadığı aynı sözdizimini kullanarak veritabanlarına arayüz sağlamasıdır .

Bu nedenle, bunları (ve muhtemelen diğer noktaları) tartmanız ve bu değiş tokuşların sizin için kabul edilebilir olup olmadığına göre karar vermeniz gerekecektir.

HTH


(1) Çoğu durumda darboğaz, verileri ana bellekten önbelleğe taşımak (ve önbellekteki verileri mümkün olduğunca kullanmak - önbellek kayıplarını azaltmak olduğundan, bellek verimli olmanın hızı doğrudan etkilediğini unutmayın (özellikle veriler büyüdükçe) - ana belleğe erişimi azaltmak için). Burada ayrıntılara girmiyorum.


4
Kesinlikle harika. Bunun için teşekkürler
David Arenburg

6
Bu iyi bir cevap, ancak olurdu mümkün etkin uygulamak için dplyr için (değil olasılıkla varsa) filter()artı summarise()SQL için dplyr kullandığı aynı yaklaşımı kullanarak - yani bir ifade bina ve sonra yalnızca talep üzerine bir kez yürütme. Bunun yakın gelecekte uygulanması pek olası değil çünkü dplyr benim için yeterince hızlı ve bir sorgu planlayıcı / iyileştirici uygulamak nispeten zor.
hadley

Hafızanın verimli olması başka bir önemli alana da yardımcı olur - hafıza bitmeden görevi gerçekten tamamlamak. Büyük veri kümeleriyle çalışırken dplyr ve pandalarda bu sorunla karşılaştım, oysa data.table işi zarif bir şekilde tamamlayacaktı.
Zaki

25

Sadece dene.

library(rbenchmark)
library(dplyr)
library(data.table)

benchmark(
dplyr = diamondsDT %>%
    filter(cut != "Fair") %>%
    group_by(cut) %>%
    summarize(AvgPrice = mean(price),
                 MedianPrice = as.numeric(median(price)),
                 Count = n()) %>%
    arrange(desc(Count)),
data.table = diamondsDT[cut != "Fair", 
                        list(AvgPrice = mean(price),
                             MedianPrice = as.numeric(median(price)),
                             Count = .N), by = cut][order(-Count)])[1:4]

Bu problemde, data.table, data.table kullanan dplyr'den 2.4 kat daha hızlı görünüyor:

        test replications elapsed relative
2 data.table          100    2.39    1.000
1      dplyr          100    5.77    2.414

Polymerase'in yorumuna göre revize edildi .


2
microbenchmarkPaketi kullanarak, OP'nin dplyrkodunu orijinal (veri çerçevesi) sürümünde çalıştırmanın diamondsmedyan zamanın 0,012 saniye sürdüğünü diamonds, bir veri tablosuna dönüştürüldükten sonra medyan süre 0,024 saniye sürdüğünü buldum . G. Grothendieck'in data.tablekodunu çalıştırmak 0.013 saniye sürdü. En azından benim sistemimde aynı performansa benziyor dplyrve data.tableaynı performansa sahip. Fakat dplyrveri çerçevesi ilk olarak bir veri tablosuna dönüştürüldüğünde neden daha yavaş olsun?
eipi10

Sevgili G. Grothendieck, bu harika. Bana bu kıyaslama aracını gösterdiğiniz için teşekkür ederim. BTW, dplyr düzenlemesinin eşdeğerliğini yapmak için verilebilir sürümde [order (-Count)] 'u unuttunuz (desc (Count)). Bunu ekledikten sonra, datatable yaklaşık x1.8 daha hızlıdır (2.9 yerine).
Polymerase

@ eipi10, burada verilebilir sürümle tezgahınızı yeniden çalıştırabilir misiniz (son adımda azalan sayıma göre sıralama eklendi): diamondsDT [cut! = "Uygun", liste (OrtFiyat = ortalama (fiyat), MedyanFiyat = as.nümerik (medyan (fiyat)), Sayı = .N), göre = kes] [order (-Count)]
Polymerase

Hala 0,013 saniye. Sıralama işlemi neredeyse hiç zaman almıyor, çünkü sadece dört satırdan oluşan son tabloyu yeniden düzenlemek.
eipi10

1
Dplyr sözdiziminden veri tablosu sözdizimine dönüştürme için bazı sabit ek yükler vardır, bu nedenle değişen sorun boyutlarını denemeye değer olabilir. Ayrıca dplyr'de en verimli veri tablosu kodunu uygulamamış olabilirim; yamalar her zaman memnuniyetle karşılanır
hadley

22

Sorularınızı cevaplamak için:

  • Evet kullanıyorsun data.table
  • Ama saf data.tablesözdizimiyle yapacağınız kadar verimli değil

Çoğu durumda bu, dplyrsözdizimi isteyenler için kabul edilebilir bir uzlaşma olacaktır, ancak muhtemelen dplyrdüz veri çerçevelerinden daha yavaş olacaktır .

Gruplama sırasında varsayılan olarak dplyrkopyalayacak büyük bir faktör gibi görünüyor data.table. Şunları düşünün (microbenchmark kullanarak):

Unit: microseconds
                                                               expr       min         lq    median
                                diamondsDT[, mean(price), by = cut]  3395.753  4039.5700  4543.594
                                          diamondsDT[cut != "Fair"] 12315.943 15460.1055 16383.738
 diamondsDT %>% group_by(cut) %>% summarize(AvgPrice = mean(price))  9210.670 11486.7530 12994.073
                               diamondsDT %>% filter(cut != "Fair") 13003.878 15897.5310 17032.609

Filtreleme benzer hızdadır, ancak gruplama değildir. Sanırım suçlu bu satırda dplyr:::grouped_dt:

if (copy) {
    data <- data.table::copy(data)
}

burada copyvarsayılan değerdir TRUE(ve görebildiğim kadarıyla kolayca YANLIŞ olarak değiştirilemez). Bu muhtemelen farkın% 100'ünü açıklamaz, ancak diamondsbüyük olasılıkla büyük olasılıkla tam bir fark olmayan bir şeyin genel yükü tek başına değildir.

Sorun, tutarlı bir dilbilgisine sahip olmak için dplyr, gruplamanın iki adımda yapılmasıdır. İlk önce anahtarları, gruplarla eşleşen orijinal veri tablosunun bir kopyasına yerleştirir ve ancak daha sonra gruplandırır. data.tablesadece en büyük sonuç grubu için bellek ayırır, bu durumda sadece bir satırdır, böylece ne kadar belleğin ayrılması gerektiği konusunda büyük bir fark yaratır.

Bilginize, kimsenin umurunda değilse, bunu çıktı için deneysel (ve hala çok fazla alfa) ağaç görüntüleyici olan treeprof( install_github("brodieg/treeprof")) kullanarak buldum Rprof:

görüntü açıklamasını buraya girin

Yukarıdakilerin şu anda yalnızca AFAIK mac'larında çalıştığını unutmayın. Ayrıca, maalesef, Rproftürden çağrıları packagename::funnameanonim olarak kaydeder , böylece aslında sorumlu olan tüm datatable::çağrılar olabilir grouped_dt, ancak hızlı testlerden datatable::copybüyük olanı gibi görünüyor .

Bununla birlikte, [.data.tablegörüşmenin etrafında ne kadar fazla ek yük olmadığını çabucak görebilirsiniz , ancak gruplama için tamamen ayrı bir şube de vardır.


DÜZENLE : kopyalamayı onaylamak için:

> tracemem(diamondsDT)
[1] "<0x000000002747e348>"    
> diamondsDT %>% group_by(cut) %>% summarize(AvgPrice = mean(price))
tracemem[0x000000002747e348 -> 0x000000002a624bc0]: <Anonymous> grouped_dt group_by_.data.table group_by_ group_by <Anonymous> freduce _fseq eval eval withVisible %>% 
Source: local data table [5 x 2]

        cut AvgPrice
1      Fair 4358.758
2      Good 3928.864
3 Very Good 3981.760
4   Premium 4584.258
5     Ideal 3457.542
> diamondsDT[, mean(price), by = cut]
         cut       V1
1:     Ideal 3457.542
2:   Premium 4584.258
3:      Good 3928.864
4: Very Good 3981.760
5:      Fair 4358.758
> untracemem(diamondsDT)

Bu harika, teşekkürler. Bu, dplyr :: group_by () 'nin dahili veri kopyalama adımı nedeniyle bellek gereksinimini (salt verilebilir sözdizimine kıyasla) ikiye katlayacağı anlamına mı geliyor? Yani, verilebilir nesne boyutum 1 GB ise ve orijinal gönderideki gibi dplyr zincirleme sözdizimini kullanıyorsam. Sonuçları almak için en az 2GB boş belleğe ihtiyacım olacak mı?
Polymerase

2
Bunu dev sürümünde düzelttiğimi hissediyorum.
hadley

@hadley, CRAN versiyonundan çalışıyordum. Dev'e baktığınızda, sorunu kısmen ele almış gibi görünüyorsunuz, ancak asıl kopya kaldı (test edilmedi, sadece R / grouped-dt.r'deki c (20, 30:32) satırlarına bakın. Muhtemelen şimdi daha hızlı, ancak Bahse girerim yavaş adım kopyadır.
BrodieG

3
Ayrıca data.table'da sığ bir kopyalama işlevi bekliyorum; o zamana kadar güvenli olmanın hızlı olmaktan daha iyi olduğunu düşünüyorum.
hadley

2

Sen kullanabilirsiniz dtplyr parçası olan şimdi tidyverse . Her zamanki gibi dplyr stil ifadelerini kullanmanıza izin verir, ancak tembel bir değerlendirme kullanır ve ifadelerinizi başlık altındaki data.table koduna çevirir. Çeviride ek yük minimum düzeydedir, ancak data.table'ın yararlarının çoğunu, değilse de elde edersiniz. Resmi git repo en fazla detay burada ve tidyverse sayfa .

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.