Apa yang dimaksud dengan .SD di data.table di R


173

.SDterlihat bermanfaat tetapi saya tidak benar-benar tahu apa yang saya lakukan dengannya. Apa artinya itu? Mengapa ada periode sebelumnya (berhenti penuh). Apa yang terjadi ketika saya menggunakannya?

Saya membaca: .SDadalah data.tablesubkumpulan xdata untuk setiap grup, tidak termasuk kolom grup. Ini dapat digunakan saat pengelompokan berdasarkan i, saat pengelompokan oleh by, dikunci by, dan _ad hoc_by

Apakah itu berarti bahwa anak perempuan data.tableitu disimpan dalam memori untuk operasi selanjutnya?


30
?data.tableditingkatkan di v1.7.10, berkat pertanyaan ini. Sekarang menjelaskan nama .SDsesuai jawaban yang diterima.
Matt Dowle

Jawaban:


205

.SDsingkatan sesuatu seperti " Subset of Data.table". Tidak ada signifikansi untuk inisial ".", kecuali bahwa itu membuat semakin tidak mungkin bahwa akan ada bentrokan dengan nama kolom yang ditentukan pengguna.

Jika ini adalah data Anda. Tabel:

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

Melakukan ini dapat membantu Anda melihat apa .SDitu:

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

Pada dasarnya, by=ypernyataan memecah data asli.data.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

dan beroperasi pada gilirannya.

Saat beroperasi pada salah satu, itu memungkinkan Anda merujuk ke sub-saat ini data.tabledengan menggunakan nama-nama / pegangan / simbol .SD. Itu sangat berguna, karena Anda dapat mengakses dan beroperasi pada kolom sama seperti jika Anda sedang duduk di baris perintah bekerja dengan data.table tunggal yang disebut .SD... kecuali bahwa di sini, data.tableakan melaksanakan operasi-operasi pada setiap sub tunggal data.tableyang didefinisikan oleh kombinasi tombol, "tempel" kembali dan kembalikan hasilnya dalam satu data.table!


12
Ya persis. Cara lain untuk melihatnya .SDadalah DT[,print(.SD),by=y].
Matt Dowle

6
@ MatthewDowle - Saat Anda di sini, pertanyaan untuk Anda. Melakukan DT[,print(.SD[,y]),by=y], menunjukkan bahwa saya memiliki akses ke nilai y, meskipun itu bukan bagian dari .SD. Dari mana nilai yscoping? Apakah tersedia jika ada nilai saat ini by?
Josh O'Brien

8
@ Astaga. Iya. Ini .SD[,y]adalah data.tablesubset reguler sehingga karena ybukan kolom .SDyang terlihat di lingkungan yang memanggilnya, yang dalam hal ini adalah jlingkungan (dari DTkueri) di mana byvariabel tersedia. Jika tidak ditemukan di sana, terlihat di induk, dan induknya dan seterusnya dengan cara R biasa. (Yah, via join scope yang diwarisi juga, yang tidak digunakan dalam contoh-contoh ini karena tidak ada is).
Matt Dowle

1
@Josh FAQ 2.8 memiliki lebih banyak tentang ini, mungkin perlu ditingkatkan (saran diterima)
Matt Dowle

3
@Josh Karena variabel grup tersedia dalam j, juga, dan panjang 1. by=list(x,y,z)artinya x, ydan ztersedia untuk j. Untuk akses generik mereka juga terlibat .BY. FAQ 2.10 memiliki beberapa riwayat di dalamnya, tetapi beberapa kejelasan dapat ditambahkan ?data.table. Hebat, bantuan docu akan sangat disambut. Lebih baik lagi jika Anda ingin bergabung dengan proyek dan berubah secara langsung.
Matt Dowle

98

Edit:

Mengingat seberapa baik jawaban ini diterima, saya telah mengubahnya menjadi sketsa paket yang sekarang tersedia di sini


Mengingat seberapa sering ini muncul, saya pikir ini memerlukan penjelasan yang sedikit lebih banyak, di luar jawaban yang bermanfaat yang diberikan oleh Josh O'Brien di atas.

