Eu tenho um longo conjunto de dados com colunas que representam os horários de início e de término e quero descartar uma linha se ela se sobrepuser a outra e tiver uma prioridade mais alta (por exemplo, 1 é a prioridade mais alta). Meus dados de exemplo são
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))
A maneira como desenvolvi ataca o problema de trás para frente, encontrando as sobreposições com um valor de prioridade mais alto e usando um anti_join
para removê-las do quadro de dados original. Esse código não funciona se houver três períodos sobrepostos ao mesmo ponto no tempo e tenho certeza de que há uma maneira mais eficiente e funcional de fazer isso.
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
Alguém pode me ajudar a obter a mesma saída, mas com uma função mais limpa? Bônus se ele puder manipular uma entrada com três ou mais períodos de tempo que se sobrepõem.
combn
, embora possa ficar caro se você tiver muitas linhas.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
que adapta IRanges / GRanges (usado para encontrar sobreposições nos genomas) para o arrumado inverso. Eu acho que você pode transformar seus tempos em intervalos "genômicos" convertendo seus dias + horas em um número inteiro de horas ("corossoma") e seus minutos + segundos em um número inteiro de segundos ("nucleotídeos"). Se você analisou a saída depair_overlaps
(e usou uma coluna de ID para remover sobreposições auto-próprias), mantenha sua prioridade e faça um bom filtro dos resultados + inner_join com sua tabela original. É hacky, mas deve otimizar a facilidade de codificação + eficiência.Respostas:
Aqui está uma
data.table
solução usadafoverlaps
para detectar os registros sobrepostos (como já mencionado por @GenesRus). Os registros sobrepostos são atribuídos a grupos para filtrar o registro com no máx. prioridade no grupo. Adicionei mais dois registros aos dados de exemplo, para mostrar que este procedimento também está funcionando para três ou mais registros sobrepostos:Edit: Modifiquei e traduzi a solução @ pgcudahy para a
data.table
qual fornece código ainda mais rápido:Para mais detalhes, consulte
?foverlaps
- Existem alguns recursos mais úteis implementados para controlar o que é considerado uma sobreposição, comomaxgap
,minoverlap
outype
(qualquer, dentro, início, fim e igual).Atualização - nova referência
Código de referência:
fonte
Eu tenho uma função auxiliar que agrupa dados sobrepostos / hora usando o pacote igraph (pode incluir um buffer de sobreposição, ou seja, o terminal fica dentro de 1 minuto ...)
Usei-o para agrupar seus dados com base em intervalos no lubridato e, em seguida, fazer algumas disputas de dados para obter apenas a entrada de prioridade mais alta em tempos de sobreposição.
Não tenho certeza de quão bem será dimensionado.
Que dá:
fonte
Desci uma toca de coelho olhando para árvores de intervalo (e implementações R como IRanges / plyranges), mas acho que esse problema não precisa de uma estrutura de dados tão envolvida, pois os horários de início podem ser facilmente classificados. Também ampliei o conjunto de testes como @ismirsehregal para abranger mais relações de intervalo em potencial , como um intervalo que começa antes e termina após o vizinho ou quando três intervalos se sobrepõem, mas o primeiro e o último não se sobrepõem, ou dois intervalos que começam e pare exatamente nos mesmos horários.
Em seguida, faço duas passagens por cada intervalo para ver se ele se sobrepõe ao seu antecessor ou sucessor
stop >= lead(start, default=FALSE)
estart <= lag(stop, default=FALSE))
Durante cada passagem, há uma segunda verificação para ver se a prioridade do intervalo tem um valor numérico mais alto que o predecessor ou sucessor
priority > lead(priority, default=(max(priority) + 1))
. Durante cada passagem, se as duas condições forem verdadeiras, um sinalizador "remover" será definido como verdadeiro em uma nova coluna usandomutate
. Todas as linhas com um sinalizador de remoção são filtradas.Isso evita a verificação de todas as combinações potenciais de intervalos, como a resposta de @ Paul (comparações 2n versus n!), Além de acomodar minha ignorância sobre a teoria dos grafos :)
Da mesma forma, a resposta de @ ismirsehregal tem magia data.table que está além da minha compreensão.
A solução do @ MKa parece não funcionar com> 2 períodos sobrepostos
Testar as soluções fornece
Deste código
fonte
tibble
estrutura e parece quepull()
estava causando o problema. Poisdataframe()
, deve funcionar como está. Apenas atualizei a resposta.data.table
tornar as coisas ainda mais rápidas (verifique meu novo benchmark).Também usando
igraph
para identificar grupos sobrepostos, você pode tentar:fonte