data.table vs dplyr: Biri iyi yapamaz mı diğeri yapamaz mı yoksa kötü yapar mı?


759

genel bakış

Nispeten aşinayım data.table, çok fazla değil dplyr. Ben SO ortaya çıktı bazı dplyrvinyet ve örnekleri okudum ve şimdiye kadar benim sonuçları şunlardır:

  1. data.tableve dplyrçok sayıda (yani> 10-100K) grup ve diğer bazı durumlarda (aşağıda kıyaslamalara bakın) hariç, karşılaştırılabilir
  2. dplyr daha erişilebilir sözdizimi var
  3. dplyr potansiyel DB etkileşimlerini özetler (veya alacaktır)
  4. Bazı küçük işlevsellik farklılıkları vardır (aşağıdaki "Örnekler / Kullanım" a bakın)

Zihnimde 2. çok fazla ağırlık taşımıyor çünkü oldukça aşinayım data.table, ancak her ikisi için de yeni kullanıcılar için büyük bir faktör olacağını anlıyorum. Bu konuda daha sezgisel olan bir tartışmadan kaçınmak istiyorum, çünkü zaten aşina olan birinin bakış açısıyla sorulan özel sorumla alakasız data.table. Ayrıca "daha sezgisel" in daha hızlı analize nasıl yol açtığı hakkında bir tartışma yapmaktan kaçınmak istiyorum (kesinlikle doğrudur, ancak yine, burada en çok ilgilendiğim şey değil).

Soru

Bilmek istediğim şey:

  1. Paketlere aşina olan insanlar için bir veya diğer paketle kodlanması çok daha kolay olan analitik görevler var mı (yani her birinin daha azının iyi bir şey olduğu gerekli ezoterizm seviyesine karşı gerekli tuş vuruşlarının bir kombinasyonu).
  2. Bir pakette diğerine karşı önemli ölçüde (yani 2 kattan fazla) daha etkin bir şekilde gerçekleştirilen analitik görevler var mı?

Bir son SO soru bana çünkü düşünmediğini nokta kadar, biraz daha bu konuda düşünme var dplyrçok ben zaten neler yapabileceğini ötesinde doğuracağını data.table. İşte dplyrçözüm (Q'nun sonundaki veriler):

dat %.%
  group_by(name, job) %.%
  filter(job != "Boss" | year == min(year)) %.%
  mutate(cumu_job2 = cumsum(job2))

Hangi benim bir data.tableçözüm kesmek kesmek den çok daha iyi oldu . Bununla birlikte, iyi data.tableçözümler de oldukça iyi (teşekkürler Jean-Robert, Arun ve burada kesinlikle en uygun çözüm üzerinde tek bir ifadeyi tercih ettim):

setDT(dat)[,
  .SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)], 
  by=list(id, job)
]

İkincisinin sözdizimi çok ezoterik görünebilir, ancak alışkınsanız aslında oldukça basittir data.table(yani daha ezoterik numaralardan bazılarını kullanmazsanız).

İdeal olarak görmek istediğim bazı iyi örneklerin dplyrya da data.tableyolun daha özlü olması ya da önemli ölçüde daha iyi performans göstermesidir.

Örnekler

kullanım
  • dplyrrastgele sayıda satır döndüren gruplanmış işlemlere izin vermez ( eddi'nin sorusundan , not: bu dplyr 0.5'de uygulanacak gibi görünüyor , ayrıca @beginneR do@ eddi sorusunun cevabında potansiyel bir geçici çözüm gösterir ).
  • data.tabledestekler haddeleme katılır sıra (sayesinde @dholstius) olarak üst üste katılır
  • data.tableaynı temel R sözdizimini kullanırken ikili arama kullanan otomatik indeksleme yoluyla formun ifadelerini DT[col == value]veya hızDT[col %in% values] için dahili olarak optimize eder . Daha fazla ayrıntı ve küçük bir kıyaslama için buraya bakın .
  • dplyrProgramlı kullanımını basitleştirebilen fonksiyonların standart değerlendirme versiyonlarını sunar (örneğin regroup, programlı kullanımının kesinlikle mümkün olduğunu unutmayın , sadece en azından bilgim için bazı dikkatli düşünce, ikame / alıntı, vb. gerektirir)summarize_each_dplyrdata.table
Deneyler

Veri

Bu, soru bölümünde gösterdiğim ilk örnek içindir.

dat <- structure(list(id = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 
2L, 2L, 2L, 2L, 2L, 2L), name = c("Jane", "Jane", "Jane", "Jane", 
"Jane", "Jane", "Jane", "Jane", "Bob", "Bob", "Bob", "Bob", "Bob", 
"Bob", "Bob", "Bob"), year = c(1980L, 1981L, 1982L, 1983L, 1984L, 
1985L, 1986L, 1987L, 1985L, 1986L, 1987L, 1988L, 1989L, 1990L, 
1991L, 1992L), job = c("Manager", "Manager", "Manager", "Manager", 
"Manager", "Manager", "Boss", "Boss", "Manager", "Manager", "Manager", 
"Boss", "Boss", "Boss", "Boss", "Boss"), job2 = c(1L, 1L, 1L, 
1L, 1L, 1L, 0L, 0L, 1L, 1L, 1L, 0L, 0L, 0L, 0L, 0L)), .Names = c("id", 
"name", "year", "job", "job2"), class = "data.frame", row.names = c(NA, 
-16L))

9
dplyras.data.table(dat)[, .SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)], by = list(name, job)]
Birine

