Analisis exploratorio de la tasa de desempleo en Colombia

Autores: Yeison Buitrago, Diego Robayo y Gilbert Rodriguez
Fecha: 13/04/2026

# --- 1.3 Inspeccionar la estructura ---
str(Tasa_Desempleo_raw)
## tibble [299 × 2] (S3: tbl_df/tbl/data.frame)
##  $ Fecha    : POSIXct[1:299], format: "2001-01-31" "2001-02-28" ...
##  $ Desempleo: num [1:299] 16.6 17.4 15.8 14.5 14 ...
head(Tasa_Desempleo_raw, 10)
## # A tibble: 10 × 2
##    Fecha               Desempleo
##    <dttm>                  <dbl>
##  1 2001-01-31 00:00:00      16.6
##  2 2001-02-28 00:00:00      17.4
##  3 2001-03-31 00:00:00      15.8
##  4 2001-04-30 00:00:00      14.5
##  5 2001-05-31 00:00:00      14.0
##  6 2001-06-30 00:00:00      15.3
##  7 2001-07-31 00:00:00      15.2
##  8 2001-08-31 00:00:00      14.9
##  9 2001-09-30 00:00:00      14.1
## 10 2001-10-31 00:00:00      14.5
tail(Tasa_Desempleo_raw, 5)
## # A tibble: 5 × 2
##   Fecha               Desempleo
##   <dttm>                  <dbl>
## 1 2025-07-31 00:00:00      8.84
## 2 2025-08-31 00:00:00      8.6 
## 3 2025-09-30 00:00:00      8.17
## 4 2025-10-31 00:00:00      8.2 
## 5 2025-11-30 00:00:00      7.02
dim(Tasa_Desempleo_raw)
## [1] 299   2
names(Tasa_Desempleo_raw)
## [1] "Fecha"     "Desempleo"
# --- 1.4 Convertir Fecha de POSIXct a Date ---
# R ya leyó las fechas bien desde Excel (POSIXct)
# Solo hay que quitarle la parte de la hora con as.Date()
Tasa_Desempleo_raw <- Tasa_Desempleo_raw %>%
  mutate(Fecha = as.Date(Fecha))

# Verificar que quedó bien
head(Tasa_Desempleo_raw)
## # A tibble: 6 × 2
##   Fecha      Desempleo
##   <date>         <dbl>
## 1 2001-01-31      16.6
## 2 2001-02-28      17.4
## 3 2001-03-31      15.8
## 4 2001-04-30      14.5
## 5 2001-05-31      14.0
## 6 2001-06-30      15.3
class(Tasa_Desempleo_raw$Fecha)   # Debe decir "Date"
## [1] "Date"
# --- 1.5 Crear objeto XTS ---
Fechas_yearmon <- as.yearmon(Tasa_Desempleo_raw$Fecha)

Desempleo_xts <- xts(
  x         = Tasa_Desempleo_raw$Desempleo,
  frequency = 12,
  order.by  = Fechas_yearmon
)

# --- 1.6 Exploración del objeto de serie de tiempo ---
ts_info(Desempleo_xts)    # Inicio, fin, frecuencia, Nº observaciones
##  The Desempleo_xts series is a xts object with 1 variable and 299 observations
##  Frequency: monthly 
##  Start time: ene. 2001 
##  End time: nov. 2025
class(Desempleo_xts)      # Debe decir "xts" "zoo"
## [1] "xts" "zoo"
# --- 1.8 Gráfico interactivo con TSstudio ---
ts_plot(Desempleo_xts,
        title  = "Tasa de Desempleo Mensual - Total Nacional",
        Ytitle = "Tasa de Desempleo (%)",
        Xtitle = "Año",
        Xgrid  = TRUE,
        Ygrid  = TRUE)
# ============================================================
# GRÁFICO 5: Comparación de tres niveles de suavidad LOESS
# Esto te permite ver cuál nivel captura mejor la tendencia
# ============================================================

# Suavidad baja (0.10) → sigue bien los ciclos
p1 <- tbl_desempleo %>%
  plot_time_series(
    .date_var    = Fecha,
    .value       = Desempleo,
    .title       = "LOESS span = 0.10 (detallado)",
    .interactive = FALSE,
    .smooth      = TRUE,
    .smooth_span = 0.10
  )

# Suavidad media (0.25) → balance entre detalle y suavidad
p2 <- tbl_desempleo %>%
  plot_time_series(
    .date_var    = Fecha,
    .value       = Desempleo,
    .title       = "LOESS span = 0.25 (medio)",
    .interactive = FALSE,
    .smooth      = TRUE,
    .smooth_span = 0.25
  )

# Suavidad alta (0.75) → solo muestra tendencia de largo plazo
p3 <- tbl_desempleo %>%
  plot_time_series(
    .date_var    = Fecha,
    .value       = Desempleo,
    .title       = "LOESS span = 0.75 (suave)",
    .interactive = FALSE,
    .smooth      = TRUE,
    .smooth_span = 0.75
  )

# Mostrar los tres juntos
library(patchwork)
p1 / p2 / p3

# --- Extraer valores y fechas de los objetos ya existentes ---
valores          <- as.numeric(Desempleo_xts)
fechas           <- zoo::index(Desempleo_xts)
tiempo_numerico  <- as.numeric(time(Desempleo_xts))

# --- Convertir a objeto ts (necesario para decompose y filter) ---
Desempleo_ts <- ts(valores,
                   start     = c(2001, 1),
                   frequency = 12)


# ============================================================
# MÉTODO 1: DESCOMPOSICIÓN CLÁSICA CON decompose()
# Promedio móvil simple con pesos iguales
# ============================================================

descomp_clasica    <- decompose(Desempleo_ts, type = "additive")
tendencia_decompose <- descomp_clasica$trend

# Gráfico de los cuatro componentes
# --- Gráfico corregido de descomposición clásica ---
plot(descomp_clasica)

# Nota: los primeros y últimos 6 valores serán NA
# porque decompose necesita una ventana completa de 12 meses


# ============================================================
# MÉTODO 2: PROMEDIO MÓVIL CON PESOS — FILTRO BOXCAR
# Pesos: 0.5 en extremos, 1 en el centro, dividido entre 12
# ============================================================

pesos_boxcar      <- c(0.5, rep(1, 11), 0.5) / 12
tendencia_boxcar  <- stats::filter(Desempleo_ts,
                                   filter = pesos_boxcar,
                                   sides  = 2)

# Gráfico
plot(Desempleo_ts,
     main = "Tendencia — Filtro Boxcar (PM con pesos)",
     ylab = "Desempleo (%)",
     col  = "gray50", lwd = 1)
lines(tendencia_boxcar, col = "orange", lwd = 2)
legend("topright",
       legend = c("Serie original", "Tendencia Boxcar"),
       col    = c("gray50", "orange"),
       lwd    = c(1, 2))

# ============================================================
# MÉTODO 3: SUAVIZAMIENTO KERNEL (NADARAYA-WATSON)
# Función ksmooth() con kernel gaussiano
# bandwidth en años: 0.5 = 6 meses, 1 = 1 año, 2 = 2 años
# ============================================================

tendencia_kernel_05 <- ksmooth(tiempo_numerico, valores,
                               kernel    = "normal",
                               bandwidth = 0.5)

tendencia_kernel_1  <- ksmooth(tiempo_numerico, valores,
                               kernel    = "normal",
                               bandwidth = 1)

tendencia_kernel_2  <- ksmooth(tiempo_numerico, valores,
                               kernel    = "normal",
                               bandwidth = 2)

# Gráfico
plot(Desempleo_ts,
     main = "Tendencia — Suavizamiento Kernel (Nadaraya-Watson)",
     ylab = "Desempleo (%)",
     col  = "gray50", lwd = 1)
lines(tendencia_kernel_05$x, tendencia_kernel_05$y, col = "red",       lwd = 2)
lines(tendencia_kernel_1$x,  tendencia_kernel_1$y,  col = "blue",      lwd = 2)
lines(tendencia_kernel_2$x,  tendencia_kernel_2$y,  col = "darkgreen", lwd = 2)
legend("topright",
       legend = c("Serie original",
                  "Kernel bw=0.5 (6 meses)",
                  "Kernel bw=1.0 (1 año)",
                  "Kernel bw=2.0 (2 años)"),
       col    = c("gray50", "red", "blue", "darkgreen"),
       lwd    = c(1, 2, 2, 2))

# ============================================================
# MÉTODO 4: SUAVIZAMIENTO LOWESS
# Función lowess() — regresión local ponderada por vecindad
# f = fracción de datos usados como vecindad en cada punto
# ============================================================

tendencia_lowess_010 <- lowess(tiempo_numerico, valores, f = 0.10)
tendencia_lowess_025 <- lowess(tiempo_numerico, valores, f = 0.25)
tendencia_lowess_075 <- lowess(tiempo_numerico, valores, f = 0.75)

# Gráfico
plot(Desempleo_ts,
     main = "Tendencia — Suavizamiento LOWESS",
     ylab = "Desempleo (%)",
     col  = "gray50", lwd = 1)
lines(fechas, tendencia_lowess_010$y, col = "red",       lwd = 2)
lines(fechas, tendencia_lowess_025$y, col = "blue",      lwd = 2)
lines(fechas, tendencia_lowess_075$y, col = "darkgreen", lwd = 2)
legend("topright",
       legend = c("Serie original",
                  "LOWESS span=0.10",
                  "LOWESS span=0.25",
                  "LOWESS span=0.75"),
       col    = c("gray50", "red", "blue", "darkgreen"),
       lwd    = c(1, 2, 2, 2))

# ============================================================
# MÉTODO 5: DESCOMPOSICIÓN STL
# Usa tsbl_desempleo ya creado en el Paso 3
# Más robusta que decompose() ante el pico del COVID (2020)
# robust = TRUE reduce el efecto de valores atípicos
# ============================================================

# --- Crear descomposición STL ---
descomp_STL <- tsbl_desempleo %>%
  model(
    STL(Desempleo ~ trend() + season(window = "periodic"),
        robust = TRUE)
  ) %>%
  components()

# --- Graficar ---
autoplot(descomp_STL) +
  labs(title = "Descomposición STL — Tasa de Desempleo (2001-2025)")
## Warning: `autoplot.dcmp_ts()` was deprecated in fabletools 0.6.0.
## ℹ Please use `ggtime::autoplot.dcmp_ts()` instead.
## ℹ Graphics functions have been moved to the {ggtime} package. Please use
##   `library(ggtime)` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

# --- Extraer tendencia y fechas STL ---

tendencia_STL       <- descomp_STL$trend
fechas_STL          <- as.Date(descomp_STL$Fecha)

# ============================================================
# GRÁFICO COMPARATIVO FINAL
# Los cinco métodos sobre la misma serie
# ============================================================

plot(Desempleo_ts,
     main = "Comparación de métodos de estimación de tendencia",
     ylab = "Desempleo (%)",
     xlab = "Año",
     col  = "gray70",
     lwd  = 1)

# M1: decompose
lines(tendencia_decompose,             col = "black",     lwd = 2)

# M2: Boxcar
lines(tendencia_boxcar,                col = "orange",    lwd = 2)

# M3: Kernel (bandwidth = 1 año)
lines(tendencia_kernel_1$x,
      tendencia_kernel_1$y,            col = "red",       lwd = 2)

# M4: LOWESS (span = 0.25)
lines(fechas, tendencia_lowess_025$y,  col = "blue",      lwd = 2)

# M5: STL
lines(fechas_STL, tendencia_STL,       col = "darkgreen", lwd = 2)

legend("topright",
       legend = c("Serie original",
                  "M1: decompose",
                  "M2: Boxcar ",
                  "M3: Kernel (bw=1 año)",
                  "M4: LOWESS (span=0.25)",
                  "M5: STL (robusto)"),
       col    = c("gray70", "black", "orange", "red", "blue", "darkgreen"),
       lwd    = c(1, 2, 2, 2, 2, 2),
       cex    = 0.80)

