Apa yang tidak bisa saya lakukan dengan dtplyr yang saya dapat di data.table

9

Haruskah saya menginvestasikan upaya belajar saya untuk perselisihan data dalam R, khususnya di antara dplyr, dtplyrdan data.table?

  • Saya menggunakan dplyrsebagian besar, tetapi ketika data terlalu besar untuk itu saya akan gunakan data.table, yang jarang terjadi. Jadi sekarang dtplyrv1.0 keluar sebagai antarmuka untuk data.table, di permukaan sepertinya saya tidak perlu khawatir menggunakan data.tableantarmuka lagi.

  • Jadi fitur atau aspek apa yang paling berguna data.tableyang tidak dapat dilakukan menggunakan dtplyrsaat ini, dan yang kemungkinan tidak akan pernah dilakukan dtplyr?

  • Di wajahnya, dplyrdengan manfaat data.tablemembuatnya terdengar seperti dtplyrakan menyusul dplyr. Apakah akan ada alasan untuk menggunakan dplyrsetelah dtplyrsepenuhnya matang?

Catatan: Saya tidak bertanya tentang dplyrvs data.table(seperti pada data.tabel vs dplyr: dapatkah seseorang melakukan sesuatu dengan baik yang lainnya tidak dapat atau melakukan dengan buruk? ), Tetapi mengingat bahwa yang satu lebih disukai daripada yang lain untuk masalah tertentu, mengapa tidak t dtplyrmenjadi alat untuk digunakan.

dule arnaux
sumber
1
Apakah ada sesuatu yang bisa Anda lakukan dengan baik dplyrsehingga Anda tidak bisa melakukannya dengan baik data.table? Jika tidak, beralih ke data.tableakan lebih baik daripada dtplyr.
sindri_baldur
2
Dari dtplyrreadme, 'Beberapa data.tableekspresi tidak memiliki dplyrpadanan langsung . Misalnya, tidak ada cara untuk mengekspresikan cross-atau rolling-gabung dengan dplyr. ' dan 'Untuk mencocokkan dplyrsemantik, mutate() tidak diubah pada tempatnya secara default. Ini berarti bahwa sebagian besar ekspresi yang terlibat mutate()harus membuat salinan yang tidak perlu jika Anda menggunakan data.tablesecara langsung. ' Ada sedikit cara di sekitar bagian kedua itu tetapi mengingat seberapa sering mutatedigunakan, itu adalah kerugian yang cukup besar di mata saya.
ClancyStats

Jawaban:

15

Saya akan mencoba memberikan panduan terbaik saya tetapi tidak mudah karena kita harus terbiasa dengan semua {data.table}, {dplyr}, {dtplyr} dan juga {base R}. Saya menggunakan {data.table} dan banyak paket {tidy-world} (kecuali {dplyr}). Cinta keduanya, meskipun saya lebih suka sintaks data.tabel untuk dplyr. Saya berharap semua paket dunia-rapi akan menggunakan {dtplyr} atau {data.table} sebagai backend kapan pun diperlukan.

Seperti halnya terjemahan lain (pikirkan dplyr-to-sparkly / SQL), ada hal-hal yang dapat atau tidak dapat diterjemahkan, setidaknya untuk saat ini. Maksud saya, mungkin suatu hari {dtplyr} dapat membuatnya diterjemahkan 100%, siapa tahu. Daftar di bawah ini tidak lengkap atau 100% benar karena saya akan mencoba yang terbaik untuk menjawab berdasarkan pengetahuan saya tentang topik / paket / masalah terkait / dll.

Yang penting, untuk jawaban-jawaban yang tidak sepenuhnya akurat, saya harap ini memberi Anda beberapa panduan tentang aspek {data.table} apa yang harus Anda perhatikan dan, bandingkan dengan {dtplyr} dan temukan jawabannya sendiri. Jangan anggap remeh jawaban ini.