7
1. için hem dplyrve data.tablebir cevap bir noktada olacak böylece ekipleri, kriterler üzerinde çalışıyoruz. # 2 (sözdizimi) imO kesinlikle yanlış, ama bu açıkça görüş alanına giriyor, bu yüzden de kapatmak için oy veriyorum.
eddi

13
peki, yine imO, daha temiz ifade edilen problemler (d)plyrölçüsü 0
eddi

28
@BrodieG ikisi hakkında gerçekten rahatsız etmez beni olduğu bir şey dplyrve plyrsözdizimi açısından ve temelde onların sözdizimi sevmediğim ana nedeni de budur, ben çok fazla yol adları ile ekstra fonksiyonlar ((1'den fazla okuyun) öğrenmek zorunda olduğunu olduğunu hala benim için anlamlı değil), ne yaptıklarını, hangi argümanları aldıklarını, vb. hatırla.
eddi

43
@ eddi [yanak dili] data.table sözdizimi hakkında beni gerçekten rahatsız eden tek şey, çok fazla işlev bağımsız değişkeninin nasıl etkileşimde bulunduğunu ve şifreli kısayolların ne anlama geldiğini (ör. .SD) öğrenmem gerektiğidir . [ciddi olarak] Bunların farklı insanlara hitap edecek meşru tasarım farklılıkları olduğunu düşünüyorum
Hadley

Yanıtlar:


532

: Biz bu yönler (önem özel bir sırada) kapsamlı cevap / karşılaştırma sağlamak için en azından kapağa ihtiyaç Speed, Memory usage, Syntaxve Features.

Amacım bunların her birini data.table perspektifinden olabildiğince açık bir şekilde ele almak.

Not: aksi açıkça belirtilmedikçe, dplyr'e atıfta bulunarak, dahili Rcpp kullanarak C ++ 'da olan dplyr'in data.frame arayüzüne atıfta bulunuruz.


Data.table sözdizimi formunda tutarlıdır - DT[i, j, by]. Tutmak için i, jve bybirlikte tasarım gereğidir. İlgili işlemleri bir arada tutarak, hız ve daha da önemlisi bellek kullanımı için işlemleri kolayca optimize etmeyi sağlar ve ayrıca sözdizimindeki tutarlılığı korurken bazı güçlü özellikler sağlar .

1. Hız

Halihazırda verileri gösteren soruya oldukça az sayıda kıyaslama (çoğunlukla gruplama işlemlerinde olsa da) eklenmiştir. 10 milyondan gruba kadar olan gruptaki kıyaslamalar da dahil olmak üzere gruplanacak grup ve / veya satır sayısı arttıkça tablo dplyr'den daha hızlı hale gelir . 100 - 10 milyon grupta 2 milyar sıra (RAM'de 100 GB) ve aynı zamanda değişen gruplama sütunları da karşılaştırılır . Ayrıca içeren ve de güncellenen karşılaştırmalı değerlendirmelere bakın .pandasSparkpydatatable

Kıyaslamalarda, bu kalan yönleri de kapsamak harika olurdu:

  • Satırların bir alt kümesini içeren gruplama işlemleri - yani DT[x > val, sum(y), by = z]tür işlemleri.

  • Güncelleme ve birleştirme gibi diğer işlemleri karşılaştırma .

  • Ayrıca çalışma süresine ek olarak her işlem için bellek ayak izini de kıyaslayın .

2. Bellek kullanımı

  1. Dplyr filter()veya slice()dplyr ile ilgili işlemler bellek verimsiz olabilir (hem dataframe'lerde hem de data.tables'da). Bu gönderiye bakın .

    Hadley'in yorumunun hız hakkında konuştuğunu unutmayın (bu dplyr onun için çok hızlıdır), oysa buradaki ana endişe hafızadır .

  2. Şu anda data.table arabirimi, sütunları referans olarak değiştirmeye / güncellemeye izin verir (sonucu bir değişkene yeniden atamamız gerekmediğini unutmayın).

    # sub-assign by reference, updates 'y' in-place
    DT[x >= 1L, y := NA]

    Ancak dplyr asla referans ile güncellenmeyecektir. Dplyr eşdeğeri (sonucun yeniden atanması gerektiğini unutmayın):

    # copies the entire 'y' column
    ans <- DF %>% mutate(y = replace(y, which(x >= 1L), NA))

    Bunun bir kaygısı referans şeffaflığıdır . Bir data.table nesnesini, özellikle bir işlev içinde referans olarak güncellemek her zaman istenmeyebilir. Ancak bu inanılmaz derecede faydalı bir özellik: ilginç durumlar için bunu ve bu yazıları görün. Ve biz onu korumak istiyoruz.

    Bu nedenle shallow(), kullanıcıya her iki olasılığı da sağlayacak olan data.table içindeki dışa aktarma işlevi üzerinde çalışıyoruz . Örneğin, giriş verisinin değiştirilmemesi isteniyorsa. Bir işlev içindeki tablo, aşağıdakileri yapabilir:

    foo <- function(DT) {
        DT = shallow(DT)          ## shallow copy DT
        DT[, newcol := 1L]        ## does not affect the original DT 
        DT[x > 2L, newcol := 2L]  ## no need to copy (internally), as this column exists only in shallow copied DT
        DT[x > 2L, x := 3L]       ## have to copy (like base R / dplyr does always); otherwise original DT will 
                                  ## also get modified.
    }

    Kullanılmadığında shallow(), eski işlevler korunur:

    bar <- function(DT) {
        DT[, newcol := 1L]        ## old behaviour, original DT gets updated by reference
        DT[x > 2L, x := 3L]       ## old behaviour, update column x in original DT.
    }

    Bir oluşturarak sığ kopya kullanarak shallow(), orijinal nesneyi değiştirmek istemiyoruz anlıyoruz. Sütunları kopyalamanızın yanı sıra yalnızca kesinlikle gerekli olduğunda değiştirdiğinizden emin olmak için dahili olarak her şeyle ilgileniyoruz . Uygulandığında, bu , kullanıcıya her iki olasılığı da sağlarken , referans şeffaflığı sorununu tamamen çözmelidir .

    Ayrıca, bir kez shallow()dplyr'in data.table arayüzü ihraç edilir, neredeyse tüm kopyalardan kaçınmalıdır. Yani dplyr'in sözdizimini tercih edenler bunu data.tables ile kullanabilirler.

    Ancak yine de data.table tarafından sağlanan (sub) atama da dahil olmak üzere birçok özelliğe sahip olmayacaktır.

  3. Katılırken topla:

    Aşağıdaki gibi iki data.tableınız olduğunu varsayın:

    DT1 = data.table(x=c(1,1,1,1,2,2,2,2), y=c("a", "a", "b", "b"), z=1:8, key=c("x", "y"))
    #    x y z
    # 1: 1 a 1
    # 2: 1 a 2
    # 3: 1 b 3
    # 4: 1 b 4
    # 5: 2 a 5
    # 6: 2 a 6
    # 7: 2 b 7
    # 8: 2 b 8
    DT2 = data.table(x=1:2, y=c("a", "b"), mul=4:3, key=c("x", "y"))
    #    x y mul
    # 1: 1 a   4
    # 2: 2 b   3

    Ve sum(z) * mulher satır için DT2sütunlarla birleşirken almak istiyorsunuz x,y. İkisinden birini yapabiliriz:

    • 1) DT1almak için toplam sum(z), 2) birleştirme ve 3) çarpma (veya)

      # data.table way
      DT1[, .(z = sum(z)), keyby = .(x,y)][DT2][, z := z*mul][]
      
      # dplyr equivalent
      DF1 %>% group_by(x, y) %>% summarise(z = sum(z)) %>% 
          right_join(DF2) %>% mutate(z = z * mul)
    • 2) hepsini tek seferde yapın ( by = .EACHIözelliği kullanarak ):

      DT1[DT2, list(z=sum(z) * mul), by = .EACHI]

    Avantajı nedir?

    • Ara sonuç için bellek ayırmamız gerekmez.

    • İki kez gruplamak / karma yapmak zorunda değiliz (biri toplama için, diğeri katılmak için).

    • Ve daha da önemlisi, gerçekleştirmek istediğimiz işlem j(2) 'ye bakarak açıktır .

    Kontrol Bu yayını ayrıntılı bir açıklama için by = .EACHI. Hiçbir ara sonuç gerçekleşmez ve birleştirme + toplamı bir seferde gerçekleştirilir.

    Göz at Bu , bu ve bu gerçek kullanım senaryoları için mesajları.

    Gelen dplyrEğer olurdu katılmak ve bunları birleştirilmiş veya birinci agrega ve katılmak bellek açısından verimli gibidir ikisi de, (bunun da hızına çevirir).

  4. Güncelle ve katıl:

    Aşağıda gösterilen data.table kodunu göz önünde bulundurun:

    DT1[DT2, col := i.mul]

    anahtar sütununun eşleştiği satırlara ile arasındaki DT1sütunu ekler / günceller . Bu işlemin tam bir eşdeğeri olduğunu düşünmüyorum , yani, sadece yeni bir sütun eklemek için tümünün kopyalanması gereken bir işlemden kaçınmak , gereksiz.colmulDT2DT2DT1dplyr*_joinDT1

    Gerçek bir kullanım senaryosu için bu yayını kontrol edin .

Özetlemek gerekirse, her optimizasyon bitinin önemli olduğunu fark etmek önemlidir. As Grace Hopper , derdi senin nanosaniye Zihin !

3. Sözdizimi

Şimdi sözdizimine bakalım . Hadley buraya yorum yaptı :

Veri tabloları son derece hızlı ama onların kısaltma öğrenmek zorlaştırır ve bunu kullanan kod yazdıktan sonra okumak zor olduğunu düşünüyorum ...

Bu ifadeyi anlamsız buluyorum çünkü çok öznel. Belki deneyebileceğimiz, sözdizimindeki tutarlılığı karşılaştırmaktır . Data.table ve dplyr sözdizimini yan yana karşılaştıracağız.

Aşağıda gösterilen kukla verilerle çalışacağız:

DT = data.table(x=1:10, y=11:20, z=rep(1:2, each=5))
DF = as.data.frame(DT)
  1. Temel toplama / güncelleme işlemleri.

    # case (a)
    DT[, sum(y), by = z]                       ## data.table syntax
    DF %>% group_by(z) %>% summarise(sum(y)) ## dplyr syntax
    DT[, y := cumsum(y), by = z]
    ans <- DF %>% group_by(z) %>% mutate(y = cumsum(y))
    
    # case (b)
    DT[x > 2, sum(y), by = z]
    DF %>% filter(x>2) %>% group_by(z) %>% summarise(sum(y))
    DT[x > 2, y := cumsum(y), by = z]
    ans <- DF %>% group_by(z) %>% mutate(y = replace(y, which(x > 2), cumsum(y)))
    
    # case (c)
    DT[, if(any(x > 5L)) y[1L]-y[2L] else y[2L], by = z]
    DF %>% group_by(z) %>% summarise(if (any(x > 5L)) y[1L] - y[2L] else y[2L])
    DT[, if(any(x > 5L)) y[1L] - y[2L], by = z]
    DF %>% group_by(z) %>% filter(any(x > 5L)) %>% summarise(y[1L] - y[2L])
    • data.table sözdizimi kompakttır ve dplyr oldukça ayrıntılıdır. Durum (a) durumunda işler aşağı yukarı eşdeğerdir.

    • Vaka (b), biz kullanmak zorunda filter()iken dplyr içinde özetleme . Ancak güncelleme yaparken , mantığı içeriye taşımak zorunda kaldık mutate(). Ancak data.table'da, her iki işlemi de aynı mantıkla ifade ediyoruz - x > 2ancak ilk durumda elde edilen satırlarda çalışır sum(y), oysa ikinci durumda ykümülatif toplamıyla bu satırları güncelleyin .

      DT[i, j, by]Formun tutarlı olduğunu söylediğimizde bunu kastediyoruz .

    • Benzer şekilde (c) if-elsedurumunda, koşulumuz olduğunda , hem data.table hem de dplyr'de "olduğu gibi" mantığını ifade edebiliriz. Ancak, yalnızca ifkoşulun yerine summarise()getirildiği ve başka bir şekilde atlandığı satırları döndürmek istiyorsak , doğrudan (AFAICT) kullanamayız . Biz zorundayız filter()birinci ve çünkü o özetlemek summarise()her zaman beklediği tek değer .

      Aynı sonucu döndürürken, filter()burada kullanmak gerçek işlemi daha az belirgin hale getirir.

      filter()İlk durumda da kullanmak çok iyi olabilir (benim için açık görünmüyor), ama benim açımdan olmamamız gerekiyor.

  2. Birden çok sütunda toplama / güncelleme

    # case (a)
    DT[, lapply(.SD, sum), by = z]                     ## data.table syntax
    DF %>% group_by(z) %>% summarise_each(funs(sum)) ## dplyr syntax
    DT[, (cols) := lapply(.SD, sum), by = z]
    ans <- DF %>% group_by(z) %>% mutate_each(funs(sum))
    
    # case (b)
    DT[, c(lapply(.SD, sum), lapply(.SD, mean)), by = z]
    DF %>% group_by(z) %>% summarise_each(funs(sum, mean))
    
    # case (c)
    DT[, c(.N, lapply(.SD, sum)), by = z]     
    DF %>% group_by(z) %>% summarise_each(funs(n(), mean))
    • (A) durumunda kodlar aşağı yukarı eşdeğerdir. data.table, tanıdık temel işlevini kullanırken lapply(), bir grup işlevle birlikte dplyrtanıtır .*_each()funs()

    • data.table's :=sütun adlarının verilmesini gerektirirken, dplyr bunu otomatik olarak oluşturur.

    • (B) durumunda, dplyr'in sözdizimi nispeten basittir. Birden çok işlevde toplamaların / güncellemelerin iyileştirilmesi data.table'ın listesindedir.

    • (C) durumunda, dplyr n()yalnızca bir kez değil, birçok sütun döndürür . Data.table içinde tek yapmamız gereken bir liste döndürmektir j. Listenin her bir elemanı sonuçta bir sütun haline gelecektir. Bu nedenle, a döndüren c()birine birleştirmek .Niçin bir kez daha tanıdık temel işlevi kullanabiliriz .listlist

    Not: Bir kez daha data.table'da tek yapmamız gereken bir liste döndürmektir j. Listenin her bir elemanı sonuç olarak bir sütun haline gelecektir. Sen kullanabilirsiniz c(), as.list(), lapply(), list()herhangi bir yeni fonksiyonlar öğrenmek zorunda kalmadan vb ... baz fonksiyonları, bunu gerçekleştirmek için.

    Sadece özel değişkenler öğrenmek gerekir - .Nve .SDen azından. Dplyr içindeki eşdeğeri n()ve.

  3. Katıldı

    dplyr, data.table öğesinin aynı sözdizimini DT[i, j, by](ve nedeni ile) kullanarak birleştirmeye izin verdiği her bir birleştirme türü için ayrı işlevler sağlar . Ayrıca merge.data.table()alternatif olarak eşdeğer bir işlev de sağlar .

    setkey(DT1, x, y)
    
    # 1. normal join
    DT1[DT2]            ## data.table syntax
    left_join(DT2, DT1) ## dplyr syntax
    
    # 2. select columns while join    
    DT1[DT2, .(z, i.mul)]
    left_join(select(DT2, x, y, mul), select(DT1, x, y, z))
    
    # 3. aggregate while join
    DT1[DT2, .(sum(z) * i.mul), by = .EACHI]
    DF1 %>% group_by(x, y) %>% summarise(z = sum(z)) %>% 
        inner_join(DF2) %>% mutate(z = z*mul) %>% select(-mul)
    
    # 4. update while join
    DT1[DT2, z := cumsum(z) * i.mul, by = .EACHI]
    ??
    
    # 5. rolling join
    DT1[DT2, roll = -Inf]
    ??
    
    # 6. other arguments to control output
    DT1[DT2, mult = "first"]
    ??
    • Bazıları her birleşme için ayrı bir işlev bulabilir (sol, sağ, iç, anti, yarı vb.), Diğerleri ise data.table'ın beğenebileceği DT[i, j, by]veya merge()R tabanına benzer.

    • Ancak dplyr birleşimleri bunu yapar. Başka bir şey yok. Daha az değil.

    • data.tables birleştirilirken sütunları seçebilir (2) ve dplyr'de select()yukarıda gösterildiği gibi katılmadan önce her iki data.frames üzerinde de ilk önce yapmanız gerekir . Aksi takdirde, birleştirmeyi yalnızca daha sonra kaldırmak için gereksiz sütunlarla ilişkilendirirsiniz ve bu da verimsizdir.

    • data.tables, birleştirme sırasında birleştirebilir (3) ve ayrıca birleştirme (4) sırasındaby = .EACHI özelliğini kullanarak güncelleyebilir . Neden sadece birkaç sütun eklemek / güncellemek için birleştirme sonucunun tamamını malzeme haline getirelim?

    • data.table, eklemleri (5) yuvarlama yeteneğine sahiptir - ileri sarma, LOCF , geri sarma , NOCB , en yakın .

    • data.table ayrıca ilk , son veya tüm maçları mult =seçen argümana sahiptir (6).

    • data.table, allow.cartesian = TRUEyanlışlıkla geçersiz birleştirmelerden korunmak için argümana sahiptir .

