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 kém?


759

Tổng quat

Tôi tương đối quen thuộc data.table, không quá nhiều với dplyr. Tôi đã đọc qua một số dplyrhọa tiết và ví dụ đã xuất hiện trên SO, và cho đến nay kết luận của tôi là:

  1. data.tabledplyrcó thể so sánh về tốc độ, ngoại trừ khi có nhiều nhóm (ví dụ> 10-100K) và trong một số trường hợp khác (xem điểm chuẩn bên dưới)
  2. dplyr có cú pháp dễ tiếp cận hơn
  3. dplyr tóm tắt (hoặc sẽ) các tương tác DB tiềm năng
  4. Có một số khác biệt về chức năng nhỏ (xem "Ví dụ / Cách sử dụng" bên dưới)

Trong suy nghĩ của tôi 2. không chịu nhiều sức nặng vì tôi khá quen thuộc với nó data.table, mặc dù tôi hiểu rằng đối với người dùng mới sử dụng cả hai thì đó sẽ là một yếu tố lớn. Tôi muốn tránh một cuộc tranh luận về vấn đề nào trực quan hơn, vì điều đó không liên quan đến câu hỏi cụ thể của tôi được hỏi từ quan điểm của một người đã quen thuộc data.table. Tôi cũng muốn tránh một cuộc thảo luận về cách "trực quan hơn" dẫn đến phân tích nhanh hơn (chắc chắn là đúng, nhưng một lần nữa, không phải là điều tôi quan tâm nhất ở đây).

Câu hỏi

Điều tôi muốn biết là:

  1. Có các nhiệm vụ phân tích dễ dàng hơn rất nhiều để mã hóa với một hoặc gói khác cho những người quen thuộc với các gói (nghĩa là một số tổ hợp phím được yêu cầu so với mức độ bí truyền được yêu cầu, trong đó ít hơn là một điều tốt).
  2. Có các nhiệm vụ phân tích được thực hiện đáng kể (tức là hơn 2 lần) hiệu quả hơn trong một gói so với gói khác.

Một câu hỏi SO gần đây khiến tôi suy nghĩ về vấn đề này nhiều hơn một chút, bởi vì cho đến thời điểm đó tôi không nghĩ dplyrsẽ cung cấp nhiều hơn những gì tôi có thể làm data.table. Đây là dplyrgiải pháp (dữ liệu ở cuối Q):

dat %.%
  group_by(name, job) %.%
  filter(job != "Boss" | year == min(year)) %.%
  mutate(cumu_job2 = cumsum(job2))

Điều đó tốt hơn nhiều so với nỗ lực hack của tôi tại một data.tablegiải pháp. Điều đó nói rằng, data.tablecác giải pháp tốt cũng khá tốt (cảm ơn Jean-Robert, Arun, và lưu ý ở đây tôi ủng hộ tuyên bố duy nhất trên giải pháp tối ưu nhất hoàn toàn):

setDT(dat)[,
  .SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)], 
  by=list(id, job)
]

Cú pháp cho cái sau có vẻ rất bí truyền, nhưng thực ra nó khá đơn giản nếu bạn đã quen data.table(nghĩa là không sử dụng một số thủ thuật bí truyền hơn).

Lý tưởng nhất là những gì tôi muốn thấy là một số ví dụ dplyrhay data.tablelà cách thức ngắn gọn hơn hoặc thực hiện tốt hơn đáng kể.

Ví dụ

Sử dụng
  • dplyrkhông cho phép các hoạt động được nhóm trả về số lượng hàng tùy ý (từ câu hỏi của eddi , lưu ý: điều này có vẻ như sẽ được triển khai trong dplyr 0.5 , ngoài ra, @beginneR cho thấy một công việc tiềm năng sử dụng dotrong câu trả lời cho câu hỏi của @ eddi).
  • data.tablehỗ trợ tham gia cán (cảm ơn @dholstius) cũng như tham gia chồng chéo
  • data.tablenội bộ tối ưu hóa các biểu thức của biểu mẫu DT[col == value]hoặc DT[col %in% values]cho tốc độ thông qua lập chỉ mục tự động sử dụng tìm kiếm nhị phân trong khi sử dụng cùng một cú pháp cơ sở R. Xem ở đây để biết thêm chi tiết và một điểm chuẩn nhỏ.
  • dplyrcung cấp các phiên bản đánh giá tiêu chuẩn của các chức năng (ví dụ regroup, summarize_each_) có thể đơn giản hóa việc sử dụng theo chương trình dplyr(lưu ý sử dụng theo chương trình data.tablelà hoàn toàn có thể, chỉ cần một số suy nghĩ cẩn thận, thay thế / trích dẫn, v.v., ít nhất là theo hiểu biết của tôi)
Điểm chuẩn

Dữ liệu

Đây là ví dụ đầu tiên tôi chỉ ra trong phần câu hỏi.

dat <- structure(list(id = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 
2L, 2L, 2L, 2L, 2L, 2L), name = c("Jane", "Jane", "Jane", "Jane", 
"Jane", "Jane", "Jane", "Jane", "Bob", "Bob", "Bob", "Bob", "Bob", 
"Bob", "Bob", "Bob"), year = c(1980L, 1981L, 1982L, 1983L, 1984L, 
1985L, 1986L, 1987L, 1985L, 1986L, 1987L, 1988L, 1989L, 1990L, 
1991L, 1992L), job = c("Manager", "Manager", "Manager", "Manager", 
"Manager", "Manager", "Boss", "Boss", "Manager", "Manager", "Manager", 
"Boss", "Boss", "Boss", "Boss", "Boss"), job2 = c(1L, 1L, 1L, 
1L, 1L, 1L, 0L, 0L, 1L, 1L, 1L, 0L, 0L, 0L, 0L, 0L)), .Names = c("id", 
"name", "year", "job", "job2"), class = "data.frame", row.names = c(NA, 
-16L))

9
Giải pháp tương tự khi đọc với dplyrmột giải pháp là:as.data.table(dat)[, .SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)], by = list(name, job)]
eddi

