Я должен проверить наблюдения за птицами, сделанные в течение более длительного периода для дублирующих / перекрывающихся записей.
Наблюдатели из разных точек (A, B, C) проводили наблюдения и отмечали их на бумажных картах. Те линии, которые были приведены в линию, содержат дополнительные данные о видах, точке наблюдения и временных интервалах, в которых они были замечены.
Обычно наблюдатели общаются друг с другом по телефону во время наблюдения, но иногда они забывают, поэтому я получаю эти дубликаты.
Я уже сократил данные до тех линий, которые касаются круга, поэтому мне не нужно делать пространственный анализ, а только сравнивать временные интервалы для каждого вида, и я могу быть совершенно уверен, что это тот же самый особь, который обнаружен при сравнении ,
Я сейчас ищу способ в R, чтобы определить те записи, которые:
- сделаны в один и тот же день с пересекающимся интервалом
- и где это тот же вид
- и которые были сделаны из разных точек наблюдения (A или B или C или ...))
В этом примере я вручную нашел, возможно, дублированные записи одного и того же человека. Точка наблюдения отличается (A <-> B), виды одинаковы (Sst) и интервал времени начала и окончания перекрывается.
Теперь я создал бы новое поле «duplicate» в моем data.frame, дав обеим строкам общий идентификатор, чтобы можно было их экспортировать, а затем решить, что делать.
Я много раз искал уже имеющиеся решения, но не нашел ни одного, касающегося того факта, что мне нужно подгруппировать процесс для вида (предпочтительно без петли) и сравнить строки для 2 + x точек наблюдения.
Некоторые данные, с которыми можно поиграться:
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")
Я нашел частичное решение с помощью функции data.table, упомянутой выше, например, здесь 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)
Конечно, это как-то «работает», но на самом деле это не то, чего я хотел бы достичь в конце.
Во-первых, мне нужно жестко закодировать точки наблюдения. Я бы предпочел найти решение с произвольным количеством точек.
Во-вторых, результат не в том формате, в котором я могу легко возобновить работу. Соответствующие строки фактически помещаются в одну и ту же строку, в то время как моя цель состоит в том, чтобы строки помещались внизу, а в новом столбце они имели бы общий идентификатор.
В-третьих, я должен снова проверить вручную, перекрывается ли интервал между всеми тремя точками (что не относится к моим данным, но, как правило, может)
В конце я просто хотел бы получить новый data.frame со всеми кандидатами, идентифицируемыми по идентификатору группы, чтобы я мог присоединиться к строкам и экспортировать результат в виде слоя для дальнейшего изучения.
Так у кого-нибудь еще идей как это сделать?
источник
for
петли!Ответы:
Как упоминали некоторые комментаторы, SQL является хорошим вариантом для выражения довольно сложных наборов ограничений. Пакет sqldf позволяет легко использовать возможности SQL в R без необходимости настраивать реляционную базу данных самостоятельно.
Вот решение с использованием SQL. Перед запуском мне пришлось переименовать интервальные столбцы ваших данных в
startTime
иendTime
потому что имяfrom
зарезервировано в SQL.Чтобы помочь пониманию, ответ SQL
dupes_wide
выглядит примерно так:Самосоединение
FROM testdata x JOIN testdata y
: поиск пар строк из одного набора данных является самосоединением. Нам нужно сравнить каждый ряд с каждым другим.ON
Выражение перечислены ограничения для содержания пары.Интервал перекрытия : я почти уверен, что определение перекрытия, которое я использовал в этом SQL ( исходном тексте), отличается от того, что
foverlaps
делалось для вас. Вы использовали тип «в пределах», который требует, чтобы наблюдение на более раннем этапеobsPoint
было полностью внутри наблюдения на более позднемobsPoint
(но оно пропускает обратное, например, если наблюдение C полностью находится в пределах B ). К счастью, в SQL легко, если вам нужно закодировать другое определение перекрытия.Различные точки : Ваше ограничение на то, что дубликаты были сделаны из разных точек наблюдения, действительно будет выражено
(x.obsPoint <> y.obsPoint)
. Если бы я набрал это, SQL возвращал бы каждую дублированную пару дважды, просто с переключением птиц в каждой строке. Вместо этого я использовал,<
чтобы сохранить только уникальную половину строк. (Это не единственный способ сделать это)Уникальный идентификатор дубликата . Как и в предыдущем решении, сам SQL перечисляет дубликаты в той же строке.
hex(randomblob(16))
это хакерский ( но рекомендуемый ) способ в SQLite для генерации уникальных идентификаторов для каждой пары.Формат вывода : вам не понравились дубликаты в одной строке, поэтому
melt
разбиваете их иmerge
присваиваете идентификаторы дубликатов обратно в исходный фрейм данных.Ограничения : Мое решение не касается случая, когда одна и та же птица поймана более чем на двух дорожках . Это сложнее и несколько плохо определено. Например, если их временные диапазоны выглядят как
тогда Bird1 является дубликатом Bird2 , который является дубликатом Bird3 , но являются ли дубликаты Bird1 и Bird3 ?
источник