2015-08-28 4 views
18

Я не очень понимаю об использовании .SD и by.Использование lapply .SD в data.table R

Например, означает ли нижеследующий фрагмент: «изменить все столбцы в DT на коэффициент, кроме A и B?» Он также говорит в руководстве data.table: «.SD относится к подмножеству data.table для каждой группы (за исключением столбцов группировки)» - поэтому столбцы A и B исключены?

DT = DT[ ,lapply(.SD, as.factor), by=.(A,B)] 

Однако, я также читал, что by означает как «группы по» в SQL, когда вы делаете агрегацию. Например, если я хотел бы суммировать (например, colsum в SQL) по всем столбцам, кроме A и B, я все еще использую нечто подобное? Или в этом случае, означает ли нижний код, чтобы взять сумму и группу по значениям в столбцах A и B? (Взять сумму и группу по A,B как в SQL)

DT[,lapply(.SD,sum),by=.(A,B)] 

Тогда как это сделать простой colsum по всем столбцам, кроме A и B?

+0

'DT [, colSums (.sd) ,. SDcols = -c (A, B)] 'или' DT [, lapply (.SD, sum), SDcols = -c (A, B)] ' – Khashaa

+0

Использование' by' означает, что в каждом спаривании 'A'x'B вы суммируете значение каждого столбца _other_ в 'DT'. @ Комментарий Khashaa (несколько способов), как суммировать по всем столбцам, кроме 'A' и' B', _not by group_ – MichaelChirico

+0

@ MichaelChirico, Когда я изменяю тип столбца, хотя, как в первом примере, 'by' означает исключаю, я думаю, да? и какой из них быстрее? 'colSums' или' lapply'? – KTY

ответ

35

Просто, чтобы проиллюстрировать комментарии выше примера, давайте возьмем

set.seed(10238) 
# A and B are the "id" variables within which the 
# "data" variables C and D vary meaningfully 
DT = data.table(A = rep(1:3, each = 5), B = rep(1:5, 3), 
       C = sample(15), D = sample(15)) 
DT 
#  A B C D 
# 1: 1 1 14 11 
# 2: 1 2 3 8 
# 3: 1 3 15 1 
# 4: 1 4 1 14 
# 5: 1 5 5 9 
# 6: 2 1 7 13 
# 7: 2 2 2 12 
# 8: 2 3 8 6 
# 9: 2 4 9 15 
# 10: 2 5 4 3 
# 11: 3 1 6 5 
# 12: 3 2 12 10 
# 13: 3 3 10 4 
# 14: 3 4 13 7 
# 15: 3 5 11 2 

Сравните следующие:

#Sum all columns 
DT[ , lapply(.SD, sum)] 
#  A B C D 
# 1: 30 45 120 120 

#Sum all columns EXCEPT A, grouping BY A 
DT[ , lapply(.SD, sum), by = A] 
# A B C D 
# 1: 1 15 38 43 
# 2: 2 15 30 49 
# 3: 3 15 52 28 

#Sum all columns EXCEPT A 
DT[ , lapply(.SD, sum), .SDcols = !"A"] 
#  B C D 
# 1: 45 120 120 

#Sum all columns EXCEPT A, grouping BY B 
DT[ , lapply(.SD, sum), by = B, .SDcols = !"A"] 
# B C D 
# 1: 1 27 29 
# 2: 2 17 30 
# 3: 3 33 11 
# 4: 4 23 36 
# 5: 5 20 14 

Несколько заметок:

  • Вы сказали, что «не ниже фрагмент ... изменить все столбцы в DT ... "

Ответ нет, и это очень важно для data.table. Возвращаемый объект: data.table, и все столбцы в DT точно такие же, как и перед запуском кода.

  • Вы упомянули желание изменить типы столбцов

со ссылкой на пункт выше снова, обратите внимание, что ваш код (DT[ , lapply(.SD, as.factor)]) возвращает новыйdata.table и не меняет DT вообще. Один (неверный) способ сделать это, что делается с data.frame s в base, состоит в том, чтобы перезаписать старый data.table с новым data.table, который вы вернули, то есть DT = DT[ , lapply(.SD, as.factor)].

Это расточительно, потому что это связано с созданием копий DT, которые могут быть эффективными убийцами, когда DT большой. Правильный подход data.table к этой проблеме заключается в обновлении столбцов по ссылке с использованием `:=`, например, DT[ , names(DT) := lapply(.SD, as.factor)], который не создает копии ваших данных. См. data.table's reference semantics vignette для получения дополнительной информации об этом.

  • Вы упомянули сравнение эффективности lapply(.SD, sum) с кодом colSums. sum внутренне оптимизирован в data.table (вы можете заметить, что это верно на выходе добавления аргумента verbose = TRUE в пределах []); чтобы увидеть это в действии, давайте говядине ваш DT немного и запустить тест:

Результаты:

library(data.table) 
set.seed(12039) 
nn = 1e7; kk = seq(100L) 
DT = as.data.table(replicate(26, sample(kk, nn, T))) 
DT[ , LETTERS[1:2] := .(sample(100, nn, T), sample(100, nn, T))] 

library(microbenchmark) 
microbenchmark(times = 100L, 
       colsums = colSums(DT[ , !c("A", "B"), with = FALSE]), 
       lapplys = DT[ , lapply(.SD, sum), .SDcols = !c("A", "B")]) 
# Unit: milliseconds 
#  expr  min  lq  mean median  uq  max neval 
# colsums 848.9310 886.6289 906.8105 896.7696 925.4353 997.0001 100 
# lapplys 144.5028 145.7165 154.4077 147.5586 153.2286 253.6726 100 
+2

Цикл 'for' с' set' - еще один вариант для преобразования большого количества столбцов, упомянутых в нижней части этого ответа, и предложенных/одобренных Аруном и Мэттом: http://stackoverflow.com/a/16846530/1191259 – Frank

+0

@Frank да, и это то, что я сейчас делаю, но мне потребовалось некоторое время, чтобы обернуть голову вокруг того, что там происходит. Но здорово разобраться с этим рано. – MichaelChirico

+0

@MichaelChirico, спасибо за отзыв об изменении типов столбцов без создания нового DT! очень полезно. – KTY

Смежные вопросы