7
Đối với # 1 cả hai dplyrdata.tablecác đội đang làm việc trên điểm chuẩn, vì vậy một câu trả lời sẽ có ở một số điểm. # 2 (cú pháp) imO hoàn toàn sai, nhưng điều đó rõ ràng mạo hiểm vào lãnh thổ ý kiến, vì vậy tôi cũng đang bỏ phiếu để đóng.
eddi

13
tốt, một lần nữa imO, tập hợp các vấn đề được thể hiện rõ ràng hơn (d)plyrcó biện pháp 0
eddi

28
@BrodieG một điều thực sự làm tôi bực mình về cả hai dplyrplyrliên quan đến cú pháp và về cơ bản là lý do chính khiến tôi không thích cú pháp của họ, là tôi phải học quá nhiều (đọc nhiều hơn 1) hàm bổ sung (với các tên vẫn còn đừng có ý nghĩa với tôi), hãy nhớ những gì họ làm, những gì họ đưa ra, v.v. Điều đó luôn luôn là một bước ngoặt lớn đối với tôi từ triết lý plyr.
eddi

43
@eddi [tặc lưỡi] một điều thực sự làm tôi bực mình về cú pháp data.table là tôi phải tìm hiểu làm thế nào quá nhiều đối số chức năng tương tác và ý nghĩa của các phím tắt khó hiểu (ví dụ .SD). [nghiêm túc] Tôi nghĩ rằng đây là những khác biệt về thiết kế hợp pháp sẽ thu hút những người khác nhau
hadley

Câu trả lời:


532

Chúng ta cần phải che ít nhất những khía cạnh này để cung cấp một câu trả lời toàn diện / so sánh (không theo thứ tự đặc biệt quan trọng): Speed, Memory usage, SyntaxFeatures.

Mục đích của tôi là bao quát từng thứ một cách rõ ràng nhất có thể theo quan điểm data.table.

Lưu ý: trừ khi được đề cập rõ ràng bằng cách khác, bằng cách tham khảo dplyr, chúng tôi đề cập đến giao diện data.frame của dplyr có phần bên trong trong C ++ bằng Rcpp.


Cú pháp data.table phù hợp ở dạng của nó - DT[i, j, by]. Để giữ i, jbycùng nhau là do thiết kế. Bằng cách giữ các hoạt động liên quan với nhau, nó cho phép dễ dàng tối ưu hóa các hoạt động về tốc độ và quan trọng hơn là sử dụng bộ nhớ , đồng thời cung cấp một số tính năng mạnh mẽ , trong khi vẫn duy trì tính nhất quán trong cú pháp.

1. Tốc độ

Khá nhiều điểm chuẩn (mặc dù chủ yếu là về các hoạt động nhóm) đã được thêm vào câu hỏi đã hiển thị dữ liệu. Có thể nhanh hơn dplyr vì số lượng nhóm và / hoặc hàng được nhóm theo nhóm tăng, bao gồm cả điểm chuẩn của Matt khi nhóm từ 10 triệu đến 2 tỷ hàng (RAM 100 GB) trên 100 - 10 triệu nhóm và các nhóm nhóm khác nhau, cũng có thể so sánh pandas. Xem thêm điểm chuẩn cập nhật , bao gồm Sparkpydatatablelà tốt.

Về điểm chuẩn, sẽ rất tuyệt nếu bao gồm cả các khía cạnh còn lại:

  • Nhóm các hoạt động liên quan đến một tập hợp con của các hàng - tức là các DT[x > val, sum(y), by = z]hoạt động loại.

  • Điểm chuẩn các hoạt động khác như cập nhậttham gia .

  • Ngoài ra dấu chân bộ nhớ chuẩn cho mỗi hoạt động ngoài thời gian chạy.

