Tôi sẽ cố gắng đưa ra những hướng dẫn tốt nhất của mình nhưng không dễ vì người ta cần phải làm quen với tất cả {data.table}, {dplyr}, {dtplyr} và cả {cơ sở R}. Tôi sử dụng {data.table} và nhiều gói {tidy-world} (ngoại trừ {dplyr}). Yêu cả hai, mặc dù tôi thích cú pháp của data.table hơn dplyr's. Tôi hy vọng tất cả các gói thế giới gọn gàng sẽ sử dụng {dtplyr} hoặc {data.table} làm phụ trợ bất cứ khi nào cần thiết.
Như với bất kỳ bản dịch nào khác (nghĩ dplyr-to-sparkly / SQL), có những thứ có thể hoặc không thể dịch, ít nhất là cho đến bây giờ. Ý tôi là, có thể một ngày nào đó {dtplyr} có thể làm cho nó được dịch 100%, ai biết được. Danh sách dưới đây không đầy đủ và cũng không chính xác 100% vì tôi sẽ cố gắng hết sức để trả lời dựa trên kiến thức của mình về các chủ đề / gói / vấn đề / v.v.
Điều quan trọng, đối với những câu trả lời không hoàn toàn chính xác, tôi hy vọng nó cung cấp cho bạn một số hướng dẫn về khía cạnh nào của {data.table} mà bạn nên chú ý và so sánh nó với {dtplyr} và tự mình tìm ra câu trả lời. Đừng coi những câu trả lời này là đương nhiên.
Và, tôi hy vọng bài đăng này có thể được sử dụng như một trong những tài nguyên cho tất cả người dùng / người tạo {dplyr}, {data.table} hoặc {dtplyr} để thảo luận và hợp tác và làm cho #RStats trở nên tốt hơn.
{data.table} không chỉ được sử dụng cho các hoạt động hiệu quả nhanh & bộ nhớ. Có nhiều người, bao gồm cả bản thân tôi, thích cú pháp tao nhã của {data.table}. Nó cũng bao gồm các hoạt động nhanh khác như các hàm chuỗi thời gian như cán gia đình (nghĩa là frollapply
) được viết bằng C. Nó có thể được sử dụng với bất kỳ chức năng nào, kể cả tidyverse. Tôi sử dụng {data.table} + {purrr} rất nhiều!
Sự phức tạp của hoạt động
Điều này có thể dễ dàng dịch
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} rất nhanh và hiệu quả về bộ nhớ vì (hầu như?) mọi thứ đều được xây dựng từ C với các khái niệm chính về cập nhật theo khóa, khóa (nghĩ SQL) và tối ưu hóa không ngừng của chúng ở mọi nơi trong gói (nghĩa là fifelse
, fread/fread
thứ tự sắp xếp cơ số được thông qua bởi cơ sở R), trong khi đảm bảo cú pháp ngắn gọn và nhất quán, đó là lý do tại sao tôi nghĩ rằng nó thanh lịch.
Từ Giới thiệu đến data.table , các thao tác thao tác dữ liệu chính như tập hợp con, nhóm, cập nhật, nối, v.v được giữ cùng nhau cho
cú pháp ngắn gọn và nhất quán ...
thực hiện phân tích trôi chảy mà không có gánh nặng nhận thức khi phải lập bản đồ cho từng thao tác ...
tự động tối ưu hóa các hoạt động bên trong và rất hiệu quả, bằng cách biết chính xác dữ liệu cần thiết cho mỗi hoạt động, dẫn đến mã rất nhanh và hiệu quả bộ nhớ
Điểm cuối cùng, là một ví dụ,
# 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))]
Chúng tôi tập hợp con đầu tiên trong i để tìm các chỉ số hàng phù hợp trong đó sân bay gốc bằng "JFK" và tháng bằng 6L. Chúng tôi chưa tập hợp toàn bộ data.table tương ứng với các hàng đó.
Bây giờ, chúng tôi nhìn vào j và thấy rằng nó chỉ sử dụng hai cột. Và những gì chúng ta phải làm là tính toán trung bình của chúng (). Do đó, chúng tôi chỉ tập hợp các cột tương ứng với các hàng khớp và tính giá trị trung bình của chúng ().
Vì ba thành phần chính của truy vấn (i, j và by) nằm cùng nhau bên trong [...] , data.table có thể nhìn thấy cả ba và tối ưu hóa truy vấn hoàn toàn trước khi đánh giá, chứ không phải riêng biệt . Do đó, chúng tôi có thể tránh toàn bộ tập hợp con (nghĩa là đặt lại các cột bên cạnh Array_delay và dep_delay), cho cả tốc độ và hiệu quả bộ nhớ.
Do đó, để gặt hái những lợi ích của {data.table}, bản dịch của {dtplr} phải chính xác theo các khía cạnh đó. Các hoạt động càng phức tạp, các bản dịch càng khó. Đối với các hoạt động đơn giản như trên, nó chắc chắn có thể dễ dàng dịch. Đối với những cái phức tạp hoặc những thứ không được {dtplyr} hỗ trợ, bạn phải tự tìm hiểu như đã đề cập ở trên, người ta phải so sánh cú pháp và điểm chuẩn được dịch và là các gói liên quan quen thuộc.
Đối với các hoạt động phức tạp hoặc các hoạt động không được hỗ trợ, tôi có thể cung cấp một số ví dụ bên dưới. Một lần nữa, tôi chỉ đang cố gắng hết sức. Hãy nhẹ nhàng với tôi.
Cập nhật theo tham chiếu
Tôi sẽ không đi vào phần giới thiệu / chi tiết nhưng đây là một số liên kết
Tài nguyên chính: Ngữ nghĩa tham khảo
Thêm chi tiết: Hiểu chính xác khi data.table là một tham chiếu đến (so với bản sao của) một data.table khác
Theo tôi, cập nhật theo tham chiếu , tính năng quan trọng nhất của {data.table} và đó là điều làm cho nó nhanh và hiệu quả về bộ nhớ. dplyr::mutate
không hỗ trợ nó theo mặc định. Vì tôi không quen thuộc với {dtplyr}, tôi không chắc có bao nhiêu và những hoạt động nào có thể hoặc không thể được hỗ trợ bởi {dtplyr}. Như đã đề cập ở trên, nó cũng phụ thuộc vào sự phức tạp của các hoạt động, từ đó ảnh hưởng đến các bản dịch.
Có hai cách để sử dụng cập nhật theo tham chiếu trong {data.table}
toán tử gán của {data.table} :=
set
-family: set
, setnames
, setcolorder
, setkey
, setDT
, fsetdiff
, và nhiều hơn nữa nhiều
:=
được sử dụng phổ biến hơn so với set
. Đối với tập dữ liệu lớn và phức tạp, cập nhật theo tham chiếu là chìa khóa để đạt được hiệu quả cao nhất về tốc độ và bộ nhớ. Cách suy nghĩ dễ dàng (không chính xác 100%, vì các chi tiết phức tạp hơn nhiều so với điều này vì nó liên quan đến bản sao cứng / nông và nhiều yếu tố khác), giả sử bạn đang xử lý bộ dữ liệu lớn 10GB, mỗi cột 10 và 1GB . Để thao tác một cột, bạn chỉ cần xử lý 1GB.
Điểm mấu chốt là, với cập nhật theo tham chiếu , bạn chỉ cần xử lý dữ liệu cần thiết. Đó là lý do tại sao khi sử dụng {data.table}, đặc biệt là xử lý tập dữ liệu lớn, chúng tôi sử dụng cập nhật theo tham chiếu mọi lúc mọi nơi có thể. Ví dụ: thao tác tập dữ liệu mô hình lớn
# 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())
)
Hoạt động lồng nhau list(.SD)
có thể không được {dtlyr} hỗ trợ khi người dùng tidyverse sử dụng tidyr::nest
? Vì vậy, tôi không chắc các hoạt động tiếp theo có thể được dịch là cách của {data.table} nhanh hơn & ít bộ nhớ hơn không.
LƯU Ý: kết quả của data.table là "mili giây", dplyr trong "phút"
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>
Có nhiều trường hợp sử dụng cập nhật theo tham chiếu và thậm chí {data.table} người dùng sẽ không sử dụng phiên bản nâng cao của nó mọi lúc vì nó yêu cầu nhiều mã hơn. Cho dù {dtplyr} có hỗ trợ những thứ vượt trội này hay không, bạn phải tự tìm hiểu.
Nhiều cập nhật theo tham chiếu cho cùng chức năng
Tài nguyên chính: Việc gán nhiều cột trong data.table với lapply ()
Điều này liên quan đến hoặc thường được sử dụng :=
hoặc 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(.))])
Theo người tạo ra {data.table} Matt Dowle
(Lưu ý có thể phổ biến hơn đối với vòng lặp được đặt trên một số lượng lớn hàng so với số lượng lớn cột.)
Tham gia + setkey + cập nhật theo tham chiếu
Tôi cần tham gia nhanh với dữ liệu tương đối lớn và các mẫu tham gia tương tự gần đây, vì vậy tôi sử dụng sức mạnh của cập nhật theo tham chiếu , thay vì tham gia bình thường. Khi họ yêu cầu nhiều mã hơn, tôi gói chúng trong gói riêng với đánh giá không chuẩn cho khả năng sử dụng lại và khả năng đọc nơi tôi gọi nó setjoin
.
Tôi đã thực hiện một số điểm chuẩn ở đây: data.table tham gia + update-by-Reference + setkey
Tóm lược
# 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 ]
LƯU Ý: dplyr::left_join
cũng đã được thử nghiệm và nó chậm nhất với ~ 9.000 ms, sử dụng nhiều bộ nhớ hơn cả {data.table} ' update_by_reference
và setkey_n_update
, nhưng sử dụng ít bộ nhớ hơn so với normal_join của {data.table}. Nó tiêu thụ khoảng ~ 2.0GB bộ nhớ. Tôi không bao gồm nó vì tôi chỉ muốn tập trung vào {data.table}.
Phát hiện chính
setkey + update
và update
nhanh hơn là ~ 11 và ~ 6,5 lần normal join
, tương ứng
- trong lần tham gia đầu tiên, hiệu suất của
setkey + update
nó tương tự update
như chi phí setkey
phần lớn bù đắp cho hiệu suất của chính nó
- khi tham gia lần thứ hai và sau đó,
setkey
không bắt buộc, setkey + update
nhanh hơn update
~ 1,8 lần (hoặc nhanh hơn normal join
~ 11 lần)
Ví dụ
Đối với performant & bộ nhớ hiệu quả tham gia, sử dụng một trong hai update
hoặc setkey + update
, nơi sau này là nhanh hơn với chi phí của mã hơn.
Chúng ta hãy xem một số mã giả , cho ngắn gọn. Các logic là như nhau.
Đối với một hoặc một vài cột
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, ...) ]
Đối với nhiều cột
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) ) ]
Trình bao bọc để kết nối nhanh và hiệu quả với bộ nhớ ... nhiều người trong số họ ... có kiểu kết nối tương tự, bọc họ như setjoin
trên - có update
- có hoặc không cósetkey
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(...)
Với setkey
, đối số on
có thể được bỏ qua. Nó cũng có thể được bao gồm để dễ đọc, đặc biệt là để cộng tác với những người khác.
Hoạt động hàng lớn
- như đã đề cập ở trên, sử dụng
set
- điền trước bảng của bạn, sử dụng các kỹ thuật cập nhật theo tham chiếu
- tập hợp con sử dụng khóa (tức là
setkey
)
Tài nguyên liên quan: Thêm một hàng bằng cách tham chiếu ở cuối đối tượng data.table
Tóm tắt cập nhật theo tham chiếu
Đây chỉ là một số trường hợp sử dụng cập nhật theo tham chiếu . Chúng còn nhiều nữa.
Như bạn có thể thấy, đối với việc sử dụng nâng cao để xử lý dữ liệu lớn, có nhiều trường hợp sử dụng và kỹ thuật sử dụng cập nhật theo tham chiếu cho tập dữ liệu lớn. Nó không dễ sử dụng trong {data.table} và liệu {dtplyr} có hỗ trợ hay không, bạn có thể tự mình tìm hiểu.
Tôi tập trung vào cập nhật theo tham chiếu trong bài đăng này vì tôi nghĩ đó là tính năng mạnh nhất của {data.table} cho các hoạt động hiệu quả nhanh & bộ nhớ. Điều đó nói rằng, có rất nhiều, nhiều khía cạnh khác làm cho nó rất hiệu quả và tôi nghĩ rằng nó không được hỗ trợ bởi {dtplyr}.
Các khía cạnh quan trọng khác
Những gì được / không được hỗ trợ, nó cũng phụ thuộc vào độ phức tạp của các hoạt động và liệu nó có liên quan đến tính năng gốc của data.table như cập nhật theo tham chiếu hay không setkey
. Và liệu mã dịch có phải là mã hiệu quả hơn (mã mà người dùng data.table sẽ viết) cũng là một yếu tố khác (tức là mã được dịch, nhưng nó có phải là phiên bản hiệu quả không?). Nhiều thứ được kết nối với nhau.
setkey
. Xem Khóa và tập hợp con dựa trên tìm kiếm nhị phân nhanh
- Chỉ số phụ và lập chỉ mục tự động
- Sử dụng .SD để phân tích dữ liệu
- chức năng chuỗi thời gian: suy nghĩ
frollapply
. chức năng lăn, cốt liệu lăn, cửa sổ trượt, di chuyển trung bình
- tham gia , tham gia không bình đẳng , (một số) tham gia "chéo"
- {data.table} đã xây dựng nền tảng về hiệu quả tốc độ và bộ nhớ, trong tương lai, nó có thể mở rộng để bao gồm nhiều chức năng (như cách họ triển khai các hàm chuỗi thời gian được đề cập ở trên)
- nói chung, các hoạt động phức tạp hơn trên của data.table
i
, j
hoặc by
hoạt động (bạn có thể sử dụng hầu hết các biểu thức trong đó), tôi nghĩ rằng các khó hơn các bản dịch, đặc biệt là khi nó kết hợp với update-by-tài liệu tham khảo , setkey
và data.table bản địa khác chức năng nhưfrollapply
- Một điểm khác liên quan đến việc sử dụng cơ sở R hoặc tidyverse. Tôi sử dụng cả data.table + tidyverse (ngoại trừ dplyr / readr / tidyr). Đối với các hoạt động lớn, tôi thường điểm chuẩn, ví dụ, các hàm
stringr::str_*
gia đình và cơ sở R và tôi thấy cơ sở R nhanh hơn ở một mức độ nhất định và sử dụng các hàm đó. Vấn đề là, đừng giữ mình chỉ gọn gàng hoặc data.table hoặc ..., khám phá các tùy chọn khác để hoàn thành công việc.
Nhiều khía cạnh trong số này có liên quan đến nhau với các điểm được đề cập ở trên
Bạn có thể tìm hiểu xem {dtplyr} có hỗ trợ các hoạt động này hay không khi chúng được kết hợp.
Một thủ thuật hữu ích khác khi xử lý tập dữ liệu nhỏ hoặc lớn, trong phiên tương tác, {data.table} thực sự đúng với lời hứa giảm chương trình và tính toán thời gian rất nhiều.
Khóa cài đặt cho biến được sử dụng lặp đi lặp lại cho cả tốc độ và 'tên siêu tăng áp' (tập hợp con mà không chỉ định tên biến).
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, ...]
Nếu các hoạt động của bạn chỉ liên quan đến những hoạt động đơn giản như trong ví dụ đầu tiên, {dtplyr} có thể hoàn thành công việc. Đối với những cái phức tạp / không được hỗ trợ, bạn có thể sử dụng hướng dẫn này để so sánh các bản dịch của {dtplyr} với cách người dùng data.table dày dạn sẽ mã hóa theo cách nhanh và hiệu quả bộ nhớ với cú pháp tao nhã của data.table. Dịch không có nghĩa là cách hiệu quả nhất vì có thể có các kỹ thuật khác nhau để xử lý các trường hợp dữ liệu lớn khác nhau. Đối với tập dữ liệu lớn hơn nữa, bạn có thể kết hợp {data.table} với {đĩa.frame} , {fst} và { Drainke } và các gói tuyệt vời khác để tận dụng tốt nhất. Ngoài ra còn có một {big.data.table} nhưng hiện tại nó không hoạt động.
Tôi hy vọng nó sẽ giúp tất cả mọi người. Chúc một ngày tốt lành
dplyr
đó bạn không thể làm tốtdata.table
? Nếu không, chuyển sangdata.table
sẽ tốt hơndtplyr
.