# --- Extraer tendencia STL y construir data frame de trabajo ---
# descomp_STL ya contiene las columnas: Fecha, Desempleo, trend, season_year, remainder

df_stl <- descomp_STL %>%
  as_tibble() %>%
  mutate(
    Fecha         = as.Date(Fecha),
    Sin_tendencia = Desempleo - trend      # Serie original menos la tendencia STL
  )

# Verificar (las primeras filas tendrán NA en trend → heredados del STL)
head(df_stl %>% select(Fecha, Desempleo, trend, Sin_tendencia), 12)
## # A tibble: 12 × 4
##    Fecha      Desempleo trend Sin_tendencia
##    <date>         <dbl> <dbl>         <dbl>
##  1 2001-01-01      16.6  14.5      2.10    
##  2 2001-02-01      17.4  14.6      2.86    
##  3 2001-03-01      15.8  14.6      1.18    
##  4 2001-04-01      14.5  14.7     -0.178   
##  5 2001-05-01      14.0  14.7     -0.714   
##  6 2001-06-01      15.3  14.8      0.508   
##  7 2001-07-01      15.2  14.9      0.325   
##  8 2001-08-01      14.9  14.9      0.000777
##  9 2001-09-01      14.1  15.0     -0.892   
## 10 2001-10-01      14.5  15.0     -0.490   
## 11 2001-11-01      13.7  15.1     -1.38    
## 12 2001-12-01      13.6  15.1     -1.51
# ============================================================
# GRÁFICO 1: Serie original
# ============================================================

p_original <- ggplot(df_stl, aes(x = Fecha)) +
  geom_line(aes(y = Desempleo), color = "steelblue", linewidth = 0.7) +
  geom_line(aes(y = trend),     color = "firebrick", linewidth = 1.2, na.rm = TRUE) +
  labs(
    title    = "Serie original con tendencia STL",
    subtitle = "Azul = serie | Rojo = tendencia STL",
    x        = NULL,
    y        = "Tasa de desempleo (%)"
  ) +
  theme_minimal(base_size = 12) +
  theme(plot.title = element_text(face = "bold"))

# ============================================================
# GRÁFICO 2: Serie sin tendencia (residuo + estacionalidad)
# ============================================================

p_sin_tend <- ggplot(df_stl %>% filter(!is.na(Sin_tendencia)),
                     aes(x = Fecha, y = Sin_tendencia)) +
  geom_line(color = "darkorange", linewidth = 0.7) +
  geom_hline(yintercept = 0, linetype = "dashed",
             color = "gray50", linewidth = 0.5) +
  labs(
    title    = "Serie sin tendencia (Desempleo − tendencia STL)",
    subtitle = "Conserva la estacionalidad y el residuo",
    x        = "Año",
    y        = "Desempleo − tendencia (%)"
  ) +
  theme_minimal(base_size = 12) +
  theme(plot.title = element_text(face = "bold"))

# ============================================================
# PANEL FINAL: los dos gráficos apilados
# ============================================================

p_original / p_sin_tend

# --- Construir Sin_tendencia_ts (faltaba en el código anterior) ---
df_stl_clean <- df_stl %>% filter(!is.na(Sin_tendencia))

Sin_tendencia_ts <- ts(
  df_stl_clean$Sin_tendencia,
  start     = c(as.integer(format(min(df_stl_clean$Fecha), "%Y")),
                as.integer(format(min(df_stl_clean$Fecha), "%m"))),
  frequency = 12
)

# --- Calcular ACF de ambas series ---
acf_original <- acf(Desempleo_ts,     lag.max = 48, plot = FALSE)
acf_sin_tend <- acf(Sin_tendencia_ts, lag.max = 48, plot = FALSE)

# --- Banda de confianza al 95% ---
n     <- length(Sin_tendencia_ts)
banda <- qnorm(0.975) / sqrt(n)

# --- Data frame unificado ---
df_acf <- bind_rows(
  data.frame(
    rezago = as.numeric(acf_original$lag),
    acf    = as.numeric(acf_original$acf),
    serie  = "Con tendencia"
  ),
  data.frame(
    rezago = as.numeric(acf_sin_tend$lag),
    acf    = as.numeric(acf_sin_tend$acf),
    serie  = "Sin tendencia (STL)"
  )
)

# --- Gráfico comparativo en un solo panel ---
ggplot(df_acf, aes(x = rezago, y = acf, color = serie)) +
  geom_hline(yintercept = 0,      color = "gray40",    linewidth = 0.4) +
  geom_hline(yintercept =  banda, color = "steelblue",
             linetype = "dashed", linewidth = 0.5) +
  geom_hline(yintercept = -banda, color = "steelblue",
             linetype = "dashed", linewidth = 0.5) +
  geom_segment(aes(xend = rezago, yend = 0),
               linewidth = 0.7, alpha = 0.8) +
  geom_point(size = 1.5, alpha = 0.9) +
  facet_wrap(~ serie, ncol = 1) +
  scale_color_manual(values = c("Con tendencia"       = "steelblue",
                                "Sin tendencia (STL)" = "darkorange")) +
  scale_x_continuous(breaks = seq(0, 48, by = 6)) +
  labs(
    title    = "Comparación ACF — Serie original vs sin tendencia (STL)",
    subtitle = "Líneas punteadas azules: bandas de confianza al 95%",
    x        = "Rezago (meses)",
    y        = "Autocorrelación"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "none",
    plot.title      = element_text(face = "bold"),
    strip.text      = element_text(face = "bold", size = 11)
  )

# ============================================================
# OPCIÓN 2: PACF en un solo gráfico comparativo con ggplot2
# (mismo enfoque que usamos para el ACF)
# ============================================================

# --- Calcular PACF de ambas series ---
pacf_original <- pacf(Desempleo_ts,     lag.max = 48, plot = FALSE)
pacf_sin_tend <- pacf(Sin_tendencia_ts, lag.max = 48, plot = FALSE)

# --- Banda de confianza al 95% (±1.96/√n) ---
n     <- length(Sin_tendencia_ts)
banda <- qnorm(0.975) / sqrt(n)

# --- Data frame unificado ---
# Nota: el PACF no incluye el rezago 0 (a diferencia del ACF)
df_pacf <- bind_rows(
  data.frame(
    rezago = as.numeric(pacf_original$lag),
    pacf   = as.numeric(pacf_original$acf),
    serie  = "Con tendencia"
  ),
  data.frame(
    rezago = as.numeric(pacf_sin_tend$lag),
    pacf   = as.numeric(pacf_sin_tend$acf),
    serie  = "Sin tendencia (STL)"
  )
)

# --- Gráfico comparativo en un solo panel ---
ggplot(df_pacf, aes(x = rezago, y = pacf, color = serie)) +
  geom_hline(yintercept = 0,      color = "gray40",    linewidth = 0.4) +
  geom_hline(yintercept =  banda, color = "steelblue",
             linetype = "dashed", linewidth = 0.5) +
  geom_hline(yintercept = -banda, color = "steelblue",
             linetype = "dashed", linewidth = 0.5) +
  geom_segment(aes(xend = rezago, yend = 0),
               linewidth = 0.7, alpha = 0.8) +
  geom_point(size = 1.5, alpha = 0.9) +
  facet_wrap(~ serie, ncol = 1) +
  scale_color_manual(values = c("Con tendencia"       = "steelblue",
                                "Sin tendencia (STL)" = "darkorange")) +
  scale_x_continuous(breaks = seq(0, 48, by = 6)) +
  labs(
    title    = "Comparación PACF — Serie original vs sin tendencia (STL)",
    subtitle = "Líneas punteadas azules: bandas de confianza al 95%",
    x        = "Rezago (meses)",
    y        = "Autocorrelación Parcial"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "none",
    plot.title      = element_text(face = "bold"),
    strip.text      = element_text(face = "bold", size = 11)
  )

# ============================================================
# MÉTODO 18: GRÁFICOS DE DISPERSIÓN DE RETARDOS
# Aplicado a la serie SIN tendencia (STL)
# Réplica de los chunks "dispersión retardos_1" y
# "dispersion con TSstudio" del profesor
# ============================================================

# ============================================================
# OPCIÓN 4: Ampliar a 24 rezagos (2 años)
# Útil para detectar si hay patrón estacional en los rezagos
# múltiplos de 12 (rezago 12 y 24 son los más importantes
# para una serie mensual con posible ciclo anual)
# ============================================================

par(mar = c(2, 2, 2, 2))

astsa::lag1.plot(Sin_tendencia_ts,
                 max.lag = 24,
                 corr    = FALSE)

# ============================================================
# LECTURA DE RESULTADOS — qué buscar en cada panel
# ============================================================

# RELACIÓN LINEAL POSITIVA: nube de puntos con pendiente
# ascendente → la serie en t está positivamente correlacionada
# con su valor k períodos atrás.

# RELACIÓN LINEAL NEGATIVA: nube con pendiente descendente →
# correlación negativa a ese rezago.

# SIN RELACIÓN: nube circular o sin patrón → rezago no
# significativo.

# RELACIÓN NO LINEAL: la curva loess (línea roja) se curva
# pero la nube no sigue una línea recta → dependencia no
# lineal, que el ACF no detecta pero este gráfico sí muestra.

# REZAGOS CLAVE PARA DESEMPLEO MENSUAL:
# - Rezago 1:  memoria de corto plazo (mes anterior)
# - Rezago 12: ciclo anual (mismo mes del año anterior)
# - Rezago 24: ciclo anual de segundo orden
# ============================================================
# MÉTODO 23: MAPA DE CALOR TEMPORAL
# Réplica de los chunks "ciclos1", "ciclos2" y "mapa de calor"
# del profesor, adaptada a la serie de Desempleo Colombia
# ============================================================

# ============================================================
# MAPA DE CALOR 1: Serie completa (2001-2025)
# Réplica del chunk "ciclos1":
# ts_heatmap(USgas, title = "...")
# ============================================================

ts_heatmap(Desempleo_ts,
           title = "Mapa de Calor — Tasa de Desempleo Mensual Colombia (2001-2025)")
# ============================================================
# MAPA DE CALOR 3: Serie sin tendencia (STL)
# Permite ver la estacionalidad pura sin el efecto
# de la tendencia de largo plazo
# ============================================================

ts_heatmap(Sin_tendencia_ts,
           title = "Mapa de Calor — Desempleo sin tendencia STL (2001-2025)")
# --- Construir tibble directamente desde df_stl_clean
# que ya tiene fechas bien formadas como columna Date ---
tbl_sin_tendencia <- df_stl_clean %>%
  select(Fecha, Sin_tendencia) %>%
  rename(sin_tend = Sin_tendencia) %>%
  mutate(Fecha = as.Date(Fecha))

head(tbl_sin_tendencia)
## # A tibble: 6 × 2
##   Fecha      sin_tend
##   <date>        <dbl>
## 1 2001-01-01    2.10 
## 2 2001-02-01    2.86 
## 3 2001-03-01    1.18 
## 4 2001-04-01   -0.178
## 5 2001-05-01   -0.714
## 6 2001-06-01    0.508
class(tbl_sin_tendencia$Fecha)   # debe decir "Date"
## [1] "Date"
# ============================================================
# BLOQUE 2: BOXPLOT POR TRIMESTRE
# ============================================================

tbl_sin_tendencia %>%
  plot_seasonal_diagnostics(
    .date_var    = Fecha,
    .value       = sin_tend,
    .feature_set = c("quarter"),
    .geom        = "boxplot",
    .interactive = FALSE,
    .title       = "Diagnóstico estacional por trimestre — Desempleo sin tendencia (STL)"
  )

