(Esta resposta respondeu a uma pergunta duplicada (agora fechada) em Detectando eventos pendentes , que apresentou alguns dados em forma gráfica.)
A detecção de outlier depende da natureza dos dados e do que você deseja assumir sobre eles. Métodos de uso geral confiam em estatísticas robustas. O espírito dessa abordagem é caracterizar a maior parte dos dados de uma maneira que não seja influenciada por discrepantes e, em seguida, apontar para quaisquer valores individuais que não se encaixam nessa caracterização.
Por se tratar de uma série temporal, ela acrescenta a complicação de precisar (re) detectar outliers continuamente. Se isso for feito à medida que a série se desenrola, temos permissão apenas para usar dados mais antigos para a detecção, não dados futuros! Além disso, como proteção contra muitos testes repetidos, gostaríamos de usar um método com uma taxa de falsos positivos muito baixa.
Essas considerações sugerem a execução de um teste externo simples e robusto da janela móvel sobre os dados . Existem muitas possibilidades, mas uma simples, fácil de entender e facilmente implementada é baseada em um MAD: desvio absoluto médio da mediana. Essa é uma medida de variação fortemente robusta dentro dos dados, semelhante a um desvio padrão. Um pico externo seria vários MADs ou mais maior que a mediana.
Ainda há alguns ajustes a serem feitos : quanto de um desvio em relação à maior parte dos dados deve ser considerado distante e a que distância do tempo se deve olhar? Vamos deixá-los como parâmetros para experimentação. Aqui está uma R
implementação aplicada aos dados (com para emular os dados) com os valores correspondentes :n = 1150 yx=(1,2,…,n)n=1150y
# Parameters to tune to the circumstances:
window <- 30
threshold <- 5
# An upper threshold ("ut") calculation based on the MAD:
library(zoo) # rollapply()
ut <- function(x) {m = median(x); median(x) + threshold * median(abs(x - m))}
z <- rollapply(zoo(y), window, ut, align="right")
z <- c(rep(z[1], window-1), z) # Use z[1] throughout the initial period
outliers <- y > z
# Graph the data, show the ut() cutoffs, and mark the outliers:
plot(x, y, type="l", lwd=2, col="#E00000", ylim=c(0, 20000))
lines(x, z, col="Gray")
points(x[outliers], y[outliers], pch=19)
Aplicado a um conjunto de dados como a curva vermelha ilustrada na pergunta, ele produz este resultado:
Os dados são mostrados em vermelho, a janela de 30 dias dos limiares medianos + 5 * MAD em cinza e os valores extremos - que são simplesmente aqueles valores acima da curva cinza - em preto.
(O limite só pode ser calculado a partir do final da janela inicial. Para todos os dados nessa janela inicial, o primeiro limite é usado: é por isso que a curva cinza é plana entre x = 0 ex = 30).
Os efeitos da alteração dos parâmetros são (a) aumentar o valor de window
tenderá a suavizar a curva cinza e (b) aumentar threshold
aumentará a curva cinza. Sabendo disso, pode-se pegar um segmento inicial dos dados e identificar rapidamente os valores dos parâmetros que melhor segregam os picos externos do restante dos dados. Aplique esses valores de parâmetro para verificar o restante dos dados. Se um gráfico mostrar que o método está piorando com o tempo, isso significa que a natureza dos dados está mudando e os parâmetros podem precisar de um novo ajuste.
Observe o quão pouco esse método assume sobre os dados: eles não precisam ser normalmente distribuídos; eles não precisam exibir periodicidade; eles nem precisam ser negativos. Tudo pressupõe que os dados se comportam de maneiras razoavelmente semelhantes ao longo do tempo e que os picos externos são visivelmente mais altos que o restante dos dados.
Se alguém quiser experimentar (ou comparar alguma outra solução à oferecida aqui), eis o código que usei para produzir dados como os mostrados na pergunta.
n.length <- 1150
cycle.a <- 11
cycle.b <- 365/12
amp.a <- 800
amp.b <- 8000
set.seed(17)
x <- 1:n.length
baseline <- (1/2) * amp.a * (1 + sin(x * 2*pi / cycle.a)) * rgamma(n.length, 40, scale=1/40)
peaks <- rbinom(n.length, 1, exp(2*(-1 + sin(((1 + x/2)^(1/5) / (1 + n.length/2)^(1/5))*x * 2*pi / cycle.b))*cycle.b))
y <- peaks * rgamma(n.length, 20, scale=amp.b/20) + baseline