R: Табулирования и вставки с data.table - PullRequest
4 голосов
/ 10 сентября 2011

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

Код для воспроизведения данных приведен ниже.

Основная идея заключается в том, что существует набор индексов, например, ix1, ix2, ix3, ..., ixK.Обычно я выбираю только пару из них, скажем, ix1 и ix2.Затем я вычисляю совокупность всех строк с совпадающими значениями ix1 и ix2 (по всем отображаемым комбинациям) для столбца с именем val.Для простоты я сосредоточусь на сумме.

Я попробовал следующие методы

  1. Через разреженные матрицы: преобразовать значения в список координат, т.е.(ix1, ix2, val), затем создайте sparseMatrix - это приятно суммирует все, и тогда мне нужно только преобразовать обратно из разреженного представления матрицы в список координат.Скорость: хорошая, но она делает больше, чем необходимо, и не обобщает более высокие измерения (например, ix1, ix2, ix3) или более общие функции, чем сумма.

  2. Использованиеиз lapply и split: создав новый индекс, уникальный для всех (ix1, ix2, ...) n-кортежей, я могу затем использовать split и apply.Плохо здесь то, что уникальный индекс конвертируется split в фактор, и это преобразование требует очень много времени.Попробуйте system({zz <- as.factor(1:10^7)}).

  3. Я сейчас пытаюсь data.table, с помощью команды, подобной sumDT <- DT[,sum(val),by = c("ix1","ix2")].Тем не менее, я еще не вижу, как я могу объединить sumDT с DT, кроме как через что-то вроде DT2 <- merge(DT, sumDT, by = c("ix1","ix2"))

Есть ли более быстрый метод для этих data.tableприсоединиться, чем через операцию merge, которую я описал?

[Я также пробовал bigsplit из пакета bigtabulate и некоторые другие методы.Все, что преобразуется в фактор, в значительной степени исключено - насколько я могу судить, этот процесс преобразования очень медленный.]


Код для генерации данных.Естественно, лучше попробовать меньший N, чтобы увидеть, что что-то работает, но не все методы очень хорошо масштабируются для N >> 1000.

N   <-  10^7
set.seed(2011)
ix1 <-  1 + floor(rexp(N, 0.01))
ix2 <-  1 + floor(rexp(N, 0.01))
ix3 <-  1 + floor(rexp(N, 0.01))
val <-  runif(N)

DF  <-  data.frame(ix1 = ix1, ix2 = ix2, ix3 = ix3, val = val)
DF  <- DF[order(DF[,1],DF[,2],DF[,3]),]
DT  <- as.data.table(DF)

1 Ответ

4 голосов
/ 10 сентября 2011

Что ж, возможно, вы обнаружите, что слияние не так уж и плохо, если ваши key настроены правильно.

Давайте снова настроим проблему:

N   <-  10^6      ## not 10^7 because RAM is tight right now
set.seed(2011)
ix1 <-  1 + floor(rexp(N, 0.01))
ix2 <-  1 + floor(rexp(N, 0.01))
ix3 <-  1 + floor(rexp(N, 0.01))
val <-  runif(N)
DT <- data.table(ix1=ix1, ix2=ix2, ix3=ix3, val=val, key=c("ix1", "ix2"))

Теперь вы можете вычислить вашу сводную статистику

info <- DT[, list(summary=sum(val)), by=key(DT)]

и объединить столбцы «data.table way», или просто с merge

m1 <- DT[info]            ## the data.table way
m2 <- merge(DT, info)     ## if you're just used to merge
identical(m1, m2)
[1] TRUE

Если любой из этихспособы слияния слишком медленны, вы можете попробовать хитрый способ построить info за счет памяти:

info2 <- DT[, list(summary=rep(sum(val), length(val))), by=key(DT)]
m3 <- transform(DT, summary=info2$summary)
identical(m1, m3)
[1] TRUE

Теперь давайте посмотрим время:

#######################################################################
## Using data.table[ ... ] or merge
system.time(info <- DT[, list(summary=sum(val)), by=key(DT)])
   user  system elapsed 
  0.203   0.024   0.232

system.time(DT[info])
   user  system elapsed 
  0.217   0.078   0.296

system.time(merge(DT, info))
   user  system elapsed 
  0.981   0.202   1.185

########################################################################
## Now the two parts of the last version done separately:
system.time(info2 <- DT[, list(summary=rep(sum(val), length(val))), by=key(DT)])
   user  system elapsed 
  0.574   0.040   0.616 

system.time(transform(DT, summary=info2$summary))
   user  system elapsed 
  0.173   0.093   0.267

Или выможете пропустить промежуточное info создание таблицы, если следующее не кажется вам непонятным:

system.time(m5 <- DT[ DT[, list(summary=sum(val)), by=key(DT)] ])
   user  system elapsed 
  0.424   0.101   0.525 

identical(m5, m1)
# [1] TRUE
...