data.table vs dplyr: dapatkah seseorang melakukan sesuatu dengan baik sedangkan yang lain tidak atau buruk?

760

Gambaran

Saya relatif akrab dengan data.table, tidak begitu banyak dengan dplyr. Saya telah membaca beberapa dplyrsketsa dan contoh yang muncul di SO, dan sejauh ini kesimpulan saya adalah:

  1. data.tabledan dplyrsebanding dalam kecepatan, kecuali ketika ada banyak (yaitu> 10-100K) kelompok, dan dalam beberapa keadaan lain (lihat tolok ukur di bawah)
  2. dplyr memiliki sintaks yang lebih mudah diakses
  3. dplyr abstrak (atau akan) potensi interaksi DB
  4. Ada beberapa perbedaan fungsionalitas kecil (lihat "Contoh / Penggunaan" di bawah)

Dalam pikiran saya 2. tidak menanggung banyak beban karena saya cukup akrab dengannya data.table, meskipun saya mengerti bahwa bagi pengguna baru keduanya akan menjadi faktor besar. Saya ingin menghindari argumen tentang mana yang lebih intuitif, karena itu tidak relevan untuk pertanyaan spesifik saya yang diajukan dari sudut pandang seseorang yang sudah terbiasa data.table. Saya juga ingin menghindari diskusi tentang bagaimana "lebih intuitif" mengarah pada analisis yang lebih cepat (tentu saja benar, tetapi sekali lagi, bukan yang paling saya minati di sini).

Pertanyaan

Yang ingin saya ketahui adalah:

  1. Apakah ada tugas analitis yang jauh lebih mudah untuk dikodekan dengan satu atau paket lain untuk orang yang akrab dengan paket (yaitu beberapa kombinasi penekanan tombol yang diperlukan vs tingkat esoterisme yang diperlukan, di mana kurang dari masing-masing adalah hal yang baik).
  2. Apakah ada tugas analitik yang dilakukan secara substansial (yaitu lebih dari 2x) lebih efisien dalam satu paket vs yang lain.

Satu pertanyaan SO baru-baru ini membuat saya berpikir tentang ini sedikit lebih, karena sampai saat itu saya tidak berpikir dplyrakan menawarkan banyak hal di luar apa yang sudah bisa saya lakukan data.table. Ini dplyrsolusinya (data di akhir Q):

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

Yang jauh lebih baik daripada upaya hack saya pada data.tablesolusi. Yang mengatakan, data.tablesolusi yang baik juga cukup bagus (terima kasih Jean-Robert, Arun, dan perhatikan di sini saya lebih suka pernyataan tunggal daripada solusi yang paling optimal):

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

Sintaksis untuk yang terakhir mungkin tampak sangat esoteris, tetapi sebenarnya cukup mudah jika Anda terbiasa data.table(yaitu tidak menggunakan beberapa trik yang lebih esoteris).

Idealnya yang ingin saya lihat adalah beberapa contoh yang baik jika dplyratau data.tablecaranya jauh lebih ringkas atau berkinerja lebih baik.

Contohnya

Pemakaian
  • dplyrtidak mengizinkan operasi yang dikelompokkan yang mengembalikan jumlah baris sewenang-wenang (dari pertanyaan eddi , perhatikan: sepertinya ini akan diimplementasikan dalam dplyr 0.5 , juga, @beginneR menunjukkan potensi penyelesaian dengan menggunakan dojawaban untuk pertanyaan @ eddi).
  • data.tablemendukung rolling bergabung (terima kasih @dholstius) serta tumpang tindih bergabung
  • data.tablesecara internal mengoptimalkan ekspresi bentuk DT[col == value]atau DT[col %in% values]untuk kecepatan melalui pengindeksan otomatis yang menggunakan pencarian biner saat menggunakan sintaks R dasar yang sama. Lihat di sini untuk detail lebih lanjut dan tolok ukur kecil.
  • dplyrmenawarkan versi evaluasi fungsi standar (misalnya regroup, summarize_each_) yang dapat menyederhanakan penggunaan programatik dplyr(perhatikan penggunaan programatik data.tablepasti dimungkinkan, hanya memerlukan beberapa pemikiran, penggantian / penawaran, dll, setidaknya setahu saya)
Tolak ukur

Data

