Ich habe einen langen Datensatz mit Spalten, die Start- und Stoppzeiten darstellen, und ich möchte eine Zeile löschen, wenn sie sich mit einer anderen überschneidet und eine höhere Priorität hat (z. B. 1 ist die höchste Priorität). Meine Beispieldaten sind
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))
Die Art und Weise, wie ich darauf gekommen bin, greift das Problem rückwärts an, indem ich die Überlappungen mit einem höheren Prioritätswert anti_join
finde und sie dann mit einem aus dem ursprünglichen Datenrahmen entferne. Dieser Code funktioniert nicht, wenn sich drei Zeiträume mit demselben Zeitpunkt überschneiden, und ich bin sicher, dass es eine effizientere und funktionalere Möglichkeit gibt, dies zu tun.
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
Kann mir jemand helfen, die gleiche Ausgabe zu erzielen, aber mit einer saubereren Funktion? Bonus, wenn eine Eingabe mit drei oder mehr Zeiträumen verarbeitet werden kann, die sich alle überschneiden.
combn
, obwohl es teuer werden kann, wenn Sie viele Zeilen haben.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
welche IRanges / GRanges (verwendet, um Überlappungen zwischen Genomen zu finden) für die Tidyverse anpassen. Ich denke, Sie könnten Ihre Zeiten in "genomische" Bereiche umwandeln, indem Sie Ihre Tage + Stunden in eine Stunden-Ganzzahl ("Choromosom") und Ihre Minuten + Sekunden in eine Sekunden-Ganzzahl ("Nukleotide") umwandeln. Wenn Sie sich die Ausgabe vonpair_overlaps
ansehen (und eine ID-Spalte zum Entfernen für Selbstüberlappungen verwenden), können Sie Ihre Priorität beibehalten und einen schönen Filter der Ergebnisse + inner_join mit Ihrer ursprünglichen Tabelle erstellen. Es ist hackig, sollte aber die einfache Codierung und Effizienz optimieren.Antworten:
Hier ist eine
data.table
Lösungfoverlaps
zum Erkennen der überlappenden Datensätze (wie bereits von @GenesRus erwähnt). Die überlappenden Datensätze werden Gruppen zugeordnet, um den Datensatz mit max. Priorität in der Gruppe. Ich habe Ihren Beispieldaten zwei weitere Datensätze hinzugefügt, um zu zeigen, dass dieses Verfahren auch für drei oder mehr überlappende Datensätze funktioniert:Bearbeiten: Ich habe die Lösung von @ pgcudahy geändert und übersetzt,
data.table
die noch schnelleren Code liefert:Für weitere Details siehe bitte
?foverlaps
- Es gibt einige weiteren nützlichen Features implementiert zu kontrollieren , was eine Überlappung wie in Betracht gezogen wirdmaxgap
,minoverlap
odertype
( überhaupt, in der , Beginn, Ende und gleich).Update - neuer Benchmark
Benchmark-Code:
quelle
Ich habe eine Hilfsfunktion, die überlappende Daten / Zeitdaten mithilfe des igraph-Pakets gruppiert (sie kann einen Überlappungspuffer enthalten, dh der Terminus liegt innerhalb von 1 Minute ...).
Ich habe es verwendet, um Ihre Daten basierend auf Intervallen in Lubridate zu gruppieren und dann einige Daten-Wrangling durchzuführen, um nur den Eintrag mit der höchsten Priorität aus überlappenden Zeiten zu erhalten.
Ich bin mir nicht sicher, wie gut es skalieren wird.
Welches gibt:
quelle
Ich bin in ein Kaninchenloch gegangen und habe mir Intervallbäume (und R-Implementierungen wie IRanges / Plyranges) angesehen, aber ich denke, dieses Problem benötigt keine so komplizierte Datenstruktur, da die Startzeiten leicht sortiert werden können. Ich habe auch den Testsatz wie @ismirsehregal erweitert, um mehr mögliche Intervallbeziehungen abzudecken z. B. ein Intervall, das vor und nach seinem Nachbarn beginnt oder wenn sich drei Intervalle überlappen, das erste und das letzte sich jedoch nicht überlappen, oder zwei Intervalle, die beginnen und genau zur gleichen Zeit anhalten.
Ich mache dann zwei Durchgänge durch jedes Intervall, um zu sehen, ob es sich mit seinem Vorgänger oder Nachfolger überschneidet
stop >= lead(start, default=FALSE)
undstart <= lag(stop, default=FALSE))
Bei jedem Durchgang wird erneut geprüft, ob die Priorität des Intervalls einen höheren numerischen Wert als der Vorgänger oder Nachfolger hat
priority > lead(priority, default=(max(priority) + 1))
. Wenn bei beiden Durchläufen beide Bedingungen erfüllt sind, wird in einer neuen Spalte mit ein "Entfernen" -Flag auf "true" gesetztmutate
. Alle Zeilen mit einem Entfernungsflag werden dann gefiltert.Dies vermeidet die Überprüfung aller möglichen Kombinationen von Intervallen wie @ Pauls Antwort (2n versus n! Vergleiche) und trägt meiner Unkenntnis der Graphentheorie Rechnung :)
In ähnlicher Weise enthält die Antwort von @ ismirsehregal data.table-Magie, die ich nicht verstehen kann.
Die Lösung von @ MKa scheint nicht mit> 2 überlappenden Perioden zu funktionieren
Testen der Lösungen ergibt
Aus diesem Code
quelle
tibble
Struktur nicht vertraut und habepull()
anscheinend das Problem verursacht. Denndataframe()
es sollte so funktionieren wie es ist. Habe gerade die Antwort aktualisiert.data.table
wodurch die Dinge noch schneller werden (bitte überprüfen Sie meinen neuen Benchmark).Auch mit
igraph
anderen Geräten überlappen Gruppen zu identifizieren, könnten Sie versuchen:quelle