Jika saya menggunakan sintaks dplyr di atas datatable , apakah saya mendapatkan semua manfaat kecepatan dari datatable saat masih menggunakan sintaks dplyr? Dengan kata lain, apakah saya salah menggunakan datatable jika saya menanyakannya dengan sintaks dplyr? Atau apakah saya perlu menggunakan sintaks datatable murni untuk memanfaatkan semua kekuatannya.
Terima kasih sebelumnya atas saran apa pun. Contoh Kode:
library(data.table)
library(dplyr)
diamondsDT <- data.table(ggplot2::diamonds)
setkey(diamondsDT, cut)
diamondsDT %>%
filter(cut != "Fair") %>%
group_by(cut) %>%
summarize(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = n()) %>%
arrange(desc(Count))
Hasil:
# cut AvgPrice MedianPrice Count
# 1 Ideal 3457.542 1810.0 21551
# 2 Premium 4584.258 3185.0 13791
# 3 Very Good 3981.760 2648.0 12082
# 4 Good 3928.864 3050.5 4906
Berikut adalah persamaan data yang saya dapatkan. Tidak yakin apakah itu sesuai dengan praktik DT yang baik. Tapi saya ingin tahu apakah kodenya benar-benar lebih efisien daripada sintaks dplyr di belakang layar:
diamondsDT [cut != "Fair"
] [, .(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = .N), by=cut
] [ order(-Count) ]
r
data.table
dplyr
Polimerase
sumber
sumber
dplyr
metode untuk tabel data, tetapi tabel data juga memiliki metode yang sebandingdplyr
digunakan padadata.frame
s dandata.table
s yang sesuai , lihat di sini (dan referensi di dalamnya).Jawaban:
Tidak ada jawaban yang lugas / sederhana karena filosofi kedua paket ini berbeda dalam aspek tertentu. Jadi beberapa kompromi tidak bisa dihindari. Berikut adalah beberapa masalah yang mungkin perlu Anda tangani / pertimbangkan.
Operasi yang melibatkan
i
(==filter()
danslice()
dalam dplyr)Asumsikan
DT
dengan katakanlah 10 kolom. Pertimbangkan ekspresi data.table ini:DT[a > 1, .N] ## --- (1) DT[a > 1, mean(b), by=.(c, d)] ## --- (2)
(1) memberikan jumlah baris di
DT
mana koloma > 1
. (2) mengembalikanmean(b)
dikelompokkan olehc,d
untuk ekspresi yang samai
seperti (1).dplyr
Ekspresi yang umum digunakan adalah:DT %>% filter(a > 1) %>% summarise(n()) ## --- (3) DT %>% filter(a > 1) %>% group_by(c, d) %>% summarise(mean(b)) ## --- (4)
Jelasnya, kode data.table lebih pendek. Selain itu mereka juga lebih hemat memori 1 . Mengapa? Karena di kedua (3) dan (4),
filter()
mengembalikan baris untuk semua 10 kolom terlebih dahulu, ketika di (3) kita hanya membutuhkan jumlah baris, dan di (4) kita hanya perlu kolomb, c, d
untuk operasi berturut-turut. Untuk mengatasinya, kita harusselect()
kolom apriori:DT %>% select(a) %>% filter(a > 1) %>% summarise(n()) ## --- (5) DT %>% select(a,b,c,d) %>% filter(a > 1) %>% group_by(c,d) %>% summarise(mean(b)) ## --- (6)
Perhatikan bahwa di (5) dan (6), kami masih subset kolom
a
yang tidak kami butuhkan. Tapi saya tidak yakin bagaimana menghindarinya. Jikafilter()
fungsi memiliki argumen untuk memilih kolom yang akan dikembalikan, kita dapat menghindari masalah ini, tetapi kemudian fungsi tersebut tidak hanya melakukan satu tugas (yang juga merupakan pilihan desain dplyr).Sub-tugaskan dengan referensi
Misalnya, di data.table Anda dapat melakukan:
DT[a %in% some_vals, a := NA]
yang memperbarui kolom
a
dengan referensi hanya pada baris-baris yang memenuhi kondisi. Saat ini dplyr deep menyalin seluruh data.table secara internal untuk menambahkan kolom baru. @BrodieG sudah menyebutkan ini dalam jawabannya.Tetapi salinan dalam dapat diganti dengan salinan dangkal ketika FR # 617 diimplementasikan. Juga relevan: dplyr: FR # 614 . Perhatikan bahwa tetap saja, kolom yang Anda ubah akan selalu disalin (oleh karena itu anak laki-laki lebih lambat / kurang efisien memori). Tidak ada cara untuk memperbarui kolom dengan referensi.
Fungsi lainnya
Dalam data.table, Anda dapat menggabungkan saat bergabung, dan ini lebih mudah dipahami dan hemat memori karena hasil gabungan antara tidak pernah terwujud. Lihat posting ini sebagai contoh. Anda tidak dapat (saat ini?) Melakukan itu menggunakan sintaks data.table / data.frame dplyr.
Fitur rolling join data.table juga tidak didukung dalam sintaks dplyr.
Kami baru-baru ini mengimplementasikan gabungan tumpang tindih di data.table untuk menggabungkan rentang interval ( berikut adalah contoh ), yang merupakan fungsi terpisah
foverlaps()
saat ini, dan oleh karena itu dapat digunakan dengan operator pipa (magrittr / pipeR? - tidak pernah mencobanya sendiri).Tetapi pada akhirnya, tujuan kami adalah untuk mengintegrasikannya ke dalam
[.data.table
sehingga kami dapat memanen fitur lain seperti pengelompokan, menggabungkan saat bergabung, dll .. yang akan memiliki batasan yang sama seperti yang diuraikan di atas.Sejak 1.9.4, data.table mengimplementasikan pengindeksan otomatis menggunakan kunci sekunder untuk subset berbasis pencarian biner cepat pada sintaks R. Contoh:
DT[x == 1]
danDT[x %in% some_vals]
secara otomatis akan membuat indeks saat pertama kali dijalankan, yang kemudian akan digunakan pada subset berturut-turut dari kolom yang sama ke subset cepat menggunakan pencarian biner. Fitur ini akan terus berkembang. Periksa inti ini untuk gambaran singkat tentang fitur ini.Dari cara
filter()
diimplementasikan untuk data.tables, tidak memanfaatkan fitur ini.Fitur dplyr adalah ia juga menyediakan antarmuka ke database menggunakan sintaks yang sama, yang saat ini tidak dimiliki data.table.
Jadi, Anda harus mempertimbangkan ini (dan mungkin poin lainnya) dan memutuskan berdasarkan apakah trade-off ini dapat Anda terima.
HTH
(1) Perhatikan bahwa efisiensi memori secara langsung berdampak pada kecepatan (terutama saat data semakin besar), karena hambatan dalam banyak kasus adalah memindahkan data dari memori utama ke cache (dan memanfaatkan data dalam cache sebanyak mungkin - kurangi cache yang hilang - sehingga mengurangi pengaksesan memori utama). Tidak membahas detailnya di sini.
sumber
filter()
plus efisiensummarise()
menggunakan pendekatan yang sama yang digunakan dplyr untuk SQL - yaitu membangun ekspresi dan kemudian hanya mengeksekusi satu kali sesuai permintaan. Ini tidak mungkin ini akan diterapkan dalam waktu dekat karena dplyr cukup cepat untuk saya dan menerapkan perencana / pengoptimal kueri relatif sulit.Cobalah.
library(rbenchmark) library(dplyr) library(data.table) benchmark( dplyr = diamondsDT %>% filter(cut != "Fair") %>% group_by(cut) %>% summarize(AvgPrice = mean(price), MedianPrice = as.numeric(median(price)), Count = n()) %>% arrange(desc(Count)), data.table = diamondsDT[cut != "Fair", list(AvgPrice = mean(price), MedianPrice = as.numeric(median(price)), Count = .N), by = cut][order(-Count)])[1:4]
Pada masalah ini tampaknya data.table 2.4x lebih cepat daripada dplyr menggunakan data.table:
test replications elapsed relative 2 data.table 100 2.39 1.000 1 dplyr 100 5.77 2.414
Direvisi berdasarkan komentar Polymerase.
sumber
microbenchmark
paket, saya menemukan bahwa menjalankandplyr
kode OP pada versi asli (bingkai data)diamonds
membutuhkan waktu rata-rata 0,012 detik, sementara itu membutuhkan waktu median 0,024 detik setelah mengonversidiamonds
ke tabel data. Menjalankandata.table
kode G. Grothendieck membutuhkan 0,013 detik. Setidaknya di sistem saya, sepertinyadplyr
dandata.table
memiliki kinerja yang hampir sama. Tetapi mengapa akandplyr
lebih lambat ketika bingkai data pertama kali diubah ke tabel data?Untuk menjawab pertanyaan Anda:
data.table
data.table
sintaks murniDalam banyak kasus, ini akan menjadi kompromi yang dapat diterima bagi mereka yang menginginkan
dplyr
sintaks, meskipun mungkin akan lebih lambat dibandingkandplyr
dengan bingkai data biasa.Satu faktor besar tampaknya
dplyr
akan menyalindata.table
secara default saat pengelompokan. Pertimbangkan (menggunakan microbenchmark):Unit: microseconds expr min lq median diamondsDT[, mean(price), by = cut] 3395.753 4039.5700 4543.594 diamondsDT[cut != "Fair"] 12315.943 15460.1055 16383.738 diamondsDT %>% group_by(cut) %>% summarize(AvgPrice = mean(price)) 9210.670 11486.7530 12994.073 diamondsDT %>% filter(cut != "Fair") 13003.878 15897.5310 17032.609
Pemfilteran memiliki kecepatan yang sebanding, tetapi pengelompokannya tidak. Saya yakin pelakunya adalah baris ini di
dplyr:::grouped_dt
:if (copy) { data <- data.table::copy(data) }
di mana
copy
defaultnya keTRUE
(dan tidak bisa dengan mudah diubah menjadi FALSE yang bisa saya lihat). Ini mungkin tidak memperhitungkan 100% perbedaan, tetapi overhead umum saja pada sesuatu yang ukurannyadiamonds
paling mungkin bukanlah perbedaan penuh.Persoalannya adalah agar memiliki tata bahasa yang konsisten,
dplyr
lakukan pengelompokan dalam dua tahap. Ini pertama-tama menetapkan kunci pada salinan tabel data asli yang cocok dengan grup, dan baru kemudian mengelompokkan.data.table
hanya mengalokasikan memori untuk grup hasil terbesar, yang dalam hal ini hanya satu baris, sehingga membuat perbedaan besar dalam berapa banyak memori yang perlu dialokasikan.FYI, jika ada yang peduli, saya menemukan ini dengan menggunakan
treeprof
(install_github("brodieg/treeprof")
), penampil pohon eksperimental (dan masih sangat banyak alfa) untukRprof
keluaran:Catatan di atas saat ini hanya berfungsi di macs AFAIK. Selain itu, sayangnya,
Rprof
merekam panggilan jenis inipackagename::funname
sebagai anonim sehingga sebenarnya bisa menjadi salah satu dan semuadatatable::
panggilan di dalamnyagrouped_dt
yang bertanggung jawab, tetapi dari pengujian cepat sepertinyadatatable::copy
itu yang paling penting.Meskipun demikian, Anda dapat dengan cepat melihat bagaimana tidak ada banyak overhead di sekitar
[.data.table
panggilan, tetapi ada juga cabang yang benar-benar terpisah untuk pengelompokan.EDIT : untuk mengkonfirmasi penyalinan:
> tracemem(diamondsDT) [1] "<0x000000002747e348>" > diamondsDT %>% group_by(cut) %>% summarize(AvgPrice = mean(price)) tracemem[0x000000002747e348 -> 0x000000002a624bc0]: <Anonymous> grouped_dt group_by_.data.table group_by_ group_by <Anonymous> freduce _fseq eval eval withVisible %>% Source: local data table [5 x 2] cut AvgPrice 1 Fair 4358.758 2 Good 3928.864 3 Very Good 3981.760 4 Premium 4584.258 5 Ideal 3457.542 > diamondsDT[, mean(price), by = cut] cut V1 1: Ideal 3457.542 2: Premium 4584.258 3: Good 3928.864 4: Very Good 3981.760 5: Fair 4358.758 > untracemem(diamondsDT)
sumber
Anda dapat menggunakan dtplyr sekarang, yang merupakan bagian dari tidyverse . Ini memungkinkan Anda untuk menggunakan pernyataan gaya dplyr seperti biasa, tetapi menggunakan evaluasi malas dan menerjemahkan pernyataan Anda ke kode data.table di bawah tenda. Overhead dalam penerjemahan minimal, tetapi Anda mendapatkan semua, jika tidak, sebagian besar manfaat data.table. Detail lebih lanjut di repo git resmi di sini dan halaman tidyverse .
sumber