Ini adalah contoh pertama yang saya tunjukkan di bagian pertanyaan.

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))
BrodieG
sumber
9
Solusi yang mirip dalam membaca dengan yang dplyradalah:as.data.table(dat)[, .SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)], by = list(name, job)]
eddi
7
Untuk # 1 baik dplyrdan data.tabletim bekerja pada tolok ukur, jadi jawabannya akan ada di beberapa titik. # 2 (sintaksis) imO benar-benar salah, tetapi jelas menjelajah ke wilayah opini, jadi saya memilih untuk menutup juga.
eddi
13
baik, sekali lagi imO, serangkaian masalah yang lebih bersih diekspresikan dalam (d)plyrukuran 0
eddi
28
@BrodieG satu hal yang benar-benar mengganggu saya tentang keduanya dplyrdan plyrberkaitan dengan sintaks dan pada dasarnya adalah alasan utama mengapa saya tidak suka sintaks mereka, adalah bahwa saya harus belajar terlalu banyak (baca lebih dari 1) fungsi tambahan (dengan nama yang masih tidak masuk akal bagi saya), ingat apa yang mereka lakukan, argumen apa yang mereka ambil, dll. Itu selalu merupakan perubahan besar bagi saya dari plyr-filsafat.
eddi
43
@eddi [tongue-in-pipi] satu hal yang benar-benar mengganggu saya tentang sintaks data.tabel adalah bahwa saya harus belajar bagaimana terlalu banyak argumen fungsi berinteraksi, dan apa artinya cara pintas samar (mis .SD.). [Serius] Saya pikir ini adalah perbedaan desain yang sah yang akan menarik bagi orang yang berbeda
Hadley

Jawaban:

532

Kita perlu penutup setidaknya aspek ini untuk memberikan yang komprehensif jawab / perbandingan (tanpa urutan tertentu penting): Speed, Memory usage, Syntaxdan Features.

Maksud saya adalah untuk membahas masing-masing sejelas mungkin dari perspektif data.table.

Catatan: kecuali disebutkan sebaliknya, dengan merujuk ke dplyr, kami merujuk ke antarmuka data.frame dplyr yang internalnya ada di C ++ menggunakan Rcpp.


Sintaks data.tabel konsisten dalam bentuknya - DT[i, j, by]. Untuk menjaga i, jdan bybersama adalah dengan desain. Dengan menjaga operasi terkait bersama-sama, ini memungkinkan untuk dengan mudah mengoptimalkan operasi untuk kecepatan dan penggunaan memori yang lebih penting , dan juga menyediakan beberapa fitur yang kuat , semuanya tetap menjaga konsistensi dalam sintaksis.

1. Kecepatan

Cukup banyak tolok ukur (meskipun sebagian besar pada operasi pengelompokan) telah ditambahkan ke pertanyaan yang sudah menunjukkan data. Tabel menjadi lebih cepat daripada dplyr karena jumlah grup dan / atau baris ke grup meningkat, termasuk tolok ukur oleh Matt pada pengelompokan dari 10 juta menjadi 2 miliar baris (100GB dalam RAM) pada 100 - 10 juta grup dan berbagai kolom pengelompokan, yang juga membandingkan pandas. Lihat juga tolok ukur yang diperbarui , yang mencakup Sparkdan pydatatablejuga.

Pada tolok ukur, akan lebih baik untuk mencakup aspek-aspek yang tersisa ini juga:

  • Pengelompokan operasi yang melibatkan subset baris - yaitu, DT[x > val, sum(y), by = z]ketik operasi.

  • Benchmark operasi lain seperti pembaruan dan bergabung .

  • Juga mengukur jejak memori untuk setiap operasi selain runtime.

