Ajustar um termo senoidal aos dados

26

Embora eu tenha lido este post, ainda não tenho idéia de como aplicar isso aos meus próprios dados e espero que alguém possa me ajudar.

Eu tenho os seguintes dados:

y <- c(11.622967, 12.006081, 11.760928, 12.246830, 12.052126, 12.346154, 12.039262, 12.362163, 12.009269, 11.260743, 10.950483, 10.522091,  9.346292,  7.014578,  6.981853,  7.197708,  7.035624,  6.785289, 7.134426,  8.338514,  8.723832, 10.276473, 10.602792, 11.031908, 11.364901, 11.687638, 11.947783, 12.228909, 11.918379, 12.343574, 12.046851, 12.316508, 12.147746, 12.136446, 11.744371,  8.317413, 8.790837, 10.139807,  7.019035,  7.541484,  7.199672,  9.090377,  7.532161,  8.156842,  9.329572, 9.991522, 10.036448, 10.797905)
t <- 18:65

E agora eu simplesmente quero encaixar uma onda senoidal

y(t)=Asin(ωt+ϕ)+C.

com as quatro incógnitas , Q , & Phi e a ele.AωϕC

O restante do meu código é o seguinte

res <- nls(y ~ A*sin(omega*t+phi)+C, data=data.frame(t,y), start=list(A=1,omega=1,phi=1,C=1))
co <- coef(res)

fit <- function(x, a, b, c, d) {a*sin(b*x+c)+d}

# Plot result
plot(x=t, y=y)
curve(fit(x, a=co["A"], b=co["omega"], c=co["phi"], d=co["C"]), add=TRUE ,lwd=2, col="steelblue")

Mas o resultado é realmente ruim.

Ajuste senoidal

Eu apreciaria muito qualquer ajuda.

Felicidades.

Pascal
fonte
Você está tentando ajustar uma onda senoidal aos dados ou está tentando ajustar algum tipo de modelo harmônico com um componente seno e cosseno? Há uma função harmônica no pacote TSA no R que você pode querer verificar. Ajuste seu modelo usando isso e veja que tipo de resultados você obtém.
Eric Peterson
5
Você já tentou diferentes valores iniciais? Sua função de perda é não convexa, portanto, valores iniciais diferentes podem levar a soluções diferentes.
Stefan Wager
11
Conte-nos mais sobre os dados. Geralmente, existe uma periodicidade conhecida e, portanto, não precisa ser estimada a partir dos dados. É uma série temporal ou algo mais? É muito mais fácil se você pode ajustar termos seno e cosseno separados por um modelo linear.
Nick Cox
2
Ter um período desconhecido torna seu modelo não linear (esse evento é mencionado na resposta selecionada na postagem vinculada). Dado que, os outros parâmetros são condicionalmente lineares; para algumas rotinas não lineares de LS, essas informações são importantes e podem melhorar o comportamento. Uma opção pode ser o uso de métodos espectrais para obter o período e a condição; outro seria atualizar o período e os outros parâmetros por meio de uma otimização não-linear e linear, respectivamente, de maneira iterativa.
Glen_b -instala Monica
(I acabou de editar a resposta lá para fazer o caso particular do período desconhecido um exemplo explícito de que pode torná-lo não-linear.)
Glen_b -Reinstate Monica

Respostas:

18

Se você deseja apenas uma boa estimativa de e não se importa muito com o erro padrão:ω

ssp <- spectrum(y)  
per <- 1/ssp$freq[ssp$spec==max(ssp$spec)]
reslm <- lm(y ~ sin(2*pi/per*t)+cos(2*pi/per*t))
summary(reslm)

rg <- diff(range(y))
plot(y~t,ylim=c(min(y)-0.1*rg,max(y)+0.1*rg))
lines(fitted(reslm)~t,col=4,lty=2)   # dashed blue line is sin fit

# including 2nd harmonic really improves the fit
reslm2 <- lm(y ~ sin(2*pi/per*t)+cos(2*pi/per*t)+sin(4*pi/per*t)+cos(4*pi/per*t))
summary(reslm2)
lines(fitted(reslm2)~t,col=3)    # solid green line is periodic with second harmonic

