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.tableS jest trzymana w pamięci na następną operację?

 179
Author: amonk, 2011-12-14

3 answers

.SD oznacza coś w rodzaju " Subset of Data.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!

 210
Author: Josh O'Brien,
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 z Lahman: {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 characterwektora 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)

dopasuj współczynnik OLS na W, różne specyfikacje, przedstawione jako bary z wyraźnymi kolorami.

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.tableS, z których każdy odpowiada pojedynczej wartości Twojej zmiennej(zmiennych) by:

Wizualne przedstawienie sposobu grupowania. po lewej jest siatka. Pierwsza kolumna nosi tytuł "kolumna ID" z wartościami wielkimi literami od A do G, a reszta danych nie jest oznakowana, ale ma ciemniejszy kolor i po prostu ma napisane "dane", aby wskazać, że jest to dowolne. Strzałka w prawo pokazuje, jak te dane są podzielone na grupy. Każda wielka litera od A do G ma siatkę po prawej stronie; Siatka po lewej została podzielona, aby utworzyć ją po prawej stronie.

W tym przypadku, .SD jest wielokrotnością w naturze - odnosi się do każdego z tych Pod - data.tables, 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.

Jest to przydatne w różnych ustawieniach, z których najczęstsze są przedstawione tutaj:

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

Histogram przedstawiający rozkład dopasowanych współczynników. Jest słabo dzwonkowaty i skupiony wokół -.2

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!

 103
Author: MichaelChirico,
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

 4
Author: Sharon,
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