Визначення часових інтервалів, що перекриваються, з двома іншими критеріями в R?


10

Я повинен перевірити спостереження за птахами, зроблені протягом більш тривалого періоду, на предмет дублювання / перекриття записів.

Спостерігачі з різних точок (A, B, C) зробили спостереження та позначили їх на паперових картах. Ті лінії, в які введено лінію, містять додаткові дані про види, точку спостереження та часові інтервали, які вони бачили.

Зазвичай спостерігачі спілкуються між собою по телефону під час спостереження, але іноді вони забувають, тому я отримую ці дублікати рядків.

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

Зараз я шукаю спосіб R визначити ті записи, які:

  • виготовляються в той же день з інтервалом перекриття
  • і де це той самий вид
  • і які були зроблені з різних точок спостереження (A або B або C або ...))

введіть тут опис зображення

У цьому прикладі я вручну знайшов можливі дублювання записів однієї особи. Точка спостереження різна (A <-> B), види однакові (Sst), а інтервал часу початку та кінця перекривається.

введіть тут опис зображення

Тепер я створив би нове поле "дублікат" у своєму 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 та Р. слід легко
Nicklas Avén

Я мушу визнати, що я не знаю нульових знань про Postgres, але насправді, випиваючи пиво сьогодні ввечері, я також мав ідею, що для цього можуть бути доступні деякі sql речі. У решті моїх операцій я маю справу з набором даних, хоча R є інструментом THE, але я знаю, що функції sql також можуть виконуватися в межах R через деякі пакети. Розслідування ....
Бернд В.

Наскільки великий набір даних - сотні, тисячі, мільйони рядків? Для функцій SQL ви знайшли sqldf ?
Simbamangu

Тим часом я знайшов робоче рішення. Сором мені, я поки не публікував це. Доведеться зробити більш загальним, щоб бути корисним для інших, і тоді я опублікую це якнайшвидше.
Бернд В.

Позначить його +1, якщо він весь векторизований і не використовує forциклів!
Simbamangu

Відповіді:


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(але воно не вистачає зворотного, наприклад, якщо спостереження С повністю знаходиться в межах В ). На щастя, у SQL легко, якщо вам потрібно кодувати інше визначення перекриття.

Різні моменти : Ваше обмеження, що дублікати були зроблені з різних точок спостереження, справді виражатиметься (x.obsPoint <> y.obsPoint). Якби я набрав це, SQL би повертав кожну дублюючу пару двічі, лише з переключенням порядку птахів у кожному рядку. Натомість я використав а, <щоб зберегти лише унікальну половину рядків. (Це не єдиний спосіб зробити це)

Унікальний ідентифікатор дубліката : Як і у попередньому рішенні, сам SQL перераховує дублікати в тому ж рядку. hex(randomblob(16))- хакі ( все ж рекомендується ) спосіб у SQLite генерувати унікальні ідентифікатори для кожної пари.

Формат виводу : Вам не сподобалися дублікати в одному рядку, тому meltрозбиває їх і mergeприсвоює дублікати ідентифікаторів назад до початкового кадру даних.

Обмеження : моє рішення не справляється із випадком, коли одного і того ж птаха захоплюють більше ніж два треки . Це складніше і дещо погано визначене. Наприклад, якщо їх часові інтервали виглядають так

    | - Птах1 - |
             | - Птах2 - |
                      | - Птах3 - |

то Bird1 - це дублікат Bird2 , який є дублікатом Bird3 , але це дублікати Bird1 та Bird3 ?

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.