Эффективно проверяет значение другой строки в данных.таблица

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

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

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

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

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

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

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

обновление:

спасибо Arun и eddi за чистый и простой код, который решает проблему. На моих данных строки ~200M это решение работает на простой комбинации слов примерно за 10 секунд, что довольно хорошо!

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

3 ответов


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

на DT вы создали из ваших данных, сначала мы добавим дополнительный столбец:

# the extra column:
DT[, I := .I]

нам это нужно, потому что мы будем setkey on docindex и wordindex. Это единственный способ подмножества без создания дополнительных столбцов (по крайней мере, то, что я мог бы придумать). Итак, нам нужен способ извлечения "исходные" позиции позже, чтобы проверить ваше состояние (следовательно,I).

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

setkey(DT, docindex, wordindex)

великолепно! Идея отсюда -извлечь должности где ваше желаемое слово соответствует-вот это значение 1L. Затем извлеките все остальные слова, которые вы можете (или не можете) хотеть прийти после этого слова в правильном положении. Затем мы просто сохраняем (или удаляем) их индексы, удовлетворяющие условию.

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

foo <- function(DT, doc_key, word_key, rest_key=NULL, match=FALSE) {
    ## note that I'm using 1.9.3, where this results in a vector
    ## if you're using 1.9.2, you'll have to change the joins accordingly
    idx1 = DT[J(doc_key, word_key), I]
    for (i in seq_along(rest_key)) {
        this_key = rest_key[i]
        idx2 = DT[J(doc_key, this_key), I]
        if (match) idx1 = idx1[which((idx1+i) %in% idx2)]
        else idx1 = idx1[which(!(idx1+i) %in% idx2)]
    }
    DT[idx1, .N, by=c(key(DT)[1L])]
}

здесь DT - это data.table, к которому добавлено а то setkey был вызван на двух столбцах, как упоминалось ранее.

doc_key в основном содержит все уникальные значения в docindex - здесь 1:10. word_key в основном 1L здесь. rest_key - это значения, которые вы хотите проверить, не встречаются в i - й позиции после позиции word_key.

Сначала мы извлекаем I для всех матчей 1L на idx1 (прост). Далее, мы петля через каждый стоимостью rest_key и добавьте эту позицию в idx1 = idx1+i и проверьте, встречается ли это значение в idx2. Если да, то на основе того, хотите ли вы извлечь соответствующего или несовпадающих записи, мы будем держать (или удалить их.)

и в конце этого цикла, idx1 должны иметь только нужные записи. Надеюсь, это поможет. Ниже показана демонстрация случаев, уже рассмотренных в другом ответе.


давайте рассмотрим первый сценарий. Количество всех записей для каждой группы в docindex где I-е место-это 1L и i+1че не 2L. Это в основном:

system.time(ans1 <- foo(DT, 1:10, 1L, 2L, FALSE))

#  user  system elapsed 
# 0.066   0.019   0.085 

# old method took 0.12 seconds

#     docindex     N
#  1:        1 22301
#  2:        2 21836
#  3:        3 22491
#  4:        4 21831
#  5:        5 22218
#  6:        6 21914
#  7:        7 22370
#  8:        8 22265
#  9:        9 22211
# 10:       10 22190

как насчет второго сценария? Здесь мы как i+1 - й и i+2положение th должно быть 2L и 3L, в отличие от не равно сценарий в предыдущем случае. Итак, мы установили match=TRUE здесь.

system.time(ans2 <- foo(DT, 1:10, 1L, 2:3,TRUE))
#  user  system elapsed 
# 0.080   0.011   0.090 

# old method took 0.22 seconds

#     docindex    N
#  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

эту функцию легко расширить. Например: если вы хотите иметь i+1th, чтобы быть равным 2L но i+2th не равно to 3L затем, вы можете изменить match получим вектор = length(rest_key) С указанием соответствующих логических значений.

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

HTH


похоже, вы просто хотите:

DT[, sum(wordindex == 1 & c(tail(wordindex, -1), 2) != 2), by = docindex]

Я не вижу смысла усложнять ее через соединения.

Кстати, вы получите другой ответ от вашего в некоторых случаях, что либо потому, что я не понимаю, что вы хотите, либо потому, что ваш метод терпит неудачу в некоторых случаях edge. Е. Г. попробовать оба метода

DT = data.table(wordindex = c(1,1,2,1,1,2), docindex = c(1,1,2,2,3,3))

создать и использовать его в своей j-expression:

lead <- function(x, n)
    if (n == 0) x else c(tail(x, -n), rep.int(NA, n))

если вы хотите получить счет, где wordindex at ith позиция 1L и i+1че не 2L, затем:

DT[, sum(wordindex == 1L & lead(wordindex, 1L) != 2L, na.rm=TRUE), by=docindex]
#     docindex    V1
#  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

если вы хотите получить графы, где wordindex at i - это 1л, i+1 это 2L и i+2 - это 3Л, а затем:

DT[, sum(wordindex == 1L & lead(wordindex, 1L) == 2L & 
          lead(wordindex, 2L) == 3L, na.rm=TRUE), by=docindex]
#     docindex   V1
#  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

обратите внимание, что не нужно setkey здесь.. adhoc-by должно работать отлично.


для адрес комментария:

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

вы говорите "большие данные", но больше ничего не говорите. Векторное сканирование всех данных (скажем, 20 миллионов строк или 200 миллионов строк) дорого стоит, да. Но, работая над каждой группой, даже если это не даст производительности двоичного поиска, не должно быть намного медленнее. Конечно, это еще зависит от количества групп и числа наблюдений в каждой группе. Но лучше проверить эти вещи и выяснить.

я оставлю вас. Удача.):