R data.table медленная агрегация при использовании .SD

Я делаю некоторые агрегации на data.table(отличный пакет!!!), и я нашел переменную .SD очень полезной для многих вещей. Однако использование этого метода значительно замедляет вычисление, когда есть много групп. Следуйте примеру:

# A moderately big data.table
x = data.table(id=sample(1e4,1e5,replace=T),
               code=factor(sample(2,1e5,replace=T)),
               z=runif(1e5)
              )

setkey(x,id,code)

system.time(x[,list(code2=nrow(.SD[code==2]), total=.N), by=id])
##  user  system elapsed 
##  6.226   0.000   6.242

system.time(x[,list(code2=sum(code==2), total=.N), by=id])
## user  system elapsed 
## 0.497   0.000   0.498

system.time(x[,list(code2=.SD[code==2,.N], total=.N), by=id])
## user  system elapsed 
## 6.152   0.000   6.168

Я что-то делаю неправильно? Должен ли я избегать .SD в пользу отдельных столбцов? Спасибо заранее.

Ответ 1

Я делаю что-то неправильно, я должен избегать .SD в пользу отдельных столбцов?

Да, точно. Используйте .SD только, если вы действительно используете все данные внутри .SD. Вы также можете обнаружить, что вызов nrow() и вызов подзапроса на [.data.table внутри j тоже являются преступниками: используйте Rprof для подтверждения.

См. последние несколько предложений FAQ 2.1:

FAQ 2.1 Как я могу избежать написания действительно длинного j выражения? Вы сказали, что я должен использовать имена столбцов, но у меня есть много столбцов.
При группировке выражение j может использовать имена столбцов в качестве переменных, как вы знаете, но он также может использовать зарезервированный символ .SD, который ссылается на подмножество таблицы данных для каждой группы (исключая группировку колонны). Итак, чтобы подвести итоги всех ваших столбцов, просто DT[,lapply(.SD,sum),by=grp]. Это может показаться сложным, но быстро писать и быстро запускать. Обратите внимание, что вам не нужно создавать анонимные функция. См. Временную виньетку и вики для сравнения с другими методы. Объект .SD эффективно реализуется внутренне и более чем передача аргумента функции. Пожалуйста, не делайте этого хотя: DT[,sum(.SD[,"sales",with=FALSE]),by=grp]. Это работает, но очень неэффективен и неэффективен. Вот что было предназначено: DT[,sum(sales),by=grp] и может быть в 100 раз быстрее.

Также см. первый пул FAQ 3.1:

FAQ 3.1 У меня есть 20 столбцов и большое количество строк. Почему выражение одного столбца так быстро?
Несколько причин:
 - Только эта колонка сгруппированы, остальные 19 игнорируются, потому что data.table проверяет jвыражение и понимает, что он не использует другие столбцы.

Когда data.table проверяет j и видит символ .SD, это повышение эффективности выходит из окна. Он должен будет заполнить все подмножество .SD для каждой группы, даже если вы не используете все его столбцы. Для data.table очень сложно узнать, какие столбцы .SD вы действительно используете (j может содержать if s, например). Однако, если вам все это нужно, это, конечно, не имеет значения, например, в DT[,lapply(.SD,sum),by=...]. Это идеальное использование .SD.

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

j. Важное значение имеет простое существование символа .SD в j.

Вот почему .SDcols был введен. Поэтому вы можете указать data.table, какие столбцы должны быть в .SD, если вы хотите только подмножество. В противном случае data.table будет заполнять .SD всеми столбцами на всякий случай, если требуется j.

Ответ 2

Попробуйте решить это, разбив вычисления на два шага, затем слияние результирующих фреймов данных:

system.time({
  x2 <- x[code==2, list(code2=.N), by=id]
  xt <- x[, list(total=.N), by=id]
  print(x2[xt])
})

На моей машине он работает через 0,04 секунды, а не 7,42 секунды, то есть примерно в 200 раз быстрее, чем ваш исходный код:

         id code2 total
   1:     1     6    14
   2:     2     8    10
   3:     3     7    13
   4:     4     5    13
   5:     5     9    18
  ---                  
9995:  9996     4     9
9996:  9997     3     6
9997:  9998     6    10
9998:  9999     3     4
9999: 10000     3     6
   user  system elapsed 
   0.05    0.00    0.04