Tengo que verificar las observaciones de aves realizadas durante un período más largo para entradas duplicadas / superpuestas.
Los observadores de diferentes puntos (A, B, C) hicieron observaciones y las marcaron en mapas de papel. Esas líneas se incorporaron a una característica de línea con datos adicionales para la especie, el punto de observación y los intervalos de tiempo en que se vieron.
Normalmente, los observadores se comunican entre sí por teléfono mientras observan, pero a veces se olvidan, así que obtengo esas líneas duplicadas.
Ya reduje los datos a esas líneas que tocan el círculo, por lo que no tengo que hacer un análisis espacial, solo comparo los intervalos de tiempo para cada especie y puedo estar bastante seguro de que es la misma persona que se encuentra en la comparación .
Ahora estoy buscando una forma en R para identificar aquellas entradas que:
- se hacen el mismo día con un intervalo superpuesto
- y donde es la misma especie
- y que se hicieron desde diferentes puntos de observación (A o B o C o ...))
En este ejemplo, encontré manualmente entradas posiblemente duplicadas de la misma persona. El punto de observación es diferente (A <-> B), la especie es la misma (Sst) y el intervalo de los tiempos de inicio y finalización se superpone.
Ahora crearía un nuevo campo "duplicado" en mi data.frame, dando a ambas filas una identificación común para poder exportarlas y luego decidir qué hacer.
Busqué muchas soluciones disponibles, pero no encontré ninguna sobre el hecho de que tengo que subconjuntar el proceso para la especie (preferiblemente sin bucle) y comparar las filas para 2 + x puntos de observación.
Algunos datos para jugar:
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")
Encontré una solución parcial con la función data.table foverlaps mencionada, por ejemplo, aquí 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)
Por supuesto, esto de alguna manera "funciona", pero en realidad no es lo que me gusta lograr al final.
Primero, tengo que codificar los puntos de observación. Preferiría encontrar una solución con un número arbitrario de puntos.
En segundo lugar, el resultado no está en un formato con el que realmente pueda continuar trabajando fácilmente. Las filas coincidentes se colocan en la misma fila, mientras que mi objetivo es que las filas se coloquen debajo, y en una nueva columna, tendrían un identificador común.
Tercero, tengo que verificar manualmente nuevamente, si un intervalo se superpone desde los tres puntos (que no es el caso con mis datos, pero generalmente podría)
Al final, me gustaría recibir un nuevo data.frame con todos los candidatos identificables por una identificación de grupo que pueda unir de nuevo a las líneas y exportar el resultado como una capa para un examen más detallado.
¿Alguien más ideas de cómo hacer esto?
fuente
for
bucles!Respuestas:
Como algunos comentaristas aludieron, SQL es una buena opción para expresar conjuntos de restricciones bastante complicados. El paquete sqldf facilita el uso de la potencia de SQL en R sin necesidad de configurar usted mismo una base de datos relacional.
Aquí hay una solución usando SQL. Antes de ejecutar, tuve que cambiar el nombre de las columnas de intervalo de datos a
startTime
yendTime
porque el nombrefrom
está reservado en SQL.Para facilitar la comprensión, la respuesta SQL
dupes_wide
termina luciendo así:Autounión
FROM testdata x JOIN testdata y
: encontrar pares de filas de un único conjunto de datos es una autounión. Necesitamos comparar cada fila con todas las demás. LaON
expresión enumera las restricciones para mantener pares.Intervalo de superposición : estoy bastante seguro de que la definición de superposición que utilicé en este SQL ( fuente ) difiere de lo que
foverlaps
estaba haciendo por usted. Usó el tipo "dentro", que requiere que la observación anteriorobsPoint
esté completamente dentro de la observación posteriorobsPoint
(pero pierde lo contrario, por ejemplo, si la observación de C está completamente dentro de B ). Afortunadamente, es fácil en SQL si necesita codificar una definición diferente de superposición.Diferentes puntos : su restricción de que se hicieron duplicados a partir de diferentes puntos de observación realmente se expresaría
(x.obsPoint <> y.obsPoint)
. Si hubiera escrito eso, SQL devolvería cada par duplicado dos veces, solo con el orden cambiado de los pájaros en cada fila. En cambio, utilicé una<
para mantener solo la mitad única de las filas. (Esta no es la única forma de hacer esto)ID duplicado único : al igual que con su solución anterior, el propio SQL enumera los duplicados en la misma fila.
hex(randomblob(16))
es una forma hacky ( pero recomendada ) en SQLite para generar ID únicos para cada par.Formato de salida : no le gustaron los duplicados en la misma fila, por lo que los
melt
divide ymerge
asigna los ID duplicados a su marco de datos inicial.Limitaciones : Mi solución no maneja el caso donde el mismo pájaro es capturado en más de dos pistas . Es más complicado y algo mal definido. Por ejemplo, si sus rangos de tiempo se ven así
entonces Bird1 es un duplicado de Bird2 , que es un duplicado de Bird3 , pero ¿son duplicados Bird1 y Bird3 ?
fuente