2013-03-07 4 views
10

Я делаю некоторые скопления на таблице данных (отличный пакет !!!), и я нашел переменную .SD очень полезной для многих вещей. Однако использование этого метода значительно замедляет вычисление, когда есть много групп. Ниже приводится пример:R 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 в пользу отдельных столбцов? Заранее спасибо.

ответ

11

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

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

Смотрите последние несколько предложений часто задаваемых вопросов 2.1:

FAQ 2.1 Как я могу избежать написания действительно длинное выражение J? Вы сказали, что я должен использовать имена столбцов, но у меня много столбцов.
При группировке, выражение j можно использовать имена столбцов в качестве переменных, так как вы знаете, но он также может использовать зарезервированный символ .SD, который относится к Подмножеству Data.table для каждой группы (за исключением группировки столбцов). Итак, чтобы подвести итог всем вашим колонкам, это всего лишь 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). Однако, если вам все это нужно, это не имеет значения, например, в DT[,lapply(.SD,sum),by=...]. Это идеальное использование .SD.

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

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

+0

Спасибо большое! Я был обманут фразой «Объект .SD эффективно реализован внутренне и более эффективно, чем передача аргумента функции», и не понял «Пожалуйста, не делайте этого, хотя: DT [, sum (.SD [,» sales ", with = FALSE]), by = grp]" из-за with = FALSE. Это ускорит мой код! – vsalmendra

+0

@ vsalmendra А, да, это может быть яснее. Это то, что было оставлено на обсуждение сообщества в прошлом. В конечном итоге мы надеемся улучшить оптимизацию 'j', чтобы пользователям не нужно было разбираться в таких вещах. –

+0

@vsalmendra Теперь я улучшил FAQ 2.1 для следующего выпуска. –

3

Попробуйте решить эту разбив расчеты в два этапа, а затем объединение полученных кадров данных:

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 
+0

(+1) Я бы заменил 'x2' на' x2 <- x [J (unique (id), "2"), list (code2 = .N)] [, code: = NULL, keyby = "ID"] '. Ваш код удаляет строки, где код! = 2 для заданного идентификатора. – Arun

+0

+1 На самом деле это намного быстрее, чем 'x [, список (сумма (код == 2), N), by = id]' (пример 2, о котором идет речь), не так ли? Возможно, потому, что вы избегаете повторного вызова '==' для каждой группы (связанное распределение и т. Д. Для этих маленьких векторов). –

+0

@ Арун Вы уверены? 'x2 [xt] [is.na (code2)]' имеет несколько строк. Вы просто получаете 'NA' вместо' 0'. Может быть, неправильно, только посмотрел быстро. –

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