r dataframe intervals

Encuentre en qué fila de intervalo en un marco de datos pertenece cada elemento de un vector



dataframe intervals (7)

Tengo un vector de elementos numéricos y un marco de datos con dos columnas que definen los puntos de inicio y final de los intervalos. Cada fila en el marco de datos es un intervalo. Quiero saber a qué intervalo pertenece cada elemento en el vector.

Aquí hay algunos datos de ejemplo:

# Find which interval that each element of the vector belongs in library(tidyverse) elements <- c(0.1, 0.2, 0.5, 0.9, 1.1, 1.9, 2.1) intervals <- frame_data(~phase, ~start, ~end, "a", 0, 0.5, "b", 1, 1.9, "c", 2, 2.5)

Los mismos datos de ejemplo para aquellos que se oponen al tidyverse:

elements <- c(0.1, 0.2, 0.5, 0.9, 1.1, 1.9, 2.1) intervals <- structure(list(phase = c("a", "b", "c"), start = c(0, 1, 2), end = c(0.5, 1.9, 2.5)), .Names = c("phase", "start", "end"), row.names = c(NA, -3L), class = "data.frame")

Aquí hay una forma de hacerlo:

library(intrval) phases_for_elements <- map(elements, ~.x %[]% data.frame(intervals[, c(''start'', ''end'')])) %>% map(., ~unlist(intervals[.x, ''phase'']))

Aquí está la salida:

[[1]] phase "a" [[2]] phase "a" [[3]] phase "a" [[4]] character(0) [[5]] phase "b" [[6]] phase "b" [[7]] phase "c"

Pero estoy buscando un método más simple con menos tipeo. He visto findInterval en preguntas relacionadas, pero no estoy seguro de cómo puedo usarlo en esta situación.


Aquí hay una especie de "one-liner" que ( foverlaps ) usa foverlaps del paquete data.table pero la data.table David es aún más concisa:

library(data.table) #v1.10.0 foverlaps(data.table(start = elements, end = elements), setDT(intervals, key = c("start", "end"))) # phase start end i.start i.end #1: a 0 0.5 0.1 0.1 #2: a 0 0.5 0.2 0.2 #3: a 0 0.5 0.5 0.5 #4: NA NA NA 0.9 0.9 #5: b 1 1.9 1.1 1.1 #6: b 1 1.9 1.9 1.9 #7: c 2 2.5 2.1 2.1


Aquí hay una posible solución usando las nuevas uniones " no equi " en data.table (v> = 1.9.8). Aunque dudo que le guste la sintaxis, debería ser una solución muy eficiente.

Además, con respecto a findInterval , esta función asume la continuidad en sus intervalos, aunque este no es el caso aquí, por lo que dudo que haya una solución sencilla que lo use.

library(data.table) #v1.10.0 setDT(intervals)[data.table(elements), on = .(start <= elements, end >= elements)] # phase start end # 1: a 0.1 0.1 # 2: a 0.2 0.2 # 3: a 0.5 0.5 # 4: NA 0.9 0.9 # 5: b 1.1 1.1 # 6: b 1.9 1.9 # 7: c 2.1 2.1

Con respecto al código anterior, me parece bastante explicativo: unir intervals y elements por la condición especificada en el operador on . Eso es practicamente todo.

Sin embargo, hay una cierta advertencia aquí: el start , el end y los elements deben ser todos del mismo tipo, por lo que si uno de ellos es integer , primero se debe convertir a numeric .


Inspirado por la solución de cut @ thelatemail, aquí hay una que usa findInterval que aún requiere mucho tipeo:

out <- findInterval(elements, t(intervals[c("start","end")]), left.open = TRUE) out[!(out %% 2)] <- NA intervals$phase[out %/% 2L + 1L] #[1] "a" "a" "a" NA "b" "b" "c"

findInterval cut y findInterval tienen intervalos abiertos a la izquierda. Por lo tanto, las soluciones que usan cut y findInterval no son equivalentes a las de Ben que usan intrval , la intrval no intrval David con data.table y mi otra solución con foverlaps .


La mención de David Arenburg de las uniones no equitativas fue muy útil para comprender qué tipo de problema general es este (¡gracias!). Ahora puedo ver que no está implementado para dplyr . Gracias a esta respuesta , veo que hay un paquete fuzzyjoin que puede hacerlo en el mismo idioma. Pero es apenas más simple que la solución de mi map anterior (aunque, desde mi punto de vista, es más legible), y no es una vela para la respuesta cut del correo electrónico por brevedad.

Para mi ejemplo anterior, la solución fuzzyjoin sería

library(fuzzyjoin) library(tidyverse) fuzzy_left_join(data.frame(elements), intervals, by = c("elements" = "start", "elements" = "end"), match_fun = list(`>=`, `<=`)) %>% distinct()

Lo que da:

elements phase start end 1 0.1 a 0 0.5 2 0.2 a 0 0.5 3 0.5 a 0 0.5 4 0.9 <NA> NA NA 5 1.1 b 1 1.9 6 1.9 b 1 1.9 7 2.1 c 2 2.5


Para completar, aquí hay otra manera, usando el paquete de intervals :

library(tidyverse) elements <- c(0.1, 0.2, 0.5, 0.9, 1.1, 1.9, 2.1) intervalsDF <- frame_data( ~phase, ~start, ~end, "a", 0, 0.5, "b", 1, 1.9, "c", 2, 2.5 ) library(intervals) library(rlist) interval_overlap( Intervals(intervalsDF %>% select(-phase) %>% as.matrix, closed = c(TRUE, TRUE)), Intervals(data_frame(start = elements, end = elements), closed = c(TRUE, TRUE)) ) %>% list.map(data_frame(interval_index = .i, element_index = .)) %>% do.call(what = bind_rows) # A tibble: 6 × 2 # interval_index element_index # <int> <int> #1 1 1 #2 1 2 #3 1 3 #4 2 5 #5 2 6 #6 3 7


Solo lapply funciona:

l <- lapply(elements, function(x){ intervals$phase[x >= intervals$start & x <= intervals$end] }) str(l) ## List of 7 ## $ : chr "a" ## $ : chr "a" ## $ : chr "a" ## $ : chr(0) ## $ : chr "b" ## $ : chr "b" ## $ : chr "c"

o en purrr , si ronroneas,

elements %>% map(~intervals$phase[.x >= intervals$start & .x <= intervals$end]) %>% # Clean up a bit. Shorter, but less readable: map_chr(~.x[1] %||% NA) map_chr(~ifelse(length(.x) == 0, NA, .x)) ## [1] "a" "a" "a" NA "b" "b" "c"


cut es posiblemente útil aquí.

out <- cut(elements, t(intervals[c("start","end")])) levels(out)[c(FALSE,TRUE)] <- NA intervals$phase[out] #[1] "a" "a" "a" NA "b" "b" "c"