# ============================================================
# BLOQUE 3: BOXPLOT POR AÑO
# ============================================================

tbl_sin_tendencia %>%
  plot_seasonal_diagnostics(
    .date_var    = Fecha,
    .value       = sin_tend,
    .feature_set = c("year"),
    .geom        = "boxplot",
    .interactive = FALSE,
    .title       = "Diagnóstico por año — Desempleo sin tendencia (STL)"
  )

# ============================================================
# MÉTODO 27: SUBSERIES Y GRÁFICOS DE TEMPORADA
# Réplica del chunk "subseries" del profesor:
# UKgrid_tstbl %>% gg_subseries(ND, period=24)
# UKgrid_tstbl %>% gg_season(ND, period=24)
# Adaptada a Desempleo SIN tendencia (STL)

# ============================================================
# PASO PREVIO: construir tsibble desde df_stl_clean
# feasts necesita un objeto tsibble con índice yearmonth
# ============================================================

tsbl_sin_tendencia <- df_stl_clean %>%
  select(Fecha, Sin_tendencia) %>%
  mutate(Fecha = yearmonth(Fecha)) %>%
  as_tsibble(index = Fecha)

# Verificar
tsbl_sin_tendencia
## # A tsibble: 299 x 2 [1M]
##        Fecha Sin_tendencia
##        <mth>         <dbl>
##  1 2001 ene.      2.10    
##  2 2001 feb.      2.86    
##  3 2001 mar.      1.18    
##  4 2001 abr.     -0.178   
##  5 2001 may.     -0.714   
##  6 2001 jun.      0.508   
##  7 2001 jul.      0.325   
##  8 2001 ago.      0.000777
##  9 2001 sep.     -0.892   
## 10 2001 oct.     -0.490   
## # ℹ 289 more rows
class(tsbl_sin_tendencia)
## [1] "tbl_ts"     "tbl_df"     "tbl"        "data.frame"
# ============================================================
# BLOQUE 1: GRÁFICO DE SUBSERIES POR MES
# Réplica de:
# UKgrid_tstbl %>% gg_subseries(ND, period=24)
# Muestra la evolución de cada mes a lo largo de todos
# los años con la media horizontal de ese mes
# period=12 porque la serie es mensual (ciclo anual)
# ============================================================

tsbl_sin_tendencia %>%
  gg_subseries(Sin_tendencia, period = 12) +
  labs(
    title    = "Subseries mensuales — Desempleo sin tendencia (STL)",
    subtitle = "Línea azul horizontal = media de cada mes a lo largo de todos los años",
    y        = "Desempleo − tendencia (%)",
    x        = "Año"
  ) +
  geom_hline(yintercept = 0,
             color      = "firebrick",
             linetype   = "dashed",
             linewidth  = 0.5) +
  theme_minimal(base_size = 11) +
  theme(plot.title = element_text(face = "bold"))

# ============================================================
# BLOQUE 4 CORREGIDO: SUBSERIES POR TRIMESTRE
# ============================================================

# --- Construir el resumen trimestral desde df_stl_clean
# directamente, sin pasar por el tsibble mensual ---

tsbl_sin_tend_trim <- df_stl_clean %>%
  select(Fecha, Sin_tendencia) %>%
  mutate(
    trimestre = yearquarter(Fecha)   # asignar trimestre a cada fila
  ) %>%
  group_by(trimestre) %>%
  summarise(
    sin_tend_trim = mean(Sin_tendencia, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  as_tsibble(index = trimestre)      # ahora sí hay un valor único por trimestre

# Verificar que no hay duplicados
duplicates(tsbl_sin_tend_trim, index = trimestre)
## # A tibble: 0 × 2
## # ℹ 2 variables: trimestre <qtr>, sin_tend_trim <dbl>
# Verificar estructura
tsbl_sin_tend_trim
## # A tsibble: 100 x 2 [1Q]
##    trimestre sin_tend_trim
##        <qtr>         <dbl>
##  1   2001 Q1        2.05  
##  2   2001 Q2       -0.128 
##  3   2001 Q3       -0.189 
##  4   2001 Q4       -1.13  
##  5   2002 Q1        1.18  
##  6   2002 Q2        0.256 
##  7   2002 Q3       -0.255 
##  8   2002 Q4       -0.192 
##  9   2003 Q1       -0.0369
## 10   2003 Q2       -0.798 
## # ℹ 90 more rows
# --- Gráfico de subseries trimestrales ---
tsbl_sin_tend_trim %>%
  gg_subseries(sin_tend_trim) +
  labs(
    title    = "Subseries trimestrales — Desempleo sin tendencia (STL)",
    subtitle = "Línea azul = media de cada trimestre a lo largo de todos los años",
    y        = "Desempleo − tendencia (%)",
    x        = "Año"
  ) +
  geom_hline(yintercept = 0,
             color      = "firebrick",
             linetype   = "dashed",
             linewidth  = 0.5) +
  theme_minimal(base_size = 11) +
  theme(plot.title = element_text(face = "bold"))

# ============================================================
# MÉTODO 28: REGRESIÓN TRIGONOMÉTRICA PARA ESTIMACIÓN DE CICLOS
# Réplica de los chunks "regresión estimar ciclo_1",
# "regresión estimar ciclo_2" y "regresión estimar ciclo_3"
# del profesor, adaptada a Desempleo SIN tendencia (STL)
# ============================================================

# ============================================================
# PASO PREVIO: definir el vector de tiempo y la serie
# ============================================================

n_obs  <- length(Sin_tendencia_ts)
tiempo <- 1:n_obs          # índice entero 1, 2, ..., n
y      <- as.numeric(Sin_tendencia_ts)

# ============================================================
# BLOQUE 1: VISUALIZACIÓN INICIAL
# Réplica del chunk "regresión estimar ciclo_1":
# plot.ts(cs), plot.ts(cs+w), plot.ts(cs+5*w)
# Permite decidir visualmente qué frecuencias explorar
# Para desempleo mensual los candidatos naturales son:
#   ω = 1/12  → ciclo anual      (periodo = 12 meses)
#   ω = 1/6   → ciclo semestral  (periodo = 6 meses)
#   ω = 1/60  → ciclo de 5 años  (periodo = 60 meses)
# ============================================================

par(mfrow = c(3, 1), mar = c(3, 4, 3, 2))

# --- Gráfico 1: Ciclo 12 meses ---
plot.ts(y,
        main = "Serie sin tendencia — Desempleo Colombia",
        ylab = "Desempleo − tendencia (%)",
        col  = "steelblue")
lines(2 * cos(2 * pi * tiempo / 12), col = "firebrick", lwd = 2)
legend("topright",
       legend = c("Serie", "Ciclo 12 meses"),
       col    = c("steelblue", "firebrick"),
       lwd    = c(1, 2), cex = 0.8)

# --- Gráfico 2: Ciclo 6 meses ---
plot.ts(y,
        main = "Serie sin tendencia — Desempleo Colombia",
        ylab = "Desempleo − tendencia (%)",
        col  = "steelblue")
lines(2 * cos(2 * pi * tiempo / 6), col = "darkgreen", lwd = 2)
legend("topright",
       legend = c("Serie", "Ciclo 6 meses"),
       col    = c("steelblue", "darkgreen"),
       lwd    = c(1, 2), cex = 0.8)

# --- Gráfico 3: Ciclo 60 meses ---
plot.ts(y,
        main = "Serie sin tendencia — Desempleo Colombia",
        ylab = "Desempleo − tendencia (%)",
        col  = "steelblue")
lines(2 * cos(2 * pi * tiempo / 60), col = "darkorange", lwd = 2)
legend("topright",
       legend = c("Serie", "Ciclo 60 meses"),
       col    = c("steelblue", "darkorange"),
       lwd    = c(1, 2), cex = 0.8)

par(mfrow = c(1, 1))

# ============================================================
# BLOQUE 2: REGRESIÓN TRIGONOMÉTRICA — CICLO ANUAL (ω = 1/12)
# Réplica del chunk "regresión estimar ciclo_2":
# z1 = cos(2*pi*1:500/50)
# z2 = sin(2*pi*1:500/50)
# summary(fit <- lm(x~0+z1+z2))
# ============================================================

# --- Construir las covariables trigonométricas ---
# Para ω = 1/12 (ciclo anual, periodo = 12 meses)
z1_12 <- cos(2 * pi * tiempo / 12)
z2_12 <- sin(2 * pi * tiempo / 12)

# --- Ajustar el modelo sin intercepto ---
fit_12 <- lm(y ~ 0 + z1_12 + z2_12)
summary(fit_12)
## 
## Call:
## lm(formula = y ~ 0 + z1_12 + z2_12)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -2.6610 -0.5885 -0.1394  0.4750  8.8002 
## 
## Coefficients:
##       Estimate Std. Error t value Pr(>|t|)    
## z1_12 -0.08534    0.10832  -0.788    0.431    
## z2_12  0.75220    0.10796   6.967 2.09e-11 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.322 on 297 degrees of freedom
## Multiple R-squared:  0.142,  Adjusted R-squared:  0.1362 
## F-statistic: 24.58 on 2 and 297 DF,  p-value: 1.321e-10
# --- Amplitud estimada del ciclo anual ---
# A² = β1² + β2²  →  A = √(β1² + β2²)
beta1_12 <- coef(fit_12)["z1_12"]
beta2_12 <- coef(fit_12)["z2_12"]
A_12     <- sqrt(beta1_12^2 + beta2_12^2)
cat("Amplitud estimada ciclo anual (12 meses):", round(A_12, 4), "\n")
## Amplitud estimada ciclo anual (12 meses): 0.757
# --- Fase estimada ---
fase_12 <- atan2(-beta2_12, beta1_12)
cat("Fase estimada (radianes):", round(fase_12, 4), "\n")
## Fase estimada (radianes): -1.6838
# ============================================================
# BLOQUE 3: REGRESIÓN TRIGONOMÉTRICA — CICLO SEMESTRAL (ω = 1/6)
# ============================================================

z1_6 <- cos(2 * pi * tiempo / 6)
z2_6 <- sin(2 * pi * tiempo / 6)

fit_6 <- lm(y ~ 0 + z1_6 + z2_6)
summary(fit_6)
## 
## Call:
## lm(formula = y ~ 0 + z1_6 + z2_6)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -1.8966 -0.6578 -0.1504  0.6539  9.9139 
## 
## Coefficients:
##      Estimate Std. Error t value Pr(>|t|)    
## z1_6  0.01215    0.10793   0.113     0.91    
## z2_6  0.77337    0.10757   7.190 5.29e-12 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.317 on 297 degrees of freedom
## Multiple R-squared:  0.1483, Adjusted R-squared:  0.1425 
## F-statistic: 25.85 on 2 and 297 DF,  p-value: 4.463e-11
beta1_6 <- coef(fit_6)["z1_6"]
beta2_6 <- coef(fit_6)["z2_6"]
A_6     <- sqrt(beta1_6^2 + beta2_6^2)
cat("Amplitud estimada ciclo semestral (6 meses):", round(A_6, 4), "\n")
## Amplitud estimada ciclo semestral (6 meses): 0.7735
# ============================================================
# BLOQUE 4: REGRESIÓN TRIGONOMÉTRICA — CICLO LARGO (ω = 1/60)
# Ciclo de 5 años, posible ciclo económico
# ============================================================

z1_60 <- cos(2 * pi * tiempo / 60)
z2_60 <- sin(2 * pi * tiempo / 60)

fit_60 <- lm(y ~ 0 + z1_60 + z2_60)
summary(fit_60)
## 
## Call:
## lm(formula = y ~ 0 + z1_60 + z2_60)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -2.1353 -0.6912 -0.1154  0.5592  8.9356 
## 
## Coefficients:
##       Estimate Std. Error t value Pr(>|t|)  
## z1_60   0.2958     0.1154   2.564   0.0108 *
## z2_60  -0.1416     0.1150  -1.231   0.2192  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.408 on 297 degrees of freedom
## Multiple R-squared:  0.02652,    Adjusted R-squared:  0.01996 
## F-statistic: 4.045 on 2 and 297 DF,  p-value: 0.01849
beta1_60 <- coef(fit_60)["z1_60"]
beta2_60 <- coef(fit_60)["z2_60"]
A_60     <- sqrt(beta1_60^2 + beta2_60^2)
cat("Amplitud estimada ciclo largo (60 meses):", round(A_60, 4), "\n")
## Amplitud estimada ciclo largo (60 meses): 0.328
# ============================================================
# BLOQUE 5: MODELO COMBINADO — ciclo anual + semestral
# Réplica de la idea del profesor de combinar frecuencias:
# x_t = β1·cos(2πt/12) + β2·sin(2πt/12)
#      + β3·cos(2πt/6)  + β4·sin(2πt/6) + w_t
# ============================================================

fit_combinado <- lm(y ~ 0 + z1_12 + z2_12 + z1_6 + z2_6)
summary(fit_combinado)
## 
## Call:
## lm(formula = y ~ 0 + z1_12 + z2_12 + z1_6 + z2_6)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -2.6494 -0.3898 -0.0828  0.3997  9.4643 
## 
## Coefficients:
##       Estimate Std. Error t value Pr(>|t|)    
## z1_12 -0.08527    0.09886  -0.863    0.389    
## z2_12  0.75220    0.09852   7.635 3.16e-13 ***
## z1_6   0.01157    0.09886   0.117    0.907    
## z2_6   0.77337    0.09852   7.850 7.77e-14 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.207 on 295 degrees of freedom
## Multiple R-squared:  0.2903, Adjusted R-squared:  0.2807 
## F-statistic: 30.17 on 4 and 295 DF,  p-value: < 2.2e-16
cat("R² modelo combinado (anual + semestral):",
    round(summary(fit_combinado)$r.squared, 4), "\n")
## R² modelo combinado (anual + semestral): 0.2903
# ============================================================
# BLOQUE 6: GRÁFICO COMPARATIVO — serie vs ajuste
# Réplica del chunk "regresión estimar ciclo_3":
# plot.ts(x, col=8)
# lines(fitted(fit), col=2)
# ============================================================

par(mfrow = c(3, 1), mar = c(3, 4, 3, 2))

# --- Ciclo anual ---
plot.ts(y, col = "gray60",
        main = "Serie vs ajuste — Ciclo anual (12 meses)",
        ylab = "Desempleo − tendencia (%)")
lines(fitted(fit_12), col = "firebrick", lwd = 2)
legend("topright",
       legend = c("Serie sin tendencia", "Ajuste trigonométrico"),
       col    = c("gray60", "firebrick"), lwd = c(1, 2), cex = 0.8)

# --- Ciclo semestral ---
plot.ts(y, col = "gray60",
        main = "Serie vs ajuste — Ciclo semestral (6 meses)",
        ylab = "Desempleo − tendencia (%)")
lines(fitted(fit_6), col = "darkgreen", lwd = 2)
legend("topright",
       legend = c("Serie sin tendencia", "Ajuste trigonométrico"),
       col    = c("gray60", "darkgreen"), lwd = c(1, 2), cex = 0.8)

# --- Modelo combinado ---
plot.ts(y, col = "gray60",
        main = "Serie vs ajuste — Modelo combinado (12 + 6 meses)",
        ylab = "Desempleo − tendencia (%)")
lines(fitted(fit_combinado), col = "darkorange", lwd = 2)
legend("topright",
       legend = c("Serie sin tendencia", "Ajuste combinado"),
       col    = c("gray60", "darkorange"), lwd = c(1, 2), cex = 0.8)

par(mfrow = c(1, 1))

# ============================================================
# BLOQUE 7: TABLA RESUMEN DE AMPLITUDES Y R²
# Permite comparar cuál ciclo explica más varianza
# ============================================================

resumen_ciclos <- data.frame(
  Ciclo      = c("Anual (12 meses)",
                 "Semestral (6 meses)",
                 "Largo (60 meses)",
                 "Combinado (12+6)"),
  Periodo    = c(12, 6, 60, NA),
  Amplitud   = c(round(A_12, 4),
                 round(A_6,  4),
                 round(A_60, 4),
                 NA),
  R_cuadrado = c(round(summary(fit_12)$r.squared,        4),
                 round(summary(fit_6)$r.squared,         4),
                 round(summary(fit_60)$r.squared,        4),
                 round(summary(fit_combinado)$r.squared, 4))
)

print(resumen_ciclos)
##                     Ciclo Periodo Amplitud R_cuadrado
## z1_12    Anual (12 meses)      12   0.7570     0.1420
## z1_6  Semestral (6 meses)       6   0.7735     0.1483
## z1_60    Largo (60 meses)      60   0.3280     0.0265
##          Combinado (12+6)      NA       NA     0.2903
# ============================================================
# MÉTODOS 29 Y 30: PERIODOGRAMA Y PERIODOGRAMA SUAVIZADO
# Réplica de los chunks "Periodograma_1", "Periodograma_2",
# "espectral_soi" y "espectral_rec" del profesor,
# adaptada a Desempleo SIN tendencia (STL)
# ============================================================

# ============================================================
# BLOQUE 1: PERIODOGRAMA CRUDO — función spectrum()
# Réplica del chunk "Periodograma_1":
# spectrum(x, log='no')
# ============================================================

par(mfrow = c(1, 1), mar = c(4, 4, 3, 2))

per_crudo <- spectrum(y,
                      log  = "no",
                      main = "Periodograma — Desempleo sin tendencia (STL)",
                      ylab = "Densidad espectral",
                      xlab = "Frecuencia (ciclos por mes)")

# --- Identificar la frecuencia dominante ---
ubicacion_max <- which.max(per_crudo$spec)

frec_max    <- per_crudo$freq[ubicacion_max]
periodo_max <- 1 / frec_max

cat("Frecuencia donde se maximiza el periodograma:",
    round(frec_max, 6), "\n")
## Frecuencia donde se maximiza el periodograma: 0.166667
cat("Periodo correspondiente (meses):",
    round(periodo_max, 2), "\n")
## Periodo correspondiente (meses): 6
cat("Periodo correspondiente (años):",
    round(periodo_max / 12, 2), "\n")
## Periodo correspondiente (años): 0.5
# Marcar la frecuencia dominante en el gráfico
abline(v   = frec_max,
       lty = 2,
       col = "firebrick",
       lwd = 2)

# --- Identificar la segunda frecuencia más importante ---
n_spec          <- length(per_crudo$spec)
valor_seg       <- sort(per_crudo$spec,
                        partial = n_spec - 1)[n_spec - 1]
ubicacion_seg   <- which(per_crudo$spec == valor_seg)
frec_seg        <- per_crudo$freq[ubicacion_seg]
periodo_seg     <- 1 / frec_seg

cat("Segunda frecuencia dominante:",
    round(frec_seg, 6), "\n")
## Segunda frecuencia dominante: 0.083333
cat("Periodo correspondiente (meses):",
    round(periodo_seg, 2), "\n")
## Periodo correspondiente (meses): 12
abline(v   = frec_seg,
       lty = 2,
       col = "darkorange",
       lwd = 2)

legend("topright",
       legend = c(
         paste0("Frec. principal: ", round(frec_max, 4),
                " (", round(periodo_max, 1), " meses)"),
         paste0("Frec. secundaria: ", round(frec_seg, 4),
                " (", round(periodo_seg, 1), " meses)")
       ),
       col    = c("firebrick", "darkorange"),
       lty    = 2, lwd = 2, cex = 0.85)

# ============================================================
# BLOQUE 2: PERIODOGRAMA VÍA TRANSFORMADA DE FOURIER (FFT)
# Réplica del chunk "Periodograma_1":
# Px = Mod(fft(x))^2
# ============================================================

Px   <- Mod(fft(y))^2
Freq <- 0:(n_obs - 1) / n_obs

# Solo graficar la mitad útil (hasta la frecuencia Nyquist)
mitad <- 1:(n_obs %/% 2)

plot(Freq[mitad], Px[mitad],
     type = "l",
     main = "Periodograma vía FFT — Desempleo sin tendencia",
     ylab = "Potencia espectral",
     xlab = "Frecuencia (ciclos por mes)",
     col  = "steelblue")

u <- which.max(Px[mitad])
cat("Frecuencia máxima vía FFT:",
    round(Freq[u], 6), "\n")
## Frecuencia máxima vía FFT: 0.167224
cat("Periodo correspondiente (meses):",
    round(1 / Freq[u], 2), "\n")
## Periodo correspondiente (meses): 5.98
abline(v   = Freq[u],
       lty = 2,
       col = "firebrick",
       lwd = 2)

# ============================================================
# BLOQUE 3: PERIODOGRAMA CON mvspec() de astsa
# Réplica del chunk "espectral_soi":
# soi.per = astsa::mvspec(soi, log="no")
# Incluye bandas de confianza al 95%
# ============================================================

par(mar = c(4, 4, 3, 2))

per_mvspec <- astsa::mvspec(y,
                            log  = "no",
                            main = "Periodograma mvspec — Desempleo sin tendencia (STL)")

# --- Frecuencia dominante con mvspec ---
ubicacion_mv <- which.max(per_mvspec$spec)
frec_mv      <- per_mvspec$freq[ubicacion_mv]
periodo_mv   <- 1 / frec_mv

cat("Frecuencia dominante (mvspec):",
    round(frec_mv, 6), "\n")
## Frecuencia dominante (mvspec): 0.166667
cat("Periodo correspondiente (meses):",
    round(periodo_mv, 2), "\n")
## Periodo correspondiente (meses): 6
# --- Segunda frecuencia (mvspec) ---
n_mv          <- length(per_mvspec$spec)
valor_seg_mv  <- sort(per_mvspec$spec,
                      partial = n_mv - 1)[n_mv - 1]
ubicacion_seg_mv <- which(per_mvspec$spec == valor_seg_mv)
frec_seg_mv      <- per_mvspec$freq[ubicacion_seg_mv]
periodo_seg_mv   <- 1 / frec_seg_mv

cat("Segunda frecuencia dominante (mvspec):",
    round(frec_seg_mv, 6), "\n")
## Segunda frecuencia dominante (mvspec): 0.083333
cat("Periodo correspondiente (meses):",
    round(periodo_seg_mv, 2), "\n")
## Periodo correspondiente (meses): 12
abline(v   = frec_mv,
       lty = 2, col = "firebrick", lwd = 2)
abline(v   = frec_seg_mv,
       lty = 2, col = "darkorange", lwd = 2)

# ============================================================
# MÉTODO 30: PERIODOGRAMA SUAVIZADO
# Réplica del chunk "Periodograma_2":
# spectrum(x, log='no', span=5)
# spectrum(x, log='no', span=c(5,5))
# El argumento span promedia frecuencias vecinas
# para reducir el ruido del periodograma crudo
# ============================================================

par(mfrow = c(2, 2), mar = c(4, 4, 3, 2))

# --- span = 5 (suavizado leve) ---
spectrum(y,
         log  = "no",
         span = 5,
         main = "Periodograma suavizado — span = 5",
         ylab = "Densidad espectral",
         xlab = "Frecuencia",
         col  = "steelblue")
abline(v   = frec_max,
       lty = 2, col = "firebrick", lwd = 1.5)

# --- span = c(5,5) (dos pasadas de suavizado) ---
spectrum(y,
         log  = "no",
         span = c(5, 5),
         main = "Periodograma suavizado — span = c(5,5)",
         ylab = "Densidad espectral",
         xlab = "Frecuencia",
         col  = "steelblue")
abline(v   = frec_max,
       lty = 2, col = "firebrick", lwd = 1.5)

# --- span = c(3,3) (suavizado moderado) ---
spectrum(y,
         log  = "no",
         span = c(3, 3),
         main = "Periodograma suavizado — span = c(3,3)",
         ylab = "Densidad espectral",
         xlab = "Frecuencia",
         col  = "steelblue")
abline(v   = frec_max,
       lty = 2, col = "firebrick", lwd = 1.5)

# --- span = c(9,9) (suavizado fuerte) ---
spectrum(y,
         log  = "no",
         span = c(9, 9),
         main = "Periodograma suavizado — span = c(9,9)",
         ylab = "Densidad espectral",
         xlab = "Frecuencia",
         col  = "steelblue")
abline(v   = frec_max,
       lty = 2, col = "firebrick", lwd = 1.5)

par(mfrow = c(1, 1))

# ============================================================
# BLOQUE 5: TABLA RESUMEN DE FRECUENCIAS DETECTADAS
# Réplica del estilo de interpretación del profesor
# en los chunks "espectral_soi" y "espectral_rec"
# ============================================================

# Extraer las 5 frecuencias más importantes
top5_idx  <- order(per_crudo$spec, decreasing = TRUE)[1:5]

resumen_frecuencias <- data.frame(
  Rango        = 1:5,
  Frecuencia   = round(per_crudo$freq[top5_idx], 6),
  Periodo_mes  = round(1 / per_crudo$freq[top5_idx], 2),
  Periodo_anio = round(1 / per_crudo$freq[top5_idx] / 12, 2),
  Potencia     = round(per_crudo$spec[top5_idx], 4)
)

print(resumen_frecuencias)
##   Rango Frecuencia Periodo_mes Periodo_anio Potencia
## 1     1   0.166667           6         0.50  40.2163
## 2     2   0.083333          12         1.00  39.4192
## 3     3   0.250000           4         0.33  16.1453
## 4     4   0.333333           3         0.25  15.8235
## 5     5   0.013333          75         6.25   9.5824
# ============================================================
# MÉTODO 31: ANÁLISIS DE ESTACIONALIDAD CON VARIABLES DUMMY
# Réplica del chunk "Dummy Est" del profesor,
# adaptada a Desempleo SIN tendencia (STL)
# ============================================================


# ============================================================
# BLOQUE 1: CONSTRUCCIÓN DE LA MATRIZ DE DUMMIES MENSUALES
# Réplica del chunk "Dummy Est":
# s=4, l=diag(s), X=matrix(rep(t(l),T/s),...)
# Aquí s=12 porque la serie es mensual
# ============================================================

s     <- 12                          # número de estaciones (meses)
T_obs <- length(Sin_tendencia_ts)    # número total de observaciones

# --- Construir matriz de dummies estacionales ---
# Cada columna indica si la observación pertenece a ese mes
# (1=enero, 2=febrero, ..., 12=diciembre)
identidad <- diag(s)
X_dummy   <- matrix(
  rep(t(identidad), ceiling(T_obs / s)),
  ncol   = ncol(identidad),
  byrow  = TRUE
)[1:T_obs, ]

# Asignar nombres de columnas
colnames(X_dummy) <- month.abb

# Verificar estructura
cat("Dimensión de la matriz de dummies:", dim(X_dummy), "\n")
## Dimensión de la matriz de dummies: 299 12
head(X_dummy, 14)
##       Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
##  [1,]   1   0   0   0   0   0   0   0   0   0   0   0
##  [2,]   0   1   0   0   0   0   0   0   0   0   0   0
##  [3,]   0   0   1   0   0   0   0   0   0   0   0   0
##  [4,]   0   0   0   1   0   0   0   0   0   0   0   0
##  [5,]   0   0   0   0   1   0   0   0   0   0   0   0
##  [6,]   0   0   0   0   0   1   0   0   0   0   0   0
##  [7,]   0   0   0   0   0   0   1   0   0   0   0   0
##  [8,]   0   0   0   0   0   0   0   1   0   0   0   0
##  [9,]   0   0   0   0   0   0   0   0   1   0   0   0
## [10,]   0   0   0   0   0   0   0   0   0   1   0   0
## [11,]   0   0   0   0   0   0   0   0   0   0   1   0
## [12,]   0   0   0   0   0   0   0   0   0   0   0   1
## [13,]   1   0   0   0   0   0   0   0   0   0   0   0
## [14,]   0   1   0   0   0   0   0   0   0   0   0   0
# ============================================================
# BLOQUE 2: AJUSTE DEL MODELO CON DUMMIES
# Réplica del chunk "Dummy Est":
# salida_dummy_ruido = stan_glm(resp~.-1, data=simul)
# Aquí usamos lm() sin intercepto (igual que el profesor)
# El modelo es:
# Y_t = δ1·D1t + δ2·D2t + ... + δ12·D12t + εt
# donde Dit = 1 si la observación t es del mes i
# ============================================================

df_dummy <- data.frame(
  Y      = as.numeric(Sin_tendencia_ts),
  X_dummy
)

# Ajuste sin intercepto (las 12 dummies lo reemplazan)
fit_dummy <- lm(Y ~ . - 1, data = df_dummy)
summary(fit_dummy)
## 
## Call:
## lm(formula = Y ~ . - 1, data = df_dummy)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -2.2452 -0.4339 -0.1157  0.2014  8.9598 
## 
## Coefficients:
##     Estimate Std. Error t value Pr(>|t|)    
## Jan  2.27104    0.22031  10.308  < 2e-16 ***
## Feb  1.26849    0.22031   5.758 2.19e-08 ***
## Mar  0.16881    0.22031   0.766 0.444163    
## Apr  0.43371    0.22031   1.969 0.049957 *  
## May  0.29042    0.22031   1.318 0.188467    
## Jun  0.03254    0.22031   0.148 0.882673    
## Jul  0.47047    0.22031   2.135 0.033567 *  
## Aug -0.04492    0.22031  -0.204 0.838575    
## Sep -0.39547    0.22031  -1.795 0.073689 .  
## Oct -0.90654    0.22031  -4.115 5.07e-05 ***
## Nov -1.21057    0.22031  -5.495 8.63e-08 ***
## Dec -0.75222    0.22485  -3.345 0.000931 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.102 on 287 degrees of freedom
## Multiple R-squared:  0.4246, Adjusted R-squared:  0.4005 
## F-statistic: 17.65 on 12 and 287 DF,  p-value: < 2.2e-16
# --- Extraer coeficientes (efectos estacionales estimados) ---
efectos_estacionales <- coef(fit_dummy)
cat("\nEfectos estacionales estimados por mes:\n")
## 
## Efectos estacionales estimados por mes:
print(round(efectos_estacionales, 4))
##     Jan     Feb     Mar     Apr     May     Jun     Jul     Aug     Sep     Oct 
##  2.2710  1.2685  0.1688  0.4337  0.2904  0.0325  0.4705 -0.0449 -0.3955 -0.9065 
##     Nov     Dec 
## -1.2106 -0.7522
# ============================================================
# BLOQUE 3: GRÁFICO DE LOS EFECTOS ESTACIONALES ESTIMADOS
# Permite ver qué meses tienen desempleo sistemáticamente
# por encima o por debajo de la tendencia
# ============================================================

df_efectos <- data.frame(
  mes    = factor(month.abb, levels = month.abb),
  efecto = as.numeric(efectos_estacionales)
)

ggplot(df_efectos, aes(x = mes, y = efecto,
                       fill = efecto > 0)) +
  geom_col(alpha = 0.85, width = 0.7) +
  geom_hline(yintercept = 0,
             color      = "black",
             linetype   = "dashed",
             linewidth  = 0.6) +
  geom_text(aes(label = round(efecto, 2),
                vjust = ifelse(efecto > 0, -0.4, 1.2)),
            size = 3.2) +
  scale_fill_manual(values = c("TRUE"  = "steelblue",
                               "FALSE" = "firebrick"),
                    guide  = "none") +
  labs(
    title    = "Efectos estacionales estimados — Modelo Dummy",
    subtitle = "Azul = meses con desempleo sobre la tendencia | Rojo = por debajo",
    x        = "Mes",
    y        = "Efecto estacional estimado (%)"
  ) +
  theme_minimal(base_size = 12) +
  theme(plot.title = element_text(face = "bold"))

# ============================================================
# BLOQUE 4 CORREGIDO: SERIE OBSERVADA vs AJUSTE POR DUMMIES
# ============================================================

df_ajuste <- data.frame(
  tiempo    = as.numeric(time(Sin_tendencia_ts)),
  observado = as.numeric(Sin_tendencia_ts),
  ajustado  = as.numeric(fitted(fit_dummy)),
  residuo   = as.numeric(residuals(fit_dummy))
)

par(mfrow = c(2, 1), mar = c(3, 4, 3, 2))

# --- Panel 1: Serie observada vs ajuste ---
plot(df_ajuste$tiempo,
     df_ajuste$observado,
     type = "l",
     col  = "gray60",
     lwd  = 1,
     main = "Serie sin tendencia vs ajuste por dummies estacionales",
     ylab = "Desempleo − tendencia (%)",
     xlab = "Año",
     ylim = range(c(df_ajuste$observado,
                    df_ajuste$ajustado)))

lines(df_ajuste$tiempo,
      df_ajuste$ajustado,
      col = "firebrick",
      lwd = 2)

abline(h   = 0,
       lty = 2,
       col = "black",
       lwd = 0.8)

legend("topright",
       legend = c("Serie sin tendencia", "Ajuste dummies"),
       col    = c("gray60", "firebrick"),
       lwd    = c(1, 2),
       cex    = 0.85)

# --- Panel 2: Residuos ---
plot(df_ajuste$tiempo,
     df_ajuste$residuo,
     type = "l",
     col  = "steelblue",
     lwd  = 1,
     main = "Residuos — Modelo dummy estacional",
     ylab = "Residuo (%)",
     xlab = "Año")

abline(h   = 0,
       lty = 2,
       col = "firebrick",
       lwd = 1)

par(mfrow = c(1, 1))

# ============================================================
# BLOQUE 5: PERIODOGRAMA — serie y residuos del modelo dummy
# ============================================================

par(mfrow = c(2, 1), mar = c(4, 4, 3, 2))

per_dummy <- spectrum(df_ajuste$observado,
                      log  = "no",
                      main = "Periodograma — Serie sin tendencia",
                      ylab = "Densidad espectral",
                      xlab = "Frecuencia")

ubicacion_dummy <- which.max(per_dummy$spec)
frec_dom        <- per_dummy$freq[ubicacion_dummy]

cat("Frecuencia dominante:",       round(frec_dom, 6), "\n")
## Frecuencia dominante: 0.166667
cat("Periodo (meses):",            round(1 / frec_dom, 2), "\n")
## Periodo (meses): 6
abline(v   = frec_dom,
       lty = 2, col = "firebrick", lwd = 2)

per_residuos <- spectrum(df_ajuste$residuo,
                         log  = "no",
                         main = "Periodograma — Residuos del modelo dummy",
                         ylab = "Densidad espectral",
                         xlab = "Frecuencia")

abline(v   = 1/12,
       lty = 2, col = "firebrick", lwd = 1.5)

text(x      = 1/12,
     y      = max(per_residuos$spec) * 0.9,
     labels = "f = 1/12",
     col    = "firebrick",
     cex    = 0.85,
     pos    = 4)

par(mfrow = c(1, 1))

# ============================================================
# BLOQUE 6: RESUMEN FINAL
# ============================================================

cat("\n=== RESUMEN DEL MODELO DUMMY ESTACIONAL ===\n")
## 
## === RESUMEN DEL MODELO DUMMY ESTACIONAL ===
cat("R²:          ", round(summary(fit_dummy)$r.squared,     4), "\n")
## R²:           0.4246
cat("R² ajustado: ", round(summary(fit_dummy)$adj.r.squared, 4), "\n")
## R² ajustado:  0.4005
cat("Frecuencia principal:", round(frec_dom, 6), "\n")
## Frecuencia principal: 0.166667
cat("Periodo:",              round(1 / frec_dom, 2), "meses\n")
## Periodo: 6 meses
coef_tabla  <- summary(fit_dummy)$coefficients
dummies_sig <- coef_tabla[coef_tabla[, 4] < 0.05, ]

cat("\nDummies significativas al 5%:\n")
## 
## Dummies significativas al 5%:
print(round(dummies_sig, 4))
##     Estimate Std. Error t value Pr(>|t|)
## Jan   2.2710     0.2203 10.3085   0.0000
## Feb   1.2685     0.2203  5.7578   0.0000
## Apr   0.4337     0.2203  1.9686   0.0500
## Jul   0.4705     0.2203  2.1355   0.0336
## Oct  -0.9065     0.2203 -4.1149   0.0001
## Nov  -1.2106     0.2203 -5.4949   0.0000
## Dec  -0.7522     0.2249 -3.3454   0.0009
# ============================================================
# PREPARACIÓN: Crear la serie sin tendencia usando STL
# Serie sin tendencia = Serie original − Tendencia STL
# ============================================================

# --- Verificar que los objetos necesarios existen ---
# tendencia_STL  → extraída en el paso anterior
# fechas_STL     → fechas correspondientes a la tendencia STL
# valores        → valores originales de Desempleo_xts

# --- Crear serie sin tendencia ---
# Nota: tendencia_STL tiene la misma longitud que valores (sin NA)
Desempleo_sintend <- valores - tendencia_STL

# --- Crear tibble de la serie sin tendencia ---
tbl_sintend <- tibble(
  Fecha     = fechas_STL,
  Desempleo = Desempleo_sintend
)

# --- Agregar variables de calendario necesarias para Fourier y GAM ---
tbl_sintend <- tbl_sintend %>%
  mutate(
    Mes      = month(Fecha),              # Mes (1 a 12)
    Anio     = year(Fecha),               # Año
    t        = as.numeric(Fecha),         # Índice numérico de fecha
    t_scaled = scale(t)[, 1]             # Índice estandarizado
  )

# --- Verificar ---
str(tbl_sintend)
## tibble [299 × 6] (S3: tbl_df/tbl/data.frame)
##  $ Fecha    : Date[1:299], format: "2001-01-01" "2001-02-01" ...
##  $ Desempleo: num [1:299] 2.104 2.857 1.176 -0.178 -0.714 ...
##  $ Mes      : num [1:299] 1 2 3 4 5 6 7 8 9 10 ...
##  $ Anio     : num [1:299] 2001 2001 2001 2001 2001 ...
##  $ t        : num [1:299] 11323 11354 11382 11413 11443 ...
##  $ t_scaled : num [1:299] -1.72 -1.71 -1.7 -1.69 -1.68 ...
head(tbl_sintend, 12)
## # A tibble: 12 × 6
##    Fecha      Desempleo   Mes  Anio     t t_scaled
##    <date>         <dbl> <dbl> <dbl> <dbl>    <dbl>
##  1 2001-01-01  2.10         1  2001 11323    -1.72
##  2 2001-02-01  2.86         2  2001 11354    -1.71
##  3 2001-03-01  1.18         3  2001 11382    -1.70
##  4 2001-04-01 -0.178        4  2001 11413    -1.69
##  5 2001-05-01 -0.714        5  2001 11443    -1.68
##  6 2001-06-01  0.508        6  2001 11474    -1.67
##  7 2001-07-01  0.325        7  2001 11504    -1.65
##  8 2001-08-01  0.000777     8  2001 11535    -1.64
##  9 2001-09-01 -0.892        9  2001 11566    -1.63
## 10 2001-10-01 -0.490       10  2001 11596    -1.62
## 11 2001-11-01 -1.38        11  2001 11627    -1.61
## 12 2001-12-01 -1.51        12  2001 11657    -1.60
# --- Gráfico de la serie sin tendencia ---
ggplot(tbl_sintend, aes(x = Fecha, y = Desempleo)) +
  geom_line(color = "gray40", linewidth = 0.8) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(
    title    = "Serie sin tendencia (STL) — Tasa de Desempleo",
    subtitle = "Lo que queda son ciclos + estacionalidad + ruido",
    y        = "Desviación respecto a la tendencia (%)",
    x        = "Fecha"
  ) +
  theme_minimal()

# ============================================================
# MÉTODO 34: MODELAMIENTO ESTACIONAL CON SERIES DE FOURIER
# Función: lm() con fourier_vec() de timetk
# Captura la estacionalidad como combinación de senos y cosenos
# K = número de armónicos (mayor K = más detalle estacional)
# ============================================================

# --- Crear variables de Fourier con diferentes armónicos ---
# Para serie mensual el período es 12

tbl_fourier <- tbl_sintend %>%
  mutate(
    # Armónico 1 (K=1): captura el ciclo anual principal
    C1 = fourier_vec(Fecha, period = 12, K = 1, type = "cos"),
    S1 = fourier_vec(Fecha, period = 12, K = 1, type = "sin"),
    # Armónico 2 (K=2): captura variaciones semestrales
    C2 = fourier_vec(Fecha, period = 12, K = 2, type = "cos"),
    S2 = fourier_vec(Fecha, period = 12, K = 2, type = "sin"),
    # Armónico 3 (K=3): captura variaciones trimestrales
    C3 = fourier_vec(Fecha, period = 12, K = 3, type = "cos"),
    S3 = fourier_vec(Fecha, period = 12, K = 3, type = "sin")
  )

# --- Modelo con 1 armónico (K=1) ---
# Captura solo el ciclo anual
modelo_fourier_K1 <- lm(Desempleo ~ C1 + S1,
                        data = tbl_fourier)
summary(modelo_fourier_K1)
## 
## Call:
## lm(formula = Desempleo ~ C1 + S1, data = tbl_fourier)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -2.3997 -0.7507 -0.2358  0.4110  8.4778 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   0.1361     0.0778   1.749   0.0813 .  
## C1           -0.4739     0.1100  -4.308 2.24e-05 ***
## S1            0.4519     0.1100   4.108 5.18e-05 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.345 on 296 degrees of freedom
## Multiple R-squared:  0.1068, Adjusted R-squared:  0.1008 
## F-statistic:  17.7 on 2 and 296 DF,  p-value: 5.489e-08
# --- Modelo con 2 armónicos (K=2) ---
# Captura ciclo anual + variaciones semestrales
modelo_fourier_K2 <- lm(Desempleo ~ C1 + S1 + C2 + S2,
                        data = tbl_fourier)
summary(modelo_fourier_K2)
## 
## Call:
## lm(formula = Desempleo ~ C1 + S1 + C2 + S2, data = tbl_fourier)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -2.4985 -0.7626 -0.2494  0.4031  8.4758 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  0.13612    0.07795   1.746   0.0818 .  
## C1          -0.47551    0.11025  -4.313 2.20e-05 ***
## S1           0.45236    0.11024   4.103 5.28e-05 ***
## C2          -0.08488    0.11011  -0.771   0.4414    
## S2          -0.04934    0.11037  -0.447   0.6551    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.348 on 294 degrees of freedom
## Multiple R-squared:  0.1092, Adjusted R-squared:  0.0971 
## F-statistic: 9.012 on 4 and 294 DF,  p-value: 7.051e-07
# --- Modelo con 3 armónicos (K=3) ---
# Captura ciclo anual + semestral + trimestral
modelo_fourier_K3 <- lm(Desempleo ~ C1 + S1 + C2 + S2 + C3 + S3,
                        data = tbl_fourier)
summary(modelo_fourier_K3)
## 
## Call:
## lm(formula = Desempleo ~ C1 + S1 + C2 + S2 + C3 + S3, data = tbl_fourier)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -2.4532 -0.7636 -0.2731  0.4099  8.3442 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  0.13600    0.07801   1.743   0.0823 .  
## C1          -0.47572    0.11032  -4.312 2.22e-05 ***
## S1           0.45274    0.11032   4.104 5.27e-05 ***
## C2          -0.08371    0.11019  -0.760   0.4481    
## S2          -0.04818    0.11045  -0.436   0.6630    
## C3           0.04643    0.11025   0.421   0.6739    
## S3           0.13184    0.11038   1.194   0.2333    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.349 on 292 degrees of freedom
## Multiple R-squared:  0.1141, Adjusted R-squared:  0.09588 
## F-statistic: 6.267 on 6 and 292 DF,  p-value: 3.284e-06
# --- Comparar los tres modelos por R² ajustado ---
cat("R² ajustado K=1:", round(summary(modelo_fourier_K1)$adj.r.squared, 4), "\n")
## R² ajustado K=1: 0.1008
cat("R² ajustado K=2:", round(summary(modelo_fourier_K2)$adj.r.squared, 4), "\n")
## R² ajustado K=2: 0.0971
cat("R² ajustado K=3:", round(summary(modelo_fourier_K3)$adj.r.squared, 4), "\n")
## R² ajustado K=3: 0.0959
# --- Gráfico comparativo de los tres modelos Fourier ---
tbl_fourier <- tbl_fourier %>%
  mutate(
    ajuste_K1 = fitted(modelo_fourier_K1),
    ajuste_K2 = fitted(modelo_fourier_K2),
    ajuste_K3 = fitted(modelo_fourier_K3)
  )

ggplot(tbl_fourier, aes(x = Fecha)) +
  geom_line(aes(y = Desempleo),  color = "gray50",    linewidth = 0.7,
            alpha = 0.8) +
  geom_line(aes(y = ajuste_K1), color = "red",        linewidth = 1.2) +
  geom_line(aes(y = ajuste_K2), color = "blue",       linewidth = 1.2) +
  geom_line(aes(y = ajuste_K3), color = "darkgreen",  linewidth = 1.2) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "black") +
  labs(
    title    = "Modelamiento estacional con Series de Fourier",
    subtitle = "Rojo=K1 (anual), Azul=K2 (+semestral), Verde=K3 (+trimestral)",
    y        = "Desviación respecto a la tendencia (%)",
    x        = "Fecha"
  ) +
  theme_minimal()

# ============================================================
# MÉTODO 35: MODELAMIENTO ESTACIONAL CON GAM
# Función: gam() del paquete mgcv
# Usa splines cúbicos cíclicos — más flexible que Fourier
# bs="cc" = spline cúbico cíclico (ideal para estacionalidad)
# ============================================================

# --- Agregar variable de mes numérico ---
tbl_gam <- tbl_sintend %>%
  mutate(Mes_num = as.numeric(Mes))

# --- Modelo GAM con spline cíclico sobre el mes ---
# k = número de nodos (máximo 12 para datos mensuales)
# bs = "cc" = cúbico cíclico (el inicio y el fin del año se conectan)
modelo_gam <- gam(Desempleo ~ s(Mes_num, bs = "cc", k = 12),
                  data = tbl_gam)

summary(modelo_gam)
## 
## Family: gaussian 
## Link function: identity 
## 
## Formula:
## Desempleo ~ s(Mes_num, bs = "cc", k = 12)
## 
## Parametric coefficients:
##             Estimate Std. Error t value Pr(>|t|)  
## (Intercept)  0.13845    0.07307   1.895   0.0591 .
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Approximate significance of smooth terms:
##              edf Ref.df     F p-value    
## s(Mes_num) 7.712     10 7.955  <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## R-sq.(adj) =  0.206   Deviance explained = 22.7%
## GCV = 1.6445  Scale est. = 1.5965    n = 299
# --- Ver la curva del efecto estacional estimado ---
plot(modelo_gam,
     main   = "Efecto estacional estimado por GAM",
     xlab   = "Mes",
     ylab   = "Efecto sobre el desempleo (%)",
     shade  = TRUE,        # Muestra banda de confianza
     col    = "blue",
     lwd    = 2)
abline(h = 0, lty = 2, col = "red")

# --- Extraer valores ajustados del GAM ---
tbl_gam <- tbl_gam %>%
  mutate(ajuste_gam = fitted(modelo_gam))

# --- Gráfico GAM sobre la serie sin tendencia ---
ggplot(tbl_gam, aes(x = Fecha)) +
  geom_line(aes(y = Desempleo),  color = "gray50",  linewidth = 0.7) +
  geom_line(aes(y = ajuste_gam), color = "purple",  linewidth = 1.5) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "black") +
  labs(
    title    = "Modelamiento estacional con GAM (spline cíclico)",
    subtitle = "Púrpura = componente estacional estimada por GAM",
    y        = "Desviación respecto a la tendencia (%)",
    x        = "Fecha"
  ) +
  theme_minimal()

# ============================================================
# GRÁFICO COMPARATIVO FINAL: FOURIER vs GAM
# ============================================================

# Unir los ajustes de Fourier K3 y GAM en un solo tibble
tbl_comparacion <- tbl_sintend %>%
  mutate(
    ajuste_fourier = fitted(modelo_fourier_K3),
    ajuste_gam     = fitted(modelo_gam)
  )

ggplot(tbl_comparacion, aes(x = Fecha)) +
  geom_line(aes(y = Desempleo),
            color = "gray60", linewidth = 0.7, alpha = 0.8) +
  geom_line(aes(y = ajuste_fourier),
            color = "darkgreen", linewidth = 1.3) +
  geom_line(aes(y = ajuste_gam),
            color = "purple",    linewidth = 1.3,
            linetype = "dashed") +
  geom_hline(yintercept = 0, linetype = "dotted", color = "black") +
  labs(
    title    = "Comparación: Fourier (K=3) vs GAM — Estacionalidad",
    subtitle = "Verde sólido = Fourier K3 | Púrpura punteado = GAM",
    y        = "Desviación respecto a la tendencia (%)",
    x        = "Fecha"
  ) +
  theme_minimal()

# ============================================================
# TABLA RESUMEN DE BONDAD DE AJUSTE
# ============================================================

