Czy identyfikujesz nakładające się przedziały czasowe za pomocą jeszcze dwóch kryteriów w R?

10

Muszę sprawdzić obserwacje ptaków dokonane w dłuższym okresie pod kątem zduplikowanych / pokrywających się wpisów.

Obserwatorzy z różnych punktów (A, B, C) dokonali obserwacji i oznaczyli je na papierowych mapach. Linie te zostały umieszczone w linii z dodatkowymi danymi dla gatunku, punktem obserwacji i przedziałami czasu, w których były widoczne.

Zwykle obserwatorzy komunikują się ze sobą przez telefon podczas obserwacji, ale czasami zapominają, więc otrzymuję te duplikaty.

Już zredukowałem dane do tych linii, które dotykają okręgu, więc nie muszę dokonywać analizy przestrzennej, a jedynie porównywać przedziały czasowe dla każdego gatunku i mogę być całkiem pewien, że jest to ta sama osoba znaleziona w porównaniu .

Teraz szukam sposobu w R, aby zidentyfikować te wpisy, które:

  • są wykonywane tego samego dnia z nakładającymi się interwałami
  • i gdzie jest to ten sam gatunek
  • i które zostały wykonane z różnych punktów obserwacyjnych (A lub B lub C lub ...)

wprowadź opis zdjęcia tutaj

W tym przykładzie ręcznie znalazłem ewentualnie zduplikowane wpisy tej samej osoby. Punkt obserwacyjny jest inny (A <-> B), gatunek jest taki sam (Sst), a przedział czasu rozpoczęcia i zakończenia nakładają się.

wprowadź opis zdjęcia tutaj

Teraz utworzę nowe pole „duplikat” w mojej data.frame, nadając obu wierszom wspólny identyfikator, aby móc je wyeksportować, a następnie zdecydować, co robić.

Dużo szukałem już dostępnych rozwiązań, ale nie znalazłem żadnego związku z faktem, że muszę podgrupować proces dla gatunku (najlepiej bez pętli) i muszę porównać rzędy dla 2 + x punktów obserwacyjnych.

Niektóre dane do zabawy:

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")

Znalazłem częściowe rozwiązanie ze wspomnianymi foverlapsami funkcji data.table, np. Tutaj https://stackoverflow.com/q/25815032

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)

Oczywiście, to jakoś „działa”, ale tak naprawdę to nie to, co lubię w końcu osiągnąć.

Najpierw muszę zakodować punkty obserwacyjne. Wolałbym znaleźć rozwiązanie przyjmujące dowolną liczbę punktów.

Po drugie, wynik nie jest w formacie, z którym naprawdę mogę łatwo wznowić pracę. Pasujące wiersze są w rzeczywistości wstawiane do tego samego wiersza, podczas gdy moim celem jest umieszczenie wierszy pod spodem, a w nowej kolumnie będą miały wspólny identyfikator.

Po trzecie, muszę ponownie sprawdzić ręcznie, czy interwał nakłada się na wszystkie trzy punkty (co nie jest prawdą w przypadku moich danych, ale ogólnie może)

Na koniec chciałbym tylko otrzymać nową ramkę danych ze wszystkimi kandydatami możliwymi do zidentyfikowania po identyfikatorze grupy, którą mogę dołączyć z powrotem do linii i wyeksportować wynik jako warstwę do dalszego badania.

Więc ktoś jeszcze pomysłów, jak to zrobić?

Bernd V.
źródło
Nie jestem pewien, czy rozumiem w pełni, ale wydaje się, że w PostgreSQL jest to zadanie dość proste. Dostępne są funkcje dla przedziałów czasowych. Jak zrozumiałem, wymiana danych między PostgreSQL i R. powinna być łatwa
Nicklas Avén
Muszę przyznać, że nie mam do zera wiedzy o Postgresie, ale tak naprawdę, pijąc piwo tego wieczoru, wpadłem również na pomysł, że mogą być do tego dostępne pewne rzeczy sql. Przez resztę moich operacji mam do czynienia z zestawem danych, R jest jednak narzędziem, ale wiem, że funkcje SQL mogą być również wykonywane w obrębie R za pośrednictwem niektórych pakietów. Dochodzenie ....
Bernd V.
Jak duży jest zbiór danych - setki, tysiące, miliony wierszy? Czy dla funkcji SQL znalazłeś sqldf ?
Simbamangu
W międzyczasie znalazłem działające rozwiązanie. Szkoda, że ​​do tej pory tego nie opublikowałem. Będę musiał uczynić bardziej ogólnym, aby był użyteczny dla innych, a potem opublikuję go jak najszybciej.
Bernd V.
Daje +1, jeśli wszystko jest wektoryzowane i nie używa forpętli!
Simbamangu,

Odpowiedzi:

1

Jak wspomnieli niektórzy komentatorzy, SQL jest dobrą opcją do wyrażania raczej skomplikowanych zestawów ograniczeń. Sqldf opakowanie sprawia, że jest łatwy w użyciu siły SQL w badania bez konieczności konfigurowania relacyjnej bazy danych siebie.

Oto rozwiązanie wykorzystujące SQL. Przed uruchomieniem musiałem zmienić nazwę kolumn interwału danych na startTimei endTimeponieważ nazwa fromjest zarezerwowana w SQL.

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)

Aby ułatwić zrozumienie, odpowiedź SQL dupes_widewygląda następująco:

                         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

Samozłączenie FROM testdata x JOIN testdata y : znajdowanie par wierszy z jednego zestawu danych jest samozłączeniem. Musimy porównać każdy wiersz z każdym innym. ONWyrażenie wymienia ograniczenia w zakresie prowadzenia par.

Interwał nakładania się : jestem prawie pewien, że definicja nakładania się, której użyłem w tym SQL ( źródle ), różni się od tego, co foverlapsdla ciebie robiłem. Użyłeś typu „wewnątrz”, który wymaga, aby obserwacja wcześniej obsPointbyła całkowicie w obrębie obserwacji w późniejszym czasie obsPoint(ale brakuje jej odwrotności, np. Jeśli obserwacja C jest całkowicie w obrębie B ). Na szczęście w SQL jest to łatwe, jeśli trzeba zakodować inną definicję nakładania się.

Różne punkty : Twoje ograniczenie, że duplikaty zostały wykonane z różnych punktów obserwacyjnych, byłoby naprawdę wyrażone (x.obsPoint <> y.obsPoint). Gdybym to wpisał, SQL zwróciłby każdą zduplikowaną parę dwa razy, tylko przy zmianie kolejności ptaków w każdym rzędzie. Zamiast tego użyłem a, <aby zachować tylko unikalną połowę wierszy. (To nie jedyny sposób, aby to zrobić)

Unikalny identyfikator duplikatu : Podobnie jak w poprzednim rozwiązaniu, sam SQL wyświetla listę duplikatów w tym samym wierszu. hex(randomblob(16))jest zhackowanym ( ale zalecanym ) sposobem w SQLite do generowania unikalnych identyfikatorów dla każdej pary.

Format wyjściowy : nie podobały Ci się duplikaty w tym samym rzędzie, więc meltdzieli je i mergeprzypisuje duplikaty z powrotem do początkowej ramki danych.

Ograniczenia : Moje rozwiązanie nie obsługuje przypadku, w którym ten sam ptak został schwytany na więcej niż dwóch torach . Jest trudniejsze i nieco źle zdefiniowane. Na przykład, jeśli wyglądają ich przedziały czasowe

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

to Bird1 jest duplikatem Bird2 , który jest duplikatem Bird3 , ale czy Bird1 i Bird3 są duplikatami?

Jeff G.
źródło