Самый быстрый способ условно заменить данные на data.table(сравнение скорости)

Почему второй метод станет медленнее, увеличив размер data.table:

library(data.table)
DF = data.table(x=rep(c("a","b","c"),each=40000000), y=sample(c(1,3,6),40000000,T), v=1:9)

1

DF1=DF2=DF

system.time(DF[y==6,"y"]<-10)
user  system elapsed 
2.793   0.699   3.497 

2:

system.time(DF1$y[DF1$y==6]<-10)
user  system elapsed 
6.525   1.555   8.107 

3:

system.time(DF2[y==6, y := 10]) # slowest!
user  system elapsed 
7.925   0.626   8.569 

>sessionInfo()
R version 3.2.1 (2015-06-18)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 14.04.3 LTS

Есть ли более быстрый способ сделать это?

Ответ 1

В последнем случае это является следствием функции автоматической индексации в data.table, так как v1.9.4+. Подробнее читайте: -).

Когда вы выполняете DT[col == .] или DT[col %in% .], при первом запуске автоматически создается индекс. Индекс - это только order указанного столбца. Вычисление индексов довольно быстро (с использованием сортировки сортировки/истинной сортировки).

Таблица составляет 120 миллионов строк и занимает примерно:

# clean session
require(data.table)
set.seed(1L)
DF = data.table(x=rep(c("a","b","c"),each=40000000), y=sample(c(1,3,6),40000000,T), v=1:9)

system.time(data.table:::forderv(DF, "y"))
#   3.923   0.736   4.712 

Боковое примечание: Столбец y не должен быть действительно двойным (на котором порядок занимает больше времени). Если мы преобразуем его в целочисленный тип:

   DF[, y := as.integer(y)]
   system.time(data.table:::forderv(DF, "y"))
   #    user  system elapsed 
   #   0.569   0.140   0.717 

Преимущество заключается в том, что любые последующие подмножества в этом столбце с использованием == или %in% будут быстро разгоняться (слайды, R script, видео презентации Мэтта). Например:

# clean session, copy/paste code from above to create DF
system.time(DF[y==6, y := 10])
#    user  system elapsed 
#   4.750   1.121   5.932 

system.time(DF[y==6, y := 10])
#    user  system elapsed 
#   4.002   0.907   4.969 

Ой, подождите... это не быстро. Но.. индексирование..?!? Мы заменяем один и тот же столбец каждый раз новым значением. Это приводит к тому, что порядок изменения столбца (таким образом, удаляется индекс). Пусть подмножество y, но изменение v:

# clean session
require(data.table)
set.seed(1L)
DF = data.table(x=rep(c("a","b","c"),each=40000000), y=sample(c(1,3,6),40000000,T), v=1:9)

system.time(DF[y==6, v := 10L])
#    user  system elapsed 
#   4.653   1.071   5.765 
system.time(DF[y==6, v := 10L])
#    user  system elapsed 
#   0.685   0.213   0.910 

options(datatable.verbose=TRUE)
system.time(DF[y==6, v := 10L])
# Using existing index 'y'
# Starting bmerge ...done in 0 secs
# Detected that j uses these columns: v 
# Assigning to 40000059 row subset of 120000000 rows
#    user  system elapsed 
#   0.683   0.221   0.914 

Вы можете видеть, что время вычисления индексов (с использованием двоичного поиска) занимает 0 секунд. Также проверьте ?set2key().

Если вы не собираетесь делать повторное подмножество или, как в вашем случае, подмножество и изменение одного и того же столбца, тогда имеет смысл отключить эту функцию, выполнив options(datatable.auto.index = FALSE), зарегистрировав # 1264:

# clean session
require(data.table)
options(datatable.auto.index = FALSE) # disable auto indexing
set.seed(1L)
DF = data.table(x=rep(c("a","b","c"),each=40000000), y=sample(c(1,3,6),40000000,T), v=1:9)

system.time(DF[y==6, v := 10L])
#    user  system elapsed 
#   1.067   0.274   1.367 
system.time(DF[y==6, v := 10L])
#    user  system elapsed 
#   1.100   0.314   1.443 

Различия здесь не так много. Время для векторного сканирования system.time(DF$y == 6)= 0.448s.

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

Функция автоматического индексирования является относительно новой и будет расширяться со временем и, вероятно, оптимизирована (возможно, есть места, на которые мы не смотрели). Отвечая на это Q, я понял, что мы не показываем время для вычисления порядка сортировки (используя fsort(), и я предполагаю, что время, проведенное там, может быть причиной того, что тайминги довольно близки, подано # 1265).


Что касается вашего второго дела, который медленно, не совсем уверен, почему. Я подозреваю, что это может быть из-за ненужных копий из R-части. Какую версию R вы используете? В будущем всегда отправляйте свой вывод sessionInfo().