cat("=== BONDAD DE AJUSTE — MODELOS ESTACIONALES ===\n\n")
## === BONDAD DE AJUSTE — MODELOS ESTACIONALES ===
cat("Fourier K=1  R² ajustado:", 
    round(summary(modelo_fourier_K1)$adj.r.squared, 4), "\n")
## Fourier K=1  R² ajustado: 0.1008
cat("Fourier K=2  R² ajustado:", 
    round(summary(modelo_fourier_K2)$adj.r.squared, 4), "\n")
## Fourier K=2  R² ajustado: 0.0971
cat("Fourier K=3  R² ajustado:", 
    round(summary(modelo_fourier_K3)$adj.r.squared, 4), "\n")
## Fourier K=3  R² ajustado: 0.0959
cat("GAM          R² ajustado:", 
    round(summary(modelo_gam)$r.sq,                 4), "\n")
## GAM          R² ajustado: 0.2064
# ============================================================
# MÉTODO 36: GRÁFICOS DE DENSIDAD POR MES
# Serie: Tasa de Desempleo SIN TENDENCIA (STL)
# Objetivo: confirmar presencia de estacionalidad
# ============================================================

# ============================================================
# PREPARACIÓN: Crear data frame con variable de mes
# ============================================================

df_sintend <- data.frame(
  Fecha     = fechas_STL,
  Desempleo = Desempleo_sintend,
  Mes       = factor(month.abb[month(fechas_STL)],
                     levels = month.abb)
)