trama senoidal

(Um ajuste melhor ainda talvez explique de alguma forma os valores extremos dessa série, reduzindo sua influência.)

---

Se você quiser ter uma idéia da incerteza em , use a probabilidade do perfil ( pdf1 , pdf2 - referências sobre como obter CIs ou SEs aproximados da probabilidade do perfil ou suas variantes não são difíceis de localizar)ω

(Como alternativa, você pode alimentar essas estimativas em nls ... e iniciá-las já convergidas.)

Glen_b -Reinstate Monica
fonte
(+1) boa resposta. Tentei ajustar o modelo linear, lm(y~sin(2*pi*t)+cos(2*pi*t)mas isso não funcionou (o costermo sempre foi 1). Apenas por curiosidade: o que as duas primeiras linhas fazem (eu sei que spectrumestima a densidade espectral)?
COOLSerdash
11
t2*pi*t
11
@COOLSerdash (ctd) - A segunda linha encontra a frequência associada ao maior pico do espectro e inverte para identificar o período. Pelo menos nesse caso (mas eu suspeito mais amplamente), os padrões nele identificam essencialmente o período que maximiza a probabilidade tão intimamente que eu excluí as etapas necessárias para maximizar a probabilidade de perfil na região naquele período. A funçãospec no TSA pode ser melhor (parece ter mais opções, uma das quais pode ser importante às vezes), mas nesse caso o pico principal estava exatamente no mesmo lugar que com o spectrumque eu não me incomodei.
Glen_b -Reinstala Monica
@Glen_b esse método faz maravilhas para o meu caso de uso. Eu também preciso ajustar uma curva cos (x), mas não funciona tão bem ... eu mudei reslmparareslm <- lm(y ~ cos(2*pi/per*t)+tan(2*pi/per*t)) mas isso não parece certo. alguma dica?
Amit Kohli
Por que você tem um termo bronzeado lá?
Glen_b -Reinstate Monica
15

2π/20

Quando eu colocar isso em nls's startlista, eu tenho uma curva que era muito mais razoável, embora ainda tenha alguns desvios sistemáticos.

Dependendo do seu objetivo com esse conjunto de dados, você pode tentar melhorar o ajuste adicionando termos adicionais ou usando uma abordagem não paramétrica como um processo gaussiano com um kernel periódico.

Ajuste senoidal

Escolhendo um valor inicial automaticamente

Se você deseja escolher a frequência dominante, pode usar uma transformada rápida de Fourier (FFT). Isso está fora da minha área de especialização, então deixarei que outras pessoas preencham os detalhes, se quiserem (especialmente as etapas 2 e 3), mas o Rcódigo abaixo deve funcionar.

# Step 1: do the FFT
raw.fft = fft(y)

# Step 2: drop anything past the N/2 - 1th element.
# This has something to do with the Nyquist-shannon limit, I believe
# (https://en.wikipedia.org/wiki/Nyquist%E2%80%93Shannon_sampling_theorem)
truncated.fft = raw.fft[seq(1, length(y)/2 - 1)]

# Step 3: drop the first element. It doesn't contain frequency information.
truncated.fft[1] = 0

# Step 4: the importance of each frequency corresponds to the absolute value of the FFT.
# The 2, pi, and length(y) ensure that omega is on the correct scale relative to t.
# Here, I set omega based on the largest value using which.max().
omega = which.max(abs(truncated.fft)) * 2 * pi / length(y)

Você também pode plotar abs(truncated.fft)para ver se há outras frequências importantes, mas precisará mexer um pouco na escala do eixo x.

Além disso, acredito que @Glen_b está correto que o problema é convexo depois que você conhece ômega (ou talvez você precise conhecer phi também? Não tenho certeza). De qualquer forma, conhecer os valores iniciais dos outros parâmetros não deve ser tão importante quanto o ômega se eles estiverem no estádio certo. Você provavelmente poderia obter estimativas decentes dos outros parâmetros da FFT, mas não tenho certeza de como isso funcionaria.

David J. Harris
fonte
11
Obrigado por essa dica. Só para esclarecer um pouco: os dados fazem parte de um microarray em que a periodicidade dos genes foi medida ao longo do tempo, ou seja, os dados mostrados são os dados de expressão de um gene. O problema agora é que eu quero aplicar esse método a cerca de 40k genes, todos com periodicidades e amplitudes diferentes. Portanto, é crucial que um bom ajuste seja encontrado independentemente das condições iniciais.
Pascal
11
@ Pascal Consulte minhas atualizações acima para obter uma recomendação para escolher automaticamente o valor inicial para o ômega.
David J. Harris
2
ϕumab
Gostaria de saber onde os valores x entram em jogo aqui. Claro que faz diferença para o ômega, se os valores de y fornecidos são separados por 1 ou por 5 x etapas, não é?
tipo protuberância
11
Dica de programação não relacionada à pergunta: cuidado ao nomear objetos R como foo.bar. Isso ocorre devido ao modo como R especifica métodos para classes .
Firebug
10

Como alternativa ao que já foi dito, pode ser interessante notar que um modelo AR (2) da classe de modelos ARIMA pode ser usado para gerar previsões com um padrão de ondas senoidais.

yt=C+ϕ1 1yt-1 1+ϕ2yt-2+umat
Cϕ1 1ϕ2umat é um termo aleatório de choque.

ϕ1 12+4ϕ2<0

Panratz (1991) nos diz o seguinte sobre ciclos estocásticos:

Um padrão de ciclo estocástico pode ser pensado em um padrão de onda senoidal distorcida no padrão de previsão: é uma onda senoidal com período estocástico (probabilístico), amplitude e ângulo de fase.

Para verificar se esse modelo poderia ser ajustado aos dados, usei o auto.arima() função do pacote de previsão para descobrir se sugeriria um modelo AR (2). Acontece que a auto.arima()função sugere um modelo ARMA (2,2); não é um modelo de AR (2) puro, mas tudo bem. Tudo bem porque um modelo ARMA (2,2) contém um componente AR (2), portanto, a mesma regra (sobre ciclos estocásticos) se aplica. Ou seja, ainda podemos verificar a condição mencionada acima para ver se serão produzidas previsões de ondas senoidais.

Os resultados de auto.arima(y) são mostrados abaixo.

Series: y 
ARIMA(2,0,2) with non-zero mean 

Coefficients:
         ar1      ar2      ma1     ma2  intercept
      1.7347  -0.8324  -1.2474  0.6918    10.2727
s.e.  0.1078   0.0981   0.1167  0.1911     0.5324

sigma^2 estimated as 0.6756:  log likelihood=-60.14
AIC=132.27   AICc=134.32   BIC=143.5

ϕ1 12+4ϕ2<0 01.73472+4(-0.8324)<0 0-0,3202914<0 0

O gráfico abaixo mostra a série original, y, o ajuste do modelo ARMA (2,2) e 14 previsões fora da amostra. Como pode ser visto, as previsões fora da amostra seguem um padrão de onda senoidal.

insira a descrição da imagem aqui

Lembre-se de duas coisas. 1) Esta é apenas uma análise muito rápida (usando uma ferramenta automatizada) e um tratamento adequado envolveria seguir a metodologia de Box-Jenkins. 2) As previsões do ARIMA são boas para previsões de curto prazo; portanto, você pode achar que as previsões de longo prazo dos modelos nas respostas de @David J. Harris e @Glen_b são mais confiáveis.

Por fim, espero que este seja um bom complemento para algumas respostas já muito informativas.

Referência : Previsão com modelos de regressão dinâmica: Alan Pankratz, 1991, (John Wiley and Sons, Nova York), ISBN 0-471-61528-5

Graeme Walsh
fonte
1

Os métodos atuais para ajustar uma curva sin a um determinado conjunto de dados requerem uma primeira estimativa dos parâmetros, seguidos por um processo interativo. Este é um problema de regressão não linear. Um método diferente consiste em transformar a regressão não linear em regressão linear, graças a uma equação integral conveniente. Então, não há necessidade de estimativa inicial nem processo iterativo: o encaixe é obtido diretamente. No caso da função y = a + r * sin (w * x + phi) ou y = a + b * sin (w * x) + c * cos (w * x), consulte as páginas 35-36 do artigo "Régression sinusoidale" publicado no Scribd: http://www.scribd.com/JJacquelin/documents No caso da função y = a + p * x + r * sin (w * x + phi): páginas 49-51 do capítulo "Regressões lineares e sinusoidais mistas". No caso de funções mais complicadas, o processo geral é explicado no capítulo "Regressão sinusoidal generalizada", páginas 54-61, seguido de um exemplo numérico y = r * sin (w * x + phi) + (b / x) + c * ln (x), páginas 62-63

JJacquelin
fonte
0

Se você conhece o ponto mais alto e mais baixo dos seus dados de aparência cosseno, pode usar esta função simples para calcular todos os coeficientes de cosseno:

getMyCosine <- function(lowest_point=c(pi,-1), highest_point=c(0,1)){
  cosine <- list(
    T = pi / abs(highest_point[1] - lowest_point[1]),
    b = - highest_point[1],
    k = (highest_point[2] + lowest_point[2]) / 2,
    A = (highest_point[2] - lowest_point[2]) / 2
  )
  return(cosine)
}

Abaixo, é usado para simular a variação de temperatura ao longo do dia com uma função cosseno, inserindo as horas e os valores de temperatura para a hora mais baixa e mais quente:

c <- getMyCosine(c(4,10),c(17,25)) 
# lowest temprature at 4:00 (10 degrees), highest at 17:00 (25 degrees)

x = seq(0,23,by=1);  y = c$A*cos(c$T*(x +c$b))+c$k ; 
library(ggplot2);   qplot(x,y,geom="step")

A saída está abaixo: Cosseno calculado a partir dos pontos mais baixos e mais altos

IVIM
fonte
3
Essa abordagem parece ser particularmente sensível a qualquer desvio aleatório do comportamento sinusoidal puro, o que tornaria inaplicável a quase todos os conjuntos de dados como o ilustrado na pergunta. É concebível que ele possa ser usado para fornecer valores iniciais para algumas das outras abordagens iterativas sugeridas neste encadeamento.
whuber
concordar, é o mais simples, seria bom para uma aproximação simples sob certas suposições
IVIM
0

Outra opção é usar a função genérica optim ou nls. Eu tentei ambos nenhum deles é completamente robusto

As seguintes funções pegam os dados em y e calculam os parâmetros.

calc.period <- function(y,t)
{     
   fs <- 1/(t[2]-t[1])
   ssp <- spectrum(y,plot=FALSE )  
   fN <- ssp$freq[which.max(ssp$spec)]
   per <- 1/(fN*fs)
   return(per)
 }

fit.sine<- function(y, t)
{ 
  data <- data.frame(x = as.vector(t), y=as.vector(y))
  min.RSS <- function (data, par){
    with(data, sum((par[1]*sin(2*pi*par[2]*x + par[3])+par[4]-y )^2))
  }  
  amp = sd(data$y)*2.**0.5
  offset = mean(data$y)
  fest <- 1/calc.period(y,t)
  guess = c( amp, fest,  0,   offset)
  #res <- optim(par=guess, fn = min.RSS, data=data ) 
  r<-nls(y~offset+A*sin(2*pi*f*t+phi), 
     start=list(A=amp, f=fest, phi=0, offset=offset))
  res <- list(par=as.vector(r$m$getPars()))
  return(res)
}

 genSine <- function(t, params)
     return( params[1]*sin(2*pi*params[2]*t+ params[3])+params[4])

o uso é o seguinte:

t <- seq(0, 10, by = 0.01)
A <- 2 
f <- 1.5
phase <- 0.2432
offset <- -2

y <- A*sin(2*pi*f*t +phase)+offset + rnorm(length(t), mean=0, sd=0.2)

reslm1 <- fit.sine(y = y, t= t)

O código a seguir compara os dados

ysin <- genSine(as.vector(t), params=reslm1$par)
ysin.cor <- genSine(as.vector(t), params=c(A, f, phase, offset))

plot(t, y)
lines(t, ysin, col=2)
lines(t, ysin.cor, col=3)
NMech
fonte