Bir kez daha sözdizimi, DT[i, j, by]çıktıyı daha fazla kontrol etmeyi sağlayan ek argümanlarla tutarlıdır .

  1. do()...

    dplyr'in özeti, tek bir değer döndüren işlevler için özel olarak tasarlanmıştır. İşleviniz birden çok / eşit olmayan değer döndürürse başvurmanız gerekir do(). Tüm fonksiyonlarınızın dönüş değeri hakkında önceden bilgi sahibi olmanız gerekir.

    DT[, list(x[1], y[1]), by = z]                 ## data.table syntax
    DF %>% group_by(z) %>% summarise(x[1], y[1]) ## dplyr syntax
    DT[, list(x[1:2], y[1]), by = z]
    DF %>% group_by(z) %>% do(data.frame(.$x[1:2], .$y[1]))
    
    DT[, quantile(x, 0.25), by = z]
    DF %>% group_by(z) %>% summarise(quantile(x, 0.25))
    DT[, quantile(x, c(0.25, 0.75)), by = z]
    DF %>% group_by(z) %>% do(data.frame(quantile(.$x, c(0.25, 0.75))))
    
    DT[, as.list(summary(x)), by = z]
    DF %>% group_by(z) %>% do(data.frame(as.list(summary(.$x))))
    • .SD'nin eşdeğeri .

    • Data.table içinde, hemen hemen her şeyi atabilirsiniz j- hatırlanması gereken tek şey, listenin her öğesinin bir sütuna dönüştürülmesi için bir liste döndürmesidir.

    • Dplyr'de bunu yapamam. do()İşlevinizin her zaman tek bir değer döndürüp döndürmeyeceğine bağlı olduğunuza bağlı olmak zorundasınız. Ve oldukça yavaş.

