Tengo un conjunto de datos largo con columnas que representan las horas de inicio y finalización, y deseo eliminar una fila si se superpone con otra y tiene una prioridad más alta (por ejemplo, 1 es la prioridad más alta). Mi ejemplo de datos es
library(tidyverse)
library(lubridate)
times_df <- tibble(start = as_datetime(c("2019-10-05 14:05:25",
"2019-10-05 17:30:20",
"2019-10-05 17:37:00",
"2019-10-06 04:43:55",
"2019-10-06 04:53:45")),
stop = as_datetime(c("2019-10-05 14:19:20",
"2019-10-05 17:45:15",
"2019-10-05 17:50:45",
"2019-10-06 04:59:00",
"2019-10-06 05:07:10")), priority = c(5,3,4,3,4))
La forma en que he encontrado ataca el problema al revés al encontrar las superposiciones con un valor de prioridad más alto y luego usar un anti_join
para eliminarlas del marco de datos original. Este código no funciona si hay tres períodos superpuestos en el mismo punto de tiempo y estoy seguro de que hay una forma más eficiente y funcional de hacerlo.
dropOverlaps <- function(df) {
drops <- df %>%
filter(stop > lead(start) | lag(stop) > start) %>%
mutate(group = ({seq(1, nrow(.)/2)} %>%
rep(each=2))) %>%
group_by(group) %>%
filter(priority == max(priority))
anti_join(df, drops)
}
dropOverlaps(times_df)
#> Joining, by = c("start", "stop", "priority")
#> # A tibble: 3 x 3
#> start stop priority
#> <dttm> <dttm> <dbl>
#> 1 2019-10-05 14:05:25 2019-10-05 14:19:20 5
#> 2 2019-10-05 17:30:20 2019-10-05 17:45:15 3
#> 3 2019-10-06 04:43:55 2019-10-06 04:59:00 3
¿Alguien puede ayudarme a obtener la misma salida pero con una función más limpia? Bonificación si puede manejar una entrada con tres o más períodos de tiempo que se superponen.
combn
, aunque puede resultar costoso si tiene muchas filas.times_df %>% mutate(interval = interval(start, stop)) %>% {combn(nrow(.), 2, function(x) if (int_overlaps(.$interval[x[1]], .$interval[x[2]])) x[which.min(.$priority[x])], simplify = FALSE)} %>% unlist() %>% {slice(times_df, -.)}
plyranges
adaptar IRanges / GRanges (utilizado para encontrar superposiciones entre genomas) para el tidyverse. Creo que podría transformar sus tiempos en rangos "genómicos" al convertir sus días + horas en un número entero de horas ("coromosoma") y sus minutos + segundos en un número entero de segundos ("nucleótidos"). Si observara la salida depair_overlaps
(y usó una columna de ID para eliminar las superposiciones de uno mismo), podría mantener su prioridad y hacer un buen filtro de los resultados + inner_join con su tabla original. Es hacky pero debería optimizar la facilidad de codificación + eficiencia.Respuestas:
Aquí hay una
data.table
solución que utilizafoverlaps
para detectar los registros superpuestos (como ya lo mencionó @GenesRus). Los registros superpuestos se asignan a grupos para filtrar el registro con máx. prioridad en el grupo. Agregué dos registros más a sus datos de ejemplo, para mostrar que este procedimiento también funciona para tres o más registros superpuestos:Editar: modifiqué y traduje la solución de @ pgcudahy a la
data.table
que da un código aún más rápido:Para más detalles, ver
?foverlaps
- Hay algunas características más útiles implementadas para controlar lo que se considera un solapamiento comomaxgap
,minoverlap
otype
(cualquiera, dentro, inicio, final e iguales).Actualización - nuevo punto de referencia
Código de referencia:
fuente
Tengo una función auxiliar que agrupa datos superpuestos / datos de tiempo usando el paquete igraph (puede incluir un búfer de superposición, es decir, los terminales están dentro de 1 minuto ...)
Lo utilicé para agrupar sus datos en función de los intervalos en lubridate, luego hice algunos cambios de datos para obtener solo la entrada de máxima prioridad de los tiempos superpuestos.
No estoy seguro de qué tan bien escalará.
Lo que da:
fuente
Bajé por una madriguera de conejo mirando los árboles de intervalos (y las implementaciones de R como IRanges / plyranges) pero creo que este problema no necesita una estructura de datos tan complicada ya que los tiempos de inicio se pueden ordenar fácilmente. También amplié el conjunto de pruebas como @ismirsehregal para cubrir más relaciones de intervalo potenciales , como un intervalo que comienza antes y termina después de su vecino, o cuando se superponen tres intervalos, pero el primero y el último no se superponen, o dos intervalos que comienzan y detenerse exactamente al mismo tiempo.
Luego hago dos pases a través de cada intervalo para ver si se superpone con su predecesor o sucesor
stop >= lead(start, default=FALSE)
ystart <= lag(stop, default=FALSE))
Durante cada pasada, hay una segunda verificación para ver si la prioridad del intervalo tiene un valor numérico más alto que el predecesor o sucesor
priority > lead(priority, default=(max(priority) + 1))
. Durante cada pasada, si ambas condiciones son verdaderas, un indicador de "eliminar" se establece en verdadero en una nueva columna usandomutate
. Cualquier fila con un indicador de eliminación se filtra.Esto evita verificar todas las combinaciones potenciales de intervalos, como la respuesta de @ Paul (2n versus n! Comparaciones), además de satisfacer mi ignorancia de la teoría de gráficos :)
Del mismo modo, la respuesta de @ ismirsehregal tiene una magia data.table que está más allá de mi comprensión.
La solución de @ MKa no parece funcionar con> 2 períodos superpuestos
Probar las soluciones da
De este código
fuente
tibble
estructura y parece quepull()
estaba causando el problema. Paradataframe()
, debería funcionar como es. Acabo de actualizar la respuesta.data.table
que las cosas sean aún más rápidas (consulte mi nuevo punto de referencia).También puede usar
igraph
para identificar grupos superpuestos, puede intentar:fuente