2. Sử dụng bộ nhớ

  1. Các hoạt động liên quan filter()hoặc slice()trong dplyr có thể là bộ nhớ không hiệu quả (trên cả data.frames và data.tables). Xem bài này .

    Lưu ý rằng nhận xét của Hadley nói về tốc độ (dplyr rất nhanh đối với anh ta), trong khi mối quan tâm chính ở đây là bộ nhớ .

  2. Giao diện data.table tại thời điểm này cho phép một người sửa đổi / cập nhật các cột theo tham chiếu (lưu ý rằng chúng ta không cần gán lại kết quả cho một biến).

    # sub-assign by reference, updates 'y' in-place
    DT[x >= 1L, y := NA]
    

    Nhưng dplyr sẽ không bao giờ cập nhật bằng cách tham khảo. Tương đương dplyr sẽ là (lưu ý rằng kết quả cần được gán lại):

    # copies the entire 'y' column
    ans <- DF %>% mutate(y = replace(y, which(x >= 1L), NA))
    

    Một mối quan tâm cho điều này là tính minh bạch tham chiếu . Cập nhật một đối tượng data.table bằng cách tham chiếu, đặc biệt là trong một hàm có thể không phải lúc nào cũng mong muốn. Nhưng đây là một tính năng cực kỳ hữu ích: xem bài này và bài này cho các trường hợp thú vị. Và chúng tôi muốn giữ nó.

    Do đó, chúng tôi đang hướng tới shallow()chức năng xuất trong data.table sẽ cung cấp cho người dùng cả hai khả năng . Ví dụ: nếu không muốn sửa đổi dữ liệu đầu vào.table trong một hàm, thì người ta có thể thực hiện:

    foo <- function(DT) {
        DT = shallow(DT)          ## shallow copy DT
        DT[, newcol := 1L]        ## does not affect the original DT 
        DT[x > 2L, newcol := 2L]  ## no need to copy (internally), as this column exists only in shallow copied DT
        DT[x > 2L, x := 3L]       ## have to copy (like base R / dplyr does always); otherwise original DT will 
                                  ## also get modified.
    }
    

    Bằng cách không sử dụng shallow(), chức năng cũ được giữ lại:

    bar <- function(DT) {
        DT[, newcol := 1L]        ## old behaviour, original DT gets updated by reference
        DT[x > 2L, x := 3L]       ## old behaviour, update column x in original DT.
    }
    

    Bằng cách tạo một bản sao nông bằng cách sử dụng shallow(), chúng tôi hiểu rằng bạn không muốn sửa đổi đối tượng ban đầu. Chúng tôi xử lý tất cả mọi thứ trong nội bộ để đảm bảo rằng đồng thời đảm bảo sao chép các cột bạn chỉ sửa đổi khi thực sự cần thiết . Khi được thực hiện, điều này sẽ giải quyết hoàn toàn vấn đề minh bạch tham chiếu trong khi cung cấp cho người dùng cả hai khả năng.

    Ngoài ra, một khi shallow()được xuất giao diện data.table của dplyr nên tránh hầu hết tất cả các bản sao. Vì vậy, những người thích cú pháp của dplyr có thể sử dụng nó với data.tables.

    Nhưng nó vẫn sẽ thiếu nhiều tính năng mà data.table cung cấp, bao gồm phân bổ (phụ) theo tham chiếu.

  3. Tổng hợp trong khi tham gia:

    Giả sử bạn có hai data.tables như sau:

    DT1 = data.table(x=c(1,1,1,1,2,2,2,2), y=c("a", "a", "b", "b"), z=1:8, key=c("x", "y"))
    #    x y z
    # 1: 1 a 1
    # 2: 1 a 2
    # 3: 1 b 3
    # 4: 1 b 4
    # 5: 2 a 5
    # 6: 2 a 6
    # 7: 2 b 7
    # 8: 2 b 8
    DT2 = data.table(x=1:2, y=c("a", "b"), mul=4:3, key=c("x", "y"))
    #    x y mul
    # 1: 1 a   4
    # 2: 2 b   3
    

    Và bạn muốn nhận được sum(z) * mulcho mỗi hàng trong DT2khi tham gia theo cột x,y. Chúng ta có thể:

    • 1) tổng hợp DT1để có được sum(z), 2) thực hiện nối và 3) nhân (hoặc)

      # data.table way
      DT1[, .(z = sum(z)), keyby = .(x,y)][DT2][, z := z*mul][]
      
      # dplyr equivalent
      DF1 %>% group_by(x, y) %>% summarise(z = sum(z)) %>% 
          right_join(DF2) %>% mutate(z = z * mul)
      
    • 2) thực hiện tất cả trong một lần (sử dụng by = .EACHItính năng):

      DT1[DT2, list(z=sum(z) * mul), by = .EACHI]

    Lợi thế là gì?

    • Chúng ta không phải phân bổ bộ nhớ cho kết quả trung gian.

    • Chúng tôi không phải nhóm / băm hai lần (một cho tổng hợp và một để tham gia).

    • Và quan trọng hơn, hoạt động mà chúng tôi muốn thực hiện là rõ ràng bằng cách xem jtrong (2).

    Kiểm tra bài này để được giải thích chi tiết by = .EACHI. Không có kết quả trung gian nào được cụ thể hóa và phép nối + tổng hợp được thực hiện tất cả trong một lần.

    Có một cái nhìn về điều này , điều này và bài viết này cho các tình huống sử dụng thực sự.

    Trong dplyrbạn sẽ phải tham gia và tổng hợp hoặc tổng hợp trước rồi sau đó tham gia , cả hai đều không hiệu quả, về mặt bộ nhớ (lần lượt chuyển thành tốc độ).

  4. Cập nhật và tham gia:

    Xem xét mã data.table được hiển thị dưới đây:

    DT1[DT2, col := i.mul]

    thêm / cập nhật DT1cột colvới multừ DT2trên các hàng có DT2cột chính khớp với DT1. Tôi không nghĩ rằng có một sự tương đương chính xác của thao tác này dplyr, nghĩa là, nếu không tránh một *_jointhao tác, sẽ phải sao chép toàn bộ DT1chỉ để thêm một cột mới vào nó, điều này là không cần thiết.

    Kiểm tra bài này cho một kịch bản sử dụng thực sự.

Tóm lại, điều quan trọng là phải nhận ra rằng mọi bit tối ưu hóa đều có vấn đề. Như Grace Hopper sẽ nói, Hãy quan tâm đến nano giây của bạn !

3. Cú pháp

Bây giờ chúng ta hãy xem cú pháp . Hadley bình luận ở đây :

Các bảng dữ liệu cực kỳ nhanh nhưng tôi nghĩ rằng cách xử lý của chúng làm cho việc học khó hơnmã sử dụng nó khó đọc hơn sau khi bạn viết nó ...

Tôi thấy nhận xét này là vô nghĩa vì nó rất chủ quan. Những gì chúng ta có thể có thể thử là tương phản tính nhất quán trong cú pháp . Chúng tôi sẽ so sánh cú pháp data.table và dplyr cạnh nhau.

Chúng tôi sẽ làm việc với dữ liệu giả được hiển thị dưới đây:

DT = data.table(x=1:10, y=11:20, z=rep(1:2, each=5))
DF = as.data.frame(DT)
  1. Hoạt động tổng hợp / cập nhật cơ bản.

    # case (a)
    DT[, sum(y), by = z]                       ## data.table syntax
    DF %>% group_by(z) %>% summarise(sum(y)) ## dplyr syntax
    DT[, y := cumsum(y), by = z]
    ans <- DF %>% group_by(z) %>% mutate(y = cumsum(y))
    
    # case (b)
    DT[x > 2, sum(y), by = z]
    DF %>% filter(x>2) %>% group_by(z) %>% summarise(sum(y))
    DT[x > 2, y := cumsum(y), by = z]
    ans <- DF %>% group_by(z) %>% mutate(y = replace(y, which(x > 2), cumsum(y)))
    
    # case (c)
    DT[, if(any(x > 5L)) y[1L]-y[2L] else y[2L], by = z]
    DF %>% group_by(z) %>% summarise(if (any(x > 5L)) y[1L] - y[2L] else y[2L])
    DT[, if(any(x > 5L)) y[1L] - y[2L], by = z]
    DF %>% group_by(z) %>% filter(any(x > 5L)) %>% summarise(y[1L] - y[2L])
    
    • Cú pháp data.table là nhỏ gọn và khá dài dòng của dplyr. Mọi thứ tương đương ít nhiều trong trường hợp (a).

    • Trong trường hợp (b), chúng tôi đã phải sử dụng filter()trong dplyr trong khi tóm tắt . Nhưng trong khi cập nhật , chúng tôi phải di chuyển logic bên trong mutate(). Tuy nhiên, trong data.table, chúng tôi biểu thị cả hai hoạt động với cùng một logic - hoạt động trên các hàng trong đó x > 2, nhưng trong trường hợp đầu tiên, get sum(y), trong trường hợp thứ hai cập nhật các hàng đó yvới tổng tích lũy của nó.

      Đây là những gì chúng tôi có nghĩa là khi chúng tôi nói các DT[i, j, by]hình thức là phù hợp .

    • Tương tự như vậy trong trường hợp (c), khi chúng ta có if-elseđiều kiện, chúng ta có thể diễn đạt logic "nguyên trạng" trong cả data.table và dplyr. Tuy nhiên, nếu chúng tôi chỉ muốn trả về những hàng có ifđiều kiện thỏa mãn và bỏ qua nếu không, chúng tôi không thể sử dụng summarise()trực tiếp (AFAICT). Chúng tôi phải filter()đầu tiên và sau đó tóm tắt vì summarise()luôn mong đợi một giá trị duy nhất .

      Mặc dù nó trả về kết quả tương tự, nhưng sử dụng filter()ở đây làm cho hoạt động thực tế ít rõ ràng hơn.

      Nó cũng có thể được sử dụng filter()trong trường hợp đầu tiên (dường như không rõ ràng đối với tôi), nhưng quan điểm của tôi là chúng ta không cần phải làm vậy.

  2. Tổng hợp / cập nhật trên nhiều cột

    # case (a)
    DT[, lapply(.SD, sum), by = z]                     ## data.table syntax
    DF %>% group_by(z) %>% summarise_each(funs(sum)) ## dplyr syntax
    DT[, (cols) := lapply(.SD, sum), by = z]
    ans <- DF %>% group_by(z) %>% mutate_each(funs(sum))
    
    # case (b)
    DT[, c(lapply(.SD, sum), lapply(.SD, mean)), by = z]
    DF %>% group_by(z) %>% summarise_each(funs(sum, mean))
    
    # case (c)
    DT[, c(.N, lapply(.SD, sum)), by = z]     
    DF %>% group_by(z) %>% summarise_each(funs(n(), mean))
    • Trong trường hợp (a), các mã tương đương ít nhiều. data.table sử dụng hàm cơ sở quen thuộc lapply(), trong khi dplyrgiới thiệu *_each()cùng với một loạt các hàm funs().

    • data.table :=yêu cầu cung cấp tên cột, trong khi dplyr tự động tạo tên.

    • Trong trường hợp (b), cú pháp của dplyr tương đối đơn giản. Cải thiện tập hợp / cập nhật trên nhiều chức năng nằm trong danh sách của data.table.

    • Trong trường hợp (c), dplyr sẽ trả về n()số lần nhiều cột, thay vì chỉ một lần. Trong data.table, tất cả những gì chúng ta cần làm là trả về một danh sách j. Mỗi yếu tố của danh sách sẽ trở thành một cột trong kết quả. Vì vậy, chúng ta có thể sử dụng, một lần nữa, hàm cơ sở quen thuộc c()để nối .Nvới một hàm listtrả về a list.

    Lưu ý: Một lần nữa, trong data.table, tất cả những gì chúng ta cần làm là trả về một danh sách j. Mỗi yếu tố của danh sách sẽ trở thành một cột trong kết quả. Bạn có thể sử dụng c(), as.list(), lapply(), list()chức năng vv ... cơ sở để thực hiện điều này, mà không cần phải học bất kỳ chức năng mới.

    Bạn sẽ chỉ cần học các biến đặc biệt - .N.SDít nhất. Tương đương trong dplyr là n().

  3. Tham gia

    dplyr cung cấp các hàm riêng biệt cho từng loại tham gia trong đó data.table cho phép các phép nối sử dụng cùng một cú pháp DT[i, j, by](và với lý do). Nó cũng cung cấp một merge.data.table()chức năng tương đương như là một thay thế.

    setkey(DT1, x, y)
    
    # 1. normal join
    DT1[DT2]            ## data.table syntax
    left_join(DT2, DT1) ## dplyr syntax
    
    # 2. select columns while join    
    DT1[DT2, .(z, i.mul)]
    left_join(select(DT2, x, y, mul), select(DT1, x, y, z))
    
    # 3. aggregate while join
    DT1[DT2, .(sum(z) * i.mul), by = .EACHI]
    DF1 %>% group_by(x, y) %>% summarise(z = sum(z)) %>% 
        inner_join(DF2) %>% mutate(z = z*mul) %>% select(-mul)
    
    # 4. update while join
    DT1[DT2, z := cumsum(z) * i.mul, by = .EACHI]
    ??
    
    # 5. rolling join
    DT1[DT2, roll = -Inf]
    ??
    
    # 6. other arguments to control output
    DT1[DT2, mult = "first"]
    ??
    • Một số có thể tìm thấy một chức năng riêng cho mỗi tham gia đẹp hơn nhiều (trái, phải, bên trong, chống, bán, v.v.), trong khi những người khác có thể thích data.table DT[i, j, by], hoặc merge()tương tự như cơ sở R.

    • Tuy nhiên dplyr tham gia làm điều đó. Chỉ có bấy nhiêu thôi. Không có gì ít hơn.

    • data.tables có thể chọn các cột trong khi tham gia (2) và trong dplyr, select()trước tiên bạn sẽ cần trên cả data.frames trước khi tham gia như được hiển thị ở trên. Nếu không, bạn sẽ thực hiện việc nối với các cột không cần thiết chỉ để loại bỏ chúng sau đó và điều đó không hiệu quả.

    • data.tables có thể tổng hợp trong khi tham gia (3) và cũng có thể cập nhật trong khi tham gia (4), sử dụng by = .EACHItính năng. Tại sao phải xem xét toàn bộ kết quả tham gia để thêm / cập nhật chỉ một vài cột?

    • data.table có khả năng cuộn các phép nối (5) - cuộn tiến, LOCF , cuộn lùi, NOCB , gần nhất .

    • data.table cũng có mult =đối số chọn đầu tiên , cuối cùng hoặc tất cả các kết quả khớp (6).

    • data.table có allow.cartesian = TRUEđối số để bảo vệ khỏi các phép nối không hợp lệ ngẫu nhiên.

