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 ...)
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ę.
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ć?
źródło
for
pętli!Odpowiedzi:
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
startTime
iendTime
ponieważ nazwafrom
jest zarezerwowana w SQL.Aby ułatwić zrozumienie, odpowiedź SQL
dupes_wide
wygląda następująco: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.ON
Wyraż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
foverlaps
dla ciebie robiłem. Użyłeś typu „wewnątrz”, który wymaga, aby obserwacja wcześniejobsPoint
była całkowicie w obrębie obserwacji w późniejszym czasieobsPoint
(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
melt
dzieli je imerge
przypisuje 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
to Bird1 jest duplikatem Bird2 , który jest duplikatem Bird3 , ale czy Bird1 i Bird3 są duplikatami?
źródło