2. Penggunaan memori

  1. Operasi yang melibatkan filter()atau slice()dalam dplyr dapat menjadi tidak efisien memori (pada data.frame dan data.tables). Lihat posting ini .

    Perhatikan bahwa komentar Hadley berbicara tentang kecepatan (bahwa dplyr sangat cepat baginya), sedangkan perhatian utama di sini adalah ingatan .

  2. antarmuka data.table saat ini memungkinkan seseorang untuk memodifikasi / memperbarui kolom dengan referensi (perhatikan bahwa kita tidak perlu menetapkan kembali hasilnya kembali ke variabel).

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

    Tapi dplyr tidak akan pernah memperbarui dengan referensi. Setara dplyr akan menjadi (perhatikan bahwa hasilnya perlu ditugaskan ulang):

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

    Kekhawatiran untuk ini adalah transparansi referensial . Memperbarui objek data.table dengan referensi, terutama dalam suatu fungsi mungkin tidak selalu diinginkan. Tetapi ini adalah fitur yang sangat berguna: lihat ini dan posting ini untuk kasus yang menarik. Dan kami ingin menyimpannya.

    Oleh karena itu kami sedang berupaya menuju shallow()fungsi ekspor di data.table yang akan menyediakan pengguna dengan kedua kemungkinan . Misalnya, jika diinginkan untuk tidak mengubah data input. Tabel dalam suatu fungsi, maka seseorang dapat melakukan:

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

    Dengan tidak menggunakan shallow() , fungsi lama dipertahankan:

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

    Dengan membuat salinan dangkal menggunakan shallow(), kami memahami bahwa Anda tidak ingin memodifikasi objek asli. Kami mengurus semuanya secara internal untuk memastikan bahwa sambil memastikan untuk menyalin kolom yang Anda modifikasi hanya ketika benar-benar diperlukan . Ketika diimplementasikan, ini harus menyelesaikan masalah transparansi referensial sekaligus memberikan pengguna dengan kedua kemungkinan.

    Juga sekali shallow() diekspor antarmuka data.tabel dplyr harus menghindari hampir semua salinan. Jadi mereka yang lebih memilih sintaks dplyr dapat menggunakannya dengan data.tables.

    Tetapi masih akan kekurangan banyak fitur yang disediakan data.table, termasuk (sub) -assignment dengan referensi.

  3. Agregat saat bergabung:

    Misalkan Anda memiliki dua data.tabel sebagai berikut:

    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

    Dan Anda ingin masuk sum(z) * muluntuk setiap baris DT2saat bergabung dengan kolomx,y . Kita dapat:

    • 1) agregat DT1untuk mendapatkan sum(z), 2) melakukan bergabung dan 3) berkembang biak (atau)

      # 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) lakukan semuanya sekaligus (menggunakan by = .EACHIfitur):

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

    Apa untungnya?

    • Kami tidak harus mengalokasikan memori untuk hasil antara.

    • Kami tidak harus mengelompokkan / hash dua kali (satu untuk agregasi dan lainnya untuk bergabung).

    • Dan yang lebih penting, operasi yang ingin kita lakukan jelas dengan melihat pada j(2).

    Periksa posting ini untuk penjelasan rinci tentang by = .EACHI. Tidak ada hasil antara yang terwujud, dan gabungan + agregat dilakukan semuanya dalam sekali jalan.

    Lihatlah skenario ini , ini dan ini untuk skenario penggunaan nyata.

    Di dalam dplyrAnda harus bergabung dan agregat atau agregat pertama dan kemudian bergabung , yang keduanya tidak seefisien, dalam hal memori (yang pada gilirannya berarti kecepatan).

  4. Perbarui dan bergabung:

    Pertimbangkan kode data.table yang ditunjukkan di bawah ini:

    DT1[DT2, col := i.mul]

    menambahkan / memperbarui DT1kolom coldengan muldari DT2pada baris di mana DT2kolom kunci cocok DT1. Saya tidak berpikir ada yang setara persis dengan operasi ini dplyr, yaitu, tanpa menghindari *_joinoperasi, yang harus menyalin keseluruhannyaDT1 hanya untuk menambahkan kolom baru ke dalamnya, yang tidak perlu.

    Periksa posting ini untuk skenario penggunaan nyata.

Untuk meringkas, penting untuk menyadari bahwa setiap sedikit optimasi penting. Seperti yang dikatakan Grace Hopper , Pikirkan nanodetik Anda !

3. Sintaks

Sekarang mari kita lihat sintaks . Hadley berkomentar di sini :

Tabel data sangat cepat tetapi saya pikir keputusan mereka membuatnya lebih sulit untuk dipelajari dan kode yang menggunakannya lebih sulit untuk dibaca setelah Anda menulisnya ...

Saya menganggap pernyataan ini tidak berguna karena sangat subyektif. Yang mungkin bisa kita coba adalah kontras konsistensi dalam sintaksis . Kami akan membandingkan data.tabel dan sintaksis dplyr secara berdampingan.

Kami akan bekerja dengan data boneka yang ditunjukkan di bawah ini:

DT = data.table(x=1:10, y=11:20, z=rep(1:2, each=5))
DF = as.data.frame(DT)
  1. Operasi pengumpulan / pembaruan dasar.

    # 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])
    • sintaks data.tabel kompak dan dplyr cukup bertele-tele. Hal-hal kurang lebih setara dalam hal (a).

    • Dalam kasus (b), kami harus menggunakan filter()dplyr saat meringkas . Tetapi saat memperbarui , kami harus memindahkan logika di dalamnya mutate(). Namun dalam data.table, kami menyatakan kedua operasi dengan logika yang sama - beroperasi pada baris di mana x > 2, tetapi dalam kasus pertama, dapatkan sum(y), sedangkan dalam kasus kedua perbarui baris tersebut ydengan jumlah kumulatifnya.

      Inilah yang kami maksudkan ketika kami mengatakan DT[i, j, by]formulirnya konsisten .

    • Demikian pula dalam kasus (c), ketika kita memiliki if-elsekondisi, kita dapat mengekspresikan logika "apa adanya" di data.table dan dplyr. Namun, jika kami ingin mengembalikan hanya baris-baris yang ifkondisinya memuaskan dan melompati sebaliknya, kami tidak dapat menggunakan summarise()secara langsung (AFAICT). Kita harus filter()meringkas dulu dan kemudian karena summarise()selalu mengharapkan a nilai tunggal .

      Sementara itu mengembalikan hasil yang sama, menggunakan filter() sini membuat operasi yang sebenarnya kurang jelas.

      Mungkin sangat mungkin untuk digunakan filter()dalam kasus pertama juga (tampaknya tidak jelas bagi saya), tetapi poin saya adalah bahwa kita tidak harus melakukannya.

  2. Agregasi / pembaruan pada banyak kolom

    # 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))
    • Dalam hal (a), kodenya kurang lebih setara. data.table menggunakan fungsi basis akrab lapply(), sedangkan dplyrmemperkenalkan *_each()bersama dengan banyak fungsifuns() .

    • data.table :=memerlukan nama kolom yang harus disediakan, sedangkan dplyr membuatnya secara otomatis.

    • Dalam kasus (b), sintaksis dplyr relatif mudah. Meningkatkan agregasi / pembaruan pada banyak fungsi ada di daftar data.table.

    • Dalam kasus (c), dplyr akan mengembalikan n()kolom sebanyak kali, bukan hanya sekali. Di data.table, yang perlu kita lakukan hanyalah mengembalikan daftar j. Setiap elemen daftar akan menjadi kolom dalam hasil. Jadi, kita dapat menggunakan, sekali lagi, fungsi dasar familiar c()untuk menggabungkan .Nke listyang mengembalikan list.

    Catatan: Sekali lagi, di data.table, yang perlu kita lakukan hanyalah mengembalikan daftar j. Setiap elemen daftar akan menjadi kolom dalam hasil. Anda dapat menggunakan c(), as.list(),lapply() , list()fungsi dll ... dasar untuk mencapai hal ini, tanpa harus mempelajari setiap fungsi baru.

    Anda perlu mempelajari hanya variabel-variabel khusus - .Ndan .SDsetidaknya. Setara dalam dplyr adalah n()dan.

  3. Bergabung

    dplyr menyediakan fungsi-fungsi terpisah untuk setiap jenis gabungan di mana data.table memungkinkan bergabung menggunakan sintaksis yang sama DT[i, j, by](dan dengan alasan). Ini juga menyediakan merge.data.table()fungsi yang setara sebagai alternatif.

    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"]
    ??
    • Beberapa mungkin menemukan fungsi terpisah untuk masing-masing bergabung jauh lebih bagus (kiri, kanan, dalam, anti, semi dll), sedangkan yang lain mungkin suka data.table DT[i, j, by], ataumerge() yang mirip dengan basis R.

    • Namun dplyr bergabung melakukan hal itu. Tidak ada lagi. Tidak kurang.

    • data.tables dapat memilih kolom saat bergabung (2), dan dalam dplyr Anda harus select()terlebih dahulu di kedua data.frame sebelum bergabung seperti yang ditunjukkan di atas. Kalau tidak, Anda akan materialiase bergabung dengan kolom yang tidak perlu hanya untuk menghapusnya nanti dan itu tidak efisien.

    • data.tables dapat mengumpulkan saat bergabung (3) dan juga memperbarui saat bergabung (4), menggunakanby = .EACHI fitur. Mengapa materi seluruh hasil bergabung untuk menambah / memperbarui hanya beberapa kolom?

    • data.table mampu menggulung bergabung (5) - roll maju, LOCF , roll mundur, NOCB , terdekat .

    • data.table juga memiliki mult =argumen yang memilih pertandingan pertama , terakhir atau semua pertandingan (6).

    • data.table memiliki allow.cartesian = TRUEargumen untuk melindungi dari gabungan tidak valid yang tidak disengaja.

Sekali lagi, sintaks konsisten dengan DT[i, j, by]argumen tambahan yang memungkinkan untuk mengontrol output lebih lanjut.

  1. do()...

    Ringkasan dplyr dirancang khusus untuk fungsi yang mengembalikan nilai tunggal. Jika fungsi Anda mengembalikan beberapa nilai / tidak setara, Anda harus beralih ke do(). Anda harus tahu sebelumnya tentang semua fungsi Anda mengembalikan nilai.

    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))))
    • .SDSetara adalah .

    • Di data.table, Anda bisa memasukkan cukup banyak j - satu-satunya hal yang perlu diingat adalah mengembalikan daftar sehingga setiap elemen daftar dikonversi menjadi kolom.

    • Dalam dplyr, tidak bisa melakukan itu. Harus menggunakan do()tergantung pada seberapa yakin Anda apakah fungsi Anda akan selalu mengembalikan nilai tunggal. Dan itu sangat lambat.

Sekali lagi, sintaks data.tabel konsisten dengan DT[i, j, by]. Kami hanya bisa terus melemparkan ekspresi jtanpa harus khawatir tentang hal-hal ini.

Lihatlah pertanyaan SO ini dan yang ini . Saya ingin tahu apakah mungkin untuk mengungkapkan jawabannya dengan langsung menggunakan sintaks dplyr ...

Sebagai rangkuman, saya secara khusus menyoroti beberapa contoh di mana sintaksis dplyr tidak efisien, terbatas, atau gagal membuat operasi menjadi mudah. Ini terutama karena data.table mendapat sedikit serangan balik tentang sintaks "sulit dibaca / dipelajari" (seperti yang disisipkan / ditautkan di atas). Sebagian besar posting yang membahas dplyr berbicara tentang operasi paling mudah. Dan itu bagus. Tetapi penting untuk menyadari sintaks dan keterbatasan fiturnya juga, dan saya belum melihat posting tentang itu.

data.table juga memiliki kebiasaannya (beberapa di antaranya telah saya tunjukkan bahwa kami berusaha untuk memperbaikinya). Kami juga berusaha meningkatkan data.tabel bergabung seperti yang telah saya soroti di sini .

Tetapi kita juga harus mempertimbangkan jumlah fitur yang kurang dplyr dibandingkan dengan data.

4. Fitur

Saya telah menunjukkan sebagian besar fitur di sini dan juga di posting ini. Sebagai tambahan:

  • fread - pembaca file cepat telah tersedia untuk waktu yang lama sekarang.

  • fwrite - penulis file cepat yang diparalelkan sekarang tersedia. Lihat posting ini untuk penjelasan rinci tentang implementasi dan # 1664 untuk melacak perkembangan lebih lanjut.

  • Pengindeksan otomatis - fitur lain yang berguna untuk mengoptimalkan sintaks dasar R seperti apa adanya, secara internal.

  • Pengelompokan Ad-hoc : dplyrsecara otomatis mengurutkan hasil berdasarkan variabel pengelompokan selama summarise(), yang mungkin tidak selalu diinginkan.

  • Banyak keuntungan dalam data.table bergabung (untuk kecepatan / efisiensi memori dan sintaksis) yang disebutkan di atas.

  • Non-equi joins : Memungkinkan bergabung menggunakan operator lain <=, <, >, >=bersama dengan semua keuntungan lain dari data.table joins.

  • Gabungan rentang yang tumpang tindih diimplementasikan dalam data.table baru-baru ini. Periksa posting ini untuk ikhtisar dengan tolok ukur.

  • setorder() fungsi dalam data.table yang memungkinkan penataan ulang data.table sangat cepat dengan referensi.

  • dplyr menyediakan antarmuka ke basis data menggunakan sintaksis yang sama, yang tidak ada pada data.tabel saat ini.

  • data.tablemenyediakan setara lebih cepat dari operasi set (ditulis oleh Jan Gorecki) - fsetdiff, fintersect, funiondan fsetequaldengan tambahan allargumen (seperti dalam SQL).

  • data.table tabel bersih tanpa peringatan masking dan memiliki mekanisme yang dijelaskan di sini untuk [.data.framekompatibilitas ketika diteruskan ke paket R. dplyr mengubah fungsi-fungsi dasar filter, lagdan [yang dapat menyebabkan masalah; misalnya di sini dan di sini .


Akhirnya:

  • Pada database - tidak ada alasan mengapa data.tabel tidak dapat menyediakan antarmuka yang sama, tetapi ini bukan prioritas sekarang. Mungkin terbentur jika pengguna sangat menyukai fitur itu .. tidak yakin.

  • Tentang paralelisme - Segalanya sulit, sampai seseorang maju dan melakukannya. Tentu saja itu akan membutuhkan usaha (menjadi thread aman).

    • Kemajuan sedang dibuat saat ini (dalam v1.9.7 devel) menuju memparalelkan suku cadang yang diketahui memakan waktu untuk peningkatan kinerja dengan menggunakan OpenMP.
Arun
sumber
9
@bluefeet: Saya rasa Anda tidak melakukan layanan hebat dengan memindahkan diskusi itu ke obrolan. Saya mendapat kesan bahwa Arun adalah salah satu pengembang dan ini mungkin menghasilkan wawasan yang berguna.
IRTFM
2
Ketika saya pergi untuk mengobrol menggunakan tautan Anda, tampaknya semua materi mengikuti komentar yang dimulai dengan "Anda harus menggunakan filter" .. hilang. Apakah saya kehilangan sesuatu tentang mekanisme obrolan SO?
IRTFM
6
Saya pikir setiap tempat di mana Anda menggunakan penugasan dengan referensi ( :=), yang dplyrsetara harus juga digunakan <-seperti dalam DF <- DF %>% mutate...bukan hanyaDF %>% mutate...
David Arenburg
4
Mengenai sintaksis. Saya percaya dplyrbisa lebih mudah bagi pengguna yang terbiasa dengan plyrsintaksis, tetapi data.tablebisa lebih mudah bagi pengguna yang terbiasa dengan sintaksis bahasa seperti SQL, dan aljabar relasional di belakangnya, yang semuanya tentang transformasi data tabel. @Arun Anda harus mencatat bahwa mengatur operator sangat mudah dilakukan dengan membungkus data.tablefungsi dan tentu saja membawa peningkatan yang signifikan.
jangorecki
9
Saya sudah membaca posting ini berkali-kali, dan itu banyak membantu saya dalam memahami data.tabel dan bisa menggunakannya dengan lebih baik. Saya, untuk kebanyakan kasus, lebih suka data.tabel lebih dplyr atau panda atau PL / pgSQL. Namun, saya tidak bisa berhenti memikirkan bagaimana mengekspresikannya. Sintaksnya tidak mudah, jelas, atau bertele-tele. Bahkan, bahkan setelah saya menggunakan data. Banyak tabel, saya sering masih berjuang untuk memahami kode saya sendiri, saya sudah menulis secara harfiah seminggu yang lalu. Ini adalah contoh nyata dari bahasa hanya menulis. en.wikipedia.org/wiki/Write-only_language Jadi, mari kita berharap, suatu hari kita akan dapat menggunakan dplyr pada data.table.
Ufos
385

Inilah usaha saya pada jawaban komprehensif dari perspektif dplyr, mengikuti garis besar jawaban Arun (tetapi agak disusun ulang berdasarkan prioritas yang berbeda).

Sintaksis

Ada beberapa subjektivitas terhadap sintaksis, tetapi saya mendukung pernyataan saya bahwa kesimpulan dari data. Tabel membuatnya lebih sulit untuk dipelajari dan lebih sulit untuk dibaca. Ini sebagian karena dplyr memecahkan masalah yang jauh lebih mudah!

Satu hal yang sangat penting yang dplyr lakukan untuk Anda adalah bahwa itu membatasi pilihan Anda. Saya mengklaim bahwa sebagian besar masalah tabel tunggal dapat diselesaikan dengan hanya lima filter kata kunci, pilih, mutasi, atur, dan rangkum, bersama dengan kata keterangan "berdasarkan grup". Kendala itu sangat membantu ketika Anda mempelajari manipulasi data, karena membantu mengatur pemikiran Anda tentang masalah tersebut. Dalam dplyr, masing-masing kata kerja ini dipetakan ke fungsi tunggal. Setiap fungsi melakukan satu pekerjaan, dan mudah dimengerti secara terpisah.

Anda menciptakan kompleksitas dengan menyalurkan operasi sederhana ini bersama %>%. Berikut adalah contoh dari salah satu pos yang terhubung dengan Arun :

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

Bahkan jika Anda belum pernah melihat dplyr sebelumnya (atau bahkan R!), Anda masih bisa mendapatkan inti dari apa yang terjadi karena fungsinya semua kata kerja bahasa Inggris. Kerugian dari kata kerja bahasa Inggris adalah bahwa mereka memerlukan lebih banyak mengetik daripada [, tapi saya pikir itu sebagian besar dapat dikurangi dengan pelengkapan otomatis yang lebih baik.

Berikut kode data.table yang setara:

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

Lebih sulit untuk mengikuti kode ini kecuali Anda sudah terbiasa dengan data.table. (Saya juga tidak tahu bagaimana cara indentasi berulang [ dengan cara yang terlihat baik di mata saya). Secara pribadi, ketika saya melihat kode yang saya tulis 6 bulan yang lalu, itu seperti melihat kode yang ditulis oleh orang asing, jadi saya datang untuk lebih suka langsung, jika verbose, kode.

Dua faktor kecil lain yang menurut saya sedikit mengurangi keterbacaan:

  • Karena hampir setiap operasi tabel data menggunakan [Anda perlu konteks tambahan untuk mencari tahu apa yang terjadi. Misalnya, apakah x[y] menggabungkan dua tabel data atau mengekstraksi kolom dari bingkai data? Ini hanya masalah kecil, karena dalam kode yang ditulis dengan baik, nama variabel harus menunjukkan apa yang terjadi.

  • Saya suka itu group_by()adalah operasi terpisah di dplyr. Ini secara mendasar mengubah perhitungan jadi saya pikir harus jelas ketika membaca kode, dan lebih mudah dikenali group_by()daripada byargumen [.data.table.

Saya juga suka pipa itu tidak hanya terbatas pada satu paket. Anda dapat mulai dengan merapikan data Anda dengan rapi , dan menyelesaikan dengan plot di ggvis . Dan Anda tidak terbatas pada paket yang saya tulis - siapa pun dapat menulis fungsi yang membentuk bagian mulus dari pipa manipulasi data. Sebenarnya, saya lebih suka kode data.table sebelumnya ditulis ulang dengan %>%:

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

Dan gagasan perpipaan dengan %>%tidak terbatas hanya pada frame data dan mudah digeneralisasi ke konteks lain: grafis web interaktif , pengikisan web , inti , kontrak run-time , ...)

Memori dan kinerja

Saya sudah menyatukan ini, karena, bagi saya, mereka tidak begitu penting. Sebagian besar pengguna R bekerja dengan baik di bawah 1 juta baris data, dan dplyr cukup cepat untuk ukuran data yang Anda tidak tahu waktu pemrosesan. Kami mengoptimalkan dplyr untuk ekspresif pada data sedang; jangan ragu untuk menggunakan data. tabel untuk kecepatan mentah pada data yang lebih besar.

Fleksibilitas dplyr juga berarti Anda dapat dengan mudah mengubah karakteristik kinerja menggunakan sintaksis yang sama. Jika kinerja dplyr dengan backend frame data tidak cukup baik untuk Anda, Anda dapat menggunakan backend data.table (walaupun dengan serangkaian fungsi yang agak terbatas). Jika data yang Anda kerjakan tidak sesuai dengan memori, maka Anda dapat menggunakan database backend.

Semua yang dikatakan, kinerja dplyr akan menjadi lebih baik dalam jangka panjang. Kami pasti akan menerapkan beberapa ide besar data. Tabel seperti pemesanan radix dan menggunakan indeks yang sama untuk bergabung & filter. Kami juga mengerjakan parallelisation sehingga kami dapat mengambil keuntungan dari banyak core.

fitur

Beberapa hal yang kami rencanakan untuk dikerjakan pada tahun 2015:

  • yang readrpaket, untuk membuatnya mudah untuk mendapatkan file dari disk dan ke memori, analog dengan fread().

  • Gabungan yang lebih fleksibel, termasuk dukungan untuk gabungan non-equi.

  • Pengelompokan yang lebih fleksibel seperti sampel bootstrap, rollup, dan lainnya

Saya juga menginvestasikan waktu untuk meningkatkan konektor database R , kemampuan untuk berbicara dengan web apis , dan membuatnya lebih mudah untuk mengikis halaman html .

Hadley
sumber
27
Hanya sebagai catatan, saya setuju dengan banyak argumen Anda (walaupun saya lebih suka data.tablesintaksnya sendiri), tetapi Anda dapat dengan mudah menggunakan %>%untuk menyalurkan data.tableoperasi jika Anda tidak suka [gaya. %>%tidak spesifik untuk dplyr, melainkan berasal dari paket terpisah (yang kebetulan Anda juga ikut menulis), jadi saya tidak yakin saya mengerti apa yang Anda katakan di sebagian besar paragraf Sintaks Anda .
David Arenburg
11
Poin bagus @vidarenburg. Saya telah menulis ulang sintaks untuk mudah-mudahan memperjelas apa poin utama saya, dan untuk menyoroti bahwa Anda dapat menggunakan %>%data.table
hadley
5
Terima kasih Hadley, ini adalah perspektif yang berguna. Kembali indenting biasanya saya lakukan DT[\n\texpression\n][\texpression\n]( inti ) yang sebenarnya bekerja cukup baik. Saya menjaga jawaban Arun sebagai jawaban karena dia lebih langsung menjawab pertanyaan spesifik saya yang tidak begitu banyak tentang aksesibilitas sintaksis, tapi saya pikir ini jawaban yang baik untuk orang yang mencoba untuk mendapatkan perasaan umum untuk perbedaan / kesamaan antara dplyrdan data.table.
BrodieG
33
Mengapa mengerjakan fastread saat sudah ada fread()? Bukankah waktu akan dihabiskan lebih baik untuk meningkatkan ketakutan () atau mengerjakan hal-hal lain (kurang berkembang)?
EDi
10
API data.tabledidasarkan pada penyalahgunaan []notasi secara masif . Itulah kekuatan terbesarnya dan kelemahan terbesarnya.
Paul
65

Sebagai tanggapan langsung terhadap Judul Pertanyaan ...

dplyr pasti melakukan hal-hal yang data.tabletidak bisa.

Poin Anda # 3

dplyr abstrak (atau akan) potensi interaksi DB

adalah jawaban langsung untuk pertanyaan Anda sendiri tetapi tidak naik ke level yang cukup tinggi. dplyrbenar-benar sebuah front-end yang dapat diperpanjang untuk beberapa mekanisme penyimpanan data di mana seperti data.tableperpanjangan satu.

Lihatlah dplyrsebagai antarmuka agnostik back-end, dengan semua target menggunakan grammer yang sama, di mana Anda dapat memperluas target dan penangan sesuka hati. data.tableadalah, dari dplyrperspektif, salah satu target tersebut.

Anda tidak akan (saya harap) melihat hari yang data.tablemencoba menerjemahkan pertanyaan Anda untuk membuat pernyataan SQL yang beroperasi dengan penyimpanan data di disk atau jaringan.

dplyrmungkin bisa melakukan hal-hal yang data.tabletidak akan atau tidak mungkin dilakukan juga.

Berdasarkan desain yang bekerja di memori, data.tablebisa memiliki waktu yang jauh lebih sulit memperluas dirinya ke pemrosesan paralel permintaan dplyr.


Menanggapi pertanyaan dalam-tubuh ...

Pemakaian

Apakah ada tugas analitis yang jauh lebih mudah untuk dikodekan dengan satu atau paket lain untuk orang yang akrab dengan paket (yaitu beberapa kombinasi penekanan tombol yang diperlukan vs tingkat esoterisme yang diperlukan, di mana kurang dari masing-masing adalah hal yang baik).

Ini mungkin tampak seperti tendangan tapi jawaban sebenarnya adalah tidak. Orang - orang yang akrab dengan alat tampaknya menggunakan salah satu yang paling akrab bagi mereka atau yang benar-benar tepat untuk pekerjaan yang sedang dilakukan. Dengan itu, kadang-kadang Anda ingin menyajikan keterbacaan tertentu, kadang-kadang tingkat kinerja, dan ketika Anda membutuhkan tingkat yang cukup tinggi dari keduanya, Anda mungkin hanya perlu alat lain untuk mengikuti apa yang sudah Anda buat untuk membuat abstraksi yang lebih jelas. .

Performa

Apakah ada tugas analitik yang dilakukan secara substansial (yaitu lebih dari 2x) lebih efisien dalam satu paket vs yang lain.

Sekali lagi, tidak. data.tableunggul dalam menjadi efisien dalam segala hal yang dilakukannya di mana dplyrmendapat beban terbatas dalam beberapa hal dengan penyimpanan data yang mendasarinya dan penangan terdaftar.

Ini berarti ketika Anda mengalami masalah kinerja dengan data.tableAnda bisa cukup yakin itu adalah dalam fungsi permintaan Anda dan jika itu adalah benar-benar sebuah hambatan dengan data.tablemaka Anda telah memenangkan diri Anda sukacita mengajukan laporan. Ini juga benar ketika dplyrmenggunakan data.tablesebagai back-end; Anda mungkin melihat beberapa overhead dplyrtetapi kemungkinan itu adalah permintaan Anda.

Ketika dplyrmemiliki masalah kinerja dengan back-end, Anda dapat mengatasinya dengan mendaftarkan fungsi untuk evaluasi hybrid atau (dalam kasus database) memanipulasi permintaan yang dihasilkan sebelum eksekusi.

Lihat juga jawaban yang diterima untuk kapan plyr lebih baik daripada data.tabel?

Baiklah
sumber
3
Tidak bisa dplyr bungkus data.tabel dengan tbl_dt? Mengapa tidak mendapatkan yang terbaik dari kedua dunia?
aaa90210
22
Anda lupa menyebutkan pernyataan terbalik "data.table pasti melakukan hal-hal yang dplyr tidak bisa" yang juga benar.
jangorecki
25
Jawaban Arun menjelaskannya dengan baik. Yang paling penting (dalam hal kinerja) adalah ketakutan, pembaruan dengan referensi, penggabungan yang bergulir, penyatuan yang tumpang tindih. Saya percaya tidak ada paket apa pun (tidak hanya dplyr) yang dapat bersaing dengan fitur-fitur itu. Contoh yang bagus dapat menjadi slide terakhir dari presentasi ini .
jangorecki
15
Benar-benar, data.table adalah alasan saya masih menggunakan R. Kalau tidak, saya akan menggunakan panda. Ini bahkan lebih baik / lebih cepat dari panda.
marbel
8
Saya suka data.table karena kesederhanaan dan kemiripannya dengan struktur sintaks SQL. Pekerjaan saya melibatkan melakukan analisis data dan grafik ad hoc yang sangat intens setiap hari untuk pemodelan statistik, dan saya benar-benar membutuhkan alat yang cukup sederhana untuk melakukan hal-hal rumit. Sekarang saya dapat mengurangi toolkit saya menjadi hanya data. Tabel untuk data dan kisi untuk grafik dalam pekerjaan sehari-hari saya. Berikan contoh saya bahkan dapat melakukan operasi seperti ini: $ DT [grup == 1, y_hat: = prediksi (fit1, data = .SD),] $, yang benar-benar rapi dan saya menganggapnya sebagai keuntungan besar dari SQL di lingkungan R klasik.
xappppp