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" - .SD
là trong chiêu bài cơ bản nhất của nó một tham chiếu phản xạ cho data.table
chí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 .SD
là chí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 .SD
là 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 .SD
trong 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 Pitching
hoặc Pitching[]
:
identical(Pitching, Pitching[ , .SD])
# [1] TRUE
Về mặt .SD
tậ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ì .SD
là giới hạn các cột có trong .SD
việ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, fwrite
không thể tự động đọc Date
hoặc POSIXct
cột và chuyển đổi qua lại giữa character
/ factor
/ numeric
là phổ biến. Chúng tôi có thể sử dụng .SD
và .SDcols
chuyể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ư character
trong tập Teams
dữ 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 list
trong đó mỗi phần tử là một cột - do đó, sapply
/ lapply
áp dụng FUN
cho mỗi cột và trả về kết quả như sapply
/ lapply
thường sẽ (ở đây, FUN == is.character
trả về một logical
có độ dài 1, do đó sapply
trả về một vectơ).
Cú pháp để chuyển đổi các cột factor
nà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 fkt
trong 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 fkt
cho RHS.
Tính linh hoạt của .SDcols
(và :=
) để chấp nhận một character
vector hoặc một integer
vector 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ả factor
cá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 team
trở 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 Pitching
bảng. Làm thế nào để mối quan hệ (tuyến tính) giữa W
(thắng) và ERA
khá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 .SD
khá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)
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.table
cú 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 i
là một logical
vector, x[i]
sẽ trả lại những hàng x
tương ứng với nơi i
là TRUE
; khi i
là khá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 key
s của x
và i
, 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_performance
vào Pitching
bả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ị.
.SD
Hoạ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.table
các quy trình j
nghĩ về việc bạn data.table
bị 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 by
biến của bạn :
Trong trường hợp này, .SD
có 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 .SD
là 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 .SD
chính nó là một data.table
và .N
liê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.table
mỗi phần .SD
(với phần cảnh báo .SDcols
cầ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.table
cá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ư sum
hoặ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 ERA
và W
, 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*teamID
sẽ 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')]
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 .SD
việc tạo điều kiện cho mã đẹp, hiệu quả trong data.table
!
?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.SD
theo câu trả lời được chấp nhận.