Selain S ubset dari akronim D ata yang biasanya dikutip / dibuat oleh Josh, saya pikir itu juga membantu untuk mempertimbangkan "S" untuk singkatan "Selfsame" atau "Self-reference" - .SDdalam kedok paling mendasar referensi refleksif terhadap data.tabledirinya sendiri - seperti yang akan kita lihat dalam contoh di bawah ini, ini sangat membantu untuk merantai bersama "pertanyaan" (ekstraksi / subset / penggunaan menggunakan dll [). Secara khusus, ini juga berarti bahwa .SDitu sendiri adata.table (dengan peringatan bahwa itu tidak memungkinkan penugasan dengan :=).

Penggunaan sederhana .SDadalah untuk subset kolom (yaitu, kapan .SDcolsditentukan); Saya pikir versi ini jauh lebih mudah untuk dipahami, jadi kami akan membahasnya terlebih dahulu di bawah ini. Penafsiran .SDdalam penggunaan kedua, skenario pengelompokan (yaitu, ketika by =atau keyby =ditentukan), sedikit berbeda, secara konseptual (meskipun pada intinya sama, karena, bagaimanapun, operasi non-kelompok adalah kasus tepi pengelompokan dengan hanya satu kelompok).


Berikut adalah beberapa contoh ilustrasi dan beberapa contoh penggunaan lain yang sering saya terapkan:

Memuat Data Lahman

Untuk memberikan kesan yang lebih nyata, daripada membuat data, mari kita memuat beberapa set data tentang baseball dari 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

Telanjang .SD

Untuk menggambarkan apa yang saya maksudkan tentang sifat refleksif .SD, pertimbangkan penggunaannya yang paling dangkal:

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

Artinya, kami baru saja kembali Pitching , yaitu, ini adalah cara penulisan yang terlalu bertele-tele Pitchingatau Pitching[]:

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

Dalam hal subsetting, .SDmasih merupakan himpunan bagian dari data, itu hanya sepele (himpunan itu sendiri).

Berlangganan Kolom: .SDcols

Cara pertama untuk memengaruhi apa .SDadalah membatasi kolom yang terkandung dalam .SDmenggunakan .SDcolsargumen[ :

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

Ini hanya untuk ilustrasi dan cukup membosankan. Tetapi bahkan penggunaan sederhana ini cocok untuk berbagai operasi manipulasi data yang sangat bermanfaat / di mana-mana:

Konversi Jenis Kolom

Konversi jenis kolom adalah fakta kehidupan untuk munging data - pada tulisan ini, fwritetidak dapat secara otomatis membaca Dateatau POSIXctkolom , dan konversi bolak-balik antara character/ factor/ numericadalah umum. Kita bisa menggunakan .SDdan.SDcols untuk mengonversi grup dalam kolom tersebut secara berkelompok.

Kami perhatikan bahwa kolom berikut disimpan seperti characterdalam Teamskumpulan data:

# 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 

Jika Anda bingung dengan penggunaan di sapplysini, perhatikan bahwa itu sama seperti untuk basis 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

Kunci untuk memahami sintaks ini adalah untuk mengingat bahwa a data.table(dan juga a data.frame) dapat dianggap sebagai di listmana setiap elemen adalah kolom - dengan demikian, sapply/ lapplyberlaku FUNuntuk setiap kolom dan mengembalikan hasilnya seperti sapply/ lapplybiasanya (di sini, FUN == is.charactermengembalikan a logicaldengan panjang 1, jadi sapplymengembalikan vektor).

Sintaks untuk mengonversi kolom factorini sangat mirip - cukup tambahkan :=operator penugasan

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

Perhatikan bahwa kita harus membungkus fkttanda kurung ()untuk memaksa R untuk menafsirkan ini sebagai nama kolom, alih-alih mencoba untuk menetapkan nama fktke RHS.

Fleksibilitas .SDcols(dan :=) untuk menerima charactervektor atau suatu integervektor posisi kolom juga dapat berguna untuk konversi berbasis pola nama kolom *. Kami dapat mengonversi semua factorkolom menjadi character:

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

Dan kemudian mengonversi semua kolom yang berisi teamkembali ke factor:

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

** Secara eksplisit menggunakan nomor kolom (seperti DT[ , (1) := rnorm(.N)]) adalah praktik yang buruk dan dapat menyebabkan kode rusak secara diam-diam seiring waktu jika posisi kolom berubah. Bahkan secara implisit menggunakan angka bisa berbahaya jika kita tidak tetap pintar / ketat mengontrol urutan ketika kita membuat indeks bernomor dan kapan kita menggunakannya.

Mengontrol RHS Model

Memvariasikan spesifikasi model adalah fitur inti dari analisis statistik yang kuat. Mari kita coba dan prediksi ERA pitcher (Earned Runs Average, ukuran kinerja) menggunakan seperangkat kovariat kecil yang tersedia di Pitchingtabel. Bagaimana hubungan (linear) antara W(menang) dan ERAbervariasi tergantung pada kovariat mana yang termasuk dalam spesifikasi?

Berikut ini skrip pendek yang memanfaatkan kekuatan .SDyang mengeksplorasi pertanyaan ini:

# 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)

sesuai dengan koefisien OLS pada W, berbagai spesifikasi

Koefisien selalu memiliki tanda yang diharapkan (pitcher yang lebih baik cenderung memiliki lebih banyak kemenangan dan lebih sedikit run yang diizinkan), tetapi besarnya dapat bervariasi secara substansial tergantung pada apa lagi yang kami kontrol.

Bergabung Bersyarat

data.tablesintaksinya indah karena kesederhanaan dan kekokohannya. Sintaks x[i]fleksibel menangani dua umum pendekatan untuk subsetting - ketika iadalah logicalvektor, x[i]akan kembali orang-baris xyang sesuai dengan mana iadalah TRUE; kapan iyang laindata.table , a joindilakukan (dalam bentuk polos, menggunakan keys dari xdan i, jika tidak, ketika on =ditentukan, menggunakan kecocokan dari kolom-kolom itu).

Ini bagus secara umum, tetapi gagal ketika kita ingin melakukan join bersyarat , di mana sifat tepat hubungan di antara tabel tergantung pada beberapa karakteristik baris dalam satu atau beberapa kolom.

Contoh ini sedikit dibuat-buat, tetapi menggambarkan ide; lihat di sini ( 1 , 2 ) untuk lebih lanjut.

Tujuannya adalah untuk menambahkan kolom team_performancepada Pitchingtabel yang mencatat kinerja tim (peringkat) dari pitcher terbaik di setiap tim (yang diukur dengan ERA terendah, di antara pitcher dengan setidaknya 6 pertandingan yang direkam).

# 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)]]

Perhatikan bahwa x[y]sintaks mengembalikan nrow(y)nilai, itulah sebabnya .SDada di sebelah kanan Teams[.SD](karena RHS :=dalam kasus ini membutuhkan nrow(Pitching[rank_in_team == 1])nilai.

.SDOperasi yang dikelompokkan

Seringkali, kami ingin melakukan beberapa operasi pada data kami di tingkat grup . Ketika kami menentukan by =(atau keyby =), model mental untuk apa yang terjadi ketika data.tableproses jadalah menganggap Anda data.tablesebagai dibagi menjadi banyak komponen data.table, yang masing-masing sesuai dengan nilai tunggal byvariabel Anda :

pengelompokan diilustrasikan

Dalam hal ini, .SDsifatnya multipel - mengacu pada masing-masing sub-satu ini data.table, satu per satu (sedikit lebih akurat, cakupannya .SDadalah satu sub- data.table). Ini memungkinkan kami untuk secara singkat mengekspresikan operasi yang ingin kami lakukan pada setiap sub-data.table sebelum hasil yang dirakit dikembalikan kepada kami.

Ini berguna dalam berbagai pengaturan, yang paling umum disajikan di sini:

Berlangganan Grup

Mari kita dapatkan data musim terbaru untuk setiap tim di data Lahman. Ini dapat dilakukan cukup sederhana dengan:

# 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]

Ingat itu .SDsendiri adalah a data.table, dan yang .Nmengacu pada jumlah total baris dalam suatu kelompok (sama dengan nrow(.SD)dalam setiap grup), jadi .SD[.N]kembalikan keseluruhan.SD untuk baris terakhir yang terkait dengan masing-masing teamID.

Versi umum lain dari ini adalah menggunakan .SD[1L]sebagai gantinya untuk mendapatkan pengamatan pertama untuk setiap kelompok.

Optima Grup

Misalkan kita ingin mengembalikan tahun terbaik untuk masing-masing tim, yang diukur dengan jumlah total skor lari mereka ( R; kita dapat dengan mudah menyesuaikan ini dengan merujuk pada metrik lain, tentu saja). Alih-alih mengambil elemen tetap dari setiap sub- data.table, kami sekarang mendefinisikan indeks yang diinginkan secara dinamis sebagai berikut:

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

Perhatikan bahwa pendekatan ini tentu saja dapat digabungkan dengan .SDcolshanya mengembalikan porsi data.tablemasing-masing .SD(dengan peringatan yang .SDcolsharus diperbaiki di berbagai subset)

NB : .SD[1L]saat ini dioptimalkan oleh GForce( lihat juga ), data.tableinternal yang secara besar-besaran mempercepat operasi yang dikelompokkan yang paling umum seperti sumatau mean- lihat ?GForceuntuk detail lebih lanjut dan perhatikan / dukungan suara untuk permintaan peningkatan fitur untuk pembaruan di bagian depan ini: 1 , 2 , 3 , 4 , 5 , 6

Regresi Kelompok

Kembali ke pertanyaan di atas mengenai hubungan antara ERAdan W, misalkan kita mengharapkan hubungan ini berbeda oleh tim (yaitu, ada kemiringan yang berbeda untuk setiap tim). Kita dapat dengan mudah menjalankan kembali regresi ini untuk mengeksplorasi heterogenitas dalam hubungan ini sebagai berikut (mencatat bahwa kesalahan standar dari pendekatan ini umumnya salah - spesifikasinya ERA ~ W*teamIDakan lebih baik - pendekatan ini lebih mudah dibaca - koefisiennya lebih baik) :

# 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')]

distribusi koefisien yang dipasang

Meskipun ada cukup heterogenitas, ada konsentrasi yang berbeda di sekitar nilai keseluruhan yang diamati

Semoga ini telah menjelaskan kekuatan .SDdalam memfasilitasi kode yang indah dan efisien data.table!


5
Penjelasan yang bagus. Hanya satu komentar: Alih-alih Tim [, .SD [yang.max (R)], dengan = teamID], Anda dapat mengeksploitasi fungsi pesanan data.table cepat: melalui Tim [pesanan (teamID, -R), .SD [ 1L], keyby = teamID], ini harus lebih cepat.
Bartleby

@ Bartleby terima kasih, memang ini benar, tetapi pendekatan umum masih berguna untuk diketahui - misalnya jika kita perlu memesan berdasarkan jumlah yang bergantung pada grup. mungkin saya akan mengubah contoh.
MichaelChirico

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.