Mục đích của việc thiết lập khóa trong data.table là gì?


113

Tôi đang sử dụng data.table và có nhiều chức năng yêu cầu tôi đặt khóa (ví dụ X[Y]:). Do đó, tôi muốn hiểu khóa làm gì để đặt đúng khóa trong bảng dữ liệu của mình.


Tôi đã đọc một nguồn ?setkey.

setkey()sắp xếp a data.tablevà đánh dấu là đã sắp xếp. Các cột được sắp xếp là chìa khóa. Chìa khóa có thể là bất kỳ cột nào theo thứ tự bất kỳ. Các cột luôn được sắp xếp theo thứ tự tăng dần. Bảng được thay đổi theo tham chiếu. Không có bản sao nào được thực hiện, ngoài bộ nhớ làm việc tạm thời lớn như một cột.

Bài học của tôi ở đây là một khóa sẽ "sắp xếp" data.table, dẫn đến hiệu ứng rất giống với order(). Tuy nhiên, nó không giải thích mục đích của việc có chìa khóa.


Câu hỏi thường gặp về data.table 3.2 và 3.3 giải thích:

3.2 Tôi không có chìa khóa trên một bảng lớn, nhưng việc nhóm vẫn thực sự nhanh chóng. Tại sao vậy?

data.table sử dụng sắp xếp theo cơ số. Điều này nhanh hơn đáng kể so với các thuật toán sắp xếp khác. Radix đặc biệt chỉ dành cho số nguyên, hãy xem ?base::sort.list(x,method="radix"). Đây cũng là một lý do tại sao setkey()nhanh chóng. Khi không có khóa nào được đặt hoặc chúng tôi nhóm theo thứ tự khác với thứ tự của khóa, chúng tôi gọi nó là đặc biệt bởi.

3.3 Tại sao nhóm theo cột trong khóa nhanh hơn nhóm đặc biệt theo?

Bởi vì mỗi nhóm nằm kề nhau trong RAM, do đó giảm thiểu việc tìm nạp trang và bộ nhớ có thể được sao chép hàng loạt ( memcpytrong C) thay vì lặp lại trong C.

Từ đây, tôi đoán rằng việc thiết lập khóa bằng cách nào đó cho phép R sử dụng "sắp xếp theo cơ số" so với các thuật toán khác và đó là lý do tại sao nó nhanh hơn.


Hướng dẫn bắt đầu nhanh 10 phút cũng có hướng dẫn về các phím.

  1. Chìa khóa

Hãy bắt đầu bằng cách xem xét data.frame, cụ thể là tên hàng (hoặc bằng tiếng Anh, tên hàng). Đó là, nhiều tên thuộc một hàng. Nhiều tên thuộc một hàng? Đó không phải là những gì chúng ta quen làm trong data.frame. Chúng ta biết rằng mỗi hàng có nhiều nhất một tên. Một người có ít nhất hai tên, tên đầu tiên và tên thứ hai. Điều đó rất hữu ích để tổ chức một danh bạ điện thoại, ví dụ, được sắp xếp theo họ, sau đó là tên. Tuy nhiên, mỗi hàng trong data.frame chỉ có thể có một tên.

Một khóa bao gồm một hoặc nhiều cột tên hàng, có thể là số nguyên, hệ số, ký tự hoặc một số lớp khác, không chỉ là ký tự. Hơn nữa, các hàng được sắp xếp theo khóa. Do đó, một data.table có thể có nhiều nhất một khóa, vì nó không thể được sắp xếp theo nhiều cách.

Tính duy nhất không được thực thi, tức là cho phép các giá trị khóa trùng lặp. Vì các hàng được sắp xếp theo khóa nên mọi bản sao trong khóa sẽ xuất hiện liên tiếp

Danh bạ điện thoại hữu ích trong việc hiểu khóa là gì, nhưng có vẻ như khóa không khác gì khi so sánh với việc có cột yếu tố. Hơn nữa, nó không giải thích tại sao lại cần một khóa (đặc biệt là để sử dụng một số chức năng nhất định) và cách chọn cột để đặt làm khóa. Ngoài ra, có vẻ như trong data.table với thời gian là một cột, việc đặt bất kỳ cột nào khác làm khóa cũng có thể làm rối cột thời gian, điều này khiến nó thậm chí còn khó hiểu hơn vì tôi không biết liệu mình có được phép đặt bất kỳ cột nào khác không Chìa khóa. Ai đó có thể khai sáng cho tôi được không?


"Tôi đoán rằng việc đặt khóa bằng cách nào đó cho phép R sử dụng" sắp xếp theo cơ số "so với các thuật toán khác" - Tôi không hiểu điều đó từ sự trợ giúp nào cả. Bài đọc của tôi là cài đặt một khóa được sắp xếp theo một khóa. Bạn có thể thực hiện sắp xếp "đặc biệt" theo các cột khác ngoài khóa và nó nhanh, nhưng không nhanh như thể bạn đã sắp xếp.
Ari B. Friedman

