La humedad relativa es una de las variables meteorológicas más importantes para describir las condiciones de la atmósfera, ya que indica qué tan cerca está el aire de alcanzar su punto de saturación de vapor de agua a una determinada temperatura (IDEAM, 2017). Debido a su influencia sobre el clima y diferentes actividades humanas, en Colombia esta variable es monitoreada permanentemente por el Instituto de Hidrología, Meteorología y Estudios Ambientales (IDEAM) mediante una red de estaciones meteorológicas distribuidas en el país.
Cali, como capital del Valle del Cauca, tiene un clima cálido tropical con un comportamiento de variaciones constantes en la humedad, en la ciudad hay dos temporadas más húmedas, generalmente entre abril-mayo y octubre-noviembre y un periodo intermedio algo más seco hacia mitad de año (Weather Atlas, s.f.). Esto hace que la humedad no se quede quieta a lo largo del año, se mueve, sube y baja, tanto por el ciclo normal del día, en la noche sube y en las horas de más calor baja, por esta estacionalidad propia de la región.
Precisamente, ese comportamiento hace que la humedad relativa sea una variable adecuada para analizar mediante técnicas de series de tiempo. Al existir dependencia entre las observaciones y patrones que se repiten de manera periódica, es posible utilizar modelos estadísticos para describir su comportamiento y realizar pronósticos. Entre los modelos más utilizados se encuentran los ARIMA, desarrollados a partir de la metodología propuesta por Box y Jenkins, los cuales han demostrado un buen desempeño en la predicción de variables meteorológicas como la temperatura, la radiación solar y la humedad relativa (Escalante-Sandoval & Reyes-Chávez, 2016).
Diversos estudios respaldan este tipo de aplicaciones. Por ejemplo, Escalante-Sandoval y Reyes-Chávez (2016) encontraron que los modelos ARIMA ofrecían mejores resultados que métodos de predicción más simples para estimar variables meteorológicas en estaciones automáticas de México. En Colombia también existen antecedentes importantes, como la aplicación de estos modelos al análisis de los caudales del río Magdalena (Cantor, 2017).
Con base en lo anterior, este trabajo analiza la serie temporal de humedad relativa registrada en la ciudad de Cali durante el periodo comprendido entre marzo y diciembre de 2018, utilizando la base de datos suministrada por el profesor del curso. El propósito es describir el comportamiento de la serie, verificar si cumple las condiciones de estacionariedad necesarias para el modelamiento, ajustar un modelo ARIMA apropiado y evaluar su capacidad predictiva mediante la comparación entre los valores pronosticados y los datos observados en el periodo de prueba.
La metodología se centró en la aplicación de un enfoque basado en el análisis de series temporales. Para el desarrollo del presente trabajo se empleó datos pertenecientes a una serie temporal de humedad relativa (%) registrada en la ciudad de Cali, Colombia. Estos datos corresponden a observaciones recolectadas con frecuencias horarias, comprendidas entre el 1 de enero de 2018 y el 1 de enero de 2019. Este enfoque busca estudiar y analizar la variable de estudio a corto y largo plazo, con la finalidad de lograr identificar variaciones y patrones asociados a los ciclos climáticos diarios, y generar un modelo de pronóstico confiable y a mediano plazo.
El proceso metodológico se articuló en las siguientes etapas: selección de la variable de estudio, preprocesamiento de datos, análisis descriptivo, análisis de estacionariedad, comparación entre los modelos, selección y validación del modelo óptimo y un pronóstico para los días del conjunto de prueba.
La variable de estudio corresponde a la humedad relativa o mejor conocida por sus siglas HR: una variable cuantitativa continua. Es una medida meteorológica (cuantificada porcentualmente) que mide la cantidad de vapor de agua presente en el aire en relación con la cantidad máxima que la atmósfera podría retener a una temperatura especifica (Oficina de pronóstico del tiempo, s.f.), permitiendo así, conocer que tan saturado (húmedo) o seco se encuentra el aire (Oficina de Gestión Ambiental de la Sede Bogotá de la Universidad Nacional de Colombia, s.f.).
Su cálculo se expresa mediante la siguiente ecuación:
\[HR = \left( \frac{P_{real}}{P_{sat}} \right) \times 100\]
En donde:
La humedad relativa constituye entonces, una variable de gran importancia, dada su influencia sobre la dinámica atmosférica y los procesos de precipitación. Debido a lo anterior, resulta pertinente modelar su variación mediante técnicas de series temporales que permitan comprender y predecir su comportamiento a lo largo del tiempo.
Previo al análisis descriptivo y modelado, es de suma importancia realizar ciertos cambios al data set original. Los registros fueron organizados cronológicamente y transformados a formato de serie temporal mediante el uso de paquete xts. Además, se eliminaron los valores nulos y se eligió el periodo de tiempo.
library(tidyverse)
library(dplyr)
library(lubridate)
library(hms)
library(readxl)
library(forecast)
library(tseries)
library(fpp2)
library(xts)
library(quantmod)
library(psych)
library(moments)
library(dendextend)
library(ggplot2)
library(plotly)
library(ggfortify)
library(gridExtra)
library(ggstance)
library(scales)
library(kableExtra)
library(DT)
Base <- read_excel("Compartir2.xlsx")
colnames(Base)[6] <- "Humedad"
Humedad <- xts(
Base$Humedad,
order.by = Base$`Fecha & Hora`)
tabla_serie <- data.frame(
Fecha = index(Humedad),
Humedad = as.numeric(Humedad)) %>%
filter(!is.na(Humedad))
datatable(
tabla_serie,
rownames = FALSE,
colnames = c("Fecha y Hora", "Humedad (%)"),
options = list(
pageLength = 8,
scrollX = TRUE,
autoWidth = FALSE,
columnDefs = list(
list(className = 'dt-center', targets = 0),
list(className = 'dt-right', targets = 1)))) %>%
formatRound(columns = 2, digits = 1)Pese a que la idea de que tomar el año entero como periodo para el análisis pueda permitir capturar de manera plena y robusta el comportamiento de la variable, es importante tener en cuenta la cantidad de observaciones faltantes. La base de datos acumula un total de 625 valores faltantes, incluyendo una interrupción prolongada durante febrero: hay un bloque consecutivo de valores nulos entre el 7 y el 27 de dicho mes.
Faltantes_mes <- aggregate(
is.na(Humedad),
by = list(Mes = format(index(Humedad), "%Y-%m")),
FUN = sum)
names(Faltantes_mes)[2] <- "Faltantes"
Obs_faltantes <- Faltantes_mes %>%
mutate(Porcentaje = Faltantes / sum(Faltantes_mes$Faltantes) * 100)
Obs_faltantes$Porcentaje <- round(Obs_faltantes$Porcentaje, 2)
Grafico_faltantes <- ggplot(Obs_faltantes, aes(x = Porcentaje, y = Mes, group = 1)) +
geom_segment(aes(x = 0, xend = Porcentaje,
y = Mes, yend = Mes),
linewidth = 1.8, color = "#E7E7E7") +
geom_point(size = 3) +
scale_x_continuous(breaks = seq(0, 80, by = 20), labels = function(x) paste0(x, "%")) +
scale_y_discrete(labels = c("2018-01" = "Enero - 2018",
"2018-02" = "Febrero - 2018",
"2018-03" = "Marzo - 2018",
"2018-04" = "Abril - 2018",
"2018-05" = "Mayo - 2018",
"2018-06" = "Junio - 2018",
"2018-07" = "Julio - 2018",
"2018-08" = "Agosto - 2018",
"2018-09" = "Septiembre - 2018",
"2018-10" = "Octubre - 2018",
"2018-11" = "Noviembre - 2018",
"2018-12" = "Diciembre - 2018",
"2019-01" = "Enero - 2019")) +
labs(title = NULL,
subtitle = NULL,
x = NULL,
y = NULL) +
theme_minimal() +
theme(plot.title = element_text(color = "gray35"),
panel.grid.major.x = element_line(linewidth = .2),
panel.grid.major.y = element_blank(),
panel.grid.minor = element_blank(),
legend.title = element_blank(),
legend.background = element_blank(),
plot.margin = margin(15, 20, 10, 10))
ggplotly(Grafico_faltantes) %>%
layout(title = list(
text = "<b>Febrero registra la mayor proporción de valores nulos</b><br><sup><span style='color:#666666;'>Distribución mensual de observaciones faltantes respecto del total</span></sup>",
x = 0,
xref = "paper",
xanchor = "left",
y = 1.2,
font = list(color = "gray21",
family = "Segoe UI",
size = 22)),
font = list(family = "Segoe UI"),
margin = list(t = 80))Este hecho podría estar relacionado con fallas en el almacenamiento de la información, más que con dificultades en la medición de la variable.
Con el fin de evitar sesgos, se decidió eliminar los valores nulos y delimitar el análisis al periodo comprendido entre el 1 de marzo de 2018 y el 1 de enero de 2019: la base de datos se redujo a 7197 observaciones, pero garantizando así, una serie continua.
La base de datos resultante fue dividida en un conjunto de entrenamiento (desde el 1 de marzo de 2018 a 31 de noviembre de 2018), con el fin de entrenar al modelo, y un conjunto de prueba (1 de diciembre de 2018 a 1 de enero de 2019), con el fin de evaluar su desempeño predictivo.
Previo al modelado y con la finalidad de verificar que la serie sea estacionaria, es decir, que no presente tendencias a largo plazo, se realizo la prueba de Dickey- Fuller Aumentada. Los resultados de esta prueba indican si se le debe realizar algún tipo de transformación a la serie de tiempo. Esta prueba se acompaña de herramientas visuales como los gráficos de autocorrelación (ACF) y de autocorrelación parcial (PACF), con la finalidad de identificar lags y determinar que modelo predictivo es más adecuado.
Media móvil integrada autorregresiva (o por sus siglas en inglés ARIMA): es uno de los enfoques más utilizados para análisis predictivos de series de tiempo, caracterizado por su precisión, flexibilidad y procesabilidad. El modelo ARIMA es ampliamente usado para predecir posibles valores futuros a partir del comportamiento pasado de una serie temporal. Este modelado consta de 3 componentes (p, d, q), explicados a continuación:
El componente autorregresivo – AR (p): se realiza una regresión de la variable contra si misma. Esto hecho indica que se pronostica la variable utilizando una combinación lineal de sus propios valores en periodos de tiempo pasado (Noble, s.f.), denominados como lags. El parámetro p indica el orden de la parte autorregresiva, es decir, cuántos periodos hacia atrás influyen en el presente.
El componente integrado – I (d): consiste en diferenciar el componente de la serie hasta que la serie temporal sea estacionaria: la media y varianza deben permanecer constantes a lo largo del tiempo (Saadeddin, 2024). El parámetro d indica el número de diferenciaciones sucesivas aplicadas a la serie. Una primera diferenciación resta el valor actual del valor anterior. Esto puede representarse a partir de la siguiente ecuación:
\[Y_t = X_t - X_{t-1}\]
El componente de media móvil – MA (q): útil para capturar cambios a corto plazo en los datos, permitiendo obtener información valiosa sobre el comportamiento de la serie (Saadeddin, 2024): considera, modela y estima la influencia de eventos inesperados o choques aleatorios sobre el valor actual. El parámetro q indica el orden de la parte de media móvil, es decir, el número de errores pasados de pronóstico incluidos en el modelo. En meteorología, el modelo ARIMA se aplica comúnmente para proyectar el comportamiento de variables atmosféricas a corto y mediano plazo.
Posterior a la elección del modelo óptimo y con la finalidad de verificar que los errores de predicción o mejor conocido como residuales no presenten dependencia temporal o patrones (ruido blanco), se realizó la prueba de Ljung-Box. Los resultados de esta prueba indican si el modelo captura la información de manera correcta.
Humedad2 <- na.omit(Humedad)
Humedad2 <- Humedad2["2018-03-01/2019-01-01"]
names(Humedad2)[1] <- "Humedad"
Serie_humedad <- ggplot(Humedad2, aes(x = Index, y = Humedad)) +
geom_line(linewidth = .2, color = "#295675") +
scale_y_continuous(limits = c(20, 90),
breaks = seq(20, 90, by = 20), labels = function(x) paste0(x, "%")) +
scale_x_datetime(date_breaks = "1 month",
date_labels = "%b") +
labs(title = NULL,
subtitle = NULL,
x = NULL, y = NULL) +
theme_minimal() +
theme(plot.title = element_text(color = "gray35"),
panel.grid.major.x = element_blank(),
panel.grid.major.y = element_line(linewidth = .2),
panel.grid.minor = element_blank(),
legend.title = element_blank(),
legend.background = element_blank(),
plot.margin = margin(15, 20, 10, 10))
ggplotly(Serie_humedad) %>%
layout(title = list(
text = "<b>Evolucion temporal de la humedad relativa</b><br>",
x = 0,
xref = "paper",
xanchor = "left",
y = 1.2,
font = list(color = "gray21",
family = "Segoe UI",
size = 22)),
font = list(family = "Segoe UI"),
margin = list(t = 60))A grandes rasgos, el gráfico ilustra un comportamiento de la humedad relativa caracterizado por una fluctuación diaria extremadamente alta y constante: la línea sube y baja de manera brusca y continua a lo largo del eje horizontal. Durante los 10 meses de análisis, los valores fluctúan de manera cíclica dentro de un rango establecido comúnmente entre el 30% y 80%, lo que sugiere la posible presencia de elementos estacionales asociados a los ciclos climáticos. Además, la serie no muestra índices pronunciados de tendencia creciente o decreciente a largo plazo.
Sin embargo, se pueden identificar ciertos extremos climáticos. Mientras que el máximo pico de humedad se registró en franjas de la mañana y tarde del 18 de septiembre: un día caracterizado por temperaturas moderadas (entre 19° y 27°) y una atmosfera húmeda (Tu Tiempo, s.f.), el nivel más bajo ocurrió en las horas vespertinas del 11 de agosto: este episodio ocurrió en un contexto de temperaturas levemente elevadas (entre 14° y 32°) y de ausencia de precipitación (Tu Tiempo, s.f.). Así mismo, resulta interesante la distancia temporal que separan ambos extremos: se encuentran separados solamente por 38 días, lo que refleja perfectamente la dinámica bimodal que poseen las zonas de clima tropical.
Tabla_desc_Humedad <- data.frame(
Estadistico = c(
"Observaciones",
"Mínimo",
"Q1 (25%)",
"Media",
"Mediana",
"Q3 (75%)",
"Máximo",
"Desv. estándar",
"Asimetria",
"Curtosis"),
Valor = c(
sum(!is.na(Humedad2$Humedad)),
round(min(Humedad2$Humedad, na.rm = TRUE), 2),
round(quantile(Humedad2$Humedad, .25, na.rm = TRUE), 2),
round(mean(Humedad2$Humedad, na.rm = TRUE), 2),
round(median(Humedad2$Humedad, na.rm = TRUE), 2),
round(quantile(Humedad2$Humedad, .75, na.rm = TRUE), 2),
round(max(Humedad2$Humedad, na.rm = TRUE), 2),
round(sd(Humedad2$Humedad, na.rm = TRUE), 2),
round(skewness(Humedad2$Humedad, na.rm = TRUE), 2),
round(kurtosis(Humedad2$Humedad, na.rm = TRUE), 2)))
datatable(
Tabla_desc_Humedad,
caption = "Estadísticas descriptivas",
filter = "top", rownames = FALSE,
extensions = "Buttons",
options = list(pageLength = 15, dom = "Bfrtip",
buttons = c("copy","csv","excel"), scrollX = TRUE))La tabla indica que, durante gran parte del año 2018, la humedad relativa presento una variabilidad moderada: los caleños percibieron ambientes tanto agradables como bochornosos. Esta variable presenta una mediana superior al promedio, un valor levemente negativo de asimetría y un nivel de curtosis bajo. Estos indicadores señalan que, aunque la humedad solía mantenerse entre niveles moderadamente altos, no estaba ajena a presentar caídas drásticas (aunque frecuentemente escasas).
Para un mejor análisis y con la finalidad de reducir el ruido de las fluctuaciones en la serie, se opto por pasar de datos horarios a promedios diarios. Esta agregación temporal permite observar con mayor claridad la realidad climática del día a día.
Humedad_diaria <- fortify.zoo(Humedad2) %>%
mutate(Fecha = as.Date(Index)) %>%
group_by(Fecha) %>%
summarise(
Media = round(mean(Humedad), 1),
.groups = "drop")
serie_humedad_diaria <- ggplot(Humedad_diaria, aes(x = Fecha, y = Media)) +
geom_line(linewidth = .2, color = "#295675") +
scale_y_continuous(limits = c(20, 90),
breaks = seq(20, 90, by = 20), labels = function(x) paste0(x, "%")) +
scale_x_date(date_breaks = "1 month",
date_labels = "%b") +
labs(title = NULL, subtitle = NULL, x = NULL, y = NULL) +
theme_minimal() +
theme(plot.title = element_text(color = "gray35"),
axis.title.y = element_text(color = "gray40", hjust = 1, face = "bold"),
axis.title.x = element_blank(),
axis.text.y = element_text(color = "gray40", size = 12, hjust = 1),
axis.text.x = element_text(color = "gray40", size = 10, hjust = 1),
panel.grid.major.x = element_blank(),
panel.grid.major.y = element_line(linewidth = .2),
panel.grid.minor = element_blank(),
legend.title = element_blank(),
legend.background = element_blank(),
plot.margin = margin(15, 20, 10, 10))
Graf_serie_humedad_diaria <- ggplotly(serie_humedad_diaria) %>%
layout(title = list(
text = "<b>Transición entre periodos húmedos y secos en Cali durante 2018</b>",
x = 0,
xref = "paper",
xanchor = "left",
y = 1.2,
font = list(color = "gray21",
family = "Segoe UI",
size = 22)),
font = list(family = "Segoe UI"),
margin = list(t = 60),
annotations = list(
list(x = as.numeric(as.Date("2018-04-15")),
y = 85,
xref = "x",
yref = "y",
text = "Primer temporada<br>lluviosa",
showarrow = FALSE,
font = list(color = "#7591BA", size = 12)),
list(x = as.numeric(as.Date("2018-08-22")),
y = 85,
xref = "x",
yref = "y",
text = "Déficit de<br>precipitación",
showarrow = FALSE,
font = list(color = "#CC8116", size = 12)),
list(x = as.numeric(as.Date("2018-11-01")),
y = 85,
text = "Segunda temporada<br>lluviosa",
showarrow = FALSE,
font = list(color = "#7591BA", size = 12))))
Graf_serie_humedad_diaria$x$layout$shapes <- list(
list(
type = "rect",
xref = "x",
yref = "paper",
x0 = as.numeric(as.Date("2018-03-20")),
x1 = as.numeric(as.Date("2018-05-13")),
y0 = 0,
y1 = 1,
fillcolor = "lightblue",
opacity = 0.3,
line = list(width = 0)),
list(
type = "rect",
xref = "x",
yref = "paper",
x0 = as.numeric(as.Date("2018-07-31")),
x1 = as.numeric(as.Date("2018-09-10")),
y0 = 0,
y1 = 1,
fillcolor = "#FFA424",
opacity = 0.3,
line = list(width = 0)),
list(
type = "rect",
xref = "x",
yref = "paper",
x0 = as.numeric(as.Date("2018-10-02")),
x1 = as.numeric(as.Date("2018-11-30")),
y0 = 0,
y1 = 1,
fillcolor = "#C4DAFA",
opacity = 0.3,
line = list(width = 0)))
Graf_serie_humedad_diariaLa humedad relativa promedio diaria presentó fluctuaciones que se mantuvieron comúnmente entre el 45% y 70%. Aunque no se observa una tendencia sostenida a largo plazo, si es posible identificar ciertos periodos asociados a dinámicas climáticas propias de la región.
En primer lugar, se observa que el periodo comienza con valores moderados, aunque demostrando una tendencia levemente ascendente: durante la última semana de marzo y el mes de abril, la humedad aumenta progresivamente. Es durante los meses de abril y mayo donde se alcanzan los picos más altos de la serie, coincidiendo con una de las temporadas lluviosas habituales del suroccidente del país.
A partir de junio se hace evidente una disminución gradual de humedad hasta llegar al 16 de julio: el día con el nivel promedio más bajo. Es durante los meses de julio y agosto donde se presenta mayor variabilidad, caso contrario ocurre con la primera mitad del mes de septiembre. Pese a ello, estos periodos constituyen en conjunto el tramo más seco de la serie. Este hecho coincide con la fase de transición del fenómeno del Niño, en donde se presentó un déficit de los volúmenes de precipitación, es decir, lluvias por debajo del promedio (Servicio Geológico Colombiano, 2018).
Con la llegada del mes de octubre, se observa una recuperación progresiva (aunque algo abrupta) de los niveles de humedad hasta alcanzar su pico en diciembre. Los meses de octubre, noviembre y diciembre muestran gran volatibilidad, lo que coincide con el desplazamiento de la Zona de Confluencia Intertropical (ZCIT) hacia el sur del país, además de la presencia de ondas tropicales en el atlántico: fenómenos que permitieron la formación de nubosidad y precipitaciones (Corporación Autónoma Regional del Valle del Cauca, 2018).
Finalmente, durante diciembre, la humedad relativa demostró una ligera tendencia descendente. La disminución de lluvias y el aumento de temperaturas (efectos del fenómeno del Niño) propiciaron un ambiente de condiciones secas.
Humedad_mes <- fortify.zoo(Humedad2) %>%
mutate(Fecha = as.Date(Index),
Mes = lubridate::month(Fecha, label = TRUE, abbr = TRUE)) %>%
mutate(
Mes = factor(Mes, levels = unique(Mes)))
Media_mes <- Humedad_mes %>%
group_by(Mes) %>%
summarise(Media = mean(Humedad, na.rm = TRUE))
Distrib_Humedad_mes <- ggplot(Humedad_mes, aes(x = Mes,
y = Humedad)) +
geom_boxplot(outlier.shape = 21,
outlier.size = 2,
outlier.colour = "#4E79A7",
color = "#4E79A7") +
geom_point(size = 3, data = Media_mes,
aes(x = Mes, y = Media),
shape = 8,
size = 3,
color = "#4E79A7") +
scale_y_continuous(limits = c(20, 90),
breaks = seq(20, 90, by = 20), labels = function(x) paste0(x, "%")) +
labs(title = NULL, subtitle = NULL, x = NULL, y = NULL) +
theme_minimal() +
theme(plot.title = element_text(color = "gray35"),
axis.title.y = element_blank(),
axis.title.x = element_blank(),
axis.text.y = element_text(color = "gray40", hjust = 1),
axis.text.x = element_text(color = "gray40", hjust = 1),
panel.grid.major.x = element_blank(),
panel.grid.major.y = element_line(linewidth = .2, linetype = "dashed"),
panel.grid.minor = element_blank(),
legend.title = element_blank(),
legend.background = element_blank(),
plot.margin = margin(15, 20, 10, 10))
ggplotly(Distrib_Humedad_mes) %>%
layout(title = list(
text = "<b>Variabilidad intra-anual de la humedad relativa</b>",
x = 0,
xref = "paper",
xanchor = "left",
y = 1.2,
font = list(color = "gray21",
family = "Segoe UI",
size = 24)),
font = list(family = "Segoe UI"),
margin = list(t = 60))Desde una mirada general, el gráfico permite visualizar rápidamente la dispersión de los datos, identificando diferencias en la variabilidad y estabilidad de las condiciones atmosféricas a lo largo del ciclo. Además, el grafico evidencia la ausencia de valores atípicos, lo que indica que los registros extremos responden a fluctuaciones propias de la serie.
Las amplitudes de las cajas muestran que la variabilidad interna no es homogénea a lo largo de todo el periodo analizado. Por ejemplo, los meses ubicados en temporadas lluviosas (abril, mayo y noviembre) muestran estructuras compactas y desplazadas al límite superior, con mínimos superiores a 30% y medianas por encima al 60%. Caso contrario ocurre con los meses caracterizados por condiciones secas (julio y agosto), dado que son los períodos con los comportamientos más volátiles: aunque las medianas se ubican cercanas al 55%, los bigotes inferiores se estiran drásticamente hasta rozar el 20%.
Adicionalmente, los meses de transición entre temporadas húmedas y secas (marzo, junio y diciembre) muestran cajas compactas, aunque con bigotes levemente alargados.
Pese a las diferencias mencionadas anteriormente, todas las distribuciones comparten una característica asociada a su simetría: la mediana se ubicó por encima del promedio en todo el ciclo. Esta diferencia demuestra que, aunque la humedad permaneció alta la mayor parte del tiempo, existieron episodios puntuales de caídas bruscas.
Humedad_horaria <- fortify.zoo(Humedad2) %>%
mutate(Hora = hour(Index)) %>%
group_by(Hora) %>%
summarise(
Promedio = round(mean(Humedad), 1),
Minimo = round(min(Humedad), 1),
Maximo = round(max(Humedad), 1),
.groups = "drop") %>%
mutate(Hora = hms::as_hms(Hora*3600))
View(Humedad_horaria)
serie_humedad_horaria <- ggplot(Humedad_horaria, aes(x = Hora, y = Promedio)) +
geom_ribbon(aes(ymin = Minimo, ymax = Maximo), fill = "#29567522") +
geom_line(linewidth = .5, color = "#295675") +
scale_y_continuous(limits = c(20, 90),
breaks = seq(20, 90, by = 20), labels = function(x) paste0(x, "%")) +
scale_x_time(breaks = date_breaks("2 hour"),
labels = label_time("%I %p")) +
labs(title = NULL, subtitle = NULL, x = NULL, y = NULL) +
theme_minimal() +
theme(plot.title = element_text(color = "gray35"),
axis.title.y = element_text(color = "gray40", hjust = 1, face = "bold"),
axis.title.x = element_blank(),
axis.text.y = element_text(color = "gray40", size = 13, hjust = 1),
axis.text.x = element_text(color = "gray40", size = 8.5, hjust = 1),
panel.grid.major.x = element_blank(),
panel.grid.major.y = element_line(linewidth = .2),
panel.grid.minor = element_blank(),
legend.title = element_blank(),
legend.background = element_blank(),
plot.margin = margin(15, 20, 10, 10))
ggplotly(serie_humedad_horaria) %>%
layout(title = list(
text = "<b>Picos de humedad en la madrugada y valles de sequedad por la tarde</b><br><sup><span style='color:#666666;'>Perfil horario promedio</span></sup>",
x = 0,
xref = "paper",
xanchor = "left",
y = 1.2,
font = list(color = "gray21",
family = "Segoe UI",
size = 20)),
font = list(family = "Segoe UI"),
margin = list(t = 60))Este gráfico permite identificar un ciclo estrechamente definido por el momento del día, reflejando la relación inversa que posee la humedad relativa con la temperatura.
Los mayores niveles de humedad promedio se registran durante las primeras horas de la madrugada, cuando las temperaturas son más bajas. Es durante este lapso de tiempo donde se puede observar una tendencia levemente ascendente, hasta alcanzar su pico a las 6 a.m. Además, este periodo presenta un rango reducido (representado por la franja celeste): sus valores extremos oscilan entre el 55% y 85%, indicando condiciones meteorológicas estables y altamente homogéneas.
Es a partir de ese punto horario y con los primeros rayos del sol, que la humedad empieza a descender continuamente, alcanzando su punto mínimo en las primeras horas de la jornada vespertina: es durante estas horas, donde comúnmente la temperatura aumenta. Pese a lo anterior, este periodo se caracteriza por ser notablemente variable: sus valores extremos oscilan entre el 20% y 89%. En otras palabras, durante las horas de la tarde de la mayor parte del año 2018, los caleños experimentaron condiciones meteorológicas que variaron entre ambientes secos y húmedos.
Tras ese mínimo y durante las horas de la noche, la humedad aumenta de forma progresiva, aunque sin alcanzar los niveles de humedad logrados durante la madrugada. Este período se caracteriza por poseer valores máximos muy alejados del promedio. Este hecho refleja como el enfriamiento del aire durante la noche interactúa con las características ambientales locales (cuerpos de agua y vegetación), las influencias geográficas y las variaciones estacionales (Equipo de AlorAir, 2024).
Humedad_diaria2 <- fortify.zoo(Humedad2) %>%
mutate(Fecha = as.Date(Index)) %>%
group_by(Fecha) %>%
summarise(
Media = round(mean(Humedad), 1),
.groups = "drop")
Humedad_diaria_xts <- xts(Humedad_diaria2$Media,
order.by = Humedad_diaria2$Fecha)
ventana1 <- window(Humedad_diaria_xts, start = "2018-03-01", end = "2018-11-30")
ventana2 <- window(Humedad_diaria_xts, start = "2018-12-01")
fechas_ventana1 <- index(ventana1)
ventana_train_xts <- ventana1
ventana_test_xts <- ventana2
df_train <- data.frame(
Fecha = index(ventana_train_xts),
Humedad = as.numeric(ventana_train_xts))
df_test <- data.frame(
Fecha = index(ventana_test_xts),
Humedad = as.numeric(ventana_test_xts))
fig_ventanas <- plot_ly() %>%
add_trace(
data = df_train,
x = ~Fecha,
y = ~Humedad,
type = "scatter",
mode = "lines+markers",
line = list(color = "#2F3C46", width = 3),
marker = list(color = "#2F3C46",
size = 8,
symbol = "circle"),
name = "Entrenamiento (2018-03-01 a 2018-11-30)",
hovertemplate = "<b>Fecha:</b> %{x}<br><b>Humedad:</b> %{y:.1f}%<extra></extra>") %>%
add_trace(
data = df_test,
x = ~Fecha,
y = ~Humedad,
type = "scatter",
mode = "lines+markers",
line = list(color = "#89ACC6", width = 3),
marker = list(color = "#89ACC6",
size = 8,
symbol = "circle"),
name = "Prueba (desde 2018-12-01)",
hovertemplate = "<b>Fecha:</b> %{x}<br><b>Humedad:</b> %{y:.1f}%<extra></extra>") %>%
layout(
title = list(
text = "<b>Ventanas de Entrenamiento y Prueba</b><br><sup>Serie diaria de Humedad</sup>",
x = 0.5,
font = list(size = 22, color = "#2F3C46")),
xaxis = list(
title = "Fecha",
titlefont = list(color = "#2F3C46", size = 16),
tickfont = list(color = "#2F3C46", size = 12),
gridcolor = "#EAEFF3"),
yaxis = list(
title = "Humedad (%)",
titlefont = list(color = "#2F3C46", size = 16),
tickfont = list(color = "#2F3C46", size = 12),
gridcolor = "#EAEFF3"),
plot_bgcolor = "white",
paper_bgcolor = "white",
legend = list(
x = 0.98,
y = 0.02,
xanchor = "right",
yanchor = "bottom",
bgcolor = "rgba(255,255,255,0.8)",
font = list(color = "#2F3C46", size = 12, family = "Segoe UI")),
hovermode = "x unified",
margin = list(t = 90),
font = list(family = "Segoe UI"))
fig_ventanasFechas_ventana1 <- index(ventana1)
ventana1 <- ts(as.numeric(ventana1), frequency = 7)
ventana2 <- ts(as.numeric(ventana2), frequency = 7)
stl_descomp <- stl(ventana1, s.window = "periodic")
df_descomp <- data.frame(
Fecha = fechas_ventana1,
Humedad = as.numeric(ventana1),
Tendencia = as.numeric(stl_descomp$time.series[, "trend"]),
Estacional = as.numeric(stl_descomp$time.series[, "seasonal"]),
Residuo = as.numeric(stl_descomp$time.series[, "remainder"]))
p1 <- plot_ly(
df_descomp,
x = ~Fecha,
y = ~Humedad,
type = "scatter",
mode = "lines",
line = list(color = "#295675", width = 2.5),
fill = "tozeroy",
fillcolor = "rgba(36,113,163,0.15)",
hovertemplate = "<b>Fecha:</b> %{x}<br><b>Humedad:</b> %{y:.1f}%<extra></extra>") %>%
layout(
title = list(text = "<b>Humedad Observada</b>"),
yaxis = list(title = "Original"))
p2 <- plot_ly(
df_descomp,
x = ~Fecha,
y = ~Tendencia,
type = "scatter",
mode = "lines",
line = list(color = "#295675", width = 2.5),
hovertemplate = "<b>Fecha:</b> %{x}<br><b>Tendencia:</b> %{y:.1f}%<extra></extra>") %>%
layout(
title = list(text = "<b>Tendencia de Largo Plazo</b>"),
yaxis = list(title = "Tendencia"))
p3 <- plot_ly(
df_descomp,
x = ~Fecha,
y = ~Estacional,
type = "scatter",
mode = "lines",
line = list(color = "#295675", width = 2.5),
hovertemplate = "<b>Fecha:</b> %{x}<br><b>Efecto estacional:</b> %{y:.1f}<extra></extra>") %>%
layout(
title = list(text = "<b>Componente Estacional</b>"),
yaxis = list(title = "Estacional"))
p4 <- plot_ly(
df_descomp,
x = ~Fecha,
y = ~Residuo,
type = "scatter",
mode = "lines",
line = list(color = "#295675", width = 2.5),
hovertemplate = "<b>Fecha:</b> %{x}<br><b>Residuo:</b> %{y:.1f}<extra></extra>") %>%
layout(
title = list(text = "<b>Variaciones No Explicadas</b>"),
yaxis = list(title = "Residuo"))
subplot(
p1, p2, p3, p4,
nrows = 4,
shareX = TRUE,
titleY = TRUE) %>%
layout(
title = list(
text = "<b>Descomposición STL de la Serie Temporal de Humedad</b><br><sup>Análisis de tendencia, estacionalidad y variaciones aleatorias</sup>",
x = 0.5,
font = list(size = 24, color = "#2F4F6A", family = "Segoe UI")),
showlegend = FALSE,
hovermode = "x unified",
margin = list(t = 90),
showlegend = FALSE,
font = list(family = "Segoe UI"))La descomposición Estacional y de Tendencia usando Loess (o mejor conocido por sus siglas en inglés: STL) sobre los datos diarios reitera todo lo abordado anteriormente y revela nuevos hallazgos. Adicionalmente, el STL se configuró con una frecuencia de 7, con la finalidad de identificar patrones semanales. Esta descomposición desglosa la serie original en 3 componentes importantes:
En primer lugar, el componente de estacionalidad, permite visualizar un patrón patrón cíclico y estable que se repite de manera idéntica durante toda la serie, indicando que la humedad no depende del día de la semana. El componente de tendencia elimina las fluctuaciones del día a día, explicando el comportamiento a largo plazo. Visualmente imita la forma de los datos originales, lo que indica que la humedad está sujeta a cambios macro.
Finalmente, el componente de residuo captura los valores que no son explicados por la tendencia ni por la estacionalidad, es decir, representa todo lo inesperado. Los residuos se encuentran distribuidos alrededor de cero y no presentan patrones sistemáticos evidentes. Esto sugiere que gran parte de la estructura de la serie fue capturada por los componentes de tendencia y estacionalidad, quedando únicamente variaciones aleatorias.
ADF1 <- adf.test(ventana1)
KPSS1 <- kpss.test(ventana1)
Tabla_estacionariedad1 <- data.frame(
Prueba = c("ADF", "KPSS"),
Estadistico = c(ADF1$statistic, KPSS1$statistic),
'Valor p' = c(ADF1$p.value, KPSS1$p.value),
'Hipótesis nula' = c("La serie no es estacionaria",
"La serie es estacionaria"),
'Conclusión' = c("No se rechaza H0",
"Se rechaza H0"))
ADF1$statistic <- round(ADF1$statistic, 2)
KPSS1$statistic <- round(KPSS1$statistic, 2)
ADF1$p.value <- round(ADF1$p.value, 2)
KPSS1$p.value <- round(KPSS1$p.value, 2)
datatable(
Tabla_estacionariedad1,
caption = "Resultados de las pruebas de estacionariedad (ADF y KPSS) - Ventana original",
filter = "top",
rownames = FALSE,
extensions = "Buttons",
options = list(
pageLength = 15,
dom = "Bfrtip",
buttons = c("copy", "csv", "excel"),
scrollX = TRUE))La serie original no era estacionaria según las pruebas ADF y KPSS, por lo que fue necesario aplicar una diferenciación de primer orden.
Numero_difer <- ndiffs(ventana1)
Ns_difer <- nsdiffs(ventana1)
ventana1_d1 <- diff(ventana1)
Serie_diff <- data.frame(
Fecha = as.Date(fechas_ventana1[-1]),
Humedad = as.numeric(ventana1_d1))
p5 <- ggplot(Serie_diff, aes(Fecha, Humedad)) +
geom_line() +
scale_x_date(
date_breaks = "2 month",
date_labels = "%d %b") +
labs(
title = "Serie de Humedad después de la Diferenciación",
x = NULL,
y = "Humedad Diferenciada") +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 14, hjust = 0.5),
axis.title.y = element_text(color = "gray40", hjust = 1, face = "bold"),
axis.title.x = element_text(color = "gray40", hjust = 1, face = "bold"),
axis.text.y = element_text(color = "gray40", size = 13, hjust = 1),
axis.text.x = element_text(color = "gray40", size = 11, hjust = 1),
panel.grid.major.x = element_blank(),
panel.grid.major.y = element_line(linewidth = .2),
panel.grid.minor = element_blank(),
legend.title = element_blank(),
legend.background = element_blank(),
plot.margin = margin(15, 20, 10, 10),
axis.line = element_line(color = "#333333"))
ggplotly(p5) %>%
layout(showlegend = FALSE,
hovermode = "x unified",
margin = list(t = 90),
font = list(family = "Segoe UI"))La serie diferenciada presenta fluctuaciones alrededor de cero sin tendencia aparente, lo cual puede observarse en la gráfica de la primera diferencia. Además:
Estos resultados confirman que la serie diferenciada es estacionaria y adecuada para el ajuste de modelos ARIMA.
ADF2 <- adf.test(ventana1_d1)
KPSS2 <- kpss.test(ventana1_d1)
grid.arrange(ggAcf(ventana1_d1),
ggPacf(ventana1_d1),
nrow=1)La grafica de autocorrelación (ACF) de la serie diferenciada presenta un primer rezago con una autocorrelación alta negativa que supera los límites de confianza, mientras que en los rezagos siguientes los coeficientes disminuyen rápidamente, y la mayoría permanece en el rango esperado. También aparecen algunos datos aislados alrededor de los rezagos 7, 14 y 21, lo que significa que todavía existe un comportamiento periódico por la dinámica semanal de la serie.
El resultado indica que la diferenciación logró que la serie perdiera gran parte de la dependencia que tenía y ahora presenta un comportamiento mucho más estable, lo que facilita el ajuste de un modelo ARIMA.
Por otro lado, la gráfica de autocorrelación parcial (PACF) de la serie diferenciada indica que la mayor parte de la información se encuentra concentrada en los primeros rezagos. El primero muestra una relación negativa importante y, a partir de ahí, los demás valores se vuelven mucho más pequeños. La mayoría de valores permanecen dentro de los límites de confianza, por tanto, no es necesario considerar un componente autorregresivo de orden elevado.
Estas herramientas dieron lugar a configuraciones iniciales como ARIMA (1, 1, 0), ARIMA (0, 1, 1) y ARIMA (1, 1, 1). Asimismo, algunos candidatos incorporan un componente estacional debido a la presencia de un patrón cíclico en la serie. Dicha estacionalidad responde al ciclo semanal, por lo que también se incluyeron modelos SARIMA: representan el comportamiento repetitivo y la dependencia temporal a corto plazo de la humedad relativa.
modelo_0 <- forecast::auto.arima(
ventana1,
seasonal = TRUE,
stepwise = FALSE,
approximation = FALSE)
modelo1 <- forecast::Arima(ventana1, order = c(1,1,0))
modelo2 <- forecast::Arima(ventana1, order = c(0,1,1))
modelo3 <- forecast::Arima(ventana1, order = c(1,1,1))
modelo4 <- forecast::Arima(ventana1,
order = c(1,1,0),
seasonal = c(0,0,1))
modelo5 <- forecast::Arima(ventana1,
order = c(0,1,1),
seasonal = c(0,0,1))comparacion <- data.frame(
Modelo = c("ARIMA(1,1,0)[7]", "ARIMA(0,1,1)[7]", "ARIMA(1,1,1)[7]",
"ARIMA(1,1,0)(0,0,1)[7]", "ARIMA(0,1,1)(0,0,1)[7]", "AUTO.ARIMA"),
AIC = c(AIC(modelo1), AIC(modelo2), AIC(modelo3),
AIC(modelo4), AIC(modelo5), AIC(modelo_0)),
BIC = c(BIC(modelo1), BIC(modelo2), BIC(modelo3),
BIC(modelo4), BIC(modelo5), BIC(modelo_0)))
comparacion$AIC <- round(comparacion$AIC, 2)
comparacion$BIC <- round(comparacion$BIC, 2)
comparacion <- comparacion[order(comparacion$AIC), ]
comparacion %>%
kbl(caption = "Comparación de Modelos ARIMA (Menor AIC/BIC es mejor)", align = "c") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE) %>%
row_spec(1, bold = TRUE, color = "white", background = "#2c3e50")| Modelo | AIC | BIC | |
|---|---|---|---|
| 6 | AUTO.ARIMA | 1544.89 | 1559.31 |
| 3 | ARIMA(1,1,1)[7] | 1545.20 | 1556.02 |
| 2 | ARIMA(0,1,1)[7] | 1567.45 | 1574.66 |
| 5 | ARIMA(0,1,1)(0,0,1)[7] | 1568.60 | 1579.42 |
| 1 | ARIMA(1,1,0)[7] | 1583.37 | 1590.58 |
| 4 | ARIMA(1,1,0)(0,0,1)[7] | 1583.61 | 1594.43 |
El modelo AUTO.ARIMA obtuvo el menor valor de AIC (1544.889), mientras que el modelo ARIMA(1,1,1) presentó el menor valor de BIC (1556.017). Ambos modelos mostraron un desempeño muy similar.
extraer_residuos <- function(modelo, nombre_modelo) {
test_res <- checkresiduals(modelo, plot = FALSE)
data.frame(
Modelo = nombre_modelo,
'Estadistico Q' = round(test_res$statistic, 2),
'Grados de libertad' = test_res$parameter,
'Valor p' = round(test_res$p.value, 4),
'Autocorrelacion' = ifelse(
test_res$p.value < 0.05,
"Sí (Falla)",
"No (Pasa/Ruido Blanco)"))}
tabla_residuos <- rbind(
extraer_residuos(modelo1, "ARIMA(1,1,0)"),
extraer_residuos(modelo2, "ARIMA(0,1,1)"),
extraer_residuos(modelo3, "ARIMA(1,1,1)"),
extraer_residuos(modelo4, "ARIMA(1,1,0)(0,0,1)[7]"),
extraer_residuos(modelo5, "ARIMA(0,1,1)(0,0,1)[7]"),
extraer_residuos(modelo_0, "AUTOARIMA"))##
## Ljung-Box test
##
## data: Residuals from ARIMA(1,1,0)
## Q* = 24.488, df = 13, p-value = 0.02692
##
## Model df: 1. Total lags used: 14
##
##
## Ljung-Box test
##
## data: Residuals from ARIMA(0,1,1)
## Q* = 26.678, df = 13, p-value = 0.01377
##
## Model df: 1. Total lags used: 14
##
##
## Ljung-Box test
##
## data: Residuals from ARIMA(1,1,1)
## Q* = 4.0628, df = 12, p-value = 0.9823
##
## Model df: 2. Total lags used: 14
##
##
## Ljung-Box test
##
## data: Residuals from ARIMA(1,1,0)(0,0,1)[7]
## Q* = 22.209, df = 12, p-value = 0.03524
##
## Model df: 2. Total lags used: 14
##
##
## Ljung-Box test
##
## data: Residuals from ARIMA(0,1,1)(0,0,1)[7]
## Q* = 24.33, df = 12, p-value = 0.01834
##
## Model df: 2. Total lags used: 14
##
##
## Ljung-Box test
##
## data: Residuals from ARIMA(1,1,1)(0,0,1)[7]
## Q* = 2.679, df = 11, p-value = 0.9943
##
## Model df: 3. Total lags used: 14
tabla_residuos %>%
kbl(caption = "Comparación de Modelos ARIMA", align = "c") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)| Modelo | Estadistico.Q | Grados.de.libertad | Valor.p | Autocorrelacion | |
|---|---|---|---|---|---|
| Q* | ARIMA(1,1,0) | 24.49 | 13 | 0.0269 | Sí (Falla) |
| Q*1 | ARIMA(0,1,1) | 26.68 | 13 | 0.0138 | Sí (Falla) |
| Q*2 | ARIMA(1,1,1) | 4.06 | 12 | 0.9823 | No (Pasa/Ruido Blanco) |
| Q*3 | ARIMA(1,1,0)(0,0,1)[7] | 22.21 | 12 | 0.0352 | Sí (Falla) |
| Q*4 | ARIMA(0,1,1)(0,0,1)[7] | 24.33 | 12 | 0.0183 | Sí (Falla) |
| Q*5 | AUTOARIMA | 2.68 | 11 | 0.9943 | No (Pasa/Ruido Blanco) |
El diagnóstico mediante la prueba de Ljung-Box mostró que únicamente los modelos ARIMA(1,1,1) y AUTO.ARIMA producen residuos compatibles con ruido blanco. Los modelos con p-valor superior a 0.05 no presentan autocorrelación significativa en sus residuos, por lo que cumplen el supuesto de ruido blanco.
##
## Ljung-Box test
##
## data: Residuals from ARIMA(1,1,0)
## Q* = 24.488, df = 13, p-value = 0.02692
##
## Model df: 1. Total lags used: 14
Existe autocorrelación residual significativa, lo que indica que el modelo no captura toda la dinámica de la serie.
##
## Ljung-Box test
##
## data: Residuals from ARIMA(0,1,1)
## Q* = 26.678, df = 13, p-value = 0.01377
##
## Model df: 1. Total lags used: 14
Presenta mayor evidencia de autocorrelación residual que el modelo anterior, lo que indica un ajuste insuficiente.
##
## Ljung-Box test
##
## data: Residuals from ARIMA(1,1,1)
## Q* = 4.0628, df = 12, p-value = 0.9823
##
## Model df: 2. Total lags used: 14
Los residuos se comportan como ruido blanco: es el mejor modelo entre los no estacionales.
##
## Ljung-Box test
##
## data: Residuals from ARIMA(1,1,0)(0,0,1)[7]
## Q* = 22.209, df = 12, p-value = 0.03524
##
## Model df: 2. Total lags used: 14
Los residuos oscilan alrededor de cero y no muestran tendencias evidentes. Sin embargo, en la ACF aparece al menos un rezago significativo fuera de las bandas de confianza. Por otro lado, el test de Ljung-Box arroja p-value = 0.035, menor que 0.05, por lo que se rechaza la hipótesis de independencia de los residuos.
##
## Ljung-Box test
##
## data: Residuals from ARIMA(0,1,1)(0,0,1)[7]
## Q* = 24.33, df = 12, p-value = 0.01834
##
## Model df: 2. Total lags used: 14
Los residuos presentan un comportamiento similar al Modelo 4. Se observan varios rezagos cercanos o ligeramente fuera de las bandas de confianza. El test de Ljung-Box da p-value = 0.018, indicando autocorrelación residual significativa.
##
## Ljung-Box test
##
## data: Residuals from ARIMA(1,1,1)(0,0,1)[7]
## Q* = 2.679, df = 11, p-value = 0.9943
##
## Model df: 3. Total lags used: 14
Los residuos se distribuyen aleatoriamente alrededor de cero y mantienen una varianza relativamente constante.La mayoría de las autocorrelaciones se encuentran dentro de las bandas de confianza. El histograma muestra una distribución aproximadamente normal, aunque con ligera asimetría positiva.
comp_accuracy <- rbind(
accuracy(modelo1),
accuracy(modelo2),
accuracy(modelo3),
accuracy(modelo4),
accuracy(modelo5),
accuracy(modelo_0))
rownames(comp_accuracy) <- c(
"ARIMA(1,1,0)",
"ARIMA(0,1,1)",
"ARIMA(1,1,1)",
"ARIMA(1,1,0)(0,0,1)[7]",
"ARIMA(0,1,1)(0,0,1)[7]",
"AUTOARIMA")
df_accuracy <- as.data.frame(comp_accuracy)
df_accuracy %>%
kable(caption = "4.4.3 Métricas de Ajuste y Precisión de los Modelos ARIMA",
digits = 4,
align = "ccccccc") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"),
full_width = FALSE,
position = "center") %>%
row_spec(0, bold = TRUE, color = "white", background = "#2C3E50") %>% # Encabezado elegante
row_spec(6, bold = TRUE, background = "#E2EFDA")| ME | RMSE | MAE | MPE | MAPE | MASE | ACF1 | |
|---|---|---|---|---|---|---|---|
| ARIMA(1,1,0) | 0.0755 | 4.4033 | 3.2736 | -0.1999 | 5.5377 | 0.6986 | -0.0472 |
| ARIMA(0,1,1) | 0.1058 | 4.2742 | 3.1822 | -0.2147 | 5.3921 | 0.6791 | 0.1371 |
| ARIMA(1,1,1) | 0.1886 | 4.0844 | 2.9934 | -0.1021 | 5.0602 | 0.6388 | 0.0047 |
| ARIMA(1,1,0)(0,0,1)[7] | 0.0733 | 4.3887 | 3.2847 | -0.2003 | 5.5596 | 0.7010 | -0.0467 |
| ARIMA(0,1,1)(0,0,1)[7] | 0.1010 | 4.2676 | 3.1865 | -0.2157 | 5.4010 | 0.6800 | 0.1289 |
| AUTOARIMA | 0.1916 | 4.0667 | 2.9897 | -0.0944 | 5.0585 | 0.6380 | 0.0023 |
Los indicadores de error muestran nuevamente una ventaja para los modelos ARIMA(1,1,1) y AUTO.ARIMA. El modelo AUTO.ARIMA presenta los menores errores RMSE, MAE y MAPE, aunque la diferencia respecto al ARIMA(1,1,1) es mínima.
##
## Ljung-Box test
##
## data: Residuals from ARIMA(1,1,1)(0,0,1)[7]
## Q* = 2.679, df = 11, p-value = 0.9943
##
## Model df: 3. Total lags used: 14
El modelo seleccionado fue AUTO.ARIMA, equivalente a un modelo ARIMA(1,1,1)(0,0,1)[7]. Se escogió debido a:
La gráfica de pronóstico del modelo ARIMA (1,1,1)(0,0,1)[7]
muestra que la humedad relativa tendería a mantenerse estable durante
los periodos futuros, con valores cercanos al promedio observado en la
serie histórica. La línea de predicción presenta un comportamiento
prácticamente horizontal, sin evidenciar tendencias crecientes o
decrecientes significativas, lo que sugiere que el modelo espera una
estabilidad de la variable en el corto plazo.
Asimismo, los intervalos de confianza se amplían gradualmente conforme aumenta el horizonte de pronóstico, reflejando el incremento natural de la incertidumbre asociado a las predicciones realizadas para periodos más lejanos. Este comportamiento es característico de los modelos de series de tiempo y evidencia que la precisión disminuye a medida que se realizan pronósticos a mayor plazo.
Tabla_accuracy <- as.data.frame(
accuracy(
as.numeric(pronostico1$mean),
as.numeric(ventana2)))
datatable(
Tabla_accuracy,
caption = "Métricas de precisión del pronóstico",
filter = "top",
rownames = FALSE,
extensions = "Buttons",
options = list(
pageLength = 10,
dom = "Bfrtip",
buttons = c("copy", "csv", "excel"),
scrollX = TRUE))En cuanto al desempeño predictivo, el modelo obtuvo un RMSE de 8.2807, un MAE de 7.5992 y un MAPE de 13.96 %, lo que indica que, en promedio, las predicciones presentan un error porcentual cercano al 14 % respecto a los valores reales. De acuerdo con la clasificación del MAPE, este resultado corresponde a un buen nivel de precisión.
La gráfica de pronóstico del modelo alternativo muestra que la humedad relativa se mantendría estable durante los periodos futuros, con valores cercanos al promedio observado en la serie histórica. La línea de predicción no presenta cambios bruscos ni una tendencia creciente o decreciente marcada, lo que indica que el modelo espera un comportamiento relativamente constante de la variable en el corto plazo.
Por otra parte, los intervalos de confianza se amplían conforme aumenta el horizonte de pronóstico, reflejando el incremento de la incertidumbre asociado a las predicciones más lejanas. Este comportamiento es esperado en los modelos de series de tiempo, ya que la precisión disminuye a medida que se proyectan más periodos hacia el futuro.
Tabla_accuracy2 <- as.data.frame(
accuracy(
as.numeric(pronostico2$mean),
as.numeric(ventana2)))
datatable(
Tabla_accuracy2,
caption = "Métricas de precisión del pronóstico con modelo alternativo",
filter = "top",
rownames = FALSE,
extensions = "Buttons",
options = list(
pageLength = 10,
dom = "Bfrtip",
buttons = c("copy", "csv", "excel"),
scrollX = TRUE))En cuanto a la exactitud del modelo, las métricas obtenidas fueron un RMSE de 8.2854, un MAE de 7.6091 y un MAPE de 13.98 %, lo que indica que, en promedio, las predicciones presentan un error porcentual cercano al 14 % respecto a los valores reales. Este resultado se considera un buen nivel de precisión, ya que el MAPE se encuentra entre el 10 % y el 20 %. Sin embargo, al compararlo con el modelo ARIMA (1,1,1)(0,0,1)[7], este presenta errores ligeramente mayores, razón por la cual fue descartado como modelo final, aunque sigue siendo una alternativa válida para realizar pronósticos de la serie.
Comparacion_grafica <- data.frame(
Fecha = seq(as.Date("2018-12-01"),
by = "day",
length.out = length(ventana2)),
Real = as.numeric(ventana2),
Pronostico = as.numeric(pronostico1$mean),
Inferior = as.numeric(pronostico1$lower),
Superior = as.numeric(pronostico1$upper))
Real_esperado <- ggplot(Comparacion_grafica, aes(x = Fecha)) +
geom_ribbon(aes(ymin = Inferior, ymax = Superior), fill = "#29567522", alpha = .2) +
geom_line(aes(y = Real, colour = "Real"), linewidth = 1.2) +
geom_line(aes(y = Pronostico, colour = "Pronóstico"), linewidth = 1.2) +
scale_colour_manual(values = c("Real" = "#1F77B4",
"Pronóstico" = "#D62728")) +
scale_y_continuous(limits = c(50, 80),
breaks = seq(50, 80, by = 10), labels = function(x) paste0(x, "%")) +
scale_x_date(
date_breaks = "7 days",
date_labels = "%d %b") +
labs(
title = "Valores reales vs pronosticados",
x = NULL,
y = "Humedad",
colour = "") +
theme_minimal() +
theme(
legend.position = "bottom",
plot.title = element_text(hjust = 0.5, face = "bold", color = "gray35"),
axis.title.y = element_text(color = "gray40", hjust = 1, face = "bold"),
axis.title.x = element_blank(),
axis.text.y = element_text(color = "gray40", size = 13, hjust = 1),
axis.text.x = element_text(color = "gray40", size = 8.5, hjust = 1),
panel.grid.major.x = element_blank(),
panel.grid.major.y = element_line(linewidth = .2),
panel.grid.minor = element_blank(),
legend.title = element_blank(),
legend.background = element_blank(),
plot.margin = margin(15, 20, 10, 10))
ggplotly(Real_esperado) %>%
layout(font = list(family = "Segoe UI"),
margin = list(t = 80))
La gráfica de pronóstico muestra que el modelo estima una
humedad promedio cercana al 62% para los períodos futuros. Además, los
intervalos de confianza se amplían conforme aumenta el horizonte de
predicción, reflejando la incertidumbre inherente al proceso de
pronóstico.
Ademas, la humedad relativa muestra una tendencia descendente durante diciembre, acompañada de gran variabilidad: en conjunto, estos dos elementos impiden que el modelo sea capaz de anticipar grandes cambios, subestimando la dinámica ambiental del periodo de testeo. Pese a lo anterior, es importante recalcar que el modelo no falla del todo: los intervalos de confianza logran contener las fluctuaciones de la humedad casi que en su totalidad.
Al comparar las predicciones con los datos reales de la ventana de prueba, se observa que el modelo logra capturar el nivel general de la serie, aunque presenta dificultades para reproducir las fluctuaciones bruscas observadas en algunos días. Esto es normal en series meteorológicas, donde existen variaciones aleatorias difíciles de anticipar mediante modelos ARIMA.
El análisis realizado permitió comprender el comportamiento de la humedad relativa en la ciudad de Cali durante el periodo estudiado y evaluar qué tan bien un modelo ARIMA puede representar este tipo de información.
En primer lugar, se observó que la serie presenta variaciones continuas entre aproximadamente el 45 % y el 75 %, pero sin una tendencia creciente o decreciente claramente definida a largo plazo. Lo cual tiene sentido, ya que Cali no tiene estaciones térmicas marcadas como sí ocurre en otras latitudes, en su clima tropical las variaciones responden principalmente a los cambios diarios y a los periodos de lluvia característicos de la región.
La descomposición STL fue útil para confirmar algo que ya se sospechaba: buena parte de esa variabilidad responde a un patrón estacional semanal bastante marcado. La tendencia, por su parte, mostró subidas y bajadas graduales mes a mes, y los residuos no dejaron ver ningún patrón diferente adicional. Esto sugiere que la descomposición logró separar adecuadamente los principales componentes de la serie.
Las pruebas de Dickey-Fuller Aumentada (ADF) y KPSS indicaron que la serie original no era estacionaria. Por esta razón fue necesario aplicar una diferenciación de primer orden. Después de realizar esta transformación, ambas pruebas confirmaron que la serie ya cumplía el supuesto de estacionariedad, condición indispensable para ajustar modelos ARIMA de manera adecuada. Entre los modelos evaluados, el seleccionado automáticamente mediante auto.arima() obtuvo el mejor desempeño general, correspondiendo a un modelo ARIMA(1,1,1)(0,0,1)[7]. No solo tuvo el AIC más bajo y los errores de pronóstico más pequeños (RMSE, MAE, MAPE), adicionalmente, el análisis de residuos mostró un comportamiento compatible con ruido blanco, respaldado por la prueba de Ljung-Box (p-valor = 0.9943). En otras palabras: el modelo logró capturar la estructura temporal de la serie sin dejar auto correlación suelta.
Los pronósticos obtenidos mostraron un comportamiento estable y cercano al promedio histórico de la serie, como se esperaba del modelo, los intervalos de confianza aumentaron conforme se amplió el horizonte de predicción, reflejando el incremento natural de la incertidumbre. El MAPE quedó cerca del 14%, lo que se considera un nivel de error aceptable tratándose de una variable meteorológica, donde siempre hay algo de aleatoriedad que ningún modelo puede anticipar del todo.
Finalmente, este trabajo nos permitió comprobar que los modelos ARIMA constituyen una herramienta útil para analizar y pronosticar series temporales de humedad relativa. Aunque los resultados obtenidos fueron satisfactorios, futuras investigaciones podrían incorporar variables como la temperatura o la precipitación para construir modelos con mayor capacidad explicativa, así como evaluar metodologías más avanzadas que representen de forma explícita patrones estacionales de mayor duración.
Cantor, D. F. (2017). Aplicación de modelo ARIMA para el análisis de series de volúmenes anuales en el río Magdalena. Boletín de Geología. Recuperado de: http://www.scielo.org.co/scielo.php?script=sci_arttext&pid=S0123-921X2017000200007
Corporación Autónoma Regional del Valle del Cauca. (5 de diciembre de 2018). EN LA REGIÓN ANDINA DEL VALLE LAS LLUVIAS SUPERARON EN UN 27% LO ESPERADO. Obtenido de Corporación Autónoma Regional del Valle del Cauca: https://www.cvc.gov.co/lluviasnoviembre2018
Equipo de AlorAir. (17 de diciembre de 2024). ¿Por qué hay más humedad por la noche? Obtenido de AlorAir: https://www.alorair.com/blog/why-is-it-more-humid-at-night/
Escalante-Sandoval, C., & Reyes-Chávez, L. (2016). Predicción de variables meteorológicas por medio de modelos ARIMA. Agrociencia / Revista mexicana. Recuperado de: https://www.scielo.org.mx/scielo.php?script=sci_arttext&pid=S1405-31952016000100001
Instituto de Hidrología, Meteorología y Estudios Ambientales [IDEAM]. (2017). Hoja metodológica del indicador: Promedio de la humedad relativa (Versión 1.1). Bogotá, Colombia: IDEAM. Recuperado de: https://bart.ideam.gov.co/indiecosistemas/ind/clima/hm/HM_humedad_relativa.pdf
Noble, J. (s.f.). Presentamos los modelos ARIMA. Obtenido de IBM: https://www.ibm.com/es-es/think/topics/arima-model
Oficina de Gestión Ambiental de la Sede Bogotá de la Universidad Nacional de Colombia. (s.f.). Humedad relativa. Obtenido de Universidad Nacional de Colombia: https://ogabogota.unal.edu.co/humedad-relativa/
Oficina de pronóstico del tiempo. (s.f.). Un análisis del vapor de agua, la humedad y el punto de rocío, y su relación con la precipitación. Obtenido de Administración Nacional Oceánica y Atmosférica de los Estados Unidos: https://www.weather.gov/lmk/humidity
Palomino Parra, J. A., Torres Cruz, O. A., & Angulo Méndez, Y. L. (2020). Dispositivo basado en modelo ARIMA para predicción de variables ambientales (temperatura, humedad, velocidad del aire) en el área agrícola del departamento del Meta. Revista GEON (Gestión, Organizaciones y Negocios), 7(2), 1-12. https://doi.org/10.22579/23463910.193
Saadeddin, Z. (10 de septiembre de 2024). ARIMA para la previsión de series temporales: Guía completa. Obtenido de DataCamp, Inc: https://www.datacamp.com/es/tutorial/arima
Servicio Geológico Colombiano. (19 de septiembre de 2018). Comunicado Nacional de las Condiciones Actuales de El Niño-La Niña - Septiembre de 2018. Obtenido de Portal Servicio Geológico Colombiano: https://www2.sgc.gov.co/Noticias/Paginas/Comunicado-Nacional-de-las-Condiciones-Actuales-de-El-Ni%C3%B1o-La-Ni%C3%B1a---Septiembre-de-2018.aspx
Tu Tiempo. (s.f.). Clima Cali / Alfonso Bonillaaragon. Obtenido de Tutiempo Network: https://www.tutiempo.net/clima/09-2018/ws-802590.html
Tu Tiempo. (s.f.). Clima Cali / Alfonso Bonillaaragon. Obtenido de Tutiempo Network: https://www.tutiempo.net/clima/08-2018/ws-802590.html
Weather Atlas. (s.f.). Clima y previsión meteorológica mensual: Cali, Colombia. Recuperado de: https://www.weather-atlas.com/es/colombia/cali-clima