Eu tenho que verificar as observações das aves feitas durante um período mais longo para entradas duplicadas / sobrepostas.
Observadores de diferentes pontos (A, B, C) fizeram observações e as marcaram em mapas em papel. Essas linhas foram trazidas para uma linha com dados adicionais para as espécies, o ponto de observação e os intervalos de tempo em que foram vistos.
Normalmente, os observadores se comunicam por telefone enquanto observam, mas às vezes esquecem, então eu recebo essas linhas duplicadas.
Eu já reduzi os dados para as linhas que tocam o círculo, então não preciso fazer uma análise espacial, mas apenas comparar os intervalos de tempo para cada espécie e ter certeza de que é o mesmo indivíduo encontrado pela comparação .
Agora estou procurando uma maneira em R para identificar as entradas que:
- são feitas no mesmo dia com um intervalo de sobreposição
- e onde é a mesma espécie
- e que foram feitas a partir de diferentes pontos de observação (A ou B ou C ou ...))
Neste exemplo, encontrei manualmente entradas possivelmente duplicadas do mesmo indivíduo. O ponto de observação é diferente (A <-> B), a espécie é a mesma (Sst) e o intervalo dos horários de início e término se sobrepõe.
Agora, eu criaria um novo campo "duplicado" no meu data.frame, fornecendo às duas linhas um ID comum para poder exportá-las e depois decidir o que fazer.
Procurei muitas soluções já disponíveis, mas não encontrei nenhuma sobre o fato de que eu tenho que sub-definir o processo para as espécies (de preferência sem loop) e tenho que comparar as linhas para 2 + x pontos de observação.
Alguns dados para brincar:
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")
Encontrei uma solução parcial com os foverlaps da função data.table mencionados, por exemplo, aqui 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)
Claro, isso de alguma forma "funciona", mas realmente não é o que eu gosto de alcançar no final.
Primeiro, eu tenho que codificar os pontos de observação. Eu preferiria encontrar uma solução com um número arbitrário de pontos.
Segundo, o resultado não está em um formato com o qual eu possa realmente continuar trabalhando com facilidade. As linhas correspondentes são na verdade colocadas na mesma linha, enquanto meu objetivo é que as linhas sejam colocadas por baixo e, em uma nova coluna, elas teriam um identificador comum.
Terceiro, tenho que verificar manualmente novamente, se um intervalo se sobrepuser aos três pontos (o que não é o caso dos meus dados, mas geralmente poderia)
No final, eu gostaria de receber um novo data.frame com todos os candidatos identificáveis por um ID de grupo que eu possa associar de volta às linhas e exportar o resultado como uma camada para uma análise mais aprofundada.
Então, alguém mais ideias de como fazer isso?
fonte
for
loops!Respostas:
Como alguns comentadores aludiram, o SQL é uma boa opção para expressar conjuntos de restrições bastante complicados. O pacote sqldf facilita o uso da energia do SQL no R sem a necessidade de você mesmo configurar um banco de dados relacional.
Aqui está uma solução usando SQL. Antes de executar, tive que renomear as colunas de intervalo dos seus dados para
startTime
eendTime
porque o nomefrom
está reservado no SQL.Para ajudar no entendimento, a resposta SQL
dupes_wide
acaba assim:Junção automática
FROM testdata x JOIN testdata y
: a localização de pares de linhas em um único conjunto de dados é uma junção automática. Precisamos comparar todas as linhas com todas as outras. AON
expressão lista as restrições para manter pares.Intervalo de sobreposição : tenho certeza de que a definição de sobreposição que usei neste SQL ( fonte ) difere do que
foverlaps
estava fazendo por você. Você usou o tipo "dentro", que requer que a observação no inícioobsPoint
seja inteiramente dentro da observação no finalobsPoint
(mas perde o inverso, por exemplo, se a observação de C estiver inteiramente dentro de B ). Felizmente, é fácil no SQL se você precisar codificar uma definição diferente de sobreposição.Pontos diferentes : sua restrição de que duplicatas foram feitas a partir de diferentes pontos de observação seria realmente expressa
(x.obsPoint <> y.obsPoint)
. Se eu digitasse isso, o SQL retornaria todos os pares duplicados duas vezes, apenas com os pássaros trocados de ordem em cada linha. Em vez disso, usei a<
para manter apenas a metade exclusiva das linhas. (Esta não é a única maneira de fazer isso)ID duplicado exclusivo : como na solução anterior, o próprio SQL lista as duplicatas na mesma linha.
hex(randomblob(16))
é uma maneira hacky ( ainda recomendada ) no SQLite para gerar IDs exclusivos para cada par.Formato de saída : você não gostou das duplicatas na mesma linha, portanto as
melt
divide emerge
atribui os IDs duplicados ao seu quadro de dados inicial.Limitações : Minha solução não lida com o caso em que o mesmo pássaro é capturado em mais de duas faixas . É mais complicado e um pouco mal definido. Por exemplo, se os intervalos de tempo parecerem
então Bird1 é uma duplicata de Bird2 , que é uma duplicata de Bird3 , mas são duplicatas de Bird1 e Bird3 ?
fonte