Эффективно проверяет значение другой строки в данных.таблица
Примечание: это вопрос, который я первоначально разместил в данных.группа справки таблицы. Мэтт Доул попросил более подробный пример, и я опубликовал его, но у меня были проблемы с форматированием в электронной почте. Я уже знаю, как форматировать на 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+1
th, чтобы быть равным 2L
но i+2
th не равно 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 i
th позиция 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 миллионов строк) дорого стоит, да. Но, работая над каждой группой, даже если это не даст производительности двоичного поиска, не должно быть намного медленнее. Конечно, это еще зависит от количества групп и числа наблюдений в каждой группе. Но лучше проверить эти вещи и выяснить.
я оставлю вас. Удача.):