Después de de una larga recopilación de datos dimos con la respuesta esta obstáculo que presentan ciertos los usuarios. Te dejamos la respuesta y esperamos serte de gran apoyo.
Solución:
Overlap joins se implementó con la confirmación 1375 en data.table v1.9.3 y está disponible en la versión estable actual, v1.9.4. La función se llama foverlaps
. De NOTICIAS:
29)
Overlap joins
¡¡# 528 ya está aquí, finalmente !! Excepto portype="equal"
ymaxgap
yminoverlap
argumentos, todo lo demás se implementa. Verificar?foverlaps
y los ejemplos allí sobre su uso. Esta es una característica importante dedata.table
.
Consideremos x, un intervalo definido como [a, b]
, dónde a <= b
, ey, otro intervalo definido como [c, d]
, dónde c <= d
. Se dice que el intervalo y superposición x en absoluto, iff d >= a
yc <= b
1. Y está completamente contenido dentro de x, iff a <= c,d <= b
2. Para conocer los diferentes tipos de superposiciones implementadas, consulte ?foverlaps
.
Su pregunta es un caso especial de una combinación superpuesta: en d1
tú tienes true intervalos físicos con start
y end
posiciones. En d2
por otro lado, solo hay posiciones (pos
), no intervalos. Para poder hacer una combinación de superposición, necesitamos crear intervalos también en d2
. Esto se logra creando una variable adicional pos2
, que es idéntico a pos
(d2[, pos2 := pos]
). Por lo tanto, ahora tenemos un intervalo en d2
, aunque con idéntico comienzo y fin coordenadas. Este 'intervalo virtual de ancho cero' en d2
luego se puede usar en foverlap
para hacer una unión superpuesta con 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
por defecto es key(y)
, así que lo omitimos. by.x
por defecto toma key(x)
si existe, y si no se lleva key(y)
. Pero un key no existe para d2
y no podemos establecer las columnas desde y
, porque no tienen los mismos nombres. Entonces, establecemos by.x
explícitamente.
los tipo de superposición es dentro dey nos gustaría tener todos coincidencias, solo si hay una coincidencia.
NÓTESE BIEN: foverlaps
utiliza la función de búsqueda binaria de data.table (junto con roll
cuando sea necesario) bajo el capó, pero algunos argumentos de función (tipos de superposiciones, maxgap, minoverlap, etc.) están inspirados en la función findOverlaps()
del paquete Bioconductor IRanges
, un paquete excelente (y también lo es GenomicRanges
, que se extiende IRanges
para Genómica).
Entonces, ¿cuál es la ventaja?
Un punto de referencia en el código anterior en sus datos da como resultado foverlaps()
más lento que la respuesta de Gabor (Tiempos: solución de tabla de datos de Gabor = 0,004 vs foverlaps = 0,021 segundos). Pero, ¿realmente importa en esta granularidad?
Lo que sería realmente interesante es ver qué tan bien escala, en términos de ambos velocidad y memoria. En la respuesta de Gabor, nos unimos basándonos en el key columna x
. Y luego filtrar los resultados.
Y si d1
tiene alrededor de 40K filas y d2
tiene 100K filas (o más)? Para cada fila en d2
eso combina x
en d1
, todos esas filas se compararán y se devolverán, solo para filtrarse más tarde. Aquí hay un ejemplo de su Q escalada solo ligeramente:
Generar datos:
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
Esto tomó ~ 1 GB de memoria en total, de los cuales ans1
es 420 MB. La mayor parte del tiempo que pasamos aquí es realmente un subconjunto. Puede verificarlo configurando el argumento verbose=TRUE
.
Soluciones de 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
Y esto tomó un total de ~ 3.5GB.
Acabo de señalar que Gabor ya menciona la memoria necesaria para obtener resultados intermedios. Entonces, probando 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
Tomó un total de ~ 1.4GB. Entonces, definitivamente usa menos memoria que la que se muestra arriba.
[The answers were verified to be identical after removing pos2
from ans1
and setting key on both answers.]
Tenga en cuenta que esta combinación de superposición está diseñada con problemas en los que d2
no tiene necesariamente coordenadas de inicio y finalización idénticas (p. ej., genómica, el campo de donde vengo, donde d2
suele tener entre 30 y 150 millones o más de filas).
foverlaps()
es estable, pero aún está en desarrollo, lo que significa que algunos argumentos y nombres pueden cambiar.
NB: Desde que mencioné GenomicRanges
arriba, también es perfectamente capaz de resolver este problema. Utiliza árboles de intervalo bajo el capó y también es bastante eficiente en la memoria. En mis puntos de referencia sobre datos genómicos, foverlaps()
es más rápido. Pero eso es para otra publicación (de blog), en otro momento.
data.table v1.9.8+
tiene una nueva característica - no equi Uniones. Con eso, esta operación se vuelve aún más sencilla:
require(data.table) #v1.9.8+
# no need to set keys on `d1` or `d2`
d2[d1, .(x, pos=x.pos, start, end), on=.(x, pos>=start, pos<=end), nomatch=0L]
# x pos start end
# 1: a 2 1 3
# 2: a 3 1 3
# 3: c 20 19 22
# 4: e 10 7 25
1) sqldf Esto no es data.table, pero los criterios de combinación complejos son fáciles de especificar de una manera directa en SQL:
library(sqldf)
sqldf("select * from d1 join d2 using (x) where pos between start and end")
donación:
x start end pos
1 a 1 3 2
2 a 1 3 3
3 c 19 22 20
4 e 7 25 10
2) tabla de datos Para obtener una respuesta de data.table, intente esto:
library(data.table)
setkey(d1, x)
setkey(d2, x)
d1[d2][between(pos, start, end)]
donación:
x start end pos
1: a 1 3 2
2: a 1 3 3
3: c 19 22 20
4: e 7 25 10
Tenga en cuenta que esto tiene la desventaja de formar el resultado intermedio posiblemente grande d1[d2]
que SQL no puede hacer. Las soluciones restantes también pueden tener este problema.
3) dplyr Esto sugiere la solución dplyr correspondiente. También usamos between
de data.table:
library(dplyr)
library(data.table) # between
d1 %>%
inner_join(d2) %>%
filter(between(pos, start, end))
donación:
Joining by: "x"
x start end pos
1 a 1 3 2
2 a 1 3 3
3 c 19 22 20
4 e 7 25 10
4) fusionar / subconjunto Usando solo la base de R:
subset(merge(d1, d2), start <= pos & pos <= end)
donación:
x start end pos
1: a 1 3 2
2: a 1 3 3
3: c 19 22 20
4: e 7 25 10
Adicional Tenga en cuenta que la solución de la tabla de datos aquí es mucho más rápida que la de la otra respuesta:
dt1 <- function()
d1 <- data.table(x=letters[1:5], start=c(1,5,19,30, 7), end=c(3,11,22,39,25))
d2 <- data.table(x=letters[c(1,1,2,2,3:5)], pos=c(2,3,3,12,20,52,10))
setkey(d1, x, start)
idx1 = d1[d2, which=TRUE, roll=Inf] # last observation carried forwards
setkey(d1, x, end)
idx2 = d1[d2, which=TRUE, roll=-Inf] # next observation carried backwards
idx = which(!is.na(idx1) & !is.na(idx2))
ans1 <<- cbind(d1[idx1[idx]], d2[idx, list(pos)])
dt2 <- function()
d1 <- data.table(x=letters[1:5], start=c(1,5,19,30, 7), end=c(3,11,22,39,25))
d2 <- data.table(x=letters[c(1,1,2,2,3:5)], pos=c(2,3,3,12,20,52,10))
setkey(d1, x)
ans2 <<- d1[d2][between(pos, start, end)]
all.equal(as.data.frame(ans1), as.data.frame(ans2))
## TRUE
benchmark(dt1(), dt2())[1:4]
## test replications elapsed relative
## 1 dt1() 100 1.45 1.667
## 2 dt2() 100 0.87 1.000 <-- from (2) above
Aquí puedes ver las comentarios y valoraciones de los usuarios
Nos encantaría que puedieras dar difusión a este ensayo si te valió la pena.