Identifizieren überlappender Zeitintervalle mit zwei weiteren Kriterien in R?

10

Ich muss die über einen längeren Zeitraum gemachten Vogelbeobachtungen auf doppelte / überlappende Einträge überprüfen.

Beobachter von verschiedenen Punkten (A, B, C) machten Beobachtungen und markierten sie auf Papierkarten. Diese Linien wurden in eine Linie gebracht, die zusätzliche Daten für die Art, den Beobachtungspunkt und die Zeitintervalle enthält, in denen sie gesehen wurden.

Normalerweise kommunizieren die Beobachter während des Beobachtens telefonisch miteinander, aber manchmal vergessen sie es, sodass ich diese doppelten Zeilen bekomme.

Ich habe die Daten bereits auf die Linien reduziert, die den Kreis berühren, sodass ich keine räumliche Analyse durchführen muss, sondern nur die Zeitintervalle für jede Art vergleiche und ziemlich sicher sein kann, dass es sich um dasselbe Individuum handelt, das durch den Vergleich gefunden wird .

Ich suche jetzt nach einer Möglichkeit in R, um die Einträge zu identifizieren, die:

  • werden am selben Tag mit einem überlappenden Intervall erstellt
  • und wo es die gleiche Art ist
  • und die von verschiedenen Beobachtungspunkten (A oder B oder C oder ...) gemacht wurden)

Geben Sie hier die Bildbeschreibung ein

In diesem Beispiel habe ich möglicherweise doppelte Einträge derselben Person manuell gefunden. Der Beobachtungspunkt ist unterschiedlich (A B), die Art ist dieselbe (Sst) und das Intervall der Start- und Endzeiten überschneidet sich.

Geben Sie hier die Bildbeschreibung ein

Ich würde jetzt ein neues Feld "duplizieren" in meinem data.frame erstellen und beiden Zeilen eine gemeinsame ID geben, um sie exportieren und später entscheiden zu können, was zu tun ist.

Ich habe viel nach bereits verfügbaren Lösungen gesucht, aber keine gefunden, was die Tatsache betrifft, dass ich den Prozess für die Art (vorzugsweise ohne Schleife) unterteilen und die Zeilen für 2 + x Beobachtungspunkte vergleichen muss.

Einige Daten zum Herumspielen:

testdata <- structure(list(bird_id = c("20150712_0810_1410_A_1", "20150712_0810_1410_A_2", 
"20150712_0810_1410_A_4", "20150712_0810_1410_A_7", "20150727_1115_1430_C_1", 
"20150727_1120_1430_B_1", "20150727_1120_1430_B_2", "20150727_1120_1430_B_3", 
"20150727_1120_1430_B_4", "20150727_1120_1430_B_5", "20150727_1130_1430_A_2", 
"20150727_1130_1430_A_4", "20150727_1130_1430_A_5", "20150812_0900_1225_B_3", 
"20150812_0900_1225_B_6", "20150812_0900_1225_B_7", "20150812_0907_1208_A_2", 
"20150812_0907_1208_A_3", "20150812_0907_1208_A_5", "20150812_0907_1208_A_6"
), obsPoint = c("A", "A", "A", "A", "C", "B", "B", "B", "B", 
"B", "A", "A", "A", "B", "B", "B", "A", "A", "A", "A"), species = structure(c(11L, 
11L, 11L, 11L, 10L, 11L, 10L, 11L, 11L, 11L, 11L, 10L, 11L, 11L, 
11L, 11L, 11L, 11L, 11L, 11L), .Label = c("Bf", "Fia", "Grr", 
"Kch", "Ko", "Lm", "Rm", "Row", "Sea", "Sst", "Wsb"), class = "factor"), 
    from = structure(c(1436687150, 1436689710, 1436691420, 1436694850, 
    1437992160, 1437991500, 1437995580, 1437992360, 1437995960, 
    1437998360, 1437992100, 1437994000, 1437995340, 1439366410, 
    1439369600, 1439374980, 1439367240, 1439367540, 1439369760, 
    1439370720), class = c("POSIXct", "POSIXt"), tzone = ""), 
    to = structure(c(1436687690, 1436690230, 1436691690, 1436694970, 
    1437992320, 1437992200, 1437995600, 1437992400, 1437996070, 
    1437998750, 1437992230, 1437994220, 1437996780, 1439366570, 
    1439370070, 1439375070, 1439367410, 1439367820, 1439369930, 
    1439370830), class = c("POSIXct", "POSIXt"), tzone = "")), .Names = c("bird_id", 
"obsPoint", "species", "from", "to"), row.names = c("20150712_0810_1410_A_1", 
"20150712_0810_1410_A_2", "20150712_0810_1410_A_4", "20150712_0810_1410_A_7", 
"20150727_1115_1430_C_1", "20150727_1120_1430_B_1", "20150727_1120_1430_B_2", 
"20150727_1120_1430_B_3", "20150727_1120_1430_B_4", "20150727_1120_1430_B_5", 
"20150727_1130_1430_A_2", "20150727_1130_1430_A_4", "20150727_1130_1430_A_5", 
"20150812_0900_1225_B_3", "20150812_0900_1225_B_6", "20150812_0900_1225_B_7", 
"20150812_0907_1208_A_2", "20150812_0907_1208_A_3", "20150812_0907_1208_A_5", 
"20150812_0907_1208_A_6"), class = "data.frame")