Dan, saya berharap posting ini dapat digunakan sebagai salah satu sumber daya untuk semua {dplyr}, {data.table} atau {dtplyr} pengguna / pembuat untuk diskusi dan kolaborasi dan menjadikan #RStats lebih baik.

{data.table} tidak hanya digunakan untuk operasi cepat & efisien memori. Ada banyak orang, termasuk saya, lebih menyukai sintaksis elegan {data.table}. Ini juga mencakup operasi cepat lainnya seperti fungsi deret waktu seperti keluarga bergulir (yaitu frollapply) yang ditulis dalam C. Ia dapat digunakan dengan fungsi apa pun, termasuk tidyverse. Saya banyak menggunakan {data.table} + {purrr}!

Kompleksitas operasi

Ini dapat dengan mudah diterjemahkan

library(data.table)
library(dplyr)
library(flights)
data <- data.table(diamonds)

# dplyr 
diamonds %>%
  filter(cut != "Fair") %>% 
  group_by(cut) %>% 
  summarize(
    avg_price    = mean(price),
    median_price = as.numeric(median(price)),
    count        = n()
  ) %>%
  arrange(desc(count))

# data.table
data [
  ][cut != 'Fair', by = cut, .(
      avg_price    = mean(price),
      median_price = as.numeric(median(price)),
      count        = .N
    )
  ][order( - count)]

{data.table} sangat cepat & hemat memori karena (hampir?) semuanya dibangun dari bawah ke atas dengan konsep kunci pembaruan-per-referensi , kunci (pikirkan SQL), dan optimalisasi tanpa henti di mana-mana dalam paket (Yaitu fifelse, fread/freadurutan sortir radix diadopsi oleh basis R), sambil memastikan sintaksisnya ringkas dan konsisten, itu sebabnya saya pikir itu elegan.

Dari Pengantar data.tabel , operasi manipulasi data utama seperti subset, grup, pembaruan, bergabung, dll disimpan bersama untuk

  • sintaksis ringkas & konsisten ...

  • melakukan analisis dengan lancar tanpa beban kognitif karena harus memetakan setiap operasi ...

  • secara otomatis mengoptimalkan operasi secara internal, dan sangat efektif, dengan mengetahui secara tepat data yang diperlukan untuk setiap operasi, yang mengarah pada kode yang sangat cepat dan efisien memori

Poin terakhir, sebagai contoh,

# Calculate the average arrival and departure delay for all flights with “JFK” as the origin airport in the month of June.
flights[origin == 'JFK' & month == 6L,
        .(m_arr = mean(arr_delay), m_dep = mean(dep_delay))]
  • Kami pertama-tama mengelompokkan dalam i untuk menemukan indeks baris yang cocok di mana bandara asal sama dengan "JFK", dan bulan sama dengan 6L. Kami belum mengelompokkan seluruh data.tabel yang sesuai dengan baris tersebut.

  • Sekarang, kita melihat j dan menemukan bahwa itu hanya menggunakan dua kolom. Dan apa yang harus kita lakukan adalah menghitung artinya (). Oleh karena itu kami mengelompokkan hanya kolom-kolom yang sesuai dengan baris yang cocok, dan menghitung rata-ratanya ().

Karena tiga komponen utama kueri (i, j dan oleh) bersama-sama di dalam [...] , data.table dapat melihat ketiganya dan mengoptimalkan kueri secara keseluruhan sebelum evaluasi, tidak masing-masing secara terpisah . Karena itu kami dapat menghindari seluruh subset (yaitu, mengatur ulang kolom selain arr_delay dan dep_delay), untuk kecepatan dan efisiensi memori.

Mengingat bahwa, untuk menuai manfaat {data.table}, terjemahan {dtplr} harus benar dalam hal itu. Semakin kompleks operasinya, semakin sulit terjemahannya. Untuk operasi sederhana seperti di atas, tentu dapat dengan mudah diterjemahkan. Untuk yang kompleks, atau yang tidak didukung oleh {dtplyr}, Anda harus mencari tahu sendiri seperti yang disebutkan di atas, Anda harus membandingkan sintaks dan tolok ukur yang diterjemahkan dan menjadi paket terkait yang familier.

