Операции со строками в data.table с использованием `by = .I`

Вот хорошее SO объяснение о строковых операциях вdata.table

Одна альтернатива, которая пришла мне в голову, заключается в использовании уникального id для каждой строки, а затем применить функцию, используя by аргумент. Как это:

library(data.table)

dt <- data.table(V0 =LETTERS[c(1,1,2,2,3)],
                 V1=1:5,
                 V2=3:7,
                 V3=5:1)

# create a column with row positions
dt[, rowpos := .I]

# calculate standard deviation by row
dt[ ,  sdd := sd(.SD[, -1, with=FALSE]), by = rowpos ] 

Вопросы:

  1. Есть ли веская причина не использовать этот подход? возможно, другие более эффективные альтернативы?

  2. Почему используется by = .I не работает так же?

    dt[ , sdd := sd(.SD[, -1, with=FALSE]), by = .I ]

1 ответ

Решение

1) Ну, одна из причин не использовать его, по крайней мере, для rowsums Примером является производительность и создание ненужного столбца. Сравните с опцией f2 ниже, которая почти в 4 раза быстрее и не нуждается в столбце rowpos:

dt <- data.table(V0 =LETTERS[c(1,1,2,2,3)], V1=1:5, V2=3:7, V3=5:1)
f1 <- function(dt){
  dt[, rowpos := .I] 
  dt[ ,  sdd := rowSums(.SD[, 2:4, with=FALSE]), by = rowpos ] }
f2 <- function(dt){dt[, sdd := rowSums(dt[, 2:4, with=FALSE])]}

library(microbenchmark)
microbenchmark(f1(dt),f2(dt))
# Unit: milliseconds
#   expr      min       lq     mean   median       uq      max neval cld
# f1(dt) 3.669049 3.732434 4.013946 3.793352 3.972714 5.834608   100   b
# f2(dt) 1.052702 1.085857 1.154132 1.105301 1.138658 2.825464   100  a 

2) На ваш второй вопрос, хотя dt[, sdd := sum(.SD[, 2:4, with=FALSE]), by = .I] не работает, dt[, sdd := sum(.SD[, 2:4, with=FALSE]), by = 1:NROW(dt)] работает отлично. Учитывая, что согласно ?data.table ".I - целочисленный вектор, равный seq_len (nrow (x))", можно ожидать, что они эквивалентны. Разница, однако, заключается в том, что .I для использования в j, не в byпотому что это значение возвращается by а не оценивается заранее.

Также можно ожидать (см. Комментарий на вопрос выше от @eddi), что by = .I надо просто скинуть ошибку. Но этого не происходит, потому что загрузка data.table пакет создает объект .I в пространстве имен data.table, которое доступно из глобальной среды и чье значение NULL, Вы можете проверить это, набрав .I в командной строке. (Обратите внимание, то же самое относится к .SD, .EACHI, .N, .GRP, а также .BY)

.I
# Error: object '.I' not found
library(data.table)
.I
# NULL
data.table::.I
# NULL

Результатом этого является то, что поведение by = .I эквивалентно by = NULL,

3) Хотя мы уже видели в части 1, что в случае rowSums, который уже выполняет цикл по строкам, есть гораздо более быстрые способы, чем создание столбца rowpos. Но как насчет цикла, когда у нас нет быстрой построчной функции?

Бенчмаркинг by = rowpos а также by = 1:NROW(dt) версии против for цикл с set() является информативным и демонстрирует, что версия цикла работает быстрее, чем любая из by = подходы:

f.rowpos <- function(){
  dt <- data.table(V0 = rep(LETTERS[c(1,1,2,2,3)], 1e3), V1=1:5, V2=3:7, V3=5:1)
  dt[, rowpos := .I] 
  dt[ ,  sdd := sum(.SD[, 2:4, with=FALSE]), by = rowpos ][] 
}

f.nrow <- function(){
  dt <- data.table(V0 = rep(LETTERS[c(1,1,2,2,3)], 1e3), V1=1:5, V2=3:7, V3=5:1)
  dt[, sdd := sum(.SD[, 2:4, with=FALSE]), by = 1:NROW(dt) ][] 
}

f.forset<- function(){
  dt <- data.table(V0 = rep(LETTERS[c(1,1,2,2,3)], 1e3), V1=1:5, V2=3:7, V3=5:1)
  dt[, sdd:=0L]
  for (i in 1L:NROW(dt)) {
    set(dt, i, 5L, sum(dt[i, 2:4]))
  }
  dt
}

microbenchmark(f.rowpos(),f.nrow(), f.forset(), times = 5)
Unit: seconds
       expr      min       lq     mean   median       uq      max neval cld
 f.rowpos() 4.465371 4.503614 4.510916 4.505922 4.521629 4.558042     5   b
   f.nrow() 4.499120 4.499920 4.541131 4.558701 4.571267 4.576647     5   b
 f.forset() 2.540556 2.603505 2.654036 2.606108 2.750719 2.769292     5  a 

Итак, в заключение, даже в ситуациях, когда нет оптимизированной функции, такой как rowSums который уже работает по строкам, всегда есть альтернативы использованию столбца rowpos, которые быстрее, но не требуют создания избыточного столбца.

Другие вопросы по тегам