Tôi không thể làm gì với dtplyr mà tôi có thể trong data.table


9

Tôi có nên đầu tư công sức học tập của tôi cho dữ liệu tranh cãi trong R, đặc biệt là giữa dplyr, dtplyrdata.table?

  • Tôi sử dụng dplyrchủ yếu, nhưng khi dữ liệu quá lớn mà tôi sẽ sử dụng data.table, đó là một điều hiếm khi xảy ra. Vì vậy, bây giờ dtplyrv1.0 đã được phát hành dưới dạng giao diện data.table, nhìn bề ngoài, có vẻ như tôi không bao giờ phải lo lắng về việc sử dụng data.tablegiao diện nữa.

  • Vì vậy, các tính năng hoặc khía cạnh hữu ích nhất của data.tableđiều đó không thể được thực hiện bằng cách sử dụng dtplyrtại thời điểm này và có thể sẽ không bao giờ được thực hiện với dtplyr?

  • Trên mặt của nó, dplyrvới những lợi ích của data.tablenó làm cho âm thanh như dtplyrsẽ vượt qua dplyr. Sẽ có bất kỳ lý do để sử dụng dplyrmột khi dtplyrđã hoàn toàn trưởng thành?

Lưu ý: Tôi không hỏi về dplyrvs data.table(như trong data.table vs dplyr: người ta có thể làm điều gì đó tốt mà người kia không thể hoặc làm không tốt? ), Nhưng cho rằng người này được ưu tiên hơn người khác cho một vấn đề cụ thể, tại sao lại không ' t dtplyrlà công cụ để sử dụng.


1
Có điều gì bạn có thể làm tốt trong dplyrđó bạn không thể làm tốt data.table? Nếu không, chuyển sang data.tablesẽ tốt hơn dtplyr.
sindri_baldur

2
Từ dtplyrreadme, 'Một số data.tablebiểu thức không có dplyrtương đương trực tiếp . Ví dụ: không có cách nào để thể hiện sự tham gia chéo hoặc lăn với dplyr. ' và 'Để phù hợp với dplyrngữ nghĩa, mutate() không sửa đổi tại chỗ theo mặc định. Điều này có nghĩa là hầu hết các biểu thức liên quan mutate()phải tạo một bản sao không cần thiết nếu bạn đang sử dụng data.tabletrực tiếp. ' Có một cách xung quanh phần thứ hai đó nhưng xem xét mức độ thường xuyên mutateđược sử dụng, đó là một nhược điểm khá lớn trong mắt tôi.
ClancyStats

Câu trả lời:


15

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/freadthứ 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 ().

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::mutatekhô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_joincũ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_referencesetkey_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 + updateupdatenhanh 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 + updatenó tương tự updatenhư chi phí setkeyphần lớn bù đắp cho hiệu suất của chính nó
  • khi tham gia lần thứ hai và sau đó, setkeykhông bắt buộc, setkey + updatenhanh hơn update~ 1,8 lần (hoặc nhanh hơn normal join~ 11 lần)

Hình ảnh

Ví dụ

Đối với performant & bộ nhớ hiệu quả tham gia, sử dụng một trong hai updatehoặ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ư setjointrê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ố oncó 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, jhoặc byhoạ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 , setkeyvà 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

  • sự phức tạp của hoạt động

  • cập nhật bằng cách tham khảo

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ìnhtí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}{ 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


2

Non-Equi tham gia và tham gia lăn đến tâm trí. Dường như không có bất kỳ kế hoạch nào để bao gồm các chức năng tương đương trong dplyr vì vậy không có gì để dtplyr dịch.

Ngoài ra còn có định hình lại (tối ưu hóa dcast và tan tương đương với các chức năng tương tự trong reshape2) cũng không có trong dplyr.

Tất cả các hàm * _if và * _at hiện tại không thể được dịch bằng dtplyr nhưng các hàm này đang hoạt động.


0

Cập nhật một cột khi tham gia Một số thủ thuật .SD Nhiều chức năng f và chúa biết những gì khác vì #rdatitable không chỉ là một thư viện đơn giản và không thể tóm tắt bằng một vài chức năng

Đó là toàn bộ hệ sinh thái

Tôi chưa bao giờ cần dplyr kể từ ngày tôi bắt đầu R. Bởi vì data.table rất tốt

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.