Một lần nữa, cú pháp phù hợp DT[i, j, by]với các đối số bổ sung cho phép kiểm soát đầu ra hơn nữa.

  1. do()...

    tóm tắt của dplyr được thiết kế đặc biệt cho các hàm trả về một giá trị duy nhất. Nếu hàm của bạn trả về nhiều giá trị / không bằng nhau, bạn sẽ phải dùng đến do(). Bạn phải biết trước về tất cả các hàm trả về giá trị.

    DT[, list(x[1], y[1]), by = z]                 ## data.table syntax
    DF %>% group_by(z) %>% summarise(x[1], y[1]) ## dplyr syntax
    DT[, list(x[1:2], y[1]), by = z]
    DF %>% group_by(z) %>% do(data.frame(.$x[1:2], .$y[1]))
    
    DT[, quantile(x, 0.25), by = z]
    DF %>% group_by(z) %>% summarise(quantile(x, 0.25))
    DT[, quantile(x, c(0.25, 0.75)), by = z]
    DF %>% group_by(z) %>% do(data.frame(quantile(.$x, c(0.25, 0.75))))
    
    DT[, as.list(summary(x)), by = z]
    DF %>% group_by(z) %>% do(data.frame(as.list(summary(.$x))))
    • .SDtương đương là .

    • Trong data.table, bạn có thể ném khá nhiều thứ vào j- điều duy nhất cần nhớ là nó trả về một danh sách để mỗi phần tử của danh sách được chuyển đổi thành một cột.

    • Trong dplyr, không thể làm điều đó. Phải sử dụng do()tùy thuộc vào mức độ chắc chắn của bạn về việc liệu hàm của bạn sẽ luôn trả về một giá trị duy nhất. Và nó khá chậm.

Một lần nữa, cú pháp của data.table phù hợp với DT[i, j, by]. Chúng ta chỉ có thể tiếp tục ném các biểu thức vào jmà không phải lo lắng về những điều này.

Có một cái nhìn vào câu hỏi SO nàycâu hỏi này . Tôi tự hỏi liệu có thể diễn đạt câu trả lời một cách đơn giản bằng cách sử dụng cú pháp của dplyr ...

Tóm lại, tôi đặc biệt nhấn mạnh một số trường hợp trong đó cú pháp của dplyr là không hiệu quả, bị giới hạn hoặc không thực hiện được các thao tác đơn giản. Điều này đặc biệt bởi vì data.table nhận được khá nhiều phản ứng dữ dội về cú pháp "khó đọc / học" hơn (như cách dán / liên kết ở trên). Hầu hết các bài viết bao gồm dplyr nói về hầu hết các hoạt động đơn giản. Và đó là tuyệt vời. Nhưng điều quan trọng là phải nhận ra các hạn chế về cú pháp và tính năng của nó, và tôi vẫn chưa thấy một bài đăng trên đó.

data.table cũng có những điểm kỳ quặc (một số trong đó tôi đã chỉ ra rằng chúng tôi đang cố gắng khắc phục). Chúng tôi cũng đang cố gắng cải thiện sự tham gia của data.table như tôi đã nhấn mạnh ở đây .

Nhưng người ta cũng nên xem xét số lượng các tính năng mà dplyr thiếu so với data.table.

4. Tính năng

Tôi đã chỉ ra hầu hết các tính năng ở đây và cả trong bài viết này. Ngoài ra:

  • fread - trình đọc tệp nhanh đã có sẵn từ lâu.

  • fwrite - một trình soạn thảo tệp nhanh song song hiện có sẵn. Xem bài đăng này để được giải thích chi tiết về việc triển khai và # 1664 để theo dõi các phát triển tiếp theo.

  • Lập chỉ mục tự động - một tính năng tiện dụng khác để tối ưu hóa cú pháp cơ sở R như trong nội bộ.

  • Phân nhóm ad-hoc : dplyrtự động sắp xếp các kết quả bằng cách nhóm các biến trong suốt summarise(), điều này có thể không phải lúc nào cũng mong muốn.

  • Vô số lợi thế trong các phép nối data.table (cho hiệu quả và tốc độ bộ nhớ / cú pháp) được đề cập ở trên.

  • Các phép nối không đẳng thức : Cho phép các phép nối sử dụng các toán tử khác <=, <, >, >=cùng với tất cả các lợi thế khác của phép nối data.table.

  • Tham gia phạm vi chồng chéo đã được triển khai trong data.table gần đây. Kiểm tra bài này để biết tổng quan với điểm chuẩn.

  • setorder() chức năng trong data.table cho phép sắp xếp lại dữ liệu rất nhanh bằng cách tham chiếu.

  • dplyr cung cấp giao diện cho cơ sở dữ liệu bằng cách sử dụng cùng một cú pháp, mà data.table hiện tại không có.

  • data.tablecung cấp các khoản tương đương nhanh hơn của hoạt động thiết lập (được viết bởi Jan Gorecki) - fsetdiff, fintersect, funionfsetequalvới thêm alltham số (như trong SQL).

  • data.table tải sạch không có cảnh báo che giấu và có một cơ chế được mô tả ở đây để [.data.frametương thích khi được chuyển đến bất kỳ gói R nào. dplyr thay đổi các chức năng cơ bản filter, lag[có thể gây ra các vấn đề; ví dụ ở đâyở đây .


Cuối cùng:

  • Trên cơ sở dữ liệu - không có lý do tại sao data.table không thể cung cấp giao diện tương tự, nhưng hiện tại đây không phải là ưu tiên. Nó có thể bị lỗi nếu người dùng rất thích tính năng đó .. không chắc chắn.

  • Về song song - Mọi thứ đều khó khăn, cho đến khi ai đó đi trước và làm điều đó. Tất nhiên nó sẽ mất nỗ lực (là chủ đề an toàn).

    • Hiện tại, tiến trình đang được thực hiện (trong phiên bản v1.9.7) hướng tới việc song song các bộ phận tiêu tốn thời gian đã biết để tăng hiệu suất sử dụng OpenMP.

9
@bluefeet: Tôi không nghĩ rằng bạn đã làm phần còn lại của chúng tôi bất kỳ dịch vụ tuyệt vời nào bằng cách chuyển cuộc thảo luận đó sang trò chuyện. Tôi có ấn tượng rằng Arun là một trong những nhà phát triển và điều này có thể dẫn đến những hiểu biết hữu ích.
IRTFM

2
Khi tôi đi trò chuyện bằng liên kết của bạn, có vẻ như tất cả các tài liệu sau bình luận bắt đầu "Bạn nên sử dụng bộ lọc" .. đã biến mất. Tôi có thiếu điều gì về cơ chế trò chuyện SO không?
IRTFM

6
Tôi nghĩ rằng mọi nơi bạn đang sử dụng phép gán bằng tham chiếu ( :=), dplyrtương đương cũng nên được sử dụng <-như DF <- DF %>% mutate...thay vì chỉDF %>% mutate...
David Arenburg

4
Về cú pháp. Tôi tin rằng dplyrngười dùng đã sử dụng plyrcú pháp data.tablecó thể dễ dàng hơn , nhưng có thể dễ dàng hơn đối với người dùng đã sử dụng truy vấn cú pháp ngôn ngữ như SQLvà đại số quan hệ đằng sau nó, tất cả là về chuyển đổi dữ liệu dạng bảng. @Arun bạn nên lưu ý rằng các toán tử thiết lập rất dễ thực hiện bằng cách gói data.tablechức năng và tất nhiên mang lại tốc độ đáng kể.
jangorecki

9
Tôi đã đọc bài đăng này rất nhiều lần và nó giúp tôi rất nhiều trong việc hiểu dữ liệu. Có thể sử dụng nó tốt hơn. Tôi, trong hầu hết các trường hợp, thích data.table hơn dplyr hoặc pandas hoặc PL / pgSQL. Tuy nhiên, tôi không thể ngừng nghĩ về cách thể hiện nó. Cú pháp không dễ, rõ ràng hoặc dài dòng. Trên thực tế, ngay cả sau khi tôi đã sử dụng dữ liệu rất nhiều, tôi vẫn thường phải vật lộn để hiểu mã của riêng mình, tôi đã viết theo nghĩa đen một tuần trước. Đây là một ví dụ cuộc sống của một ngôn ngữ chỉ viết. vi.wikipedia.org/wiki/Write-only_lingu Vì vậy, hãy hy vọng, một ngày nào đó chúng ta sẽ có thể sử dụng dplyr trên data.table.
Ufos

385

Đây là nỗ lực của tôi về một câu trả lời toàn diện từ góc độ dplyr, theo đề cương rộng rãi về câu trả lời của Arun (nhưng phần nào được sắp xếp lại dựa trên các ưu tiên khác nhau).

Cú pháp

Có một số sự chủ quan đối với cú pháp, nhưng tôi đứng trước tuyên bố của mình rằng sự trùng khớp của data.table làm cho nó khó học và khó đọc hơn. Điều này một phần vì dplyr đang giải quyết một vấn đề dễ dàng hơn nhiều!

Một điều thực sự quan trọng mà dplyr làm cho bạn là nó ràng buộc các lựa chọn của bạn. Tôi khẳng định rằng hầu hết các vấn đề bảng đơn có thể được giải quyết chỉ bằng năm bộ lọc động từ chính, chọn, thay đổi, sắp xếp và tóm tắt, cùng với trạng từ "theo nhóm". Hạn chế đó là một trợ giúp lớn khi bạn học thao tác dữ liệu, bởi vì nó giúp sắp xếp suy nghĩ của bạn về vấn đề. Trong dplyr, mỗi động từ này được ánh xạ tới một chức năng duy nhất. Mỗi chức năng thực hiện một công việc và dễ hiểu trong sự cô lập.

Bạn tạo ra sự phức tạp bằng cách kết hợp các hoạt động đơn giản này cùng với %>%. Đây là một ví dụ từ một trong những bài đăng Arun liên kết đến :

diamonds %>%
  filter(cut != "Fair") %>%
  group_by(cut) %>%
  summarize(
    AvgPrice = mean(price),
    MedianPrice = as.numeric(median(price)),
    Count = n()
  ) %>%
  arrange(desc(Count))

Ngay cả khi bạn chưa bao giờ thấy dplyr trước đó (hoặc thậm chí R!), Bạn vẫn có thể nhận được ý chính của những gì đang xảy ra vì các chức năng đều là động từ tiếng Anh. Nhược điểm của động từ tiếng Anh là chúng yêu cầu gõ nhiều hơn [, nhưng tôi nghĩ rằng phần lớn có thể được giảm thiểu bằng cách tự động hoàn thành tốt hơn.

Đây là mã data.table tương đương:

diamondsDT <- data.table(diamonds)
diamondsDT[
  cut != "Fair", 
  .(AvgPrice = mean(price),
    MedianPrice = as.numeric(median(price)),
    Count = .N
  ), 
  by = cut
][ 
  order(-Count) 
]

Thực hiện theo mã này khó hơn trừ khi bạn đã quen với data.table. (Tôi cũng không thể tìm ra cách thụt lề lặp đi lặp lại [ theo cách có vẻ tốt cho mắt của tôi). Cá nhân, khi tôi nhìn vào mã tôi đã viết 6 tháng trước, nó giống như nhìn vào một mã được viết bởi một người lạ, vì vậy tôi đã đến để thích đơn giản hơn, nếu dài dòng, mã.

Hai yếu tố nhỏ khác mà tôi nghĩ giảm nhẹ khả năng đọc:

  • Vì hầu hết mọi hoạt động của bảng dữ liệu đều sử dụng [nên bạn cần bối cảnh bổ sung để tìm hiểu điều gì đang xảy ra. Ví dụ: x[y] việc nối hai bảng dữ liệu hoặc trích xuất các cột từ khung dữ liệu? Đây chỉ là một vấn đề nhỏ, bởi vì trong mã được viết tốt, các tên biến sẽ gợi ý những gì đang xảy ra.

  • Tôi thích đó group_by()là một hoạt động riêng biệt trong dplyr. Về cơ bản, nó thay đổi tính toán nên tôi nghĩ nên rõ ràng khi đọc lướt mã và dễ dàng phát hiện group_by()hơn so với byđối số [.data.table.

Tôi cũng thích rằng đường ống không chỉ giới hạn trong một gói. Bạn có thể bắt đầu bằng cách thu dọn dữ liệu của bạn với tidyr và kết thúc với một cốt truyện trong ggvis . Và bạn không bị giới hạn trong các gói mà tôi viết - bất kỳ ai cũng có thể viết một hàm tạo thành một phần liền mạch của ống thao tác dữ liệu. Trong thực tế, tôi thích mã data.table trước đó được viết lại bằng %>%:

diamonds %>% 
  data.table() %>% 
  .[cut != "Fair", 
    .(AvgPrice = mean(price),
      MedianPrice = as.numeric(median(price)),
      Count = .N
    ), 
    by = cut
  ] %>% 
  .[order(-Count)]

Và ý tưởng của đường ống với %>%không giới hạn khung dữ liệu chỉ và có thể dễ dàng khái quát hóa để bối cảnh khác: đồ họa web tương tác , cào web , GIST , hợp đồng thời gian chạy , ...)

Bộ nhớ và hiệu suất

Tôi đã gộp những thứ này lại với nhau, bởi vì, đối với tôi, chúng không quan trọng đến thế. Hầu hết người dùng R làm việc với dưới 1 triệu hàng dữ liệu và dplyr đủ nhanh đủ cho kích thước dữ liệu mà bạn không biết về thời gian xử lý. Chúng tôi tối ưu hóa dplyr cho tính biểu cảm trên dữ liệu trung bình; thoải mái sử dụng data.table cho tốc độ thô trên dữ liệu lớn hơn.

Tính linh hoạt của dplyr cũng có nghĩa là bạn có thể dễ dàng điều chỉnh các đặc tính hiệu suất bằng cách sử dụng cùng một cú pháp. Nếu hiệu suất của dplyr với phụ trợ khung dữ liệu không đủ tốt cho bạn, bạn có thể sử dụng phụ trợ data.table (mặc dù có một bộ chức năng bị hạn chế phần nào). Nếu dữ liệu bạn đang làm việc không phù hợp với bộ nhớ, thì bạn có thể sử dụng phụ trợ cơ sở dữ liệu.

Tất cả những gì đã nói, hiệu suất dplyr sẽ trở nên tốt hơn trong dài hạn. Chúng tôi chắc chắn sẽ thực hiện một số ý tưởng tuyệt vời của data.table như đặt hàng cơ số và sử dụng cùng một chỉ mục cho các tham gia & bộ lọc. Chúng tôi cũng đang làm việc song song để chúng tôi có thể tận dụng nhiều lõi.

Đặc trưng

Một vài điều chúng tôi dự định sẽ làm trong năm 2015:

  • các readrgói, để làm cho nó dễ dàng để có được file ra đĩa và bộ nhớ, tương tự fread().

  • Các phép nối linh hoạt hơn, bao gồm hỗ trợ cho các phép nối không bằng.

  • Nhóm linh hoạt hơn như mẫu bootstrap, rollup và nhiều hơn nữa

Tôi cũng đang đầu tư thời gian vào việc cải thiện các trình kết nối cơ sở dữ liệu của R , khả năng nói chuyện với apis web và giúp việc quét các trang html dễ dàng hơn .


27
Chỉ cần một lưu ý phụ, tôi đồng ý với nhiều đối số của bạn (mặc dù tôi thích data.tablecú pháp hơn), nhưng bạn có thể dễ dàng sử dụng %>%để thực hiện các data.tablethao tác ống nếu bạn không thích [kiểu. %>%không cụ thể dplyr, thay vì đến từ một gói riêng biệt (mà bạn cũng là đồng tác giả của nó), vì vậy tôi không chắc tôi hiểu những gì bạn đang cố gắng nói trong hầu hết đoạn Syntax của bạn .
David Arenburg

11
@DavidArenburg điểm tốt. Tôi đã viết lại cú pháp để hy vọng làm rõ hơn những điểm chính của tôi là gì và để làm nổi bật rằng bạn có thể sử dụng %>%với data.table
hadley

5
Cảm ơn Hadley, đây là một quan điểm hữu ích. Tôi thụt lề thường làm DT[\n\texpression\n][\texpression\n]( ý chính ) mà thực sự hoạt động khá tốt. Tôi đang giữ câu trả lời của Arun là câu trả lời khi anh ấy trả lời trực tiếp hơn các câu hỏi cụ thể của tôi không liên quan nhiều đến khả năng truy cập của cú pháp, nhưng tôi nghĩ đây là một câu trả lời tốt cho những người đang cố gắng cảm nhận chung về sự khác biệt / điểm chung giữa dplyrdata.table.
BrodieG

33
Tại sao làm việc trên fastread khi đã có fread()? Sẽ không có thời gian tốt hơn để cải thiện fread () hoặc làm việc trên những thứ khác (kém phát triển)?
EDi

10
API của data.tableđược thành lập dựa trên sự lạm dụng lớn của []ký hiệu. Đó là điểm mạnh lớn nhất và điểm yếu lớn nhất của nó.
Paul

65

Trả lời trực tiếp Tiêu đề Câu hỏi ...

dplyr chắc chắn làm những điều data.tablekhông thể.

Quan điểm của bạn # 3

tóm tắt dplyr (hoặc sẽ) tương tác DB tiềm năng

là câu trả lời trực tiếp cho câu hỏi của bạn nhưng không được nâng lên mức đủ cao. dplyrthực sự là một mặt trước có thể mở rộng cho nhiều cơ chế lưu trữ dữ liệu trong đó data.tablelà một phần mở rộng cho một cơ chế duy nhất.

Nhìn vào dplyrnhư một giao diện bất khả tri, với tất cả các mục tiêu sử dụng cùng một ngữ pháp, nơi bạn có thể mở rộng các mục tiêu và trình xử lý theo ý muốn. data.tablelà, từ dplyrquan điểm, một trong những mục tiêu đó.

Bạn sẽ không bao giờ (tôi hy vọng) thấy một ngày data.tablecố gắng dịch các truy vấn của bạn để tạo các câu lệnh SQL hoạt động với các kho lưu trữ dữ liệu trên đĩa hoặc mạng.

dplyrcó thể có thể làm những điều data.tablesẽ không hoặc có thể không làm tốt.

Dựa trên thiết kế của bộ nhớ làm việc, data.tablecó thể có một thời gian khó khăn hơn nhiều khi tự mở rộng ra để xử lý các truy vấn song song hơn dplyr.


Để trả lời các câu hỏi trong cơ thể ...

Sử dụng

Có các nhiệm vụ phân tích dễ dàng hơn rất nhiều để mã hóa với một hoặc gói khác cho những người quen thuộc với các gói (nghĩa là một số tổ hợp phím được yêu cầu so với mức độ bí truyền được yêu cầu, trong đó ít hơn là một điều tốt).

Điều này có vẻ giống như một trò hề nhưng câu trả lời thực sự là không. Những người quen thuộc với các công cụ dường như sử dụng một trong những công cụ quen thuộc nhất với họ hoặc một công cụ thực sự phù hợp với công việc trong tay. Như đã nói, đôi khi bạn muốn trình bày một khả năng đọc cụ thể, đôi khi là mức hiệu suất và khi bạn cần mức độ đủ cao của cả hai, bạn có thể chỉ cần một công cụ khác để đi cùng với những gì bạn đã có để trừu tượng rõ ràng hơn .

Hiệu suất

Có các nhiệm vụ phân tích được thực hiện đáng kể (tức là hơn 2 lần) hiệu quả hơn trong một gói so với gói khác.

Một lần nữa, không. data.tablevượt trội về hiệu quả trong mọi thứ làm khi dplyrgánh nặng bị giới hạn ở một số khía cạnh đối với kho dữ liệu cơ bản và trình xử lý đã đăng ký.

Điều này có nghĩa khi bạn chạy vào một vấn đề hiệu suất với data.tablebạn có thể khá chắc chắn rằng nó là chức năng truy vấn của bạn và nếu nó thực sự là một nút cổ chai với data.tablesau đó bạn đã giành cho mình những niềm vui của gửi báo cáo. Điều này cũng đúng khi dplyrsử dụng data.tablelàm back-end; bạn có thể thấy một số chi phí từ dplyrnhưng tỷ lệ cược là truy vấn của bạn.

Khi dplyrcó vấn đề về hiệu năng với back-end, bạn có thể khắc phục chúng bằng cách đăng ký một hàm để đánh giá kết hợp hoặc (trong trường hợp cơ sở dữ liệu) thao tác truy vấn được tạo trước khi thực hiện.

Cũng xem câu trả lời được chấp nhận khi nào plyr tốt hơn data.table?


3
Không thể bao bọc một data.table với tbl_dt? Tại sao không chỉ có được tốt nhất của cả hai thế giới?
aaa90210

22
Bạn quên đề cập đến câu lệnh ngược "data.table chắc chắn làm những điều mà dplyr không thể" cũng đúng.
jangorecki

25
Arun trả lời giải thích nó tốt. Quan trọng nhất (về hiệu suất) sẽ là khó khăn, cập nhật bằng cách tham khảo, tham gia cán, tham gia chồng chéo. Tôi tin rằng không có gói nào (không chỉ dplyr) có thể cạnh tranh với các tính năng đó. Một ví dụ đẹp có thể là slide cuối cùng từ bài thuyết trình này .
jangorecki

15
Hoàn toàn, data.table là lý do tại sao tôi vẫn sử dụng R. Nếu không tôi sẽ sử dụng gấu trúc. Nó thậm chí còn tốt hơn / nhanh hơn gấu trúc.
marbel

8
Tôi thích data.table vì tính đơn giản và tương đồng với cấu trúc cú pháp SQL. Công việc của tôi liên quan đến việc thực hiện phân tích dữ liệu và đồ họa ad hoc rất mãnh liệt hàng ngày cho mô hình thống kê và tôi thực sự cần công cụ đủ đơn giản để làm những việc phức tạp. Bây giờ tôi có thể giảm bộ công cụ của mình xuống chỉ còn data.table cho dữ liệu và mạng cho biểu đồ trong công việc hàng ngày của tôi. Cho một ví dụ tôi thậm chí có thể thực hiện các thao tác như thế này: $ DT [group == 1, y_hat: = dự đoán (fit1, data = .SD),] $, điều này thực sự gọn gàng và tôi coi đó là một lợi thế lớn từ SQL trong môi trường R cổ điển.
xappppp
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.