# Verificar
head(df_sintend, 12)
##         Fecha     Desempleo Mes
## 1  2001-01-01  2.1042055689 Jan
## 2  2001-02-01  2.8570385644 Feb
## 3  2001-03-01  1.1757190264 Mar
## 4  2001-04-01 -0.1778309758 Apr
## 5  2001-05-01 -0.7137696451 May
## 6  2001-06-01  0.5079885642 Jun
## 7  2001-07-01  0.3247080855 Jul
## 8  2001-08-01  0.0007774362 Aug
## 9  2001-09-01 -0.8922146811 Sep
## 10 2001-10-01 -0.4897514609 Oct
## 11 2001-11-01 -1.3807930091 Nov
## 12 2001-12-01 -1.5081431878 Dec
str(df_sintend)
## 'data.frame':    299 obs. of  3 variables:
##  $ Fecha    : Date, format: "2001-01-01" "2001-02-01" ...
##  $ Desempleo: num  2.104 2.857 1.176 -0.178 -0.714 ...
##  $ Mes      : Factor w/ 12 levels "Jan","Feb","Mar",..: 1 2 3 4 5 6 7 8 9 10 ...
# ============================================================
# GRÁFICO 1: Densidad por mes en paneles separados
# Replica exactamente lo que hizo el profesor con USgas
# ============================================================

ggplot(df_sintend, aes(x = Desempleo)) +
  geom_density(aes(fill = Mes), alpha = 0.6) +
  facet_grid(rows = vars(Mes)) +
  geom_vline(xintercept = 0,
             linetype   = "dashed",
             color      = "black",
             linewidth  = 0.5) +
  labs(
    title    = "Densidad por mes — Serie sin tendencia (STL)",
    subtitle = "Distribuciones distintas entre meses indican estacionalidad",
    x        = "Desviación respecto a la tendencia (%)",
    y        = "Densidad"
  ) +
  theme_minimal() +
  theme(legend.position = "none")

# ============================================================
# GRÁFICO 2: Densidad superpuesta de todos los meses
# Permite ver de un vistazo si los meses se separan entre sí
# ============================================================

ggplot(df_sintend, aes(x = Desempleo,
                       fill  = Mes,
                       color = Mes)) +
  geom_density(alpha = 0.20, linewidth = 0.8) +
  geom_vline(xintercept = 0,
             linetype   = "dashed",
             color      = "black",
             linewidth  = 0.8) +
  labs(
    title    = "Densidad superpuesta por mes — Serie sin tendencia",
    subtitle = "Curvas separadas = estacionalidad fuerte | Curvas mezcladas = sin estacionalidad",
    x        = "Desviación respecto a la tendencia (%)",
    y        = "Densidad",
    fill     = "Mes",
    color    = "Mes"
  ) +
  theme_minimal()

# ============================================================
# GRÁFICO 3: Boxplot por mes
# Complementa las densidades mostrando mediana y dispersión
# ============================================================

ggplot(df_sintend, aes(x = Mes, y = Desempleo, fill = Mes)) +
  geom_boxplot(alpha         = 0.7,
               outlier.color = "red",
               outlier.shape = 16,
               outlier.size  = 2) +
  geom_hline(yintercept = 0,
             linetype   = "dashed",
             color      = "black",
             linewidth  = 0.8) +
  labs(
    title    = "Boxplot por mes — Serie sin tendencia (STL)",
    subtitle = "Medianas distintas entre meses confirman estacionalidad",
    x        = "Mes",
    y        = "Desviación respecto a la tendencia (%)"
  ) +
  theme_minimal() +
  theme(legend.position = "none")

# ============================================================
# GRÁFICO 4: Medias por mes con barras de error
# Si las medias varían sistemáticamente → estacionalidad presente
# ============================================================

df_medias <- df_sintend %>%
  group_by(Mes) %>%
  summarise(
    Media = mean(Desempleo, na.rm = TRUE),
    DE    = sd(Desempleo,   na.rm = TRUE),
    .groups = "drop"
  )

ggplot(df_medias, aes(x = Mes, y = Media, fill = Mes)) +
  geom_bar(stat  = "identity", alpha = 0.8) +
  geom_errorbar(aes(ymin = Media - DE,
                    ymax = Media + DE),
                width     = 0.3,
                linewidth = 0.8) +
  geom_hline(yintercept = 0,
             linetype   = "dashed",
             color      = "black",
             linewidth  = 0.8) +
  labs(
    title    = "Media mensual — Serie sin tendencia (STL)",
    subtitle = "Barras de error = ±1 desviación estándar",
    x        = "Mes",
    y        = "Desviación promedio respecto a la tendencia (%)"
  ) +
  theme_minimal() +
  theme(legend.position = "none")

# --- Tabla de medias por mes ---
cat("=== MEDIAS POR MES — SERIE SIN TENDENCIA ===\n\n")
## === MEDIAS POR MES — SERIE SIN TENDENCIA ===
print(df_medias)
## # A tibble: 12 × 3
##    Mes     Media    DE
##    <fct>   <dbl> <dbl>
##  1 Jan    2.27   0.558
##  2 Feb    1.27   0.462
##  3 Mar    0.169  0.665
##  4 Apr    0.434  1.70 
##  5 May    0.290  1.95 
##  6 Jun    0.0325 1.62 
##  7 Jul    0.470  1.54 
##  8 Aug   -0.0449 0.878
##  9 Sep   -0.395  0.698
## 10 Oct   -0.907  0.501
## 11 Nov   -1.21   0.358
## 12 Dec   -0.752  0.464
# ============================================================
# MÉTODO 37: GRÁFICOS DE TEMPORADA Y ESTACIONALES
# Replica chunks "mas acerca estacionalidad_1" y "_2"
# Usa tanto la serie original como la serie sin tendencia
# ============================================================

# ============================================================
# PREPARACIÓN: Convertir serie sin tendencia a objeto ts
# Necesario para ggseasonplot() y ts_seasonal()
# ============================================================

# --- Serie sin tendencia como objeto ts ---
Desempleo_sintend_ts <- ts(Desempleo_sintend,
                           start     = c(2001, 1),
                           frequency = 12)

# --- Tsibble de la serie sin tendencia ---
# Necesario para gg_season() y gg_subseries() de feasts
tsbl_sintend <- tsibble(
  Fecha     = yearmonth(fechas_STL),
  Desempleo = Desempleo_sintend,
  index     = Fecha
)

# Verificar
ts_info(Desempleo_sintend_ts)
##  The Desempleo_sintend_ts series is a ts object with 1 variable and 299 observations
##  Frequency: 12 
##  Start time: 2001 1 
##  End time: 2025 11
tsbl_sintend
## # A tsibble: 299 x 2 [1M]
##        Fecha Desempleo
##        <mth>     <dbl>
##  1 2001 ene.  2.10    
##  2 2001 feb.  2.86    
##  3 2001 mar.  1.18    
##  4 2001 abr. -0.178   
##  5 2001 may. -0.714   
##  6 2001 jun.  0.508   
##  7 2001 jul.  0.325   
##  8 2001 ago.  0.000777
##  9 2001 sep. -0.892   
## 10 2001 oct. -0.490   
## # ℹ 289 more rows
# ============================================================
# BLOQUE A: GRÁFICOS ESTACIONALES — SIN forecast
# Usando feasts y ggplot2 que ya están cargados
# ============================================================

# --- A1: Gráfico estacional — Serie ORIGINAL ---
# Replica ggseasonplot usando gg_season() de feasts
tsbl_desempleo %>%
  gg_season(Desempleo, period = "year") +
  labs(
    title    = "Gráfico estacional — Serie ORIGINAL",
    subtitle = "Cada línea de color representa un año distinto",
    x        = "Mes",
    y        = "Tasa de Desempleo (%)"
  ) +
  theme_minimal()

# --- A2: Gráfico estacional — Serie SIN TENDENCIA ---
tsbl_sintend %>%
  gg_season(Desempleo, period = "year") +
  labs(
    title    = "Gráfico estacional — Serie SIN TENDENCIA (STL)",
    subtitle = "Si las líneas se superponen → patrón estacional estable en el tiempo",
    x        = "Mes",
    y        = "Desviación respecto a la tendencia (%)"
  ) +
  theme_minimal()