Untuk operasi yang kompleks atau operasi yang tidak didukung, saya mungkin dapat memberikan beberapa contoh di bawah ini. Sekali lagi, saya hanya mencoba yang terbaik. Bersikaplah lembut padaku.

Perbarui dengan referensi

Saya tidak akan masuk ke intro / detail tetapi di sini ada beberapa tautan

Sumber daya utama: Referensi semantik

Lebih detail: Memahami kapan tepatnya data.table adalah referensi ke (vs salinan) data.table

Pembaruan-oleh-referensi , menurut saya, fitur terpenting dari {data.table} dan itulah yang membuatnya begitu cepat & hemat memori. dplyr::mutatetidak mendukungnya secara default. Karena saya tidak terbiasa dengan {dtplyr}, saya tidak yakin berapa banyak dan operasi apa yang dapat atau tidak dapat didukung oleh {dtplyr}. Seperti disebutkan di atas, itu juga tergantung pada kerumitan operasi, yang pada gilirannya mempengaruhi terjemahan.

Ada dua cara untuk menggunakan pembaruan dengan referensi di {data.table}

  • operator penugasan {data.table} :=

  • set-keluarga: set, setnames, setcolorder, setkey, setDT, fsetdiff, dan masih banyak lagi

:=lebih umum digunakan dibandingkan dengan set. Untuk dataset yang kompleks dan besar, pembaruan dengan referensi adalah kunci untuk mendapatkan kecepatan tertinggi & efisiensi memori. Cara berpikir yang mudah (tidak 100% akurat, karena perinciannya jauh lebih rumit daripada ini karena melibatkan hard / copy dangkal dan banyak faktor lainnya), misalnya Anda berurusan dengan dataset besar 10GB, masing-masing dengan 10 kolom dan 1GB . Untuk memanipulasi satu kolom, Anda hanya perlu berurusan dengan 1GB.

Kuncinya adalah, dengan pembaruan-per-referensi , Anda hanya perlu berurusan dengan data yang diperlukan. Itu sebabnya ketika menggunakan {data.table}, terutama yang berhubungan dengan dataset besar, kami menggunakan pembaruan-per-referensi setiap saat kapan pun memungkinkan. Misalnya, memanipulasi dataset pemodelan besar

# Manipulating list columns

df <- purrr::map_dfr(1:1e5, ~ iris)
dt <- data.table(df)

# data.table
dt [,
    by = Species, .(data   = .( .SD )) ][,  # `.(` shorthand for `list`
    model   := map(data, ~ lm(Sepal.Length ~ Sepal.Width, data = . )) ][,
    summary := map(model, summary) ][,
    plot    := map(data, ~ ggplot( . , aes(Sepal.Length, Sepal.Width)) +
                           geom_point())]

# dplyr
df %>% 
  group_by(Species) %>% 
  nest() %>% 
  mutate(
    model   = map(data, ~ lm(Sepal.Length ~ Sepal.Width, data = . )),
    summary = map(model, summary),
    plot    = map(data, ~ ggplot( . , aes(Sepal.Length, Sepal.Width)) +
                          geom_point())
  )

Operasi bersarang list(.SD)mungkin tidak didukung oleh {dtlyr} karena pengguna rapi merapikan tidyr::nest? Jadi saya tidak yakin apakah operasi selanjutnya dapat diterjemahkan sebagai cara {data.table} lebih cepat & lebih sedikit memori.

CATATAN: hasil data.tabel adalah dalam "milidetik", dplyr dalam "menit"

df <- purrr::map_dfr(1:1e5, ~ iris)
dt <- copy(data.table(df))