Tôi nghĩ rằng tìm kiếm nhị phân nhanh hơn quét véc tơ khi chọn hàng. Tôi không phải là một nhà khoa học máy tính, vì vậy tôi không biết điều đó thực sự có nghĩa là gì. Bên cạnh Câu hỏi thường gặp, hãy xem phần giới thiệu .
Frank

Câu trả lời:


125

Cập nhật nhỏ: Vui lòng tham khảo thêm các họa tiết HTML mới . Vấn đề này làm nổi bật các họa tiết khác mà chúng tôi dự định.


Tôi đã cập nhật lại câu trả lời này (tháng 2 năm 2016) do on=tính năng mới cũng cho phép tham gia đặc biệt . Xem lịch sử để biết các câu trả lời trước đó (lỗi thời).

Chính xác thì setkey(DT, a, b)làm gì?

Nó thực hiện hai điều:

  1. sắp xếp lại các hàng của data.table DT theo (các) cột được cung cấp ( a , b ) theo tham chiếu , luôn theo thứ tự tăng dần .
  2. đánh dấu các cột đó là cột chính bằng cách đặt thuộc tính được gọi sortedđến DT.

Việc sắp xếp lại vừa nhanh (do sắp xếp cơ số bên trong của data.table ) vừa hiệu quả về bộ nhớ (chỉ cấp phát thêm một cột kiểu double ).

Khi nào được setkey()yêu cầu?

Đối với các hoạt động nhóm, setkey()không bao giờ là một yêu cầu tuyệt đối. Đó là, chúng ta có thể thực hiện cold-by hoặc adhoc-by .

## "cold" by
require(data.table)
DT <- data.table(x=rep(1:5, each=2), y=1:10)
DT[, mean(y), by=x] # no key is set, order of groups preserved in result

Tuy nhiên, trước đó v1.9.6, các phép nối của biểu mẫu x[i]bắt buộc keyphải được đặt trên x.Với on=đối số mới từ v1.9.6 + , điều này không còn đúng nữa và việc đặt khóa do đó cũng không phải là một yêu cầu tuyệt đối ở đây.

## joins using < v1.9.6 
setkey(X, a) # absolutely required
setkey(Y, a) # not absolutely required as long as 'a' is the first column
X[Y]

## joins using v1.9.6+
X[Y, on="a"]
# or if the column names are x_a and y_a respectively
X[Y, on=c("x_a" = "y_a")]

Lưu ý rằng on=đối số có thể được chỉ định rõ ràng ngay cả đối với các phép keyednối.

Thao tác duy nhất yêu cầu keyphải được đặt hoàn toàn là foverlaps () . Nhưng chúng tôi đang làm việc trên một số tính năng khác mà khi hoàn thành sẽ loại bỏ yêu cầu này.

  • Vậy lý do để triển khai on=lập luận là gì?

    Có khá nhiều lý do.

    1. Nó cho phép phân biệt rõ ràng hoạt động như một hoạt động liên quan đến hai dữ liệu. Bảng . Việc chỉ làm X[Y]cũng không phân biệt được điều này, mặc dù có thể rõ ràng bằng cách đặt tên các biến một cách thích hợp.

    2. Nó cũng cho phép hiểu các cột mà tham gia / tập hợp con đang được thực hiện ngay lập tức bằng cách xem dòng mã đó (và không phải truy ngược lạisetkey() dòng ).

    3. Trong các hoạt động mà cột được thêm vào hoặc cập nhật bằng tham chiếu , các on=hoạt động có hiệu suất cao hơn nhiều vì nó không cần toàn bộ data.table được sắp xếp lại chỉ để thêm / cập nhật (các) cột. Ví dụ,

      ## compare 
      setkey(X, a, b) # why physically reorder X to just add/update a column?
      X[Y, col := i.val]
      
      ## to
      X[Y, col := i.val, on=c("a", "b")]

      Trong trường hợp thứ hai, chúng tôi không phải sắp xếp lại. Nó không phải tính toán thứ tự tốn thời gian, mà là sắp xếp lại vật lý data.table trong RAM, và bằng cách tránh nó, chúng tôi giữ lại thứ tự ban đầu và nó cũng hoạt động hiệu quả.

    4. Ngay cả khi khác, trừ khi bạn đang thực hiện các phép nối lặp đi lặp lại, không có sự khác biệt về hiệu suất đáng chú ý giữa phép nối có khóaphép nối đặc biệt .

Điều này dẫn đến câu hỏi, việc khóa data.table có lợi thế gì nữa?

  • Có một lợi thế khi khóa một data.table?

    Khóa một data.table sắp xếp lại vật lý nó dựa trên (các) cột đó trong RAM. Tính toán đơn đặt hàng thường không phải là phần tốn thời gian, mà là sắp xếp lại chính nó. Tuy nhiên, khi chúng ta đã sắp xếp dữ liệu trong RAM, các hàng thuộc cùng một nhóm đều nằm liền nhau trong RAM và do đó bộ nhớ đệm rất hiệu quả. Đó là sự sắp xếp giúp tăng tốc hoạt động trên các bảng data.tables được khóa.

    Do đó, điều cần thiết là phải tìm hiểu xem thời gian dành cho việc sắp xếp lại toàn bộ data.table có xứng đáng với thời gian để thực hiện một phép nối / tổng hợp hiệu quả bộ nhớ cache hay không. Thông thường, trừ khi có các hoạt động nhóm / nối lặp lại được thực hiện trên cùng một data.table có khóa , không nên có sự khác biệt đáng chú ý.

Do đó, trong hầu hết các trường hợp, không cần thiết phải đặt khóa nữa. Chúng tôi khuyên bạn nên sử dụng on=bất cứ khi nào có thể, trừ khi khóa cài đặt có cải thiện đáng kể về hiệu suất mà bạn muốn khai thác.

Câu hỏi: Bạn nghĩ hiệu suất sẽ như thế nào so với phép nối có khóa , nếu bạn sử dụng setorder()để sắp xếp lại data.table và sử dụng on=? Nếu bạn đã theo dõi cho đến nay, bạn sẽ có thể tìm ra nó :-).


3
Tuyệt thật, cảm ơn nhé! Cho đến tận bây giờ, tôi vẫn chưa nghĩ về "tìm kiếm nhị phân" thực sự có nghĩa là gì, cũng như không thực sự hiểu lý do tại sao nó được sử dụng thay vì băm.
Frank

@Arun, DT[J(1e4:1e5)]thực sự tương đương với DF[DF$x > 1e4 & DF$x < 1e5, ]? Bạn có thể chỉ cho tôi những gì Jcó nghĩa là? Ngoài ra, tìm kiếm đó sẽ không trả về bất kỳ hàng nào vì sample(1e4, 1e7, TRUE)không bao gồm các số trên 1e4.
fishtank

@fishtank, trong trường hợp này, nó phải được >=<=- cố định. J(và .) là bí danh của list(nghĩa là chúng tương đương). Bên trong khi ilà một danh sách, nó được chuyển đổi thành data.table sau đó tìm kiếm nhị phân được sử dụng để tính toán các chỉ số hàng. Đã 1e4sửa 1e5để tránh nhầm lẫn. Cảm ơn vì đã phát hiện. Lưu ý rằng on=bây giờ chúng ta có thể sử dụng trực tiếp đối số để thực hiện các tập hợp con nhị phân thay vì thiết lập khóa. Đọc thêm từ các lần tải trang HTML mới . Và hãy theo dõi trang đó để biết các chi tiết cho các lần tham gia.
Arun

có lẽ điều này có thể được cập nhật kỹ lưỡng hơn? các "khi cần thiết" phần dường như lỗi thời, ví dụ
MichaelChirico

Chức năng nào cho bạn biết chìa khóa đang được sử dụng?
skan

20

Khóa về cơ bản là một chỉ mục trong một tập dữ liệu, cho phép thực hiện các thao tác sắp xếp, lọc và nối rất nhanh và hiệu quả. Đây có lẽ là những lý do tốt nhất để sử dụng bảng dữ liệu thay vì khung dữ liệu (cú pháp sử dụng bảng dữ liệu cũng thân thiện hơn với người dùng, nhưng điều đó không liên quan gì đến phím).

Nếu bạn không hiểu các chỉ mục, hãy xem xét điều này: danh bạ điện thoại được "lập chỉ mục" theo tên. Vì vậy, nếu tôi muốn tra cứu số điện thoại của ai đó, khá đơn giản. Nhưng giả sử tôi muốn tìm kiếm theo số điện thoại (ví dụ: tra cứu ai có một số điện thoại cụ thể)? Trừ khi tôi có thể "lục lại" danh bạ bằng số điện thoại, nếu không sẽ rất lâu.

Hãy xem xét ví dụ sau: giả sử tôi có một bảng, ZIP, của tất cả các mã zip ở Hoa Kỳ (> 33.000) cùng với thông tin liên quan (thành phố, tiểu bang, dân số, thu nhập trung bình, v.v.). Nếu tôi muốn tra cứu thông tin cho một mã zip cụ thể, thì tìm kiếm (bộ lọc) sẽ nhanh hơn khoảng 1000 lần nếu tôisetkey(ZIP,zipcode) lần đầu tiên.

Một lợi ích khác liên quan đến các phép nối. Giả sử có một danh sách người và mã zip của họ trong bảng dữ liệu (gọi là "PPL") và tôi muốn thêm thông tin từ bảng ZIP (ví dụ: thành phố, tiểu bang, v.v.). Đoạn mã sau sẽ thực hiện điều đó:

setkey(ZIP,zipcode)
setkey(PPL,zipcode)
full.info <- PPL[ZIP, nomatch=F]

Đây là "tham gia" theo nghĩa là tôi đang kết hợp thông tin từ 2 bảng dựa trên một trường chung (mã vùng). Các phép nối như thế này trên các bảng rất lớn cực kỳ chậm với khung dữ liệu và cực kỳ nhanh với bảng dữ liệu. Trong một ví dụ thực tế, tôi đã phải thực hiện hơn 20.000 lượt tham gia như thế này trên một bảng đầy đủ các mã zip. Với bảng dữ liệu, tập lệnh mất khoảng 20 phút. chạy. Tôi thậm chí đã không thử nó với các khung dữ liệu vì nó sẽ mất hơn 2 tuần.

IMHO bạn không nên chỉ đọc mà hãy học tài liệu Câu hỏi thường gặp và Giới thiệu. Sẽ dễ hiểu hơn nếu bạn gặp vấn đề thực tế để áp dụng điều này.

[Phản hồi nhận xét của @ Frank]

Re: sắp xếp so với lập chỉ mục - Dựa trên câu trả lời cho câu hỏi này , có vẻ như nó setkey(...)thực sự sắp xếp lại các cột trong bảng (ví dụ: sắp xếp vật lý) và không tạo chỉ mục theo nghĩa cơ sở dữ liệu. Điều này có một số ý nghĩa thực tế: đối với một điều nếu bạn đặt khóa trong bảng setkey(...)và sau đó thay đổi bất kỳ giá trị nào trong cột khóa, data.table chỉ tuyên bố bảng không được sắp xếp nữa (bằng cách tắt sortedthuộc tính); nó không tự động tái lập chỉ mục để duy trì thứ tự sắp xếp thích hợp (như sẽ xảy ra trong cơ sở dữ liệu). Ngoài ra, "loại bỏ phím" sử dụng setky(DT,NULL)không không khôi phục bảng để gốc, trật tự không được phân loại của nó.

Re: filter so với join - sự khác biệt thực tế là lọc trích xuất một tập con từ một tập dữ liệu duy nhất, trong khi tham gia kết hợp dữ liệu từ hai tập dữ liệu dựa trên một trường chung. Có nhiều kiểu nối khác nhau (trong, ngoài, trái). Ví dụ trên là một phép nối bên trong (chỉ các bản ghi có khóa chung cho cả hai bảng được trả về) và điều này có nhiều điểm tương đồng với việc lọc.


1
+1. Về câu đầu tiên của bạn ... nó đã được sắp xếp rồi phải không? Và phép nối không phải là một trường hợp đặc biệt của bộ lọc (hoặc một phép toán lấy bộ lọc làm bước đầu tiên của nó)? Có vẻ như "lọc tốt hơn" tổng kết toàn bộ lợi ích.
Frank

1
Hoặc tôi cho là quét tốt hơn.
Wet Feet

1
@jlhoward Cảm ơn. Niềm tin trước đây của tôi là việc sắp xếp không nằm trong số những lợi ích của việc đặt khóa (vì nếu bạn muốn sắp xếp, bạn chỉ nên sắp xếp) và điều đó setkeythực sự không thể thay đổi thứ tự các hàng. Nếu nó chỉ dành cho mục đích hiển thị, thì làm cách nào để in mười hàng đầu tiên theo thứ tự "true" (mà tôi đã thấy trước khi sử dụng phím setkey)? Tôi khá chắc chắn là setkey(DT,NULL)không làm điều này ... (tiếp)
Frank

... (tiếp) Ngoài ra, tôi chưa xem mã của gói, nhưng để tham gia X[Y,...], bạn cần "lọc" các hàng của X bằng cách sử dụng khóa. Đúng là, những thứ khác sẽ xảy ra sau đó (các cột của Y được cung cấp sẵn và có một ẩn số không rõ ràng), nhưng tôi vẫn không thấy đó là một lợi ích khác biệt về mặt khái niệm. Tuy nhiên, tôi đoán câu trả lời của bạn dựa trên các thao tác mà bạn có thể muốn thực hiện, khi mà sự phân biệt có thể hữu ích.
Frank

1
@Frank - Vì vậy, setkey(DT,NULL)loại bỏ khóa nhưng không ảnh hưởng đến thứ tự sắp xếp. Đặt một câu hỏi về điều này ở đây . Hãy xem nào.
jlhoward
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.