Ich habe eine Teillösung mit der Funktion data.table gefunden, die zB hier /programming//q/25815032 erwähnt wird

library(data.table)
#Subsetting the data for each observation point and converting them into data.tables
A <- setDT(testdata[testdata$obsPoint=="A",])
B <- setDT(testdata[testdata$obsPoint=="B",])
C <- setDT(testdata[testdata$obsPoint=="C",])

#Set a key for these subsets (whatever key exactly means. Don't care as long as it works ;) )
setkey(A,species,from,to)    
setkey(B,species,from,to)
setkey(C,species,from,to)

#Generate the match results for each obsPoint/species combination with an overlapping interval
matchesAB <- foverlaps(A,B,type="within",nomatch=0L) #nomatch=0L -> remove NA
matchesAC <- foverlaps(A,C,type="within",nomatch=0L) 
matchesBC <- foverlaps(B,C,type="within",nomatch=0L)

Natürlich "funktioniert" das irgendwie, aber es ist wirklich nicht das, was ich am Ende gerne erreichen möchte.

Zuerst muss ich die Beobachtungspunkte hart codieren. Ich würde es vorziehen, eine Lösung mit einer beliebigen Anzahl von Punkten zu finden.

Zweitens liegt das Ergebnis nicht in einem Format vor, mit dem ich wirklich problemlos wieder arbeiten kann. Die übereinstimmenden Zeilen werden tatsächlich in dieselbe Zeile eingefügt, während mein Ziel darin besteht, die Zeilen darunter einzufügen, und in einer neuen Spalte würden sie eine gemeinsame Kennung haben.

Drittens muss ich erneut manuell prüfen, ob sich ein Intervall von allen drei Punkten überschneidet (was bei meinen Daten nicht der Fall ist, aber im Allgemeinen möglich ist).

Am Ende möchte ich nur einen neuen data.frame mit allen Kandidaten erhalten, die durch eine Gruppen-ID identifiziert werden können, die ich wieder mit den Zeilen verbinden und das Ergebnis als Ebene zur weiteren Prüfung exportieren kann.

Also noch jemand Ideen, wie das geht?

Bernd V.
quelle
Ich bin nicht sicher, ob ich es vollständig verstehe, aber es scheint eine Aufgabe zu sein, die in PostgreSQL ziemlich einfach ist. Es gibt Funktionen für Zeitbereiche. Wie ich verstanden habe, sollte es einfach sein, Daten zwischen PostgreSQL und R.
Nicklas Avén
Ich muss zugeben, dass ich keine bis null Kenntnisse über Postgres habe, aber als ich heute Abend ein Bier trank, hatte ich auch die Idee, dass einige SQL-Sachen dafür verfügbar sein könnten. Für den Rest meiner Operationen habe ich mit dem Dataset zu tun, R ist zwar DAS Werkzeug, aber ich weiß, dass SQL-Funktionen auch in R über einige Pakete ausgeführt werden können. Untersuchung ....
Bernd V.
Wie groß ist der Datensatz - Hunderte, Tausende, Millionen von Zeilen? Haben Sie für SQL-Funktionen sqldf gefunden ?
Simbamangu
In der Zwischenzeit habe ich eine funktionierende Lösung gefunden. Schade um mich, dass ich es bisher nicht gepostet habe. Muss es allgemeiner machen, um für andere von Nutzen zu sein, und dann werde ich es so schnell wie möglich veröffentlichen.
Bernd V.
Wird +1, wenn alles vektorisiert ist und keine forSchleifen verwendet!
Simbamangu

