Co? does.SD / align = "left" / tabela W R
.SD
wygląda na przydatny, ale tak naprawdę Nie wiem, co z nim robię. Co to oznacza? Dlaczego jest poprzedzający okres (kropka). Co się dzieje, gdy go używam?
I read:
.SD
jest data.table
zawierającym podzbiór danych x
dla każdej grupy, z wyłączeniem kolumn grupy. Może być używany podczas grupowania przez i
, podczas grupowania przez by
, keyed by
i _ad hoc_ by
Czy to znaczy, że córka data.table
S jest trzymana w pamięci na następną operację?
3 answers
.SD
oznacza coś w rodzaju " S
ubset of D
ata.tabela". Nie ma znaczenia dla początkowego "."
, z wyjątkiem tego, że jest jeszcze bardziej prawdopodobne, że dojdzie do zderzenia z nazwą kolumny zdefiniowaną przez użytkownika.
Jeśli to są Twoje dane.tabela:
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
Zrobienie tego może Ci pomóc zobacz czym jest .SD
:
DT[ , .SD[ , paste(x, v, sep="", collapse="_")], by=y]
# y V1
# 1: 1 a1_b3_c5
# 2: 3 a2_b4_c6
Zasadniczo, Instrukcja by=y
łamie oryginalne dane.tabela do tych dwóch Pod - 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
I działa na nich z kolei.
Podczas gdy działa na jednym z nich, pozwala odnosić się do bieżącego Pod - data.table
za pomocą nazwy Nicka/uchwytu / symbolu .SD
. Jest to bardzo przydatne, ponieważ możesz uzyskać dostęp i operować na kolumnach tak, jakbyś siedział w wierszu poleceń, pracując z pojedynczymi danymi.tabela o nazwie .SD
... z tym wyjątkiem, że tutaj data.table
przeprowadzi te operacje na każdym Pod - data.table
zdefiniowanym przez kombinacje klucza, "wklejając" je z powrotem i zwracając wyniki w jednym data.table
!
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-15 07:36:46
Edit:
Biorąc pod uwagę, jak dobrze odebrano tę odpowiedź, przekonwertowałem ją do pakietu winiety teraz dostępne tutaj
Biorąc pod uwagę, jak często to pojawia się, myślę, że to zasługuje na nieco więcej ekspozycji, poza pomocną odpowiedzią udzieloną przez Josha O ' Briena powyżej.
OpróczS ubsetD ata akronim Zwykle cytowany/stworzony przez Josha, myślę, że warto również rozważyć "S", aby oznaczać" Selfsame " lub "Self-reference" -- .SD
jest w swoim najbardziej podstawowym przebraniurefleksyjnym odniesieniem do samego data.table
-- jak zobaczymy w przykładach poniżej, jest to szczególnie pomocne w łączeniu "zapytań" (ekstrakcji/podzbiorów/etc za pomocą [
). W szczególności oznacza to również, że .SD
jest samym data.table
(z zastrzeżeniem, że nie pozwala na przypisanie z :=
).
Prostszym zastosowaniem .SD
jest podzbiór kolumn( tj. gdy .SDcols
jest określony); myślę, że ta wersja jest o wiele prostsza do zrozumienia, więc omówimy to najpierw poniżej. Interpretacja .SD
w drugim użyciu, grupowanie scenariuszy (tj. gdy podano by =
lub keyby =
), jest nieco inna, koncepcyjnie (choć w istocie jest taka sama, ponieważ w końcu operacja niezagrupowana jest skrajnym przypadkiem grupowania tylko z jedną grupą).
Oto kilka przykładów ilustracyjnych i kilka innych przykładów zastosowań, które sam realizuję często:
Ładowanie Danych Lahmana
Aby nadać temu bardziej realny charakter, zamiast tworzyć dane, załadujmy kilka zestawów danych o baseballu zLahman
: {144]}
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
Naked .SD
[141]} aby zilustrować, co mam na myśli o refleksyjnej naturze .SD
, rozważ jego najbardziej banalne użycie: {144]}
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
To oznacza, że właśnie wróciliśmy Pitching
, tzn. był to zbyt słowny sposób pisania Pitching
lub Pitching[]
:
identical(Pitching, Pitching[ , .SD])
# [1] TRUE
Pod względem podzbiorów, .SD
jest wciąż podzbiór danych, to tylko trywialny (sam zbiór).
Podzbiór Kolumny: .SDcols
Pierwszym sposobem wpływania na to, co .SD
jest ograniczenie kolumn zawartych w .SD
, używając argumentu .SDcols
do [
:
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
To jest tylko dla ilustracji i było dość nudne. Jednak nawet to proste użycie Nadaje się do szerokiej gamy wysoce korzystnych / wszechobecnych operacji manipulacji danymi: [144]}
Typ Kolumny Konwersja
Konwersja typu kolumny jest faktem życia dla danych munging -- od tego zapisu, fwrite
nie można automatycznie odczytywać Date
lub POSIXct
kolumn i konwertować tam i z powrotem między character
/factor
/numeric
są powszechne. Możemy użyć .SD
i .SDcols
do konwersji wsadowej grup takich kolumn.
Zauważamy, że następujące kolumny są przechowywane jako character
w zbiorze danych Teams
:
# 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
Jeśli jesteś zdezorientowany użyciem sapply
tutaj, zauważ, że to to samo co dla bazy 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
Kluczem do zrozumienia tej składni jest przypomnienie, że a data.table
(a także a data.frame
) można uznać za list
, gdzie każdy element jest kolumną - stąd, sapply
/lapply
stosuje FUN
do każdej kolumny i zwraca wynik jako sapply
/lapply
zazwyczaj (tutaj FUN == is.character
zwraca logical
o długości 1, więc sapply
zwraca wektor).
Składnia konwersji tych kolumn do factor
jest bardzo podobna - wystarczy dodać :=
operator przypisania
Teams[ , (fkt) := lapply(.SD, factor), .SDcols = fkt]
Zauważ, że musimy zawinąć fkt
w nawiasy ()
, aby zmusić R do zinterpretowania tego jako nazw kolumn, zamiast próbować przypisać nazwę fkt
do RHS.
Elastyczność .SDcols
(i :=
) w przyjmowaniu character
wektora lub wektora integer
pozycji kolumn może być również przydatna do konwersji nazw kolumn na podstawie wzorców*. Możemy przekonwertować wszystkie factor
kolumny na character
:
fkt_idx = which(sapply(Teams, is.factor))
Teams[ , (fkt_idx) := lapply(.SD, as.character), .SDcols = fkt_idx]
A następnie przekonwertować wszystkie kolumny, które contain team
back to factor
:
team_idx = grep('team', names(Teams), value = TRUE)
Teams[ , (team_idx) := lapply(.SD, factor), .SDcols = team_idx]
** jawnie używanie numerów kolumn (jak DT[ , (1) := rnorm(.N)]
) jest złą praktyką i może prowadzić do cichego uszkodzenia kodu w czasie, jeśli zmieni się pozycja kolumn. Nawet użycie liczb w sposób niejawny może być niebezpieczne, jeśli nie zachowujemy inteligentnej / ścisłej kontroli nad porządkiem, kiedy tworzymy indeks numerowany i kiedy go używamy.
Kontrolowanie RHS modelu
Zróżnicowana Specyfikacja Modelu jest podstawową cechą solidnych statystyk analiza. Spróbujmy przewidzieć erę miotacza (Earned Runs Average, miara wydajności), używając małego zestawu kowariantnych dostępnych w tabeli Pitching
. W jaki sposób (liniowa) relacja pomiędzy W
(wins) i ERA
różni się w zależności od innych kowariantnych zawartych w specyfikacji?
Oto krótki skrypt wykorzystujący moc .SD
, który bada to pytanie:
# 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)
Współczynnik zawsze ma oczekiwaną znak (lepsi miotacze mają zwykle więcej zwycięstw i mniej runów dozwolonych), ale wielkość może się znacznie różnić w zależności od tego, co jeszcze kontrolujemy.
Połączenia Warunkowe
data.table
składnia jest piękna ze względu na swoją prostotę i solidność. Składnia x[i]
elastycznie obsługuje dwa wspólne podejścia do podzbioru-gdy i
jest wektorem logical
, x[i]
zwróci te wiersze x
odpowiadające gdzie i
jest TRUE
; gdy i
jest innym data.table
, a join
jest wykonywana (w postaci prostej, używając key
s z x
i i
, w przeciwnym razie, gdy podano on =
, używając dopasowań tych kolumn).
Jest to ogólnie świetne rozwiązanie, ale zawodzi, gdy chcemy wykonać połączenie warunkowe, w którym dokładny charakter relacji między tabelami zależy od pewnych cech wierszy w jednej lub kilku kolumnach.
Ten przykład jest nieco wymyślony, ale ilustruje pomysł; zobacz tutaj (1, 2) po więcej.
Celem jest dodanie kolumny team_performance
do tabeli Pitching
, która rejestruje wydajność (rangę) drużyny najlepszego miotacza w każdej drużynie (mierzoną przez najniższą erę, wśród miotaczy z co najmniej 6 zarejestrowanymi meczami).
# 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)]]
Zauważ, że składnia x[y]
zwraca wartości nrow(y)
, dlatego .SD
znajduje się po prawej stronie w Teams[.SD]
(ponieważ RHS :=
w tym przypadku wymaga wartości nrow(Pitching[rank_in_team == 1])
.
Zgrupowane .SD
operacje
Często chciałbym wykonać jakąś operację na naszych danych na poziomie grupy. Kiedy określimy by =
(lub keyby =
), mentalnym modelem tego, co dzieje się, gdy data.table
procesy j
jest myślenie o Twoim data.table
jako o podzielonym na wiele Pod-data.table
S, z których każdy odpowiada pojedynczej wartości Twojej zmiennej(zmiennych) by
:
W tym przypadku, .SD
jest wielokrotnością w naturze - odnosi się do każdego z tych Pod - data.table
s, jeden-w-czasie (nieco dokładniej, zakres .SD
jest pojedynczym podzbiorem data.table
). To pozwala nam zwięźle wyrazić operację, którą chcielibyśmy wykonać na każdym sub-data.table
zanim ponownie zmontowany wynik zostanie nam zwrócony.
Podgrupa
Weźmy najnowszy sezon danych dla każdej drużyny w danych Lahman. Można to zrobić po prostu z:
# 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]
Przypomnijmy, że .SD
jest sama w sobie data.table
i że .N
odnosi się do całkowitej liczby wierszy w grupie (jest równa nrow(.SD)
w każdej grupie), więc .SD[.N]
zwraca całość .SD
dla ostatniego wiersza związanego z każdym teamID
.
Inną powszechną wersją tego jest użycie .SD[1L]
zamiast uzyskania pierwszej obserwacji dla każdej grupy.
Grupa Optima
Załóżmy, że chcemy zwrócić najlepsze rok dla każdej drużyny, mierzony ich całkowitą liczbą zdobytych runów (R
; możemy łatwo dostosować to, aby odnosić się do innych wskaźników, oczywiście). Zamiast przyjmować stały element z każdego Pod - data.table
, definiujemy teraz pożądany indeks dynamicznie w następujący sposób:
Teams[ , .SD[which.max(R)], by = teamID]
Zauważ, że to podejście można oczywiście połączyć z .SDcols
, aby zwracać tylko części data.table
dla każdego .SD
(z zastrzeżeniem, że .SDcols
należy ustalić w różnych podzbiory)
NB: .SD[1L]
jest obecnie zoptymalizowany przez GForce
(data.table
wewnętrzne, które znacznie przyspieszają najczęściej grupowane operacje, takie jak sum
lub mean
-- zobacz ?GForce
aby uzyskać więcej szczegółów i miej oko na obsługę głosową dla próśb o ulepszenia funkcji na tym froncie: 1, 2, 3, 4, 5, 6
Zgrupowane Regresja
Wracając do powyższego zapytania dotyczącego relacji między ERA
i W
, Załóżmy, że spodziewamy się, że ta relacja będzie różna w zależności od zespołu(tj. Możemy łatwo ponownie uruchomić tę regresję, aby zbadać heterogeniczność w tej relacji w następujący sposób (zauważając, że standardowe błędy z tego podejścia są na ogół nieprawidłowe -- Specyfikacja {136]} będzie lepsza - to podejście jest łatwiejsze do odczytania i współczynniki są OK):
# 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')]
Chociaż istnieje duża ilość niejednorodności, istnieje wyraźna koncentracja wokół obserwowanej wartości całkowitej
Miejmy nadzieję, że to wyjaśniło moc .SD
w ułatwianiu pięknego, skutecznego kodu w data.table
!
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-10-19 03:39:12
Zrobiłem o tym filmik po rozmowie z Mattem Dowle o .SD, Można go zobaczyć na YouTube: https://www.youtube.com/watch?v=DwEzQuYfMsI
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-07-18 19:15:25