Bir kez daha data.table'ın sözdizimi ile tutarlıdır DT[i, j, by]. jBu şeyler hakkında endişelenmenize gerek kalmadan ifadeler vermeye devam edebiliriz .

Bu SO sorusuna ve buna bir göz atın . Cevabı dplyr'ın sözdizimini kullanarak açık bir şekilde ifade etmenin mümkün olup olmadığını merak ediyorum ...

Özetlemek gerekirse, özellikle dplyr'in sözdiziminin ya verimsiz, sınırlı ya da işlemleri basitleştiremediği birkaç örneği vurguladım . Bunun nedeni özellikle data.table'ın "okunması / öğrenilmesi daha zor" sözdizimi (yukarıda yapıştırılan / bağlanan gibi) hakkında biraz geri tepme almasıdır. Dplyr'ı kapsayan çoğu yazı, en basit operasyonlar hakkında konuşur. Ve bu harika. Ancak sözdizimini ve özellik sınırlamalarını da gerçekleştirmek önemlidir ve henüz üzerinde bir yazı göremiyorum.

data.table'ın da tuhaflıkları var (bazıları düzeltmeye çalıştığımıza işaret ettim). Burada vurguladığım gibi data.table'ın birleşimlerini de geliştirmeye çalışıyoruz .

Ancak, dplyr'ın data.table'a kıyasla eksik olan özelliklerin sayısını da düşünmeliyiz.

4. Özellikler

