Ответ 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 значительно быстрее.
См. также: