Identificazione di intervalli di tempo sovrapposti con altri due criteri in R?


10

Devo controllare le osservazioni degli uccelli fatte su un periodo più lungo per voci duplicate / sovrapposte.

Osservatori di diversi punti (A, B, C) hanno fatto osservazioni e li hanno contrassegnati su mappe cartacee. Quelle linee sono state portate in una linea con dati aggiuntivi per la specie, il punto di osservazione e gli intervalli di tempo in cui sono stati visti.

Normalmente, gli osservatori comunicano tra loro via telefono mentre osservano, ma a volte dimenticano, quindi ottengo quelle linee duplicate.

Ho già ridotto i dati a quelle linee che toccano il cerchio, quindi non devo fare un'analisi spaziale, ma solo confrontare gli intervalli di tempo per ogni specie e posso essere abbastanza sicuro che sia lo stesso individuo che si trova nel confronto .

Ora sto cercando un modo in R per identificare quelle voci che:

  • sono realizzati lo stesso giorno con un intervallo di sovrapposizione
  • e dove si trova la stessa specie
  • e che sono stati fatti da diversi punti di osservazione (A o B o C o ...))

inserisci qui la descrizione dell'immagine

In questo esempio, ho trovato manualmente possibili voci duplicate della stessa persona. Il punto di osservazione è diverso (A <-> B), la specie è la stessa (Sst) e l'intervallo dei tempi di inizio e fine si sovrappone.

inserisci qui la descrizione dell'immagine

Vorrei ora creare un nuovo campo "duplicato" nel mio data.frame, dando a entrambe le righe un ID comune per poterle esportare e in seguito decidere cosa fare.

Ho cercato molte soluzioni già disponibili, ma non ho trovato nulla riguardo al fatto che devo sottoinsieme il processo per la specie (preferibilmente senza un anello) e devo confrontare le righe per 2 + x punti di osservazione.

Alcuni dati con cui giocare:

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

Ho trovato una soluzione parziale con la funzione data.table foverlaps menzionata ad esempio qui 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)

Certo, questo in qualche modo "funziona", ma non è davvero quello che mi piace ottenere alla fine.

Innanzitutto, devo codificare i punti di osservazione. Preferirei trovare una soluzione prendendo un numero arbitrario di punti.

In secondo luogo, il risultato non è in un formato in cui posso davvero riprendere a lavorare facilmente. Le righe corrispondenti vengono effettivamente inserite nella stessa riga, mentre il mio obiettivo è quello di inserire le righe sottostanti e, in una nuova colonna, avrebbero un identificatore comune.

In terzo luogo, devo verificare di nuovo manualmente, se un intervallo si sovrappone da tutti e tre i punti (che non è il caso dei miei dati, ma generalmente potrebbe)

Alla fine, vorrei solo ricevere un nuovo data.frame con tutti i candidati identificabili da un ID di gruppo che posso unire nuovamente alle righe ed esportare il risultato come layer per un ulteriore esame.

Qualcuno ha più idee su come farlo?


Non sono sicuro di aver compreso appieno, ma sembra un compito abbastanza semplice in PostgreSQL. Ci sono funzioni per gli intervalli di tempo. Come ho capito, dovrebbe essere facile condividere i dati tra PostgreSQL e R.
Nicklas Avén,

Devo ammettere che non ho nessuna conoscenza di Postgres, ma in realtà, bevendo una birra questa sera, ho anche avuto l'idea che alcune cose sql potrebbero essere disponibili per questo. Per il resto delle mie operazioni ho a che fare con il set di dati, tuttavia R è lo strumento, ma so che le funzioni sql possono essere eseguite anche all'interno di R attraverso alcuni pacchetti. Indagare ....
Bernd V.

Quanto è grande il set di dati: centinaia, migliaia, milioni di righe? Per le funzioni SQL hai trovato sqldf ?
Simbamangu,

Nel frattempo, ho trovato una soluzione funzionante. Peccato per me non l'ho pubblicato finora. Dovrà renderlo più generale per essere utile per gli altri, e poi lo posterò al più presto.
Bernd V.

Lo farà +1 se è tutto vettorializzato e non usa forloop!
Simbamangu,

Risposte:


1

Come alludevano alcuni commentatori, SQL è una buona opzione per esprimere serie piuttosto complicate di vincoli. Il pacchetto sqldf semplifica l'utilizzo della potenza di SQL in R senza la necessità di impostare da soli un database relazionale.

Ecco una soluzione che utilizza SQL. Prima di eseguire, ho dovuto rinominare le colonne degli intervalli dei dati in startTimee endTimeperché il nome fromè riservato in 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)

Per facilitare la comprensione, la risposta SQL dupes_widefinisce così:

                         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

Auto-join FROM testdata x JOIN testdata y : la ricerca di coppie di righe da un singolo set di dati è un self-join. Dobbiamo confrontare ogni riga con ogni altra. L' ONespressione elenca i vincoli per mantenere le coppie.

Intervallo di sovrapposizione : sono abbastanza sicuro che la definizione di sovrapposizione che ho usato in questo SQL ( sorgente ) differisca da ciò che foverlapsstava facendo per te. Hai usato il tipo "entro", che richiede che l'osservazione in precedenza obsPointsia interamente all'interno dell'osservazione in un secondo momento obsPoint(ma manca il contrario, ad esempio se l'osservazione di C è interamente all'interno di B ). Fortunatamente in SQL è facile se è necessario codificare una diversa definizione di sovrapposizione.

Punti diversi : il tuo vincolo che i duplicati sono stati fatti da diversi punti di osservazione sarebbe davvero espresso (x.obsPoint <> y.obsPoint). Se lo avessi digitato, SQL avrebbe restituito ogni coppia duplicata due volte, solo con gli uccelli passati in ordine in ogni riga. Invece ho usato a <per mantenere solo la metà unica delle righe. (Questo non è l'unico modo per farlo)

ID duplicato univoco : come per la soluzione precedente, lo stesso SQL elenca i duplicati nella stessa riga. hex(randomblob(16))è un modo complicato ( ma consigliato ) in SQLite per generare ID univoci per ogni coppia.

Formato di output : i duplicati non ti sono piaciuti nella stessa riga, quindi meltli divide e mergeassegna gli ID duplicati al frame di dati iniziale.

Limitazioni : la mia soluzione non gestisce il caso in cui lo stesso uccello viene catturato in più di due tracce . È più complicato e un po 'mal definito. Ad esempio, se i loro intervalli di tempo sembrano

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

allora Bird1 è un duplicato di Bird2 , che è un duplicato di Bird3 , ma sono duplicati Bird1 e Bird3 ?

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.