Эффективно проверяя значение другой строки в data.table

Замечания: Это вопрос, который я изначально разместил в справочной группе data.table. Мэтт Доул попросил более подробный пример, и я опубликовал его, но у меня были проблемы с форматированием в электронной почте. Я уже знаю, как форматировать на SO, поэтому я решил опубликовать это здесь.

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

Приведенный ниже пример иллюстрирует подход, который я использую сейчас. Пример использует слова в документах (я использую числовые индексы для обоих). Я хочу создать подмножество для конкретного слова, но только если ему предшествует или следует другое слово или набор слов:

Сначала я создаю фиктивный набор данных с десятью документами, содержащими миллион слов. В наборе три уникальных слова.

library(data.table)
set.seed(1000)
DT<-data.table(wordindex=sample(1:3,1000000,replace=T),docindex=sample(1:10,1000000,replace=T))
setkey(DT,docindex)
DT[,position:=seq.int(1:.N),by=docindex]


          wordindex docindex position
      1:         1        1        1
      2:         1        1        2
      3:         3        1        3
      4:         3        1        4
      5:         1        1        5
    ---                            
 999996:         2       10    99811
 999997:         2       10    99812
 999998:         3       10    99813
 999999:         1       10    99814
1000000:         3       10    99815

Обратите внимание, что просто подсчитать вхождения первого уникального слова во всех документах легко и красиво.

setkey(DT,wordindex)
count<-DT[J(1),list(count.1=.N),by=docindex]
count

    docindex count.1
 1:        1   33533
 2:        2   33067
 3:        3   33538
 4:        4   33053
 5:        5   33231
 6:        6   33002
 7:        7   33369
 8:        8   33353
 9:        9   33485
10:       10   33225

Это становится грязнее, если принять во внимание позицию впереди. Это запрос для подсчета вхождений первого уникального слова во всех документах.если за ним следует второе уникальное слово. Сначала я создаю новый столбец, содержащий слово на одну позицию впереди, а затем введите оба слова.

setkey(DT,docindex,position)
DT[,lead_wordindex:=DT[list(docindex,position+1)][,wordindex]]

         wordindex docindex position lead_wordindex
      1:         1        1        1              1
      2:         1        1        2              3
      3:         3        1        3              3
      4:         3        1        4              1
      5:         1        1        5              2
     ---                                           
 999996:         2       10    99811              2
 999997:         2       10    99812              3
 999998:         3       10    99813              1
 999999:         1       10    99814              3
1000000:         3       10    99815             NA

setkey(DT,wordindex,lead_wordindex)
countr2<-DT[J(c(1,1),c(1,3)),list(count.1=.N),by=docindex]
countr2

    docindex count.1
 1:        1   22301
 2:        2   21835
 3:        3   22490
 4:        4   21830
 5:        5   22218
 6:        6   21914
 7:        7   22370
 8:        8   22265
 9:        9   22211
10:       10   22190

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

setkey(DT,wordindex)
filter<-DT[J(1),list(wordindex,docindex,position)]
filter[,lead_position:=position+1]

        wordindex wordindex docindex position lead_position
     1:         1         1        2    99717         99718
     2:         1         1        3    99807         99808
     3:         1         1        4   100243        100244
     4:         1         1        1        1             2
     5:         1         1        1       42            43
    ---                                                    
332852:         1         1       10    99785         99786
332853:         1         1       10    99787         99788
332854:         1         1       10    99798         99799
332855:         1         1       10    99804         99805
332856:         1         1       10    99814         99815

setkey(DT,docindex,position)
filter[,lead_wordindex:=DT[J(filter[,list(docindex,lead_position)])][,wordindex]]

        wordindex wordindex docindex position lead_position lead_wordindex
     1:         1         1        2    99717         99718             NA
     2:         1         1        3    99807         99808             NA
     3:         1         1        4   100243        100244             NA
     4:         1         1        1        1             2              1
     5:         1         1        1       42            43              1
    ---                                                                   
332852:         1         1       10    99785         99786              3
332853:         1         1       10    99787         99788              3
332854:         1         1       10    99798         99799              3
332855:         1         1       10    99804         99805              3
332856:         1         1       10    99814         99815              3

setkey(filter,wordindex,lead_wordindex)
countr2.1<-filter[J(c(1,1),c(1,3)),list(count.1=.N),by=docindex]
countr2.1

    docindex count.1
 1:        1   22301
 2:        2   21835
 3:        3   22490
 4:        4   21830
 5:        5   22218
 6:        6   21914
 7:        7   22370
 8:        8   22265
 9:        9   22211
10:       10   22190

Думаю, довольно некрасиво. Кроме того, я, возможно, захочу посмотреть больше, чем на одно слово, что потребует создания еще одного столбца. Простой, но дорогой способ:

setkey(DT,docindex,position)
DT[,lead_lead_wordindex:=DT[list(docindex,position+2)][,wordindex]]

         wordindex docindex position lead_wordindex lead_lead_wordindex
      1:         1        1        1              1                   3
      2:         1        1        2              3                   3
      3:         3        1        3              3                   1
      4:         3        1        4              1                   2
      5:         1        1        5              2                   3
     ---                                                               
 999996:         2       10    99811              2                   3
 999997:         2       10    99812              3                   1
 999998:         3       10    99813              1                   3
 999999:         1       10    99814              3                  NA
1000000:         3       10    99815             NA                  NA

setkey(DT,wordindex,lead_wordindex,lead_lead_wordindex)
countr23<-DT[J(1,2,3),list(count.1=.N),by=docindex]
countr23

    docindex count.1
 1:        1    3684
 2:        2    3746
 3:        3    3717
 4:        4    3727
 5:        5    3700
 6:        6    3779
 7:        7    3702
 8:        8    3756
 9:        9    3702
10:       10    3744

Тем не менее, в настоящее время я должен использовать уродливый способ фильтрации и объединения из-за размера.

Итак, вопрос в том, есть ли более простой и красивый способ?

ОБНОВИТЬ:

Спасибо Аруну и Эдди за чистый и простой код, который решает проблему. По моим ~ 200М данным строки, это решение работает с простой комбинацией слов примерно за 10 секунд, что довольно хорошо!

Однако у меня есть дополнительная проблема, которая делает подход векторного сканирования менее оптимальным. Хотя в примере я ищу только одну комбинацию слов, на практике у меня может быть вектор слов для поиска в каждой позиции. Когда для этой цели я изменяю операторы "==" на "% in%" (векторы по 100 слов или более), выполнение запроса занимает гораздо больше времени. Таким образом, я все еще был бы заинтересован в решении бинарного поиска, если оно существует. Однако, если Арун не знает ни одного, это может не произойти, и я с радостью приму его ответ.

Ответы на вопрос(3)

Ваш ответ на вопрос