bench::mark(
  check = FALSE,

  dt[, by = Species, .(data = list(.SD))],
  df %>% group_by(Species) %>% nest()
)
# # A tibble: 2 x 13
#   expression                                   min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc
#   <bch:expr>                              <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>
# 1 dt[, by = Species, .(data = list(.SD))] 361.94ms 402.04ms   2.49      705.8MB     1.24     2     1
# 2 df %>% group_by(Species) %>% nest()        6.85m    6.85m   0.00243     1.4GB     2.28     1   937
# # ... with 5 more variables: total_time <bch:tm>, result <list>, memory <list>, time <list>,
# #   gc <list>

Ada banyak kasus penggunaan pembaruan-oleh-referensi dan bahkan {data.table} pengguna tidak akan menggunakan versi lanjutan sepanjang waktu karena memerlukan lebih banyak kode. Apakah {dtplyr} mendukung ini di luar kotak, Anda harus mencari tahu sendiri.

Banyak pembaruan dengan referensi untuk fungsi yang sama

Sumber daya utama: Menempatkan beberapa kolom secara elegan dalam data.tabel dengan lapply ()

Ini melibatkan yang lebih umum digunakan :=atau set.

dt <- data.table( matrix(runif(10000), nrow = 100) )

# A few variants

for (col in paste0('V', 20:100))
  set(dt, j = col, value = sqrt(get(col)))

for (col in paste0('V', 20:100))
  dt[, (col) := sqrt(get(col))]

# I prefer `purrr::map` to `for`
library(purrr)
map(paste0('V', 20:100), ~ dt[, (.) := sqrt(get(.))])

Sesuai pencipta {data.table} Matt Dowle

(Perhatikan bahwa mungkin lebih umum untuk loop diatur pada sejumlah besar baris daripada sejumlah besar kolom.)

Bergabunglah + setkey + perbarui-oleh-referensi

Saya perlu bergabung cepat dengan data yang relatif besar dan pola bergabung serupa baru-baru ini, jadi saya menggunakan kekuatan pembaruan-oleh-referensi , bukan bergabung normal. Karena mereka membutuhkan lebih banyak kode, saya membungkusnya dalam paket pribadi dengan evaluasi non-standar untuk dapat digunakan kembali dan mudah dibaca di mana saya menyebutnya setjoin.

Saya melakukan beberapa tolok ukur di sini: data.table bergabung + perbarui-oleh-referensi + setkey

Ringkasan

# For brevity, only the codes for join-operation are shown here. Please refer to the link for details

# Normal_join
x <- y[x, on = 'a']

# update_by_reference
x_2[y_2, on = 'a', c := c]

# setkey_n_update
setkey(x_3, a) [ setkey(y_3, a), on = 'a', c := c ]

CATATAN: dplyr::left_joinjuga diuji dan ini paling lambat dengan ~ 9.000 ms, gunakan lebih banyak memori daripada {data.table} update_by_referencedan setkey_n_update, tetapi gunakan lebih sedikit memori daripada normal_join {data.table}. Ini menghabiskan sekitar ~ 2.0GB memori. Saya tidak memasukkannya karena saya hanya ingin fokus pada {data.table}.

Temuan Utama

  • setkey + updatedan update~ 11 dan ~ 6,5 kali lebih cepat daripada normal joinmasing-masing
  • pada bergabung pertama, kinerja setkey + updatemirip dengan updateoverhead setkeysebagian besar mengimbangi keuntungan kinerjanya sendiri
  • pada gabungan kedua dan selanjutnya, karena setkeytidak diperlukan, setkey + updatelebih cepat daripada update~ 1,8 kali (atau lebih cepat dari normal join~ 11 kali)

Gambar

Contohnya

Untuk penggabungan yang berkinerja & efisien memori, gunakan salah satu updateatau setkey + update, di mana yang terakhir lebih cepat dengan mengorbankan lebih banyak kode.

