Выявление перекрывающихся временных интервалов с еще двумя критериями в R?

10

Я должен проверить наблюдения за птицами, сделанные в течение более длительного периода для дублирующих / перекрывающихся записей.

Наблюдатели из разных точек (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 со всеми кандидатами, идентифицируемыми по идентификатору группы, чтобы я мог присоединиться к строкам и экспортировать результат в виде слоя для дальнейшего изучения.

Так у кого-нибудь еще идей как это сделать?

Бернд В.
источник
Я не уверен, что полностью понимаю, но это похоже на задачу, которая довольно проста в PostgreSQL. Есть функции для временных диапазонов. Как я понял, должно быть легко обмениваться данными между PostgreSQL и R.
Никлас Авен
Я должен признать, что у меня нет нулевых знаний о Postgres, но на самом деле, выпивая пиво этим вечером, я также думал, что некоторые вещи sql могут быть доступны для этого. Для остальной части моих операций я имею дело с набором данных, хотя R - это инструмент THE, но я знаю, что функции sql могут также выполняться в R через некоторые пакеты. Расследование ....
Бернд В.
Насколько велик набор данных - сотни, тысячи, миллионы строк? Для функций SQL вы нашли sqldf ?
Симбамангу
А пока я нашел рабочее решение. Позор мне, я не опубликовал это до сих пор. Придется сделать его более общим, чтобы быть полезным для других, и тогда я опубликую его как можно скорее.
Бернд В.
+1, если все векторизовано и не использует forпетли!
Симбамангу

Ответы:

1

Как упоминали некоторые комментаторы, SQL является хорошим вариантом для выражения довольно сложных наборов ограничений. Пакет sqldf позволяет легко использовать возможности SQL в R без необходимости настраивать реляционную базу данных самостоятельно.

Вот решение с использованием SQL. Перед запуском мне пришлось переименовать интервальные столбцы ваших данных в startTimeи endTimeпотому что имя fromзарезервировано в 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)

Чтобы помочь пониманию, ответ SQL dupes_wideвыглядит примерно так:

                         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

Самосоединение 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 является дубликатом Bird2 , который является дубликатом Bird3 , но являются ли дубликаты Bird1 и Bird3 ?

Джефф Дж
источник