Подстановка таблицы данных по диапазону с использованием бинарного поиска

Как вы относитесь к подмножеству data.table с помощью числового диапазона с намерением использовать бинарный поиск?

Например:

require(data.table)
set.seed(1)

x<-runif(10000000,min=0,max=10)
y<-runif(10000000,min=0,max=10)

DF<-data.frame(x,y)
DT<-data.table(x,y)

system.time(DFsub<-DF[DF$x>5 & DF$y<7,])
# user  system elapsed 
# 1.529   0.250   1.821 

#subset DT
system.time(DTsub<-DT[x>5 & y<7])
# user  system elapsed 
#0.716   0.119   0.841 

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

EDIT: этот вопрос аналогичен, но пока не показывает, как подмножество диапазона: data.table: векторное сканирование v бинарный поиск с числовыми столбцами - супер-медленная настройка

Ответы

Ответ 1

Интересный вопрос. Сначала рассмотрим данные примера:

> print(DT)
                     x        y
       1: 2.607703e-07 5.748127
       2: 8.894131e-07 5.233994
       3: 1.098961e-06 9.834267
       4: 1.548324e-06 2.016585
       5: 1.569279e-06 7.957730
      ---                      
 9999996: 9.999996e+00 9.977782
 9999997: 9.999998e+00 2.666575
 9999998: 9.999999e+00 6.869967
 9999999: 9.999999e+00 1.953145
10000000: 1.000000e+01 4.001616
> length(DT$x)
[1] 10000000
> length(unique(DT$x))
[1] 9988478
> length(DT$y)
[1] 10000000
> length(unique(DT$y))
[1] 9988225
> DT[,.N,by=x][,table(N)]
N
      1       2       3 
9976965   11504       9 
> DT[,.N,by="x,y"][,table(N)]
N
       1 
10000000 
> 

Таким образом, в первом столбце имеется почти 10 миллионов уникальных значений с плавающей запятой: несколько групп размером 2 и 3 строки, но в основном 1 группа строк. После включения второго столбца существует 10 миллионов уникальных групп размером 1 строка. Это довольно сложная проблема, поскольку data.table больше подходит для сгруппированных данных; например, (id, date), (id1, id2, дата, время) и т.д.

Тем не менее, data.table и setkey поддерживают данные с плавающей запятой в ключах, поэтому дайте ему перейти.

На моем медленном нетбуке:

> system.time(setkey(DT,x,y))
   user  system elapsed 
  7.097   0.520   7.650 

> system.time(DT[x>5 & y<7])
   user  system elapsed 
  2.820   0.292   3.122 

Таким образом, подход векторного сканирования работает быстрее, чем установка ключа (и мы еще не использовали ключ). Учитывая, что данные являются плавающей точкой и почти уникальными, это не слишком удивительно, но я думаю, что довольно быстрое время для setkey сортировать 10 миллионов тщательно случайных и почти уникальных удвоений.

Сравните, например, с базой, просто сортируя x даже y:

> system.time(base::order(x))
   user  system elapsed 
 72.445   0.292  73.072 

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

system.time(w <- DT[.(5),which=TRUE,roll=TRUE])
   user  system elapsed 
  0.004   0.000   0.003 
> w
[1] 4999902

Но здесь мы застряли. Следующий шаг, подобный DT[(w+1):nrow(DT)], является уродливым и копирует. Я не могу придумать достойный способ использовать ключ отсюда, чтобы сделать часть y<7. В других примерах мы делаем что-то вроде DT[.(unique(x), 7), which=TRUE, roll=TRUE], но в этом случае данные настолько уникальны, что плавающая точка будет медленной.

В идеале для этой задачи требуется объединение диапазонов (FR # 203). Синтаксис в этом примере может быть:

DT[.( c(5,Inf), c(-Inf,7) )]

или, чтобы облегчить его, DT[x>5 & y<7] можно было бы оптимизировать, чтобы сделать это под капотом. Разрешить двухколоночный диапазон в i, который соединяется с соответствующими столбцами x, может быть весьма полезным и несколько раз возникал.

Ускорение в v1.9.2 нужно было сделать сначала, прежде чем мы могли бы перейти к таким вещам. Если вы попробуете setkey по этим данным в v1.8.10, вы обнаружите, что v1.9.2 значительно быстрее.

См. также:

Как присоединяться к таблице данных на условиях

Удалить диапазон в data.table