Comprobando eficientemente el valor de otra fila en data.table

Nota: Esta es una pregunta que publiqué originalmente en el grupo de ayuda data.table. Matt Dowle solicitó un ejemplo más detallado y publiqué este, pero tuve problemas con el formato del correo electrónico. Ya sé cómo formatear en SO, así que pensé en publicarlo aquí.

Lo que básicamente intento hacer es subconjuntar filas de una tabla data.basada en un valor en esa filatanto como un valor en una fila anterior o siguiente. En este momento, estoy creando nuevas columnas para filas futuras y pasadas, y luego escribo data.table en estas columnas, pero esto requiere muchos recursos y es oneroso.

El siguiente ejemplo ilustra el enfoque que estoy usando ahora. El ejemplo usa palabras en documentos (yo uso índices numéricos para ambos). Quiero subconjunto para una palabra en particular, pero solo si está precedida o seguida por otra palabra o conjunto de palabras:

Primero creo un conjunto de datos ficticio con diez documentos que contienen un millón de palabras. Hay tres palabras únicas en el conjunto.

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

Tenga en cuenta que simplemente contar las apariciones de la primera palabra única en todos los documentos es fácil y hermoso.

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

Se vuelve más desordenado cuando se tiene en cuenta la posición por delante. Esta es una consulta para contar las apariciones de la primera palabra única en todos los documentosa no ser que es seguido por la segunda palabra única. Primero creo una nueva columna que contiene la palabra una posición adelante y luego escribo ambas palabras.

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

Tengo un conjunto de datos muy grande para el cual la consulta anterior falla para la asignación de memoria. Como alternativa, podemos crear esta nueva columna solo para el subconjunto de datos relevante filtrando el conjunto de datos original y luego uniéndolo nuevamente en la posición deseada:

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

Bastante feo, creo. Además, es posible que desee mirar más de una palabra por delante, lo que requiere la creación de otra columna. La manera fácil pero costosa es:

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

Sin embargo, actualmente tengo que usar la forma fea de filtro y unión debido al tamaño.

Entonces la pregunta es, ¿hay una manera más fácil y hermosa?

ACTUALIZAR:

Gracias a Arun y eddi por un código limpio y simple que resuelve el problema. En mis ~ 200 millones de datos de fila, esta solución funciona en una combinación simple de palabras en aproximadamente 10 segundos, ¡lo cual es bastante bueno!

Sin embargo, tengo un problema adicional que hace que el enfoque de escaneo vectorial sea menos que óptimo. Aunque en el ejemplo busco solo una combinación de palabras, en la práctica puedo tener un vector de palabras para buscar en cada posición. Cuando cambio las declaraciones "==" a "% en%" para ese propósito (vectores de 100 palabras o más), la consulta tarda mucho más en ejecutarse. Entonces todavía estaría interesado en una solución de búsqueda binaria si existe. Sin embargo, si Arun no sabe de uno, podría no saberlo, y con mucho gusto acepto su respuesta.

Respuestas a la pregunta(3)

Su respuesta a la pregunta