Mari kita lihat beberapa kode semu , untuk singkatnya. Logikanya sama.

Untuk satu atau beberapa kolom

a <- data.table(x = ..., y = ..., z = ..., ...)
b <- data.table(x = ..., y = ..., z = ..., ...)

# `update`
a[b, on = .(x), y := y]
a[b, on = .(x),  `:=` (y = y, z = z, ...)]
# `setkey + update`
setkey(a, x) [ setkey(b, x), on = .(x), y := y ]
setkey(a, x) [ setkey(b, x), on = .(x),  `:=` (y = y, z = z, ...) ]

Untuk banyak kolom

cols <- c('x', 'y', ...)
# `update`
a[b, on = .(x), (cols) := mget( paste0('i.', cols) )]
# `setkey + update`
setkey(a, x) [ setkey(b, x), on = .(x), (cols) := mget( paste0('i.', cols) ) ]

Wrapper untuk sambungan cepat & hemat memori ... banyak dari mereka ... dengan pola gabungan yang sama, bungkus seperti di setjoinatas - dengan update - dengan atau tanpasetkey

setjoin(a, b, on = ...)  # join all columns
setjoin(a, b, on = ..., select = c('columns_to_be_included', ...))
setjoin(a, b, on = ..., drop   = c('columns_to_be_excluded', ...))
# With that, you can even use it with `magrittr` pipe
a %>%
  setjoin(...) %>%
  setjoin(...)

Dengan setkey, argumen onbisa dihilangkan. Ini juga dapat dimasukkan untuk keterbacaan, terutama untuk berkolaborasi dengan orang lain.

Operasi baris besar

  • seperti yang disebutkan di atas, gunakan set
  • pra-mengisi meja Anda, gunakan teknik pembaruan-oleh-referensi
  • subset menggunakan kunci (yaitu setkey)

Sumberdaya terkait: Tambahkan baris dengan referensi di akhir objek data.table

Ringkasan pembaruan dengan referensi

Ini hanya beberapa kasus penggunaan pembaruan-oleh-referensi . Masih banyak lagi.

Seperti yang Anda lihat, untuk penggunaan lanjutan berurusan dengan data besar, ada banyak kasus dan teknik penggunaan menggunakan pembaruan-oleh-referensi untuk dataset besar. Tidak mudah digunakan di {data.table} dan apakah {dtplyr} mendukungnya, Anda bisa mengetahuinya sendiri.

Saya fokus pada pembaruan-oleh-referensi dalam posting ini karena saya pikir ini adalah fitur yang paling kuat dari {data.table} untuk operasi cepat & efisien memori. Yang mengatakan, ada banyak, banyak aspek lain yang membuatnya sangat efisien juga dan saya pikir itu tidak didukung oleh {dtplyr}.

Aspek kunci lainnya

Apa yang didukung / tidak, itu juga tergantung pada kompleksitas operasi dan apakah itu melibatkan fitur asli data.table seperti pembaruan-oleh-referensi atau setkey. Dan apakah kode yang diterjemahkan lebih efisien (kode yang akan ditulis oleh pengguna data) juga merupakan faktor lain (yaitu kode diterjemahkan, tetapi apakah ini versi efisien?). Banyak hal yang saling berhubungan.

  • setkey. Lihat Kunci dan subset berbasis pencarian biner cepat
  • Indeks sekunder dan pengindeksan otomatis
  • Menggunakan .SD untuk Analisis Data
  • fungsi time-series: berpikir frollapply. fungsi bergulir, agregat bergulir, jendela geser, moving average
  • rolling join , non-equi join , (beberapa) "cross" join
  • {data.table} telah membangun fondasi dalam efisiensi kecepatan & memori, di masa depan, dapat meluas hingga mencakup banyak fungsi (seperti bagaimana mereka mengimplementasikan fungsi time-series yang disebutkan di atas)
  • secara umum, operasi yang lebih kompleks pada ini data.table i, jatau byoperasi (Anda dapat menggunakan hampir semua ekspresi di sana), saya pikir sulit terjemahan, terutama ketika menggabungkan dengan update-by-referensi , setkeydan data.table asli lainnya fungsi sepertifrollapply
  • Poin lain terkait dengan penggunaan basis R atau tidyverse. Saya menggunakan kedua data.table + tidyverse (kecuali dplyr / readr / tidyr). Untuk operasi besar, saya sering melakukan benchmark, misalnya, stringr::str_*fungsi keluarga vs basis R dan saya menemukan basis R lebih cepat sampai batas tertentu dan menggunakannya. Intinya adalah, jangan biarkan diri Anda hanya merapikan atau data. Tabel atau ..., mengeksplorasi opsi lain untuk menyelesaikan pekerjaan.

Banyak dari aspek-aspek ini saling terkait dengan poin-poin yang disebutkan di atas

  • kompleksitas operasi

  • perbarui-oleh-referensi

Anda dapat mengetahui apakah {dtplyr} mendukung operasi ini terutama ketika dikombinasikan.

Trik lain yang berguna ketika berhadapan dengan dataset kecil atau besar, selama sesi interaktif, {data.table} benar-benar memenuhi janjinya untuk mengurangi pemrograman dan menghitung waktu secara luar biasa.

Tombol pengaturan untuk variabel yang digunakan berulang untuk kedua kecepatan dan 'rownames supercharged' (subset tanpa menentukan nama variabel).

dt <- data.table(iris)
setkey(dt, Species) 

dt['setosa',    do_something(...), ...]
dt['virginica', do_another(...),   ...]
dt['setosa',    more(...),         ...]

# `by` argument can also be omitted, particularly useful during interactive session
# this ultimately becomes what I call 'naked' syntax, just type what you want to do, without any placeholders. 
# It's simply elegant
dt['setosa', do_something(...), Species, ...]

Jika operasi Anda hanya melibatkan yang sederhana seperti pada contoh pertama, {dtplyr} dapat menyelesaikan pekerjaan. Untuk yang kompleks / tidak didukung, Anda dapat menggunakan panduan ini untuk membandingkan yang diterjemahkan {dtplyr} dengan bagaimana data berpengalaman. Pengguna akan mengkodekan dengan cara cepat & efisien memori dengan sintaks elegan data.table. Penerjemahan tidak berarti itu cara yang paling efisien karena mungkin ada teknik yang berbeda untuk menangani berbagai kasus data besar. Untuk set data yang lebih besar, Anda dapat menggabungkan {data.table} dengan {disk.frame} , {fst} dan {drake} dan paket luar biasa lainnya untuk mendapatkan yang terbaik darinya. Ada juga {big.data.table} tetapi saat ini tidak aktif.

Saya harap ini membantu semua orang. Semoga harimu menyenangkan ☺☺

K22
sumber
2

Bergabung non-equi dan rolling bergabung ke pikiran. Sepertinya tidak ada rencana untuk memasukkan fungsi yang setara di dplyr sehingga tidak ada yang bisa diterjemahkan oleh dtplyr.

Ada juga pembentukan kembali (optimal dcast dan mencair setara dengan fungsi yang sama dalam membentuk kembali2) yang tidak ada dalam dplyr juga.

Semua fungsi * _if dan * _at saat ini tidak dapat diterjemahkan dengan dtplyr juga, tetapi semua itu masih berfungsi.

EdTeD
sumber
0

Perbarui kolom saat bergabung dengan Beberapa. Trik SD Banyak fungsi f Dan Tuhan tahu apa lagi karena #rdatatable lebih dari sekadar perpustakaan sederhana dan tidak dapat diringkas dengan beberapa fungsi

Ini seluruh ekosistem sendiri

Saya tidak pernah membutuhkan dplyr sejak hari saya mulai R. Karena data.tabel sangat bagus

Vikram
sumber