Antworten:

1

Wie einige Kommentatoren angedeutet haben, ist SQL eine gute Option, um ziemlich komplizierte Sätze von Einschränkungen auszudrücken. Das sqldf- Paket macht es einfach, die Leistung von SQL in R zu nutzen, ohne selbst eine relationale Datenbank einrichten zu müssen.

Hier ist eine Lösung mit SQL. Vor dem Ausführen musste ich die Intervallspalten Ihrer Daten in startTimeund umbenennen, endTimeda der Name fromin SQL reserviert ist.

library(reshape2)
library(sqldf)

dupes_wide <- sqldf("SELECT hex(randomblob(16)) dupe_id, x.bird_id x_bird_id, y.bird_id y_bird_id
                     FROM testdata x JOIN testdata y
                          ON (x.startTime <= y.endTime)
                         AND (x.endTime >= y.startTime)
                         AND (x.species = y.species)
                         AND (x.obsPoint < y.obsPoint)")
dupes_long <- melt(dupes_wide, id.vars='dupe_id', value.name='bird_id')
merge(testdata, dupes_long[, c('dupe_id', 'bird_id')], by='bird_id', all.x=TRUE)

Zum besseren Verständnis dupes_widesieht die SQL-Antwort folgendermaßen aus :

                         dupe_id x_bird_id y_bird_id
253FCC7A58FD8401960FC5D95153356C 20150727_1130_1430_A_2 20150727_1120_1430_B_1
9C1C1A13306ECC2DF78004D421F70CE6 20150727_1130_1430_A_5 20150727_1120_1430_B_4
1E8316DBF631BBF6D2CCBD15A85E6EF3 20150812_0907_1208_A_5 20150812_0900_1225_B_6

Self-Join FROM testdata x JOIN testdata y : Das Suchen von Zeilenpaaren aus einem einzelnen Dataset ist ein Self-Join. Wir müssen jede Zeile mit jeder anderen vergleichen. Der ONAusdruck listet die Einschränkungen für das Halten von Paaren auf.

Überlappendes Intervall : Ich bin mir ziemlich sicher, dass sich die Definition der Überlappung, die ich in dieser SQL ( Quelle ) verwendet habe, von der foverlapsfür Sie durchgeführten unterscheidet. Sie haben den Typ "innerhalb" verwendet, bei dem die Beobachtung am früheren obsPointvollständig innerhalb der Beobachtung am späteren sein muss obsPoint(aber das Gegenteil fehlt, z. B. wenn die Beobachtung von C vollständig innerhalb der von B liegt ). Glücklicherweise ist es in SQL einfach, wenn Sie eine andere Definition der Überlappung codieren müssen.

Unterschiedliche Punkte : Ihre Einschränkung, dass Duplikate von unterschiedlichen Beobachtungspunkten erstellt wurden, würde wirklich zum Ausdruck gebracht (x.obsPoint <> y.obsPoint). Wenn ich das eingegeben hätte, würde SQL jedes duplizierte Paar zweimal zurückgeben, nur mit der Reihenfolge der Vögel in jeder Zeile. Stattdessen habe ich a verwendet <, um nur die eindeutige Hälfte der Zeilen beizubehalten. (Dies ist nicht der einzige Weg, dies zu tun)

Eindeutige Duplikat-ID : Wie bei Ihrer vorherigen Lösung listet SQL selbst die Duplikate in derselben Zeile auf. hex(randomblob(16))ist eine hackige ( aber empfohlene ) Methode in SQLite, um eindeutige IDs für jedes Paar zu generieren.

Ausgabeformat : Sie mochten die Duplikate in derselben Zeile nicht, meltteilen sie also auf und mergeweisen die doppelten IDs wieder Ihrem ursprünglichen Datenrahmen zu.

Einschränkungen : Meine Lösung behandelt nicht den Fall, dass derselbe Vogel in mehr als zwei Spuren gefangen wird . Es ist schwieriger und etwas schlecht definiert. Zum Beispiel, wenn ihre Zeitbereiche so aussehen

    | - Bird1 - |
             | - Bird2 - |
                      | - Bird3 - |

dann ist Bird1 ein Duplikat von Bird2 , was ein Duplikat von Bird3 ist , aber sind Bird1 und Bird3 Duplikate?

Jeff G.
quelle