.SD có nghĩa là gì trong data.table trong R


172

.SDcó vẻ hữu ích nhưng tôi không thực sự biết những gì tôi đang làm với nó. nó đứng để làm gì? Tại sao có một giai đoạn trước (dừng hoàn toàn). Điều gì đang xảy ra khi tôi sử dụng nó?

Tôi đọc: .SDlà một data.tabletập hợp con của xdữ liệu cho mỗi nhóm, ngoại trừ (các) cột nhóm. Nó có thể được sử dụng khi nhóm theo i, khi nhóm theo by, khóa byvà _ad hoc_by

Điều đó có nghĩa là con gái data.tableđược giữ trong bộ nhớ cho các hoạt động tiếp theo?


30
?data.tableđã được cải thiện trong v1.7.10, nhờ câu hỏi này. Bây giờ nó giải thích tên .SDtheo câu trả lời được chấp nhận.
Matt Dowle

Câu trả lời:


205

.SDlà viết tắt của một cái gì đó như " Subset of Data.table". Không có ý nghĩa gì với ban đầu ".", ngoại trừ việc nó càng khiến nhiều khả năng sẽ xảy ra xung đột với tên cột do người dùng xác định.

Nếu đây là dữ liệu của bạn.

DT = data.table(x=rep(c("a","b","c"),each=2), y=c(1,3), v=1:6)
setkey(DT, y)
DT
#    x y v
# 1: a 1 1
# 2: b 1 3
# 3: c 1 5
# 4: a 3 2
# 5: b 3 4
# 6: c 3 6

Làm điều này có thể giúp bạn thấy những gì .SDlà:

DT[ , .SD[ , paste(x, v, sep="", collapse="_")], by=y]
#    y       V1
# 1: 1 a1_b3_c5
# 2: 3 a2_b4_c6

Về cơ bản, by=ycâu lệnh phá vỡ dữ liệu gốc. Có thể vào hai mục con nàydata.tables

DT[ , print(.SD), by=y]
# <1st sub-data.table, called '.SD' while it's being operated on>
#    x v
# 1: a 1
# 2: b 3
# 3: c 5
# <2nd sub-data.table, ALSO called '.SD' while it's being operated on>
#    x v
# 1: a 2
# 2: b 4
# 3: c 6
# <final output, since print() doesn't return anything>
# Empty data.table (0 rows) of 1 col: y

và lần lượt hoạt động trên chúng.

Trong khi nó đang hoạt động trên một trong hai, nó cho phép bạn tham khảo phụ hiện tại data.tablebằng cách sử dụng tên nick / xử lý / biểu tượng .SD. Điều đó rất tiện lợi, vì bạn có thể truy cập và vận hành trên các cột giống như khi bạn đang ngồi ở dòng lệnh làm việc với một dữ liệu duy nhất được gọi là .SD... ngoại trừ ở đây, data.tablesẽ thực hiện các hoạt động đó trên mỗi phụ data.tableđược xác định bởi kết hợp các phím, "dán" chúng lại với nhau và trả lại kết quả trong một lần duy nhất data.table!


12
Đúng chính xác. Một cách khác để xem .SDDT[,print(.SD),by=y].
Matt Dowle

6
@MatthewDowle - Trong khi bạn ở đây, một câu hỏi cho bạn. Làm DT[,print(.SD[,y]),by=y], chỉ ra rằng tôi có quyền truy cập vào giá trị của y, mặc dù đó không phải là một phần của .SD. Giá trị của yphạm vi là từ đâu? Có sẵn b / c đó là giá trị hiện tại của by?
Josh O'Brien

8
@Josh Đẹp. Đúng. Đây .SD[,y]là một data.tabletập hợp con thông thường vì vậy ykhông phải là một cột của .SDnó trông trong môi trường được gọi là nó, mà trong trường hợp này là jmôi trường (của DTtruy vấn) nơi các bybiến có sẵn. Nếu không tìm thấy ở đó, nó nhìn vào cha mẹ, và cha mẹ của nó và cứ thế theo cách R thông thường. (Vâng, thông qua phạm vi kế thừa tham gia, cũng không được sử dụng trong các ví dụ này vì không có is).
Matt Dowle

1
@Josh FAQ 2.8 có nhiều hơn về điều này, có thể cần cải thiện (hoan nghênh đề xuất).
Matt Dowle

3
@Josh Bởi vì các biến nhóm có sẵn trong j cũng vậy, và có chiều dài 1. by=list(x,y,z)có nghĩa x, yzcó sẵn để j. Để truy cập chung, họ cũng bị cuốn vào .BY. FAQ 2.10 có một số lịch sử về nó, nhưng một số sự rõ ràng có thể được thêm vào ?data.table. Tuyệt vời, sự giúp đỡ của docu sẽ rất được hoan nghênh. Thậm chí tốt hơn nếu bạn muốn tham gia dự án và thay đổi trực tiếp.
Matt Dowle

