Análisis espectral: periodograma, regresión con componentes.

Componentes estacionales y periodograma

Otra de las componentes que nos gustaría analizar cuando exploramos un ajuste de serie temporal es la presencia de componentes periódicas.

Ejemplos:

  1. La mezcla de \(\sin\) y \(\cos\) del ejercicio ya visto.

  2. La serie SOI ya analizada (y también el recruitement si le creemos al libro).

Idea: ¿Como sistematizar el encontrar componentes periódicas?

Ejemplo: señal artificial (mezcla de \(\sin\) y \(\cos\))

import numpy as np

t=np.arange(0,500)
f=1/50 #frecuencia fundamental
signal = 0.5*np.cos(2*np.pi*f*t) + 0.5*np.sin(2*np.pi*f*t) + 0.5*np.cos(2*np.pi*2*f*t)
noise = np.random.normal(size=t.size,loc=0,scale=0.2)

x = signal + noise

plt.plot(t,x);
plt.title("Señal con componentes periódicas afectada por ruido");
../_images/b6739da9b6e606ec368a408b8870cc0ebde36a5fd6415645686b2da25862d65e.png

Idea 1: recurrir a la ACF

Calcular la autocorrelación de la señal y ver si aparecen picos. Similar a lo hecho antes para el SOI.

import statsmodels.api as sm
from statsmodels.graphics.tsaplots import plot_acf

plot_acf(x, lags=100, bartlett_confint=False);
../_images/f2eb185977f9b8d8742a47c833c959778b6bdf983503f35731174ff920ee2676.png

Idea 2: correlacionar con una señal de frecuencia conocida

La idea es tomar señales de la forma:

\[ c^f_t = \cos(2\pi f t) \quad s^f_t = \sin(2 \pi f t).\]

Si el valor \(P^f = \rho(x,c^f_t) ^2 + \rho(x,s^f_t)^2\) es alto, entonces esa frecuencia está presente en la señal. Aqui \(\rho\) indica correlación cruzada entre la señal y el \(\cos/\sin\).

Ejemplo:

#Probar con diferentes frecuencias:
f=1/500         # frecuencia a correlacionar. Si no está presente, no hay correlación.
#f=1/50          # frecuencia a correlacionar. Si está presente, hay correlación.
#f=1/25          # frecuencia a correlacionar. Si está presente, no hay correlación.

c1 = np.cos(2*np.pi*f*t)
s1 = np.sin(2*np.pi*f*t)

plt.subplot(1, 2,1)
plt.scatter(x,c1)
plt.title(f"Correlacion con coseno de frecuencia {f} : {np.corrcoef(x,c1)[0,1]}");
plt.subplot(1,2,2)
plt.scatter(x,s1)
plt.title(f"Correlacion con seno de frecuencia {f} : {np.corrcoef(x,s1)[0,1]}");
../_images/7f405d9db2e611b542f36493b2d6870267dbedb97e9457407b466994a8f10e3c.png

Periodograma (transformada discreta de Fourier)

El periodograma es una forma sistemática de recorrer las correlaciones anteriores y calcular cuáles son significativas.

Definición: Transformada Discreta de Fourier

Dada una serie temporal \(x_t\) de largo \(n\) se define su DFT (Discrete Fourier Transform) como el vector complejo:

\[DFT_x(j/n) = \sum_{t} x_t \cos\left(2\pi \frac{j}{n} t\right) - i \sum_{t} x_t \sin\left(2\pi \frac{j}{n} t\right) = \rho(x,c^{j/n}_t) - i \rho(x,s^{j/n}_t)\]

Si \(x_t\) está centrada, \(DFT_x\) guarda las covarianzas (correlaciones no escaladas) con cosenos y senos de las frecuencias de la forma \(j/n\) siendo \(n\) la cantidad de datos.

Observar que el módulo \(|DFT_x(j/n)|^2 = \rho(x,c^{j/n}_t) ^2 + \rho(x,s^{j/n}_t)^2\) nos da idea de la “fuerza” de esta frecuencia.

El rol de \(j\)

La idea de la DFT es calcular sistemáticamente las correlaciones contra las frecuencias de la forma \(j/n\), siendo \(n\) el largo de los datos.

  • \(j=0\) correspondería a \(\cos(0) \equiv 1\) y \(\sin(0)\equiv 0\), que es lo mismo que sumar los datos. Es por ello que conviene centrar los datos antes de trabajar (eliminar la media).

  • \(0<j<n/2\) corresponde a las frecuencias observables. La interpretación de \(j\) aquí es la cantidad de ciclos en la muestra que se presentan a dicha frecuencia.

    Ejemplo: En una serie de frecuencia anual (\(12\) muestras por año) y \(120\) observaciones (\(10\) años), una componente anual debería aparecer para \(j=10\) (\(10\) ciclos en la muestra) o bien \(j/n=10/120=1/12\).

  • \(j=n/2\) es la máxima frecuencia observable. ¿Por qué? Porque para poder “observar” adecuadamente una frecuencia necesitamos al menos dos muestras por ciclo. Si tenemos \(n\) datos, dicha frecuencia máxima corresponde a \(j=n/2\) ciclos en la muestra.

FFT: Fast Fourier Transform

Numéricamente se calcula la \(DFT\) usando el algoritmo FFT: Fast Fourier Transform, que reusa varios de los cálculos de correlación para una implementación más eficiente.

En python usando numpy:

  • La FFT (DFT) se obtiene mediante numpy.fft(x).

  • Se calcula \(I(j/n) = |DFT_x (j/n)|^2\), el módulo cuadrado del complejo resultante.

  • Luego se obtiene el Periodograma \(P\) escalado definido como: $\(P(j/n) = \frac{4}{n^2} I(j/n) , \quad j=0,\ldots,n/2.\)\( El coeficiente \)4/n^2$ lleva la escala a los coeficientes de regresión contra seno y coseno.

  • Si se desea graficar el periodograma \(P\) en las frecuencias originales de la serie, se debe usar en las abscisas el vector de frecuencias:

    f = [0, 1/n, 2/n, ..., (n/2-1)/n] * freq

    siendo freq la frecuencia de la serie (frecuencia de muestreo).

Utilizando este método definimos la siguiente función que calcula el periodograma escalado para una serie:

def periodogram(x,sampling_frequency=1):

    """Función que calcula el periodograma y grafica.
    
    Parameters
    ----------
        x : array_like, data.
        sampling_frequency: frecuencia de muestreo de la serie, para graficar.
    """

    n = x.size
    P = 4/n**2 * np.abs(np.fft.fft(x))**2
    P = P[0:round(n/2)]
    f = np.arange(0,round(n/2))/n * sampling_frequency
    plt.stem(f,P,basefmt='');
    plt.xlabel("Frequency")
    plt.ylabel("Power")
periodogram(x)
../_images/c35caa5e3cd68d0ed76f6639e204883a35d878e49a3a01e3ae508fef2a1e7223.png

Observación

Notar que las alturas del periodograma guardan relación con los coeficientes de seno y coseno involucrados en la señal.

Análisis espectral

Con la misma idea podemos hacer una función que indique cuáles fueron las frecuencias más importantes:

import pandas as pd

def spectrum(x,sampling_frequency=1, nfreq=10):

    """Función que calcula las componentes más relevantes del periodograma.
    
    Parameters
    ----------
        x : array_like, data.
        sampling_frequency: frecuencia de muestreo de la serie, para graficar.
    """

    n = x.size
    nfreq = np.minimum(nfreq,round(n/2)) #corto nfreq si la serie es muy corta!
    P = 4/n**2 * np.abs(np.fft.fft(x))**2
    P = P[0:round(n/2)]
    f = np.arange(0,round(n/2))/n * sampling_frequency
    index = np.argsort(P)[::-1] #ordeno P decreciente
    return pd.DataFrame({"Frecuencia" : f[index[0:nfreq]],"Potencia" : P[index[0:nfreq]]})
df = spectrum(x)
print(df)
   Frecuencia  Potencia
0       0.020  0.479893
1       0.040  0.263745
2       0.196  0.001549
3       0.336  0.001422
4       0.468  0.001235
5       0.316  0.001145
6       0.364  0.001123
7       0.288  0.001105
8       0.066  0.001056
9       0.000  0.001028

Regresión lineal para ajustar

Luego de identificadas las frecuencias más relevantes, el último paso es simplemente ajustar una regresión lineal incluyendo todos los términos que sean necesarios.

Ejemplo:

En el caso anterior identificamos las frecuencias \(0.02\) y \(0.04\) que estaban en la serie original. Hacemos entonces una regresión de la forma:

\[x_t = \beta_0 + \beta_1 \cos(2\pi (0.02) t) + \beta_2 \sin(2\pi (0.02) t) + \beta_3 \cos(2\pi (0.04) t) + \beta_4 \sin(2\pi (0.04) t) + w_t\]

y luego analizamos los residuos. Si sabemos que la serie está centrada podemos prescindir de \(\beta_0\) (el intercept).

from statsmodels.formula.api import ols

t = np.arange(0,500)
data = pd.Series({"x":x,"time":t})
fit = ols(formula='x~np.cos(2*np.pi*0.02*time) + np.sin(2*np.pi*0.02*time) + np.cos(2*np.pi*0.04*time) + np.sin(2*np.pi*0.04*time)', data=data).fit()
fit.summary()
OLS Regression Results
Dep. Variable: x R-squared: 0.908
Model: OLS Adj. R-squared: 0.907
Method: Least Squares F-statistic: 1216.
Date: Tue, 22 Apr 2025 Prob (F-statistic): 2.02e-254
Time: 16:13:58 Log-Likelihood: 109.17
No. Observations: 500 AIC: -208.3
Df Residuals: 495 BIC: -187.3
Df Model: 4
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Intercept 0.0160 0.009 1.834 0.067 -0.001 0.033
np.cos(2 * np.pi * 0.02 * time) 0.4928 0.012 39.856 0.000 0.468 0.517
np.sin(2 * np.pi * 0.02 * time) 0.4869 0.012 39.381 0.000 0.463 0.511
np.cos(2 * np.pi * 0.04 * time) 0.5135 0.012 41.533 0.000 0.489 0.538
np.sin(2 * np.pi * 0.04 * time) -0.0074 0.012 -0.599 0.550 -0.032 0.017
Omnibus: 0.735 Durbin-Watson: 2.071
Prob(Omnibus): 0.692 Jarque-Bera (JB): 0.750
Skew: 0.093 Prob(JB): 0.687
Kurtosis: 2.960 Cond. No. 1.41


Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

Luego descartamos los no significativos (ej: el intercept y el seno de frecuencia alta en este caso).

fit = ols(formula='x~0+np.cos(2*np.pi*0.02*time) + np.sin(2*np.pi*0.02*time) + np.cos(2*np.pi*0.04*time)', data=data).fit()
fit.summary()
OLS Regression Results
Dep. Variable: x R-squared (uncentered): 0.907
Model: OLS Adj. R-squared (uncentered): 0.906
Method: Least Squares F-statistic: 1616.
Date: Tue, 22 Apr 2025 Prob (F-statistic): 7.73e-256
Time: 16:13:58 Log-Likelihood: 107.30
No. Observations: 500 AIC: -208.6
Df Residuals: 497 BIC: -195.9
Df Model: 3
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
np.cos(2 * np.pi * 0.02 * time) 0.4928 0.012 39.787 0.000 0.468 0.517
np.sin(2 * np.pi * 0.02 * time) 0.4869 0.012 39.313 0.000 0.463 0.511
np.cos(2 * np.pi * 0.04 * time) 0.5135 0.012 41.461 0.000 0.489 0.538
Omnibus: 0.696 Durbin-Watson: 2.055
Prob(Omnibus): 0.706 Jarque-Bera (JB): 0.710
Skew: 0.090 Prob(JB): 0.701
Kurtosis: 2.961 Cond. No. 1.00


Notes:
[1] R² is computed without centering (uncentered) since the model does not contain a constant.
[2] Standard Errors assume that the covariance matrix of the errors is correctly specified.

Ajuste:

plt.plot(t,x, label="Observaciones");
plt.plot(t,fit.fittedvalues, label="Ajuste");
plt.legend();
../_images/2a0a51e8b86d9d09ba1d8232e7a6cea093073290900a037358fafed8c5486c04.png

Residuos:

plt.plot(t,fit.resid);
plt.title(f"Residuos: RMSE = {np.sqrt(fit.mse_resid)}");
../_images/506e59cdbfd718895ed36266d551eb59abd865a6a3f9407e03592724f7fd1169.png
plot_acf(fit.resid,bartlett_confint=False);
../_images/571f32e3a4694e03afe08bd67f3c7ef787f8211988512bc9940c6b7e137342ff.png

Aplicación:

Apliquemos la idea del periodograma a las series SOI y Recruitement de la biblioteca astsa

soi = astsa.soi
rec = astsa.rec
soi.head() #notar que las medidas son mensuales
value
index
1950-01 0.377
1950-02 0.246
1950-03 0.311
1950-04 0.104
1950-05 -0.016

Importante:

Como ya mencionamos, antes de calcular el periodograma, debemos centrar la serie.

s = soi-np.mean(soi)
s.plot().legend(["SOI centrado"]);
../_images/bdf008b93f325b32dc23aa5505be165f277b0a5003bfb644739a132f11586dea.png
periodogram(s.value, sampling_frequency=12)
../_images/4c5cc42df06e54bd4879f131ab0c7893a8fb0dd036ca3c6137406edc848917d2.png
spectrum(s.value, sampling_frequency=12)
Frecuencia Potencia
0 1.006623 0.083315
1 0.980132 0.013316
2 0.052980 0.013200
3 0.158940 0.010678
4 0.291391 0.009385
5 0.264901 0.007391
6 0.238411 0.006961
7 0.476821 0.005480
8 0.026490 0.004373
9 2.993377 0.004042

Luego hacemos la regresión, volviendo a incorporar el intercept con los datos sin centrar. El intercept se hace cargo de la media.

time = pd.Series(np.arange(0,soi.size)/12,index=soi.index) #creo una columna de tiempos
data = pd.concat([time,soi], axis=1)
data.columns = ["time","soi"]

fit = ols(formula="soi~np.cos(2*np.pi*time) + np.sin(2*np.pi*time)",data=data).fit()
fit.summary()
OLS Regression Results
Dep. Variable: soi R-squared: 0.355
Model: OLS Adj. R-squared: 0.352
Method: Least Squares F-statistic: 123.8
Date: Tue, 22 Apr 2025 Prob (F-statistic): 1.47e-43
Time: 16:13:59 Log-Likelihood: -107.94
No. Observations: 453 AIC: 221.9
Df Residuals: 450 BIC: 234.2
Df Model: 2
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Intercept 0.0815 0.014 5.627 0.000 0.053 0.110
np.cos(2 * np.pi * time) 0.3082 0.020 15.073 0.000 0.268 0.348
np.sin(2 * np.pi * time) -0.0937 0.020 -4.571 0.000 -0.134 -0.053
Omnibus: 5.111 Durbin-Watson: 1.078
Prob(Omnibus): 0.078 Jarque-Bera (JB): 4.859
Skew: -0.206 Prob(JB): 0.0881
Kurtosis: 2.705 Cond. No. 1.42


Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
plt.plot(time,soi, label="Observaciones");
plt.plot(time,fit.fittedvalues, label="Ajuste");
plt.legend();
../_images/2006bdd2d4b67ddf10e3b41244514cfeaaf00d1869d7809ad31e899fe0873d4f.png

Agregamos ahora componentes de baja frecuencia del espectro:

f0 = 1.0
f1 = 0.052980
f2 = 0.158940
fit = ols(formula="soi~np.cos(2*np.pi*f0*time) + np.sin(2*np.pi*f0*time) + np.cos(2*np.pi*f1*time) + np.sin(2*np.pi*f1*time) + np.cos(2*np.pi*f2*time) + np.sin(2*np.pi*f2*time) ",data=data).fit()
fit.summary()
OLS Regression Results
Dep. Variable: soi R-squared: 0.438
Model: OLS Adj. R-squared: 0.430
Method: Least Squares F-statistic: 57.83
Date: Tue, 22 Apr 2025 Prob (F-statistic): 8.95e-53
Time: 16:13:59 Log-Likelihood: -76.873
No. Observations: 453 AIC: 167.7
Df Residuals: 446 BIC: 196.6
Df Model: 6
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Intercept 0.0815 0.014 6.000 0.000 0.055 0.108
np.cos(2 * np.pi * f0 * time) 0.3087 0.019 16.093 0.000 0.271 0.346
np.sin(2 * np.pi * f0 * time) -0.0939 0.019 -4.885 0.000 -0.132 -0.056
np.cos(2 * np.pi * f1 * time) 0.0057 0.019 0.295 0.768 -0.032 0.043
np.sin(2 * np.pi * f1 * time) 0.1150 0.019 5.987 0.000 0.077 0.153
np.cos(2 * np.pi * f2 * time) 0.0338 0.019 1.759 0.079 -0.004 0.072
np.sin(2 * np.pi * f2 * time) 0.0989 0.019 5.152 0.000 0.061 0.137
Omnibus: 7.674 Durbin-Watson: 1.236
Prob(Omnibus): 0.022 Jarque-Bera (JB): 7.513
Skew: -0.280 Prob(JB): 0.0234
Kurtosis: 2.710 Cond. No. 1.43


Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
plt.plot(time,soi, label="Observaciones");
plt.plot(time,fit.fittedvalues, label="Ajuste");
plt.legend();
../_images/5d871182b737f2533abbc4f5e72b8fc2e9ba1833d38e7dbd057953c1b6b313ff.png
plt.plot(time,fit.resid); 
plt.title(f"Residuos del ajuste - RMSE = {np.sqrt(fit.mse_resid)}");
../_images/9597ce55e8cd5f7e3f5cbf0b02a6d021e3f55710cfaaf1d9241c13acdf51b788.png
plot_acf(fit.resid, bartlett_confint=False);
../_images/d9ec98bc543835871cc7c14725a2c394e2fd6c722365304293242706cb20d58e.png

Podemos hacer lo mismo para el Recruitment…

r = rec - np.mean(rec)
r.plot().legend(["Recruitment centrado"]);
../_images/25315dc5e4ff33ecac6013d2d377294cd391bd92a4154e36b1d551d83cd13367.png
periodogram(r.value, sampling_frequency=12)
../_images/e7c5a85640d2eafaccdf5953ffa18d26f86e2f913ddc8dfccab6d782ff4130f8.png

Observaciones

  • La frecuencia anual aparece en el periodograma (el pico es en \(j=38\), cuando la muestra tiene casi \(38\) años de datos precisamente). No era clara en la correlación en el caso de rec pero aquí sí se observa claramente.

  • Aparecen frecuencias más lentas, tanto en soi como en rec. En el caso de rec aparecen varios picos en frecuencias casi contiguas, de períodos mayores a un año.

  • No siempre los picos son todos relevantes. A veces es necesario suavizar el periodograma para extraer las frecuencias relevantes.

  • Por ejemplo, aparecen picos cercanos a \(f=0.25\) que corresponden con el ciclo el fenómeno del Niño (cada 4 años).

Periodograma de Welch

Una forma de suavizar el periodograma anterior es realizar la transformación en ventanas más cortas de la serie, analizar la \(DFT\) en cada ventana y promediar. Este método se denomina periodograma de Welch.

from scipy.signal import welch

f,P = welch(soi.value.values,fs=12) 
plt.plot(f,P)
plt.title("Periodograma suavizado de la serie SOI");
../_images/abdaad700155066eeb829f5c1fe0ae01fff7320d28b50ad500cb905c8c071f79.png
from scipy.signal import welch

f,P = welch(rec.value.values,fs=12) 
plt.plot(f,P)
plt.title("Periodograma suavizado de la serie rec");
../_images/c318d2f57e6b6575af04d34387ba27355ac80bec791e70f0efe85624bbee6667.png

Un ejemplo completo: la serie Air Passengers

Esta es una serie clásica que también analizaremos con otras técnicas más adelante. Por ahora veamos como podemos combinar todo lo visto anteriormente para hacer un ajuste.

Se tiene \(x_t=\) miles de pasajeros mensuales internacionales en el transporte aéreo.

df = pd.read_csv('../data/international-airline-passengers.csv', names = ['year','passengers'], header=0)
air = pd.Series(df["passengers"].values, index=np.arange(1949,1961,1/12))
time = pd.Series(np.arange(1949,1961,1/12), index=np.arange(1949,1961,1/12))
air.plot();
plt.title("Total mensual de pasajeros en el período 1949-1960")
plt.ylabel("Miles de pasajeros");
../_images/ea0bb8aece8409df423719214094a19ae103fefac67bd1dc1aacca751ea46564.png
logair = np.log(air);
logair.plot();
plt.title("Pasajeros, escala logaritmica");
../_images/e0c029f6237c95fa92f977113c0ea67f2c2adc63317ce723fe5f7d4ad04ff2c0.png
data = pd.concat([air,time], axis=1)
fit = ols(formula="logair~time", data=data).fit()
fit.summary()
OLS Regression Results
Dep. Variable: logair R-squared: 0.902
Model: OLS Adj. R-squared: 0.901
Method: Least Squares F-statistic: 1300.
Date: Tue, 22 Apr 2025 Prob (F-statistic): 2.41e-73
Time: 16:14:00 Log-Likelihood: 80.794
No. Observations: 144 AIC: -157.6
Df Residuals: 142 BIC: -151.6
Df Model: 1
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Intercept -230.1878 6.539 -35.203 0.000 -243.114 -217.262
time 0.1206 0.003 36.050 0.000 0.114 0.127
Omnibus: 3.750 Durbin-Watson: 0.587
Prob(Omnibus): 0.153 Jarque-Bera (JB): 2.722
Skew: 0.184 Prob(JB): 0.256
Kurtosis: 2.436 Cond. No. 1.10e+06


Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 1.1e+06. This might indicate that there are
strong multicollinearity or other numerical problems.
logair.plot()
fit.fittedvalues.plot();
plt.title("Ajuste");
../_images/6bdd0ebed51535d735cefd63112a7ff92976885e7521e62aa354c715adc080ca.png
fit.resid.plot()
plt.title("Residuos");
../_images/62f8090f97cd3b02104e92e0df3969b3b7139cab74b1b73df764767a72d5b2be.png
fit = ols(formula="logair~time+np.cos(2*np.pi*time)+np.sin(2*np.pi*time)+np.cos(2*np.pi*2*time)+np.sin(2*np.pi*2*time)", data=air).fit()
fit.summary()
OLS Regression Results
Dep. Variable: logair R-squared: 0.977
Model: OLS Adj. R-squared: 0.976
Method: Least Squares F-statistic: 1177.
Date: Tue, 22 Apr 2025 Prob (F-statistic): 3.03e-111
Time: 16:14:01 Log-Likelihood: 185.79
No. Observations: 144 AIC: -359.6
Df Residuals: 138 BIC: -341.8
Df Model: 5
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Intercept -230.9817 3.208 -71.998 0.000 -237.325 -224.638
time 0.1210 0.002 73.725 0.000 0.118 0.124
np.cos(2 * np.pi * time) -0.1475 0.008 -18.392 0.000 -0.163 -0.132
np.sin(2 * np.pi * time) 0.0282 0.008 3.511 0.001 0.012 0.044
np.cos(2 * np.pi * 2 * time) 0.0567 0.008 7.077 0.000 0.041 0.073
np.sin(2 * np.pi * 2 * time) 0.0591 0.008 7.371 0.000 0.043 0.075
Omnibus: 0.996 Durbin-Watson: 1.099
Prob(Omnibus): 0.608 Jarque-Bera (JB): 0.908
Skew: -0.193 Prob(JB): 0.635
Kurtosis: 2.959 Cond. No. 1.11e+06


Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 1.11e+06. This might indicate that there are
strong multicollinearity or other numerical problems.
logair.plot()
fit.fittedvalues.plot()
plt.title("Ajuste");
../_images/e7992647132f6c0baae89ceb327cc61ef7187387186762a45d71e7c827f58f68.png
fit.resid.plot()
plt.title(f"Residuos del ajuste, RMSE = {np.sqrt(fit.mse_resid)}");
../_images/d21f29ea913f634632dcf608315747227afa72bbd0541ce47e5f7a4e9b4f5b6b.png
## QQ-plot es una verificación de gaussianidad.
sm.qqplot(fit.resid, line="s");
../_images/8aefb7db5ae0e44db71b1bc7fac2dc974af4773e1159b2ca877c768e1c20733d.png
plot_acf(fit.resid.values, bartlett_confint=False);
../_images/c99ac4c47379f62aa19539ca75827bdb5d6d2e3c49b690b4e3162208dbf0ab7a.png

Volvemos ahora a la escala original

fitted_values = np.exp(fit.fittedvalues)
air.plot()
fitted_values.plot()
rmse = np.std(air-fitted_values)
R2 = (np.var(air)-np.var(air-fitted_values))/np.var(air)
plt.title(f"Ajuste. RMSE del ajuste = {rmse}, R2 = {R2}");
../_images/e753c3441e1bf449a567e8a47e428729c050f31f383da1b916a7df0c33d958fe.png

Ejercicio

Análisis frecuencial del fenómeno del niño (serie \(soi\)).

En este ejercicio, se busca explorar la naturaleza periódica de \(S_t\), la serie SOI ya analizada.

  1. Quitar la tendencia a las serie mediante una regresión lineal en la componente tiempo. ¿Hay una tendencia significativa en la temperatura de superficie?

  2. Calcular el periodograma para la serie sin tendencia (residuos) de la parte anterior. Identificar las frecuencias principales (una obvia es la anual). ¿Cuál es el ciclo probable del fenómeno del Niño que refleja el pico más pequeño?