Ответ 1
Объединение переходов реализовано с commit 1375 в data.table v1.9.3 и доступен в текущей стабильной версии, v1.9.4. Функция называется foverlaps
. Из НОВОСТИ:
29)
Overlap joins
# 528 теперь здесь, наконец!! За исключением аргументовtype="equal"
иmaxgap
иminoverlap
, все остальное реализовано. Проверьте?foverlaps
и примеры там, где он используется. Это существенное дополнение кdata.table
.
Рассмотрим х, интервал, определенный как [a, b]
, где a <= b
и y, другой интервал, определенный как [c, d]
, где c <= d
. Интервал y называется перекрытием x вообще, iff d >= a
и c <= b
1. И y полностью содержится внутри x, iff a <= c,d <= b
2. Для различных типов перекрытий, пожалуйста, посмотрите ?foverlaps
.
Ваш вопрос является частным случаем объединения перекрытия: в d1
у вас есть истинные физические интервалы с позициями start
и end
. В d2
, с другой стороны, есть только позиции (pos
), а не интервалы. Чтобы иметь возможность выполнить перекрытие, нам нужно также создавать интервалы в d2
. Это достигается путем создания дополнительной переменной pos2
, которая идентична pos
(d2[, pos2 := pos]
). Таким образом, теперь мы имеем интервал в d2
, хотя и с одинаковыми начальными и конечными координатами. Этот "виртуальный интервал нулевой ширины" в d2
может быть затем использован в foverlap
для соединения с перекрытием с помощью d1
:
require(data.table) ## 1.9.3
setkey(d1)
d2[, pos2 := pos]
foverlaps(d2, d1, by.x = names(d2), type = "within", mult = "all", nomatch = 0L)
# x start end pos pos2
# 1: a 1 3 2 2
# 2: a 1 3 3 3
# 3: c 19 22 20 20
# 4: e 7 25 10 10
by.y
по умолчанию - key(y)
, поэтому мы его пропустили. by.x
по умолчанию принимает key(x)
, если он существует, а если не принимает key(y)
. Но для d2
ключа нет, и мы не можем установить столбцы из y
, потому что у них нет одинаковых имен. Итак, мы явно устанавливаем by.x
.
Тип перекрытия внутри, и мы хотели бы иметь все совпадения, только если есть совпадение.
NB: foverlaps
использует функцию бинарного поиска data.table(вместе с roll
при необходимости) под капотом, но некоторые аргументы функции (типы перекрытий, maxgap, minoverlap и т.д.) вдохновлены функцией findOverlaps()
из пакета Bioconductor IRanges
, отличный пакет (и, следовательно, GenomicRanges
, который расширяет IRanges
для геномики).
Итак, какое преимущество?
Тест на код выше на ваших данных приводит к foverlaps()
медленнее, чем ответ Габора (Timings: Gabor data.table solution = 0.004 vs foverlaps = 0.021 секунд). Но действительно ли это имеет значение в этой гранулярности?
Что было бы действительно интересно, так это то, насколько хорошо он масштабируется - как по скорости, так и по памяти. В ответе Gabor мы присоединяемся к ключевому столбцу x
. А затем отфильтруйте результаты.
Что делать, если d1
имеет около 40K строк и d2
имеет 100K строк (или больше)? Для каждой строки в d2
, которая соответствует x
в d1
, все эти строки будут сопоставлены и возвращены, только для фильтрации позже. Здесь пример вашего Q слегка масштабируется:
Сгенерировать данные:
require(data.table)
set.seed(1L)
n = 20e3L; k = 100e3L
idx1 = sample(100, n, TRUE)
idx2 = sample(100, n, TRUE)
d1 = data.table(x = sample(letters[1:5], n, TRUE),
start = pmin(idx1, idx2),
end = pmax(idx1, idx2))
d2 = data.table(x = sample(letters[1:15], k, TRUE),
pos1 = sample(60:150, k, TRUE))
foverlaps:
system.time({
setkey(d1)
d2[, pos2 := pos1]
ans1 = foverlaps(d2, d1, by.x=1:3, type="within", nomatch=0L)
})
# user system elapsed
# 3.028 0.635 3.745
Для этого потребовалось ~ 1 ГБ памяти, из которых ans1
- 420 МБ. Большая часть времени, проведенного здесь, действительно на подмножестве. Вы можете проверить это, установив аргумент verbose=TRUE
.
Решение Gabor:
## new session - data.table solution
system.time({
setkey(d1, x)
ans2 <- d1[d2, allow.cartesian=TRUE, nomatch=0L][between(pos1, start, end)]
})
# user system elapsed
# 15.714 4.424 20.324
И это заняло в общей сложности ~ 3,5 ГБ.
Я только отметил, что Габор уже упоминает память, необходимую для промежуточных результатов. Итак, попробуйте sqldf
:
# new session - sqldf solution
system.time(ans3 <- sqldf("select * from d1 join
d2 using (x) where pos1 between start and end"))
# user system elapsed
# 73.955 1.605 77.049
В общей сложности было 1,4 ГБ. Таким образом, он определенно использует меньше памяти, чем показанный выше.
[Ответы были проверены как идентичные после удаления pos2
из ans1
и установки ключа для обоих ответов.]
Обратите внимание, что это соединение перекрытия спроектировано с проблемами, при которых d2
не обязательно имеет одинаковые начальные и конечные координаты (например: genomics, поле, откуда я родом, где d2
обычно составляет около 30-150 миллионов или больше строк).
foverlaps()
является стабильным, но все еще находится в разработке, что означает, что некоторые аргументы и имена могут быть изменены.
NB: поскольку я упомянул выше GenomicRanges
, он также отлично справляется с этой проблемой. Он использует интервальные деревья под капотом и также обладает достаточной памятью. В моих тестах по данным геномики foverlaps()
выполняется быстрее. Но это для другого (блога) сообщения, в другое время.