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"