# --- A4: Subseries por mes (equivalente al polar de forecast) ---
tsbl_sintend %>%
  gg_subseries(Desempleo, period = "year") +
  labs(
    title    = "Subseries por mes — Serie sin tendencia",
    subtitle = "Línea azul = media del mes a lo largo de todos los años",
    x        = "Año",
    y        = "Desviación respecto a la tendencia (%)"
  ) +
  theme_minimal()

# ============================================================
# BLOQUE B: GRÁFICOS CON TSstudio::ts_seasonal()
# Replica chunk "mas acerca estacionalidad_2"
# Cuatro vistas del patrón estacional
# ============================================================

# --- B1: Vista normal — líneas por año ---
ts_seasonal(Desempleo_sintend_ts, type = "normal")
# --- B2: Vista de ciclo — orden cronológico por mes ---
# Permite ver si el patrón cambia a lo largo de los años
ts_seasonal(Desempleo_sintend_ts, type = "cycle")
# --- B3: Vista de caja — boxplot por mes ---
ts_seasonal(Desempleo_sintend_ts, type = "box")
# --- B4: Todas las vistas juntas ---
ts_seasonal(Desempleo_sintend_ts, type = "all")
# ============================================================
# BLOQUE C: GRÁFICOS CON feasts
# gg_season() y gg_subseries()
# ============================================================

# --- C1: gg_season — Superpone años en el mismo gráfico ---
tsbl_sintend %>%
  gg_season(Desempleo, period = "year") +
  labs(
    title    = "Gráfico de temporada — Serie sin tendencia (feasts)",
    subtitle = "Cada línea de color representa un año distinto",
    x        = "Mes",
    y        = "Desviación respecto a la tendencia (%)"
  ) +
  theme_minimal()

# --- C2: gg_subseries — Subseries por mes a través del tiempo ---
# Muestra la evolución de cada mes a lo largo de todos los años
# La línea horizontal azul es la media del mes
tsbl_sintend %>%
  gg_subseries(Desempleo, period = "year") +
  labs(
    title    = "Subseries por mes — Serie sin tendencia (feasts)",
    subtitle = "Línea azul = media del mes | Cambios en la línea = estacionalidad cambiante",
    x        = "Año",
    y        = "Desviación respecto a la tendencia (%)"
  ) +
  theme_minimal()

# ============================================================
# BLOQUE D: COMPARACIÓN SERIE ORIGINAL vs SIN TENDENCIA
# Para ver el impacto de remover la tendencia en la lectura
# del patrón estacional
# ============================================================

par(mfrow = c(1, 2),
    mar   = c(4, 4, 3, 2))

# Serie original
monthplot(Desempleo_ts,
          main = "Monthplot — Serie ORIGINAL",
          ylab = "Desempleo (%)",
          xlab = "Mes")

# Serie sin tendencia
monthplot(Desempleo_sintend_ts,
          main = "Monthplot — Serie SIN TENDENCIA",
          ylab = "Desviación (%)",
          xlab = "Mes")

par(mfrow = c(1, 1))
# ============================================================
# MÉTODOS 39 Y 40: WAVELET Y SUAVIZAMIENTO SPLINES
# Serie: Tasa de Desempleo SIN TENDENCIA (STL)
# ============================================================

# MÉTODO 39: ANÁLISIS WAVELET
# Función: biwavelet::wt()
# Detecta ciclos cuya frecuencia o amplitud cambian en el tiempo
# Es un análisis TIEMPO-FRECUENCIA simultáneo:
# → el eje X muestra el tiempo
# → el eje Y muestra el período (en meses)
# → el color muestra la intensidad del ciclo
# ============================================================

# --- Preparar el input para biwavelet ---
# Requiere una matriz de 2 columnas: [índice numérico, valores]
indice_wavelet <- 1:length(Desempleo_sintend)

serie_wavelet <- cbind(
  indice_wavelet,
  as.numeric(Desempleo_sintend)
)

# Verificar
head(serie_wavelet)
##      indice_wavelet           
## [1,]              1  2.1042056
## [2,]              2  2.8570386
## [3,]              3  1.1757190
## [4,]              4 -0.1778310
## [5,]              5 -0.7137696
## [6,]              6  0.5079886
dim(serie_wavelet)
## [1] 299   2
# --- Calcular el wavelet ---
# Puede tardar unos segundos
wt_resultado <- biwavelet::wt(serie_wavelet)

# --- Gráfico 1: Potencia corregida por sesgo (recomendada) ---
par(oma = c(0, 0, 0, 1),
    mar = c(5, 4, 4, 5) + 0.1)

plot(wt_resultado,
     type    = "power.corr.norm",
     main    = "Wavelet — Potencia corregida (serie sin tendencia)",
     plot.cb = TRUE)         # Muestra barra de color

# --- Gráfico 2: Potencia con sesgo ---
plot(wt_resultado,
     type    = "power.norm",
     main    = "Wavelet — Potencia con sesgo (serie sin tendencia)",
     plot.cb = TRUE)

# --- Gráfico 3: Los dos juntos para comparar ---
par(mfrow = c(1, 2),
    oma   = c(0, 0, 0, 1),
    mar   = c(5, 4, 4, 5) + 0.1)

plot(wt_resultado,
     type    = "power.corr.norm",
     main    = "Potencia corregida",
     plot.cb = TRUE)

plot(wt_resultado,
     type    = "power.norm",
     main    = "Potencia con sesgo",
     plot.cb = TRUE)

par(mfrow = c(1, 1))

# --- Cómo leer los gráficos wavelet ---
# Eje X   → tiempo (meses desde enero 2001)
# Eje Y   → período en meses (escala logarítmica)
#            período=12 → ciclo anual
#            período=6  → ciclo semestral
#            período=60 → ciclo de 5 años
# Color   → intensidad del ciclo en ese momento
#            rojo = ciclo muy fuerte
#            azul = ciclo débil o ausente
# Cono    → región fuera del cono = resultados menos confiables
#            (efecto de borde por los extremos de la serie)


# ============================================================
# MÉTODO 40: SUAVIZAMIENTO SPLINES
# Función: smooth.spline() de R base
# Minimiza el balance entre ajuste y suavidad
# spar controla la suavidad:
#      spar cercano a 0 → muy detallado, sigue bien la serie
#      spar cercano a 1 → muy suave, solo muestra tendencia residual
# ============================================================

# --- Preparar índice numérico ---
tiempo_spline <- as.numeric(fechas_STL)

# --- Ajustar splines con diferentes niveles de suavidad ---
spline_05  <- smooth.spline(tiempo_spline, Desempleo_sintend, spar = 0.5)
spline_07  <- smooth.spline(tiempo_spline, Desempleo_sintend, spar = 0.7)
spline_10  <- smooth.spline(tiempo_spline, Desempleo_sintend, spar = 1.0)

# --- Gráfico 1: Comparación de tres niveles de suavidad ---
plot(fechas_STL, Desempleo_sintend,
     type = "l",
     main = "Suavizamiento Splines — Serie sin tendencia",
     ylab = "Desviación respecto a la tendencia (%)",
     xlab = "Fecha",
     col  = "gray60",
     lwd  = 1)

lines(as.Date(spline_05$x), spline_05$y,
      col = "red",       lwd = 2)
lines(as.Date(spline_07$x), spline_07$y,
      col = "blue",      lwd = 2)
lines(as.Date(spline_10$x), spline_10$y,
      col = "darkgreen", lwd = 2)

abline(h   = 0,
       lty = 2,
       col = "black")

legend("topright",
       legend = c("Serie sin tendencia",
                  "Spline spar=0.5 (detallado)",
                  "Spline spar=0.7 (medio)",
                  "Spline spar=1.0 (suave)"),
       col    = c("gray60", "red", "blue", "darkgreen"),
       lwd    = c(1, 2, 2, 2),
       cex    = 0.85)

# --- Gráfico 2: Tres paneles separados para ver mejor cada nivel ---
par(mfrow = c(3, 1),
    mar   = c(3, 4, 3, 2))

# Panel 1: spar = 0.5 (detallado)
plot(fechas_STL, Desempleo_sintend,
     type = "l", col = "gray60", lwd = 1,
     main = "Spline spar=0.5 (detallado — sigue bien los ciclos)",
     ylab = "Desviación (%)")
lines(as.Date(spline_05$x), spline_05$y,
      col = "red", lwd = 2)
abline(h = 0, lty = 2)

# Panel 2: spar = 0.7 (medio)
plot(fechas_STL, Desempleo_sintend,
     type = "l", col = "gray60", lwd = 1,
     main = "Spline spar=0.7 (medio — balance entre detalle y suavidad)",
     ylab = "Desviación (%)")
lines(as.Date(spline_07$x), spline_07$y,
      col = "blue", lwd = 2)
abline(h = 0, lty = 2)

# Panel 3: spar = 1.0 (suave)
plot(fechas_STL, Desempleo_sintend,
     type = "l", col = "gray60", lwd = 1,
     main = "Spline spar=1.0 (suave — solo estructura general)",
     ylab = "Desviación (%)")
lines(as.Date(spline_10$x), spline_10$y,
      col = "darkgreen", lwd = 2)
abline(h = 0, lty = 2)

par(mfrow = c(1, 1))

# --- Gráfico 3: versión ggplot2 del spline elegido ---
# Usamos spar=0.7 como balance recomendado
df_spline <- data.frame(
  Fecha       = fechas_STL,
  Desempleo   = as.numeric(Desempleo_sintend),
  Spline_07   = spline_07$y
)

ggplot(df_spline, aes(x = Fecha)) +
  geom_line(aes(y = Desempleo),
            color = "gray60", linewidth = 0.7) +
  geom_line(aes(y = Spline_07),
            color = "blue",   linewidth = 1.5) +
  geom_hline(yintercept = 0,
             linetype = "dashed", color = "black") +
  labs(
    title    = "Suavizamiento Spline (spar=0.7) — Serie sin tendencia",
    subtitle = "Azul = curva spline | Captura la estructura residual tras remover tendencia STL",
    x        = "Fecha",
    y        = "Desviación respecto a la tendencia (%)"
  ) +
  theme_minimal()

# ============================================================
# SERIE SIN TENDENCIA Y SIN ESTACIONALIDAD
# Residuo puro = Serie original − Tendencia STL − Estacionalidad STL
# ============================================================

# ── Extraer los tres componentes del objeto fable ──────────────
tendencia_STL      <- descomp_STL$trend
estacionalidad_STL <- descomp_STL$season_year   # así se llama en fable, NO "seasonal"
residuo_STL        <- descomp_STL$remainder      # así se llama en fable, NO "remainder" de stl()
fechas_STL         <- as.Date(descomp_STL$Fecha)

# ── Verificación cruzada ────────────────────────────────────────
residuo_manual <- valores - tendencia_STL - estacionalidad_STL

# ── Data frame para ggplot ──────────────────────────────────────
df_residuo <- data.frame(
  Fecha   = fechas_STL,
  Residuo = as.numeric(residuo_STL)
)

# ── Gráfico ────────────────────────────────────────────────────
ggplot(df_residuo, aes(x = Fecha, y = Residuo)) +
  geom_line(color = "gray30", linewidth = 0.7) +
  geom_hline(yintercept = 0,
             linetype   = "dashed",
             color      = "red",
             linewidth  = 0.8) +
  geom_smooth(method = "loess", span = 0.15,
              color  = "steelblue",
              se     = FALSE,
              linewidth = 1.2) +
  labs(
    title    = "Residuo STL — Serie sin tendencia y sin estacionalidad",
    subtitle = "Lo que queda es ruido + ciclos irregulares no capturados por STL",
    x        = "Fecha",
    y        = "Residuo (%)"
  ) +
  theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'