Buradaki ve bu yazıdaki özelliklerin çoğuna dikkat çektim . Ek olarak:

  • fread - hızlı dosya okuyucu uzun zamandır mevcut.

  • fwrite - paralel bir hızlı dosya yazarı artık kullanılabilir. Uygulamaya ilişkin ayrıntılı bir açıklama ve daha fazla gelişmeyi takip etmek için # 1664 bu yazıya bakın .

  • Otomatik indeksleme - temel R sözdizimini dahili olarak olduğu gibi optimize etmek için başka bir kullanışlı özellik.

  • Geçici gruplama : dplyrsonuçları değişkenleri gruplayarak otomatik olarak sıralar summarise(), bu her zaman istenmeyebilir.

  • Yukarıda belirtilen data.table birleşimlerinde çok sayıda avantaj (hız / bellek verimliliği ve sözdizimi için).

  • <=, <, >, >=Eşit olmayan birleşimler: data.table birleşimlerinin diğer tüm avantajlarıyla birlikte diğer işleçleri kullanarak birleşmelere izin verir .

  • Son zamanlarda örtüşen aralık birleşimleri data.table içinde uygulandı. Kıyaslamaları olan bir genel bakış için bu yayını kontrol edin .

  • setorder() data.table içindeki data.table dosyalarının gerçekten hızlı bir şekilde yeniden düzenlenmesini sağlar.

  • dplyr, data.table'ın şu anda yapmadığı aynı sözdizimini kullanarak veritabanlarına arabirim sağlar .

  • data.tabledaha hızlı eşdeğerleri sağlanmaktadır seti operasyonları - (Jan Gorecki tarafından yazılmış) fsetdiff, fintersect, funionve fsetequalek ile allargüman (SQL gibi).

  • data.table, maskeleme uyarısı olmadan temiz yükler ve açıklanan bir mekanizmaya sahiptir [.data.frame herhangi bir R paketine aktarıldığında uyumluluk için burada . dplyr baz fonksiyonları değiştiren filter, lagve [sorunlara yol açabilir; örneğin burada ve burada .


En sonunda:

  • Veritabanlarında - data.table'ın benzer arayüz sağlayamamasının bir nedeni yoktur, ancak bu artık bir öncelik değildir. Kullanıcılar bu özelliği çok isterlerse çarpılabilir .. emin değilim.

  • Paralellik üzerine - Birisi devam edene ve bunu yapana kadar her şey zordur. Tabii ki çaba sarf edecektir (iplik güvenli olmak).

    • Şu anda (v1.9.7 devel'de), artan performans kazançları için bilinen zaman alıcı parçaların paralel hale getirilmesi yönünde ilerleme kaydedilmektedir. OpenMP .

9
@bluefeet: Bu tartışmayı sohbete taşıyarak geri kalanımızın büyük bir hizmet yaptığınızı sanmıyorum. Arun'un geliştiricilerden biri olduğu izlenimindeydim ve bu da yararlı içgörülere yol açabilirdi.
IRTFM

2
Bağlantınızı kullanarak sohbete gittiğimde, "Bir filtre kullanmalısınız" ifadesini izleyen yorumu izleyen tüm materyaller gitmiş gibi görünüyordu. SO-sohbet mekanizması hakkında bir şey eksik mi?
IRTFM

6
:=dplyr<-DF <- DF %>% mutate...DF %>% mutate...
Başvuruyu

4
Sözdizimi ile ilgili. Sözdizimi dplyrkullanan kullanıcılar için daha kolay olabileceğine inanıyorum plyr, ancak söz dizimi sözdizimini data.tablesorgulayan kullanıcılar SQLve arkasındaki ilişkisel cebir, daha çok tablosal veri dönüşümü ile ilgili olabilir. @Ayarlanan işleçlerin sarma data.tableişleviyle yapmanın çok kolay olduğunu ve elbette önemli bir hızlanma getirdiğini unutmamalısınız.
jangorecki

9
Bu yazıyı birçok kez okudum ve data.table'ı anlamada ve daha iyi kullanabilmemde bana çok yardımcı oldu. Çoğu durumda, verileri tercih ederim. Dplyr veya pandalar veya PL / pgSQL üzerinden tablo. Ancak, bunu nasıl ifade edeceğimi düşünmeyi bırakamadım. Sözdizimi değil kolay, berrak veya ayrıntılı. Aslında, data.table'ı çok kullandıktan sonra bile, hala kendi kodumu anlamak için mücadele ediyorum, tam anlamıyla bir hafta önce yazdım. Bu salt yazılan bir dilin yaşam örneğidir. en.wikipedia.org/wiki/Write-only_language Umarım, bir gün data.table üzerinde dplyr kullanabilirsiniz.
Ufos

385

İşte Arun'un cevabının geniş ana hatlarını takip ederek (ancak farklı önceliklere dayanarak yeniden düzenlenmiş) dplyr perspektifinden kapsamlı bir cevap verme girişimim.

Sözdizimi

Sözdizimine bazı öznellik var, ama benim ifademle, data.table'ın kısaltılmasının öğrenmeyi ve okumayı zorlaştırdığını düşünüyorum. Bunun nedeni, dplyr'ın çok daha kolay bir sorunu çözmesidir!

Dplyr'in sizin için yaptığı gerçekten önemli bir şey , seçeneklerinizi kısıtlamasıdır . Çoğu tek tablo probleminin sadece beş anahtar fiil filtresi, seçme, değiştirme, düzenleme ve özetleme ile birlikte bir "gruba göre" zarf ile çözülebileceğini iddia ediyorum. Bu kısıtlama, veri manipülasyonunu öğrenirken büyük bir yardımcıdır, çünkü sorun hakkındaki düşüncelerinizi sıralamanıza yardımcı olur. Dplyr'de, bu fiillerin her biri tek bir işleve eşlenir. Her fonksiyon bir iş yapar ve izole edilmesi kolaydır.

Bu basit işlemleri bir araya getirerek karmaşıklık yaratırsınız %>%. Arun'un bağlantı verdiği yayınlardan birinden örnek :

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

Daha önce hiç dplyr görmemiş olsanız bile (hatta R!), Olanların özünü hala alabilirsiniz, çünkü işlevlerin hepsi İngilizce fiillerdir. İngilizce fiillerin dezavantajı, daha fazla yazım gerektirmeleri [ , ancak bunun daha iyi otomatik tamamlama ile büyük ölçüde azaltılabileceğini düşünüyorum.

Eşdeğer data.table kodu şöyledir:

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

Data.table'a aşina değilseniz bu kodu takip etmek daha zordur. (Tekrarlananı [ gözüme iyi görünen bir şekilde girintilemeyi de anlayamadım ). Şahsen, 6 ay önce yazdığım koda baktığımda, bir yabancı tarafından yazılan bir koda bakmak gibi, bu yüzden, eğer ayrıntılı, kodu tercih ettim.

Bence diğer iki küçük faktör okunabilirliği biraz düşürüyor:

  • Hemen hemen her veri tablosu işlemi kullandığından [, neler olduğunu anlamak için ek içeriğe ihtiyacınız vardır. Örneğin,x[y] iki veri tablosuna katılmak mı yoksa bir veri çerçevesinden sütunlar mı çıkarmak istiyorsunuz? Bu sadece küçük bir konudur, çünkü iyi yazılmış bir kodda değişken adları neler olduğunu göstermelidir.

  • Ben group_by()dplyr ayrı bir işlem gibi. Temelde hesaplama değiştirir, bu yüzden kodu gözden geçirirken açık olmalı group_by()ve byargümandan daha kolay olduğunu düşünüyorum [.data.table.

Ayrıca borunun sadece bir paketle sınırlı olmadığını da seviyorum . Verilerinizi tidyr ile toplayarak başlayabilir ve ggvis'te bir çizim ile bitirebilirsiniz . Ve yazdığım paketlerle sınırlı değilsiniz - herkes veri işleme borusunun kesintisiz bir parçasını oluşturan bir işlev yazabilir. Aslında, daha önce ile yeniden yazılmış önceki data.table kodunu tercih ederim %>%:

diamonds %>% 
  data.table() %>% 
  .[cut != "Fair", 
    .(AvgPrice = mean(price),
      MedianPrice = as.numeric(median(price)),
      Count = .N
    ), 
    by = cut
  ] %>% 
  .[order(-Count)]

Ve borulama fikri %>%sadece veri çerçeveleri ile sınırlı değildir ve diğer bağlamlarla kolayca genelleştirilebilir: etkileşimli web grafikleri , web kazıma , gist'ler , çalışma zamanı sözleşmeleri , ...)

Bellek ve performans

Bunları bir araya getirdim, çünkü benim için o kadar önemli değiller. Çoğu R kullanıcısı 1 milyondan az veri satırı ile çalışır ve dplyr, işlem süresinin farkında olmadığınız veri boyutu için yeterince hızlıdır. Biz dplyr'i orta verideki ifade gücü için optimize ediyoruz; verileri kullanmaktan çekinmeyin. daha büyük verilerde ham hız için tablo.

Dplyr'ın esnekliği aynı zamanda aynı sözdizimini kullanarak performans özelliklerini kolayca değiştirebileceğiniz anlamına gelir. Dplyr'ın veri çerçevesi arka ucu ile performansı sizin için yeterince iyi değilse, data.table arka ucunu kullanabilirsiniz (biraz sınırlı işlevsellik olsa da). Çalıştığınız veriler belleğe sığmıyorsa, bir veritabanı arka ucu kullanabilirsiniz.

Tüm bunlar, dplyr performansının uzun vadede daha iyi hale geleceğini söyledi. Kesin veri fikirlerinden bazılarını kesinlikle uygulayacağız. Sayı tabanı sıralaması ve birleştirmeler ve filtreler için aynı dizini kullanma gibi tablo. Aynı zamanda çoklu çekirdeklerden yararlanabilmemiz için paralelleştirme üzerinde çalışıyoruz.

Özellikleri

2015 yılında üzerinde çalışmayı planladığımız birkaç şey:

  • readrpaket, benzer belleğe disk kapalı ve dosyaları, almak kolay yapmak fread().

  • Eşit olmayan birleşimler için destek dahil daha esnek birleşimler.

  • Önyükleme örnekleri, toplamalar ve daha fazlası gibi daha esnek gruplama

Ayrıca R'nin veritabanı konektörlerini , web apis ile konuşma yeteneğini geliştirmek ve html sayfalarını kazımayı kolaylaştırmak için zaman harcıyorum .


27
Sadece bir yan not, argümanlarınızın çoğuna katılıyorum ( data.tablesözdizimini kendim tercih etsem de), ancak stili sevmiyorsanız işlemleri %>%borulamak için kolayca kullanabilirsiniz . özel değil , ayrı bir paketten geliyor (ki aynı zamanda ortak yazar da olursunuz), bu yüzden sözdizimi paragrafınızın çoğunda ne söylemeye çalıştığınızı anladığımdan emin değilim . data.table[%>%dplyr
David Arenburg

11
@DavidArenburg iyi bir nokta. Umarım ana noktalarımın ne olduğunu daha açık hale getirmek ve %>%data.table ile kullanabileceğinizi vurgulamak için sözdizimini yeniden yazdım
hadley

5
Teşekkürler Hadley, bu yararlı bir perspektif. Yeniden girintili ben aslında DT[\n\texpression\n][\texpression\n]( gist ) aslında oldukça iyi çalışır. Arun'un cevabını, sözdiziminin erişilebilirliği hakkında çok fazla olmayan belirli sorularıma daha doğrudan cevap verdiği için cevap olarak saklıyorum, ancak bu, dplyrve arasındaki farklar / ortaklıklar hakkında genel bir fikir edinmeye çalışan insanlar için iyi bir cevap olduğunu düşünüyorum. data.table.
BrodieG

33
Zaten varken neden fastread üzerinde çalışıyorsunuz fread()? Fread'i () geliştirmek veya diğer (az gelişmiş) şeyler üzerinde çalışmak için daha iyi zaman harcamaz mıydınız?
EDi

10
API data.table, []gösterimin büyük bir şekilde kötüye kullanılması üzerine kurulmuştur . Bu onun en büyük gücü ve en büyük zayıflığıdır.
Paul

65

Soru Başlığına doğrudan yanıt olarak ...

dplyr kesinlikledata.table yapamayacağınız şeyleri yapar .

3. noktanız

dplyr potansiyel DB etkileşimlerini özetler (ya da alacaktır)

kendi sorunuza doğrudan bir cevaptır, ancak yeterince yüksek bir seviyeye yükseltilmez. dplyrgerçekten birden fazla veri depolama mekanizması için genişletilebilir bir ön uçtur.data.table .

dplyrTüm hedefleri aynı dilbilgisini kullanan, hedefleri ve işleyicileri istediğiniz zaman genişletebileceğiniz bir arka uç agnostik arayüzü olarak bakın . data.table,dplyr perspektifi, bu hedeflerden birine.

data.tableDiskte veya ağa bağlı veri depolarıyla çalışan SQL ifadeleri oluşturmak için sorgularınızı çevirmeye çalışan bir gün (umarım) göremezsiniz .

dplyr muhtemelen bir şeyler yapabilir data.table yapmaz ya da yapmaz.

Bellek içi çalışma tasarımına dayanarak, data.tablesorguların paralel işlenmesine kadar çok daha zor bir zaman geçirebilirdi dplyr.


Beden içi sorulara yanıt olarak ...

kullanım

Paketlere aşina olan insanlar için bir veya diğer paketle kodlanması çok daha kolay olan analitik görevler var mı (yani her birinin daha azının iyi bir şey olduğu gerekli ezoterizm seviyesine karşı gerekli tuş vuruşlarının bir kombinasyonu).

Bu bir punt gibi görünebilir ama gerçek cevap hayır. Tanıdık insanlar olan ya kendilerine en tanıdık olanı ya da eldeki iş için gerçekten doğru olanı kullanıyor gibi görünüyor. Bununla birlikte, bazen belirli bir okunabilirlik, bazen bir performans düzeyi sunmak istersiniz ve her ikisinin de yeterince yüksek bir seviyesine ihtiyacınız olduğunda, daha net soyutlamalar yapmak için zaten sahip olduğunuz şeyle birlikte gitmek için başka bir araca ihtiyacınız olabilir. .

Verim

Bir pakette diğerine karşı önemli ölçüde (yani 2 kattan fazla) daha etkin bir şekilde gerçekleştirilen analitik görevler var mı?

Yine hayır. data.tableher şeyde etkili olmayı üstünlük o nereye çıkıyor dplyryatan veri deposu ve tescil işleyicileri bazı bakımlardan sınırlı olmak yükünü alır.

İle bu araçlar bir performans sorunla karşılaştığınız data.tableoldukça emin sorgu işlevi olduğunu ve eğer olabilir olduğunu aslında bir darboğaz data.tablesonra kendinize bir bildirimde bulunmadan sevincini kazandınız. Bu, arka uç olarak dplyrkullanıldığında da geçerlidir data.table; Eğer açabilir bkz bazı dan yüküdplyr ancak oran sizin sorgu vardır.

dplyrArka uçlarla ilgili performans sorunları olduğunda , karma değerlendirme için bir işlev kaydederek veya (veritabanlarında) yürütme işleminden önce oluşturulan sorguyu değiştirerek bunları çözebilirsiniz.

Ayrıca , plyr veriden ne zaman daha iyidir?


3
Dblr tbl_dt ile data.table ekleyemez mi? Neden sadece her iki dünyanın en iyisini elde etmiyorsunuz?
aaa90210

22
"Data.table kesinlikle dplyr yapamaz şeyler yapar" ters ifadeden bahsetmeyi unutma , bu da doğrudur.
jangorecki

25
Arun cevap iyi açıklıyor. En önemli (performans açısından) fread, referans ile güncelleme, yuvarlanma birleşimleri, çakışan birleşimler olacaktır. Bu özelliklerle rekabet edebilecek herhangi bir paket (sadece dplyr değil) olduğuna inanıyorum. Güzel bir örnek, bu sunumdaki son slayt olabilir .
jangorecki

15
Tamamen, data.table hala R kullanıyorum. Aksi takdirde panda kullanırdım. Pandalardan daha iyi / daha hızlı.
marbel

8
SQL sözdizimi yapısına basitliği ve benzerliği nedeniyle data.table'ı seviyorum. İşim, istatistiksel modelleme için her gün çok yoğun geçici veri analizi ve grafikler yapmayı içeriyor ve gerçekten karmaşık şeyler yapmak için yeterince basit bir araca ihtiyacım var. Artık araç setimi günlük işimde veri için veri ve grafik için kafes olarak azaltabilirim. Bir örnek vermek ben bile böyle işlemler yapabilirsiniz: $ DT [grup == 1, y_hat: = tahmin (fit1, veri = .SD),] $, gerçekten temiz ve ben SQL büyük bir avantaj olarak kabul klasik R ortamı.
xappppp
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.