Saltar al contenido

Unión superpuesta con posiciones inicial y final

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 por type="equal" y maxgap y minoverlap argumentos, todo lo demás se implementa. Verificar ?foverlaps y los ejemplos allí sobre su uso. Esta es una característica importante de data.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 >= ayc <= 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 d2y 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.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *