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/fread
urutan 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::mutate
tidak 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_join
juga diuji dan ini paling lambat dengan ~ 9.000 ms, gunakan lebih banyak memori daripada {data.table} update_by_reference
dan 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 + update
dan update
~ 11 dan ~ 6,5 kali lebih cepat daripada normal join
masing-masing
- pada bergabung pertama, kinerja
setkey + update
mirip dengan update
overhead setkey
sebagian besar mengimbangi keuntungan kinerjanya sendiri
- pada gabungan kedua dan selanjutnya, karena
setkey
tidak diperlukan, setkey + update
lebih cepat daripada update
~ 1,8 kali (atau lebih cepat dari normal join
~ 11 kali)
Contohnya
Untuk penggabungan yang berkinerja & efisien memori, gunakan salah satu update
atau 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 setjoin
atas - 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 on
bisa 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
, j
atau by
operasi (Anda dapat menggunakan hampir semua ekspresi di sana), saya pikir sulit terjemahan, terutama ketika menggabungkan dengan update-by-referensi , setkey
dan 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 ☺☺
dplyr
sehingga Anda tidak bisa melakukannya dengan baikdata.table
? Jika tidak, beralih kedata.table
akan lebih baik daripadadtplyr
.