Identifier les intervalles de temps qui se chevauchent avec deux autres critères dans R?

10

Je dois vérifier les observations d'oiseaux faites sur une période plus longue pour les entrées en double / qui se chevauchent.

Des observateurs de différents points (A, B, C) ont fait des observations et les ont marquées sur des cartes papier. Ces lignes ont été introduites dans une ligne avec des données supplémentaires pour l'espèce, le point d'observation et les intervalles de temps où elles ont été vues.

Normalement, les observateurs communiquent entre eux par téléphone pendant l'observation, mais parfois ils oublient, donc je reçois ces lignes en double.

J'ai déjà réduit les données aux lignes qui touchent le cercle, donc je n'ai pas à faire d'analyse spatiale, mais je compare seulement les intervalles de temps pour chaque espèce et je peux être sûr que c'est le même individu que l'on trouve par la comparaison .

Je cherche maintenant un moyen dans R d'identifier les entrées qui:

  • sont faites le même jour avec un intervalle qui se chevauchent
  • et où c'est la même espèce
  • et qui ont été faites à partir de différents points d'observation (A ou B ou C ou ...))

entrez la description de l'image ici

Dans cet exemple, j'ai trouvé manuellement des entrées éventuellement dupliquées de la même personne. Le point d'observation est différent (A <-> B), l'espèce est la même (Sst) et l'intervalle des heures de début et de fin se chevauche.

entrez la description de l'image ici

Je créerais maintenant un nouveau champ "dupliquer" dans mon data.frame, donnant aux deux lignes un identifiant commun pour pouvoir les exporter et décider plus tard quoi faire.

J'ai cherché beaucoup de solutions déjà disponibles, mais je n'ai trouvé aucune préoccupation concernant le fait que je dois sous-définir le processus pour l'espèce (de préférence sans boucle) et comparer les lignes pour 2 + x points d'observation.

Quelques données avec lesquelles jouer:

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

J'ai trouvé une solution partielle avec les foverlaps de la fonction data.table mentionnés par exemple ici 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)

Bien sûr, cela "fonctionne" en quelque sorte, mais ce n'est vraiment pas ce que j'aime à la fin.

Tout d'abord, je dois coder en dur les points d'observation. Je préférerais trouver une solution prenant un nombre arbitraire de points.

Deuxièmement, le résultat n'est pas dans un format avec lequel je peux vraiment recommencer à travailler facilement. Les lignes correspondantes sont en fait placées dans la même ligne, alors que mon objectif est de placer les lignes en dessous, et dans une nouvelle colonne, elles auraient un identifiant commun.

Troisièmement, je dois vérifier à nouveau manuellement, si un intervalle chevauche les trois points (ce qui n'est pas le cas avec mes données, mais le pourrait généralement)

En fin de compte, je voudrais simplement recevoir un nouveau data.frame avec tous les candidats identifiables par un identifiant de groupe que je peux joindre aux lignes et exporter le résultat sous forme de couche pour un examen plus approfondi.

Alors, quelqu'un a-t-il d'autres idées sur la façon de procéder?

Bernd V.
la source
Je ne suis pas sûr de bien comprendre, mais cela semble être une tâche assez simple dans PostgreSQL. Il existe des fonctions pour les plages horaires. Comme je l'ai compris, il devrait être facile de partager des données entre PostgreSQL et R.
Nicklas Avén
Je dois admettre que je n'ai aucune connaissance de Postgres, mais en fait, en buvant une bière ce soir, j'ai également eu l'idée que des trucs sql pourraient être disponibles pour cela. Pour le reste de mes opérations, je dois faire avec l'ensemble de données, R est cependant l'outil, mais je sais que les fonctions sql peuvent également être exécutées dans R via certains packages. Enquête ....
Bernd V.
Quelle est la taille de l'ensemble de données - des centaines, des milliers, des millions de lignes? Pour les fonctions SQL, avez-vous trouvé sqldf ?
Simbamangu
En attendant, j'ai trouvé une solution de travail. Honte à moi je ne l'ai pas encore posté. Devra le rendre plus général pour être utile aux autres, puis je le posterai dès que possible.
Bernd V.
Est-ce que +1 si tout est vectorisé et n'utilise pas de forboucles!
Simbamangu

Réponses:

1

Comme certains commentateurs l'ont fait allusion, SQL est une bonne option pour exprimer des ensembles de contraintes plutôt compliqués. Le package sqldf facilite l'utilisation de la puissance de SQL dans R sans avoir à configurer vous-même une base de données relationnelle.

Voici une solution utilisant SQL. Avant de courir, j'ai dû renommer les colonnes d'intervalle de vos données en startTimeet endTimeparce que le nom fromest réservé en 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)

Pour faciliter la compréhension, la réponse SQL dupes_widefinit par ressembler à ceci:

                         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-jointure FROM testdata x JOIN testdata y : la recherche de paires de lignes à partir d'un seul ensemble de données est une auto-jointure. Nous devons comparer chaque ligne avec toutes les autres. L' ONexpression répertorie les contraintes de conservation des paires.

Intervalle de chevauchement : je suis presque sûr que la définition du chevauchement que j'ai utilisée dans ce SQL ( source ) diffère de ce foverlapsque vous faisiez. Vous avez utilisé le type "dedans", qui exige que l'observation au plus tôt obsPointsoit entièrement dans l'observation au plus tard obsPoint(mais il manque l'inverse, par exemple si l'observation de C est entièrement dans les B ). Heureusement, c'est facile en SQL si vous devez encoder une définition différente du chevauchement.

Différents points : Votre contrainte que des doublons ont été faits à partir de différents points d'observation serait vraiment exprimée (x.obsPoint <> y.obsPoint). Si j'avais tapé cela, SQL retournerait chaque paire dupliquée deux fois, juste avec l'ordre de basculement des oiseaux dans chaque ligne. Au lieu de cela, j'ai utilisé un <pour ne conserver que la moitié unique des lignes. (Ce n'est pas la seule façon de le faire)

ID de doublon unique : comme pour votre solution précédente, le SQL lui-même répertorie les doublons sur la même ligne. hex(randomblob(16))est un moyen hacky ( mais recommandé ) dans SQLite pour générer des ID uniques pour chaque paire.

Format de sortie : vous n'aimiez pas les doublons dans la même ligne, alors meltdivisez-les et mergeattribuez les ID en double à votre bloc de données initial.

Limitations : ma solution ne gère pas le cas où le même oiseau est capturé sur plus de deux pistes . C'est plus délicat et quelque peu mal défini. Par exemple, si leurs plages de temps ressemblent à

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

alors Bird1 est un doublon de Bird2 , qui est un doublon de Bird3 , mais les doublons Bird1 et Bird3 sont - ils ?

Jeff G
la source