96

Biên tập:

Cho rằng câu trả lời này đã được đón nhận như thế nào, tôi đã chuyển đổi nó thành một họa tiết gói hiện có sẵn ở đây


Dựa vào mức độ thường xuyên xảy ra, tôi nghĩ rằng điều này đảm bảo giải thích thêm một chút, ngoài câu trả lời hữu ích được đưa ra bởi Josh O'Brien ở trên.

Ngoài các S ubset của D ata từ viết tắt thường được trích dẫn / tạo ra bởi Josh, tôi nghĩ rằng nó cũng hữu ích để xem xét "S" để đứng cho "selfsame" hoặc "tự tham khảo" - .SDlà trong chiêu bài cơ bản nhất của nó một tham chiếu phản xạ cho data.tablechính nó - như chúng ta sẽ thấy trong các ví dụ dưới đây, điều này đặc biệt hữu ích cho việc kết nối các "truy vấn" (trích xuất / tập hợp con / vv bằng cách sử dụng [). Đặc biệt, điều này cũng có nghĩa .SDchính nódata.table (với sự cảnh báo mà nó không cho phép gán với :=).

Việc sử dụng đơn giản hơn .SDlà cho tập hợp cột (nghĩa là khi .SDcolsđược chỉ định); Tôi nghĩ phiên bản này dễ hiểu hơn nhiều, vì vậy chúng tôi sẽ đề cập đến phiên bản đầu tiên bên dưới. Việc giải thích .SDtrong cách sử dụng thứ hai của nó, các kịch bản nhóm (nghĩa là khi by =hoặc keyby =được chỉ định), hơi khác nhau, về mặt khái niệm (mặc dù về cốt lõi, nó giống nhau, vì xét cho cùng, một hoạt động không được nhóm là một trường hợp cạnh của nhóm một nhóm).


Dưới đây là một số ví dụ minh họa và một số ví dụ khác về cách sử dụng mà bản thân tôi thường thực hiện:

Đang tải dữ liệu Lahman

Để mang lại cảm giác thực tế hơn, thay vì tạo dữ liệu, hãy tải một số bộ dữ liệu về bóng chày từ Lahman:

library(data.table) 
library(magrittr) # some piping can be beautiful
library(Lahman)
Teams = as.data.table(Teams)
# *I'm selectively suppressing the printed output of tables here*
Teams
Pitching = as.data.table(Pitching)
# subset for conciseness
Pitching = Pitching[ , .(playerID, yearID, teamID, W, L, G, ERA)]
Pitching

Khỏa thân .SD

Để minh họa những gì tôi muốn nói về bản chất phản xạ của .SD, hãy xem xét cách sử dụng tầm thường nhất của nó:

Pitching[ , .SD]
#         playerID yearID teamID  W  L  G   ERA
#     1: bechtge01   1871    PH1  1  2  3  7.96
#     2: brainas01   1871    WS3 12 15 30  4.50
#     3: fergubo01   1871    NY2  0  0  1 27.00
#     4: fishech01   1871    RC1  4 16 24  4.35
#     5: fleetfr01   1871    NY2  0  1  1 10.00
#    ---                                       
# 44959: zastrro01   2016    CHN  1  0  8  1.13
# 44960: zieglbr01   2016    ARI  2  3 36  2.82
# 44961: zieglbr01   2016    BOS  2  4 33  1.52
# 44962: zimmejo02   2016    DET  9  7 19  4.87
# 44963:  zychto01   2016    SEA  1  0 12  3.29

Đó là, chúng tôi vừa trở về Pitching, tức là, đây là một cách viết quá dài dòng Pitchinghoặc Pitching[]:

identical(Pitching, Pitching[ , .SD])
# [1] TRUE

Về mặt .SDtập hợp con , vẫn là một tập hợp con của dữ liệu, nó chỉ là một tập hợp nhỏ (chính tập hợp).

Cột con: .SDcols

Cách đầu tiên để tác động đến những gì .SDlà giới hạn các cột có trong .SDviệc sử dụng .SDcolsđối số để [:

Pitching[ , .SD, .SDcols = c('W', 'L', 'G')]
#         W  L  G
#     1:  1  2  3
#     2: 12 15 30
#     3:  0  0  1
#     4:  4 16 24
#     5:  0  1  1
# ---         
# 44959:  1  0  8
# 44960:  2  3 36
# 44961:  2  4 33
# 44962:  9  7 19
# 44963:  1  0 12

Đây chỉ là để minh họa và đã khá nhàm chán. Nhưng ngay cả việc sử dụng này chỉ đơn giản là cho vay rất nhiều hoạt động thao tác dữ liệu có lợi / có mặt ở khắp mọi nơi:

Chuyển đổi loại cột

Chuyển đổi loại cột là một thực tế của việc trộn dữ liệu - kể từ khi viết bài này, fwritekhông thể tự động đọc Datehoặc POSIXctcột và chuyển đổi qua lại giữa character/ factor/ numericlà phổ biến. Chúng tôi có thể sử dụng .SD.SDcolschuyển đổi hàng loạt các nhóm như vậy.

Chúng tôi lưu ý rằng các cột sau được lưu trữ như charactertrong tập Teamsdữ liệu:

# see ?Teams for explanation; these are various IDs
#   used to identify the multitude of teams from
#   across the long history of baseball
fkt = c('teamIDBR', 'teamIDlahman45', 'teamIDretro')
# confirm that they're stored as `character`
Teams[ , sapply(.SD, is.character), .SDcols = fkt]
# teamIDBR teamIDlahman45    teamIDretro 
#     TRUE           TRUE           TRUE 

Nếu bạn bối rối khi sử dụng sapplyở đây, hãy lưu ý rằng nó giống với cơ sở R data.frames:

setDF(Teams) # convert to data.frame for illustration
sapply(Teams[ , fkt], is.character)
# teamIDBR teamIDlahman45    teamIDretro 
#     TRUE           TRUE           TRUE 
setDT(Teams) # convert back to data.table

Chìa khóa để hiểu cú pháp này là nhớ lại rằng một data.table(cũng như a data.frame) có thể được coi là một listtrong đó mỗi phần tử là một cột - do đó, sapply/ lapplyáp dụng FUNcho mỗi cột và trả về kết quả như sapply/ lapplythường sẽ (ở đây, FUN == is.charactertrả về một logicalcó độ dài 1, do đó sapplytrả về một vectơ).

Cú pháp để chuyển đổi các cột factornày rất giống nhau - chỉ cần thêm :=toán tử gán

Teams[ , (fkt) := lapply(.SD, factor), .SDcols = fkt]

Lưu ý rằng chúng ta phải gói fkttrong ngoặc đơn ()để buộc R diễn giải đây là tên cột, thay vì cố gắng gán tên fktcho RHS.

Tính linh hoạt của .SDcols(và :=) để chấp nhận một charactervector hoặc một integervector của các vị trí cột cũng có thể có ích cho việc chuyển đổi mô hình dựa trên các tên cột *. Chúng tôi có thể chuyển đổi tất cả factorcác cột thành character:

fkt_idx = which(sapply(Teams, is.factor))
Teams[ , (fkt_idx) := lapply(.SD, as.character), .SDcols = fkt_idx]

Và sau đó chuyển đổi tất cả các cột có chứa teamtrở lại factor:

team_idx = grep('team', names(Teams), value = TRUE)
Teams[ , (team_idx) := lapply(.SD, factor), .SDcols = team_idx]

** Rõ ràng sử dụng số cột (như DT[ , (1) := rnorm(.N)]) là thực tế xấu và có thể dẫn đến mã bị hỏng âm thầm theo thời gian nếu vị trí cột thay đổi. Ngay cả việc sử dụng số ngầm cũng có thể nguy hiểm nếu chúng ta không kiểm soát thông minh / nghiêm ngặt đối với việc đặt hàng khi chúng ta tạo chỉ mục được đánh số và khi chúng ta sử dụng nó.

Kiểm soát RHS của người mẫu

Đặc điểm kỹ thuật mô hình khác nhau là một tính năng cốt lõi của phân tích thống kê mạnh mẽ. Hãy thử và dự đoán ERA của người ném bóng (Earned Runs Average, thước đo hiệu suất) bằng cách sử dụng tập hợp số nhỏ có sẵn trong Pitchingbảng. Làm thế nào để mối quan hệ (tuyến tính) giữa W(thắng) và ERAkhác nhau tùy thuộc vào các hiệp phương sai khác được bao gồm trong đặc tả?

Đây là một đoạn script ngắn tận dụng sức mạnh .SDkhám phá câu hỏi này:

# this generates a list of the 2^k possible extra variables
#   for models of the form ERA ~ G + (...)
extra_var = c('yearID', 'teamID', 'G', 'L')
models =
  lapply(0L:length(extra_var), combn, x = extra_var, simplify = FALSE) %>%
  unlist(recursive = FALSE)

# here are 16 visually distinct colors, taken from the list of 20 here:
#   https://sashat.me/2017/01/11/list-of-20-simple-distinct-colors/
col16 = c('#e6194b', '#3cb44b', '#ffe119', '#0082c8', '#f58231', '#911eb4',
          '#46f0f0', '#f032e6', '#d2f53c', '#fabebe', '#008080', '#e6beff',
          '#aa6e28', '#fffac8', '#800000', '#aaffc3')

par(oma = c(2, 0, 0, 0))
sapply(models, function(rhs) {
  # using ERA ~ . and data = .SD, then varying which
  #   columns are included in .SD allows us to perform this
  #   iteration over 16 models succinctly.
  #   coef(.)['W'] extracts the W coefficient from each model fit
  Pitching[ , coef(lm(ERA ~ ., data = .SD))['W'], .SDcols = c('W', rhs)]
}) %>% barplot(names.arg = sapply(models, paste, collapse = '/'),
               main = 'Wins Coefficient with Various Covariates',
               col = col16, las = 2L, cex.names = .8)

phù hợp với hệ số OLS trên W, thông số kỹ thuật khác nhau

Hệ số luôn có dấu hiệu mong đợi (người ném tốt hơn có xu hướng có nhiều chiến thắng hơn và ít lần chạy hơn được phép), nhưng cường độ có thể thay đổi đáng kể tùy thuộc vào những gì chúng ta kiểm soát.

Tham gia có điều kiện

data.tablecú pháp là đẹp cho sự đơn giản và mạnh mẽ của nó. Cú pháp x[i]linh hoạt xử lý hai chung cách tiếp cận Subsetting - khi ilà một logicalvector, x[i]sẽ trả lại những hàng xtương ứng với nơi iTRUE; khi ikhácdata.table , một joinđược thực hiện (theo hình thức đơn giản, bằng cách sử dụng keys của xi, nếu không, khi on =được chỉ định, sử dụng các trận đấu của các cột).

Điều này nói chung là tuyệt vời, nhưng lại thiếu khi chúng ta muốn thực hiện một phép nối có điều kiện , trong đó bản chất chính xác của mối quan hệ giữa các bảng phụ thuộc vào một số đặc điểm của các hàng trong một hoặc nhiều cột.

Ví dụ này là một chút giả tạo, nhưng minh họa ý tưởng; xem tại đây ( 1 , 2 ) để biết thêm.

Mục tiêu là thêm một cột team_performancevào Pitchingbảng ghi lại thành tích (thứ hạng) của người ném bóng tốt nhất trong mỗi đội (được đo bằng ERA thấp nhất, trong số các bình có ít nhất 6 trận được ghi).

# to exclude pitchers with exceptional performance in a few games,
#   subset first; then define rank of pitchers within their team each year
#   (in general, we should put more care into the 'ties.method'
Pitching[G > 5, rank_in_team := frank(ERA), by = .(teamID, yearID)]
Pitching[rank_in_team == 1, team_performance := 
           # this should work without needing copy(); 
           #   that it doesn't appears to be a bug: 
           #   https://github.com/Rdatatable/data.table/issues/1926
           Teams[copy(.SD), Rank, .(teamID, yearID)]]

Lưu ý rằng x[y]cú pháp trả về nrow(y)các giá trị, đó là lý do tại sao .SDở bên phải Teams[.SD](vì RHS :=trong trường hợp này yêu cầu nrow(Pitching[rank_in_team == 1])các giá trị.

.SDHoạt động theo nhóm

Thông thường, chúng tôi muốn thực hiện một số thao tác trên dữ liệu của chúng tôi ở cấp độ nhóm . Khi chúng tôi chỉ định by =(hoặc keyby =), mô hình tinh thần cho những gì xảy ra khi data.tablecác quy trình jnghĩ về việc bạn data.tablebị chia thành nhiều tiểu thành phần data.table, mỗi mô hình tương ứng với một giá trị của bybiến của bạn :

nhóm minh họa

Trong trường hợp này, .SDcó nhiều bản chất - nó đề cập đến từng phụ data.table, từng lúc một (chính xác hơn một chút, phạm vi của .SDlà một phụ data.table). Điều này cho phép chúng tôi thể hiện chính xác một thao tác mà chúng tôi muốn thực hiện trên mỗi phụ -data.table trước khi kết quả được lắp ráp lại được trả lại cho chúng tôi.

Điều này hữu ích trong nhiều cài đặt, phổ biến nhất được trình bày ở đây:

Tập đoàn con

Chúng ta hãy lấy mùa dữ liệu gần đây nhất cho mỗi đội trong dữ liệu Lahman. Điều này có thể được thực hiện khá đơn giản với:

# the data is already sorted by year; if it weren't
#   we could do Teams[order(yearID), .SD[.N], by = teamID]
Teams[ , .SD[.N], by = teamID]

Hãy nhớ lại .SDchính nó là một data.table.Nliên quan đến tổng số hàng trong một nhóm (nó bằng với nrow(.SD)mỗi nhóm), do đó .SD[.N]trả về toàn bộ.SD cho hàng cuối cùng được liên kết với mỗi hàng teamID.

Một phiên bản phổ biến khác của điều này là sử dụng .SD[1L]thay thế để có được sự quan sát đầu tiên cho mỗi nhóm.

Nhóm Optima

Giả sử chúng tôi muốn trả lại năm tốt nhất cho mỗi đội, được đo bằng tổng số lần chạy của họ được ghi (tất nhiên R, chúng tôi có thể dễ dàng điều chỉnh điều này để tham khảo các số liệu khác). Thay vì lấy một phần tử cố định từ mỗi phụ data.table, bây giờ chúng tôi xác định chỉ mục mong muốn một cách linh hoạt như sau:

Teams[ , .SD[which.max(R)], by = teamID]

Lưu ý rằng phương pháp này tất nhiên có thể được kết hợp với .SDcolsđể chỉ trả về các phần của data.tablemỗi phần .SD(với phần cảnh báo .SDcolscần được cố định trên các tập con khác nhau)

NB : .SD[1L]hiện được tối ưu hóa bởi GForce( xem thêm ), data.tablecác phần bên trong giúp tăng tốc ồ ạt các hoạt động được nhóm phổ biến nhất như sumhoặc mean- xem ?GForceđể biết thêm chi tiết và theo dõi / hỗ trợ giọng nói cho các yêu cầu cải tiến tính năng để cập nhật ở mặt trước này: 1 , 2 , 3 , 4 , 5 , 6

Hồi quy nhóm

Quay trở lại cuộc điều tra ở trên về mối quan hệ giữa ERAW, giả sử chúng tôi hy vọng mối quan hệ này sẽ khác nhau theo nhóm (nghĩa là có một độ dốc khác nhau cho mỗi nhóm). Chúng ta có thể dễ dàng chạy lại hồi quy này để khám phá tính không đồng nhất trong mối quan hệ này như sau (lưu ý rằng các lỗi tiêu chuẩn từ phương pháp này nói chung là không chính xác - đặc điểm kỹ thuật ERA ~ W*teamIDsẽ tốt hơn - cách tiếp cận này dễ đọc hơn và các hệ số đều ổn) :

# use the .N > 20 filter to exclude teams with few observations
Pitching[ , if (.N > 20) .(w_coef = coef(lm(ERA ~ W))['W']), by = teamID
          ][ , hist(w_coef, 20, xlab = 'Fitted Coefficient on W',
                    ylab = 'Number of Teams', col = 'darkgreen',
                    main = 'Distribution of Team-Level Win Coefficients on ERA')]

phân phối các hệ số được trang bị

Mặc dù có một số lượng không đồng nhất khá lớn, có một sự tập trung khác biệt xung quanh giá trị tổng thể quan sát được

Hy vọng rằng điều này đã làm sáng tỏ sức mạnh của .SDviệc tạo điều kiện cho mã đẹp, hiệu quả trong data.table!


5
Lời giải thích hay. Chỉ cần một nhận xét: Thay vì Nhóm [, .SD [which.max (R)], by = teamID], bạn có thể khai thác chức năng thứ tự dữ liệu nhanh.table: thông qua Đội [order (teamID, -R), .SD [ 1L], keyby = teamID], việc này sẽ nhanh hơn.
bartleby

@bartleby cảm ơn, thực sự điều này là chính xác, nhưng cách tiếp cận chung vẫn hữu ích để biết - ví dụ: nếu chúng ta cần đặt hàng theo số lượng phụ thuộc vào nhóm. có lẽ tôi sẽ thay đổi ví dụ.
MichaelChirico

3

Tôi đã làm một video về điều này sau khi nói chuyện với Matt Dowle về .SD, bạn có thể xem nó trên YouTube: https://www.youtube.com/watch?v=DwEzQuYfMsI


2
Thật không may, Stack Overflow thường không chào đón các câu trả lời chủ yếu bao gồm các liên kết bên ngoài. Có lẽ bạn muốn chỉnh sửa thêm thông tin văn bản và mã vào nó? Fyi Matt đã thêm video của bạn vào wiki: github.com/Rdatitable/data.table/wiki/Pftimeations
Frank
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.