CEMEX Colombia S.A., filial de la multinacional mexicana CEMEX, es uno de los principales actores del sector cementero en el país. Con una sólida presencia nacional, CEMEX destaca por su capacidad de producción, su aporte a proyectos de infraestructura y su influencia directa en el desarrollo de la construcción y la industria regional.
En este estudio, tomamos a CEMEX Colombia como referencia para analizar cómo las tendencias en la producción de cemento, las licencias de construcción y el Índice de Producción Industrial Regional reflejan y afectan el comportamiento del sector industrial colombiano.
El sector industrial colombiano, y en particular la industria del cemento, juega un papel estratégico como termómetro de la actividad económica y del desarrollo de infraestructura en el país. El cemento es un insumo esencial en la construcción, cuyo comportamiento se ve directamente influenciado por la dinámica de las licencias de construcción otorgadas y por el desempeño del Índice de Producción Industrial Regional (IPIR).
En este análisis se seleccionaron tres variables fundamentales para evaluar el desempeño sectorial y proyectar tendencias a futuro:
Producción Nacional de Cemento (PNCEM): Indicador directo de la actividad manufacturera y la demanda del sector construcción.
Licencias de Construcción (LICC): Refleja la dinámica de nuevos proyectos, y es un predictor adelantado de la demanda de materiales.
Índice de Producción Industrial Regional (IPIR): Permite monitorear la evolución y el dinamismo del sector industrial a nivel regional, siendo un termómetro de la economía real.
La selección de estas variables responde a su alta correlación histórica y relevancia para la toma de decisiones estratégicas, tanto para la planeación sectorial como para la formulación de políticas públicas y empresariales. Además, el análisis conjunto de estas series permite entender los ciclos económicos, anticipar cambios en la demanda y evaluar el impacto de choques exógenos como la pandemia, variaciones en la inversión pública o reformas regulatorias.
Metodología: Descomposición STL y Modelado ARIMA Y SARIMA
Descomposición STL La descomposición STL (Seasonal-Trend decomposition using Loess) es una técnica robusta y flexible para descomponer series temporales en tres componentes principales:
Tendencia (Trend): Captura los cambios estructurales y la evolución de largo plazo.
Estacionalidad (Seasonal): Identifica patrones recurrentes periódicos, como fluctuaciones mensuales o anuales.
Residuos (Remainder): Aisla las variaciones no explicadas por tendencia ni estacionalidad (ruido o eventos atípicos).
Este procedimiento es especialmente valioso para datos económicos y de producción, donde es común observar tendencias de crecimiento o decrecimiento junto a fuertes patrones estacionales. STL permite analizar cada componente por separado, facilitando la identificación de shocks, quiebres estructurales y la generación de señales para la planeación.
Para el pronóstico, se emplea el modelo ARIMA (AutoRegressive Integrated Moving Average), reconocido por su capacidad para capturar dependencias temporales, tendencias y componentes estocásticos en series económicas.
AutoRegresivo (AR): Relaciona el valor presente con sus valores pasados.
Integrado (I): Aplica diferenciaciones para lograr estacionariedad.
Media Móvil (MA): Modela el error como combinación lineal de errores pasados.
En casos de estacionalidad fuerte, se puede aplicar la variante SARIMA (ARIMA estacional) para capturar fluctuaciones periódicas adicionales.
La elección de los parámetros del modelo se basa en el análisis de autocorrelación, criterios de información (AIC/BIC) y validación cruzada, asegurando pronósticos robustos para los próximos cinco años. El modelo se calibra, valida y proyecta, evaluando la precisión mediante métricas como RMSE y análisis gráfico de residuos.
Instalar/Cargar librerias necesarias para el análisis
#Cargar librerías necesarias
library(readxl) # Para leer archivos Excel
library(tseries) # Para pruebas de estacionariedad
library(forecast) # Para modelado ARIMA y pronósticos
library(ggplot2) # Para visualización de datos
library(plotly) # Para gráficos interactivos
library(timetk) #timetk simplifica y acelera el análisis exploratorio, visualización, y preparación de datos temporales para modelado. Es ideal para quienes trabajan con series temporales en un flujo de trabajo "tidy" y buscan integrar análisis visuales, detección de patrones y forecasting en un solo paquete.
Cargar base de datos
library(readxl)
data_col <- read_excel("Base Caso2.xlsx")
PASO INDISPENSABLE: Declarar la (s) variable (s) como serie (s) temporal (es):
Variable 1
# Convertir/declarar variable 1=PNCEM en serie de tiempo mensual
variable1_ts <- ts(data_col$PNCEM, start = c(2012, 1), frequency = 12)
Variable 2
# Convertir/declarar el LICC en serie de tiempo mensual
variable2_ts <- ts(data_col$LICC, start = c(2012, 1), frequency = 12)
Variable 3
# Convertir/declarar las exportaciones de combustibles en serie de tiempo mensual
variable3_ts <- ts(data_col$IPIR, start = c(2012, 1), frequency = 12)
Gráfico inicial de la variable 1 en niveles -Original
library(ggplot2)
library(plotly)
# Convertir la serie temporal a un vector numérico para lograr graficar con ggplot2
data_col$variable1 <- as.numeric(variable1_ts)
# Crear el gráfico
grafico_serie <- ggplot(data_col, aes(x = seq.Date(from = as.Date("2012-01-01"), by = "month", length.out = nrow(data_col)),
y = variable1)) +
geom_line(color = "grey", linewidth = 0.4) + # Cambiado 'size' por 'linewidth'
geom_point(color = "black", size = 0.1) +
ggtitle("Variable 1: Serie original grafico 1") +
xlab("Tiempo") +
ylab("Unidad Variable 1") +
theme_minimal()
ggplotly(grafico_serie)
La serie original (grafico 1) de la Variable 1 muestra una tendencia general creciente a lo largo del periodo analizado, con variaciones estacionales evidentes y un impacto negativo atípico alrededor del año 2020, probablemente asociado a la pandemia de COVID-19. Tras este choque, la serie recupera rápidamente su nivel previo, reflejando la resiliencia del sector. El comportamiento reciente sugiere cierta estabilización, aunque persisten fluctuaciones estacionales que deben ser consideradas en el modelado y pronóstico.
Extracción señales variable 1
# Cargar librerías necesarias
library(ggplot2)
library(plotly)
# Descomposición de la serie temporal
stl_decomp_var1 <- stl(variable1_ts, s.window = "periodic")
# Convertir la descomposición a un data frame para graficar con ggplot2
stl_df_var1 <- data.frame(
Time = rep(time(variable1_ts), 4), # Tiempo repetido para cada componente (son 4 componentes)
Value = c(stl_decomp_var1$time.series[, "seasonal"],
stl_decomp_var1$time.series[, "trend"],
stl_decomp_var1$time.series[, "remainder"],
variable1_ts),
Component = rep(c("Estacional", "Tendencia", "Residuo", "Serie Original"), each = length(variable1_ts))
)
# Crear gráfico con ggplot2
p <- ggplot(stl_df_var1, aes(x = Time, y = Value, color = Component)) +
geom_line() +
facet_wrap(~Component, scales = "free_y", ncol = 1) +
theme_minimal() +
labs(title = "Descomposición temporal de la variable 1 grafico 2",
x = "Tiempo",
y = "Valor")
# Convertir a gráfico interactivo con plotly
ggplotly(p)
La descomposición temporal (grafico 2) evidencia una marcada estacionalidad en la variable 1, con patrones recurrentes cada año. La tendencia muestra un crecimiento sostenido hasta 2020, cuando se observa una caída abrupta seguida de una recuperación progresiva. Los residuos se mantienen mayoritariamente estables, salvo por el shock negativo en 2020, lo que confirma que la mayor parte de la variabilidad es explicada por los componentes de tendencia y estacionalidad.
Extracción señales variable 2
# Cargar librerías necesarias
library(ggplot2)
library(plotly)
# Descomposición de la serie temporal
stl_decomp_var2 <- stl(variable2_ts, s.window = "periodic")
# Convertir la descomposición a un data frame para graficar con ggplot2
stl_df_var2 <- data.frame(
Time = rep(time(variable2_ts), 4), # Tiempo repetido para cada componente
Value = c(stl_decomp_var2$time.series[, "seasonal"],
stl_decomp_var2$time.series[, "trend"],
stl_decomp_var2$time.series[, "remainder"],
variable2_ts),
Component = rep(c("Estacional", "Tendencia", "Residuo", "Serie Original"), each = length(variable2_ts))
)
# Crear gráfico con ggplot2
p <- ggplot(stl_df_var2, aes(x = Time, y = Value, color = Component)) +
geom_line() +
facet_wrap(~Component, scales = "free_y", ncol = 1) +
theme_minimal() +
labs(title = "Descomposición temporal de la variable 2 grafico 3",
x = "Tiempo",
y = "Valor")
# Convertir a gráfico interactivo con plotly
ggplotly(p)
Esta descomposición muestra que la variable 2 (grafico 3) tiene patrones estacionales muy claros, es decir, picos que se repiten cada año. La tendencia general sube al inicio, pero después cae y solo tiene una recuperación puntual alrededor de 2020. Aunque hay algunas variaciones inesperadas, la mayor parte de los cambios se explican por esos ciclos regulares y la tendencia general.
Extracción señales variable 3
# Cargar librerías necesarias
library(ggplot2)
library(plotly)
# Descomposición de la serie temporal
stl_decomp_var3 <- stl(variable3_ts, s.window = "periodic")
# Convertir la descomposición a un data frame para graficar con ggplot2
stl_df_var3 <- data.frame(
Time = rep(time(variable3_ts), 4), # Tiempo repetido para cada componente
Value = c(stl_decomp_var3$time.series[, "seasonal"],
stl_decomp_var3$time.series[, "trend"],
stl_decomp_var3$time.series[, "remainder"],
variable3_ts),
Component = rep(c("Estacional", "Tendencia", "Residuo", "Serie Original"), each = length(variable3_ts))
)
# Crear gráfico con ggplot2
p <- ggplot(stl_df_var3, aes(x = Time, y = Value, color = Component)) +
geom_line() +
facet_wrap(~Component, scales = "free_y", ncol = 1) +
theme_minimal() +
labs(title = "Descomposición temporal de la variable 3 grafico 4",
x = "Tiempo",
y = "Valor")
# Convertir a gráfico interactivo con plotly
ggplotly(p)
En esta gráfica se ve que la variable 3 (grafico 4) sigue un patrón que se repite todos los años. Además, la tendencia general muestra que hubo una caída fuerte alrededor de 2020, pero después la variable se recuperó y se ha mantenido estable. Estos ciclos y cambios ayudan a entender cómo ha evolucionado la variable en el tiempo.
Después de la descomposición temporal de cada variable, se extrae la variable ajustada por estacionalidad para graficarla junto con la serie original:
Se crea la variable1 ajustada por estacionalidad
# Extraer los componentes de la descomposición
variable1_sa <- variable1_ts - stl_decomp_var1$time.series[, "seasonal"]
Se crea la variable2 ajustada por estacionalidad
# Extraer los componentes de la descomposición
variable2_sa <- variable2_ts - stl_decomp_var2$time.series[, "seasonal"]
Se crea la variable3 ajustada por estacionalidad
# Extraer los componentes de la descomposición
variable3_sa <- variable3_ts - stl_decomp_var3$time.series[, "seasonal"]
Ahora si se puede graficar las series originales versus la ajustada por estacionalidad
Gráfico serie original VS ajustada Variable 1
library(ggplot2)
library(plotly)
library(tidyr)
library(dplyr)
# Crear vector de fechas
fechas_var1 <- seq.Date(from = as.Date("2012-01-01"), by = "month", length.out = length(variable1_ts))
# Armar data frame en formato largo para que la leyenda se genere automáticamente
df_var1 <- data.frame(
Fecha = fechas_var1,
Serie_Original = variable1_ts,
Serie_Ajustada = variable1_sa
) %>%
pivot_longer(cols = c("Serie_Original", "Serie_Ajustada"),
names_to = "Serie",
values_to = "Valor")
# Gráfico con leyenda automática y colores diferenciados
grafico_ajustada_var1 <- ggplot(df_var1, aes(x = Fecha, y = Valor, color = Serie)) +
geom_line(size = 0.2) +
scale_color_manual(values = c("Serie_Original" = "grey", "Serie_Ajustada" = "black"),
labels = c("Serie Original", "Serie Ajustada por Estacionalidad")) +
ggtitle("Variable 1: Serie Original vs Ajustada por Estacionalidad grafico 5") +
xlab("Tiempo") +
ylab("Unidad de medida variable 1") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "top", # Leyenda en la parte superior
legend.title = element_blank())
# Convertir a gráfico interactivo
ggplotly(grafico_ajustada_var1)
En el gráfico 5 se comparan los valores originales de la variable 1 con los valores ajustados para eliminar los efectos de los ciclos estacionales. Se observa que, aunque hay variaciones a lo largo del tiempo, la tendencia general se mantiene similar y los grandes cambios, como la caída en 2020, siguen presentes en ambas líneas. Gráfico serie original VS ajustada Variable 2
library(ggplot2)
library(plotly)
library(tidyr)
library(dplyr)
# Crear vector de fechas
fechas_var2 <- seq.Date(from = as.Date("2012-01-01"), by = "month", length.out = length(variable2_ts))
# Armar data frame largo para la leyenda
df_var2 <- data.frame(
Fecha = fechas_var2,
Serie_Original = variable2_ts,
Serie_Ajustada = variable2_sa
) %>%
pivot_longer(cols = c("Serie_Original", "Serie_Ajustada"),
names_to = "Serie",
values_to = "Valor")
# Gráfico con leyenda diferenciada y colores correctos
grafico_ajustada_var2 <- ggplot(df_var2, aes(x = Fecha, y = Valor, color = Serie)) +
geom_line(size = 0.2) +
scale_color_manual(values = c("Serie_Original" = "grey", "Serie_Ajustada" = "black"),
labels = c("Serie Original", "Serie Ajustada por Estacionalidad")) +
ggtitle("Variable 2: Serie Original vs Ajustada por Estacionalidad grafico 6") +
xlab("Tiempo") +
ylab("Unidad de medida variable 2") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "top",
legend.title = element_blank())
# Convertir a gráfico interactivo
ggplotly(grafico_ajustada_var2)
El gráfico 6 muestra que, al quitar los efectos de los ciclos estacionales, la evolución general de la variable 2 sigue siendo muy parecida a la serie original. Los picos y caídas importantes se mantienen en ambas líneas, lo que permite analizar los cambios reales a lo largo del tiempo sin la influencia de patrones repetitivos de cada año.
Gráfico serie original VS ajustada Variable 3
library(ggplot2)
library(plotly)
library(tidyr)
library(dplyr)
# Crear vector de fechas
fechas_var3 <- seq.Date(from = as.Date("2012-01-01"), by = "month", length.out = length(variable3_ts))
# Armar data frame largo para la leyenda
df_var3 <- data.frame(
Fecha = fechas_var3,
Serie_Original = variable3_ts,
Serie_Ajustada = variable3_sa
) %>%
pivot_longer(cols = c("Serie_Original", "Serie_Ajustada"),
names_to = "Serie",
values_to = "Valor")
# Gráfico con leyenda diferenciada y colores correctos
grafico_ajustada_var3 <- ggplot(df_var3, aes(x = Fecha, y = Valor, color = Serie)) +
geom_line(size = 0.2) +
scale_color_manual(values = c("Serie_Original" = "grey", "Serie_Ajustada" = "black"),
labels = c("Serie Original", "Serie Ajustada por Estacionalidad")) +
ggtitle("Variable 3: Serie Original vs Ajustada por Estacionalidad grafico 7") +
xlab("Tiempo") +
ylab("Unidad de medida variable 3") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "top",
legend.title = element_blank())
# Convertir a gráfico interactivo
ggplotly(grafico_ajustada_var3)
En el gráfico 7 se puede ver que, al quitar los efectos de la estacionalidad, la tendencia general y los cambios importantes de la variable 3 se mantienen. La caída fuerte y la recuperación después de 2020 siguen siendo evidentes, lo que permite analizar la evolución real de la variable sin los patrones que se repiten cada año.
Ahora graficamos serie original vs tendencia
Primero se debe obtener la tendencia de cada variable y luego graficarla
Tendencia Variable 1
library(ggplot2)
library(plotly)
# Convertir la serie a un vector numérico
variable1_vec <- as.numeric(variable1_ts)
tendencia_var1 <- as.numeric(stl_decomp_var1$time.series[, "trend"])
# Crear secuencia de fechas
fechas <- seq.Date(from = as.Date("2012-01-01"), by = "month", length.out = length(variable1_ts))
# Armar dataframe para asegurar consistencia en leyenda
df_tendencia_var1 <- data.frame(
Fecha = fechas,
Serie_Original = variable1_vec,
Tendencia = tendencia_var1
) %>%
tidyr::pivot_longer(cols = c("Serie_Original", "Tendencia"),
names_to = "Serie",
values_to = "Valor")
# Gráfico interactivo de la serie original vs tendencia
grafico_tendencia_var1 <- ggplot(df_tendencia_var1, aes(x = Fecha, y = Valor, color = Serie)) +
geom_line(size = 0.2) +
scale_color_manual(values = c("Serie_Original" = "grey", "Tendencia" = "black"),
labels = c("Serie Original", "Tendencia")) +
ggtitle("Variable 1: Serie Original vs Tendencia grafico 8") +
xlab("Tiempo") +
ylab("Unidad de medida Variable 1") +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "top",
legend.title = element_blank()
)
# Convertir a gráfico interactivo con plotly
ggplotly(grafico_tendencia_var1)
En el gráfico 7 se compara la serie original de la variable 1 con su tendencia general. Se puede ver que, a pesar de las subidas y bajadas a lo largo del tiempo, la tendencia ayuda a identificar los cambios más importantes, como la caída alrededor de 2020 y la recuperación posterior.
Tendencia Variable 2
library(ggplot2)
library(plotly)
library(tidyr)
library(dplyr)
# Convertir la serie a un vector numérico
variable2_vec <- as.numeric(variable2_ts)
tendencia_var2 <- as.numeric(stl_decomp_var2$time.series[, "trend"])
# Crear secuencia de fechas
fechas <- seq.Date(from = as.Date("2012-01-01"), by = "month", length.out = length(variable2_ts))
# Armar dataframe en formato largo
df_tendencia_var2 <- data.frame(
Fecha = fechas,
Serie_Original = variable2_vec,
Tendencia = tendencia_var2
) %>%
pivot_longer(cols = c("Serie_Original", "Tendencia"),
names_to = "Serie",
values_to = "Valor")
# Gráfico interactivo de la serie original vs tendencia
grafico_tendencia_var2 <- ggplot(df_tendencia_var2, aes(x = Fecha, y = Valor, color = Serie)) +
geom_line(size = 0.2) +
scale_color_manual(values = c("Serie_Original" = "grey", "Tendencia" = "black"),
labels = c("Serie Original", "Tendencia")) +
ggtitle("Variable 2: Serie Original vs Tendencia grafico 9") +
xlab("Tiempo") +
ylab("Unidad de medida Variable 2") +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "top",
legend.title = element_blank()
)
# Convertir a gráfico interactivo con plotly
ggplotly(grafico_tendencia_var2)
En el gráfico 9 se observa la evolución de la variable 2 y su tendencia a lo largo del tiempo. Aunque la serie original presenta muchas subidas y bajadas, la línea de tendencia permite ver de forma más clara los cambios importantes, como el crecimiento inicial, la caída y el repunte alrededor de 2020, y la disminución en los años más recientes. Tendencia Variable 3
library(ggplot2)
library(plotly)
library(tidyr)
library(dplyr)
# Convertir la serie a un vector numérico
variable3_vec <- as.numeric(variable3_ts)
tendencia_var3 <- as.numeric(stl_decomp_var3$time.series[, "trend"])
# Crear secuencia de fechas
fechas <- seq.Date(from = as.Date("2012-01-01"), by = "month", length.out = length(variable3_ts))
# Armar dataframe en formato largo
df_tendencia_var3 <- data.frame(
Fecha = fechas,
Serie_Original = variable3_vec,
Tendencia = tendencia_var3
) %>%
pivot_longer(cols = c("Serie_Original", "Tendencia"),
names_to = "Serie",
values_to = "Valor")
# Gráfico interactivo de la serie original vs tendencia
grafico_tendencia_var3 <- ggplot(df_tendencia_var3, aes(x = Fecha, y = Valor, color = Serie)) +
geom_line(size = 0.2) +
scale_color_manual(values = c("Serie_Original" = "grey", "Tendencia" = "black"),
labels = c("Serie Original", "Tendencia")) +
ggtitle("Variable 3: Serie Original vs Tendencia grafico 10") +
xlab("Tiempo") +
ylab("Unidad de medida Variable 3") +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "top",
legend.title = element_blank()
)
# Convertir a gráfico interactivo con plotly
ggplotly(grafico_tendencia_var3)
En el gráfico 10 muestra la evolución de la variable 3 junto con su tendencia. Aunque la serie original tiene muchos altibajos, la tendencia permite ver que, después de una caída en 2020, hubo un fuerte crecimiento y luego cierta estabilidad. Ahora calculamos la tasa de crecimiento de la serie original vs tendencia:
Tasa de crecimiento de la serie de tendencia y original para la variable 1
#Cálculo de la tasa de crecimiento anual correctamente alineada
tasa_crecimiento_var1 <- (variable1_ts[(13:length(variable1_ts))] / variable1_ts[1:(length(variable1_ts) - 12)] - 1) * 100
tasa_tendencia_var1 <- (tendencia_var1[(13:length(tendencia_var1))] / tendencia_var1[1:(length(tendencia_var1) - 12)] - 1) * 100
# Crear vector de fechas corregido, es decir que inicie desde enero 2013
fechas_corregidas_var1 <- seq(from = as.Date("2013-01-01"), by = "month", length.out = length(tasa_crecimiento_var1))
# Verificar longitudes
print(length(fechas_corregidas_var1))
## [1] 144
print(length(tasa_crecimiento_var1))
## [1] 144
print(length(tasa_tendencia_var1))
## [1] 144
*Gráfico variable original y tendencia variable 1: tasa de crecimiento anual**
library(ggplot2)
library(plotly)
library(tidyr)
library(dplyr)
# Crear el dataframe base
df_var1 <- data.frame(
fecha = fechas_corregidas_var1,
crecimiento = tasa_crecimiento_var1,
tendencia = tasa_tendencia_var1
)
# Filtrar desde el tercer trimestre de 2022 (julio 2022 en adelante)
df_var1_post2022Q3 <- df_var1 %>%
filter(fecha >= as.Date("2022-07-01"))
# Pasar a formato largo para leyenda automática
df_var1_long <- df_var1_post2022Q3 %>%
pivot_longer(cols = c("crecimiento", "tendencia"),
names_to = "Serie",
values_to = "Valor")
# Etiquetas para la leyenda
labels_leyenda <- c(
crecimiento = "Tasa de Crecimiento Anual (Serie Original)",
tendencia = "Tasa de Crecimiento Anual (Tendencia)"
)
# Colores para la leyenda
colores_leyenda <- c(
crecimiento = "grey",
tendencia = "black"
)
# Graficar
grafico_crecimiento_var1_post2022Q3 <- ggplot(df_var1_long, aes(x = fecha, y = Valor, color = Serie, linetype = Serie)) +
geom_line(size = 0.4) +
scale_color_manual(values = colores_leyenda, labels = labels_leyenda) +
scale_linetype_manual(values = c(crecimiento = "solid", tendencia = "dashed"), labels = labels_leyenda) +
ggtitle("Variable 1: Tasa de crecimiento anual % (Desde 3T 2022) gráfico 11") +
xlab("Tiempo") +
ylab("% de Crecimiento Anual") +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "top",
legend.title = element_blank()
)
# Gráfico interactivo
ggplotly(grafico_crecimiento_var1_post2022Q3)
En el gráfico 11 muestra cómo ha cambiado el crecimiento anual de la variable 1 desde el tercer trimestre de 2022. Se observa que la tasa de crecimiento ha venido bajando y, en los últimos meses, la mayoría de los valores son negativos. Esto indica que la variable está teniendo una desaceleración y viene disminuyendo en comparación con el año anterior. Ahora calculamos la tasa de crecimiento de la serie original vs tendencia: variable 2
#Cálculo de la tasa de crecimiento anual correctamente alineada
tasa_crecimiento_var2 <- (variable2_ts[(13:length(variable2_ts))] / variable2_ts[1:(length(variable2_ts) - 12)] - 1) * 100
tasa_tendencia_var2 <- (tendencia_var2[(13:length(tendencia_var2))] / tendencia_var2[1:(length(tendencia_var2) - 12)] - 1) * 100
# Crear vector de fechas corregido
fechas_corregidas_var2 <- seq(from = as.Date("2013-01-01"), by = "month", length.out = length(tasa_crecimiento_var2))
# Verificar longitudes
print(length(fechas_corregidas_var2))
## [1] 144
print(length(tasa_crecimiento_var2))
## [1] 144
print(length(tasa_tendencia_var2))
## [1] 144
library(ggplot2)
library(plotly)
library(tidyr)
library(dplyr)
# Unir datos en un dataframe
df_var2 <- data.frame(
fecha = fechas_corregidas_var2,
crecimiento = tasa_crecimiento_var2,
tendencia = tasa_tendencia_var2
)
# Formato largo para leyenda automática
df_var2_long <- df_var2 %>%
pivot_longer(cols = c("crecimiento", "tendencia"),
names_to = "Serie",
values_to = "Valor")
# Etiquetas y colores personalizados
labels_leyenda <- c(
crecimiento = "Tasa de Crecimiento Anual (Serie Original)",
tendencia = "Tasa de Crecimiento Anual (Tendencia)"
)
colores_leyenda <- c(
crecimiento = "grey",
tendencia = "black"
)
# Gráfico mejorado
grafico_crecimiento_var2 <- ggplot(df_var2_long, aes(x = fecha, y = Valor, color = Serie, linetype = Serie)) +
geom_line(size = 0.2) +
scale_color_manual(values = colores_leyenda, labels = labels_leyenda) +
scale_linetype_manual(values = c(crecimiento = "solid", tendencia = "dashed"), labels = labels_leyenda) +
ggtitle("Variable 2:Tasa de crecimiento anual % de la serie Original y la Tendencia gráfico 12") +
xlab("Tiempo") +
ylab("% de Crecimiento Anual") +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "top",
legend.title = element_blank()
)
# Convertir a gráfico interactivo
ggplotly(grafico_crecimiento_var2)
En este gráfico se observa cómo ha cambiado el crecimiento anual de la variable 2 a lo largo del tiempo. Aunque hay varios picos y caídas, la línea de tendencia muestra que la mayor parte del tiempo el crecimiento se ha mantenido cerca de cero, con algunas variaciones importantes en ciertos años.
Ahora calculamos la tasa de crecimiento de la serie original vs tendencia: variable 3
#Cálculo de la tasa de crecimiento anual correctamente alineada
tasa_crecimiento_var3 <- (variable3_ts[(13:length(variable3_ts))] / variable3_ts[1:(length(variable3_ts) - 12)] - 1) * 100
tasa_tendencia_var3 <- (tendencia_var3[(13:length(tendencia_var3))] / tendencia_var3[1:(length(tendencia_var3) - 12)] - 1) * 100
# Crear vector de fechas corregido
fechas_corregidas_var3 <- seq(from = as.Date("2013-01-01"), by = "month", length.out = length(tasa_crecimiento_var3))
# Verificar longitudes
print(length(fechas_corregidas_var3))
## [1] 144
print(length(tasa_crecimiento_var3))
## [1] 144
print(length(tasa_tendencia_var3))
## [1] 144
library(ggplot2)
library(plotly)
library(tidyr)
library(dplyr)
# Si no lo has hecho ya, crea el dataframe base
df_var3 <- data.frame(
fecha = fechas_corregidas_var3,
crecimiento = tasa_crecimiento_var3,
tendencia = tasa_tendencia_var3
)
# Formato largo para leyenda automática
df_var3_long <- df_var3 %>%
pivot_longer(cols = c("crecimiento", "tendencia"),
names_to = "Serie",
values_to = "Valor")
# Etiquetas y colores para la leyenda
labels_leyenda <- c(
crecimiento = "Tasa de Crecimiento Anual (Serie Original)",
tendencia = "Tasa de Crecimiento Anual (Tendencia)"
)
colores_leyenda <- c(
crecimiento = "grey",
tendencia = "black"
)
# Gráfico interactivo con leyenda profesional
grafico_crecimiento_var3 <- ggplot(df_var3_long, aes(x = fecha, y = Valor, color = Serie, linetype = Serie)) +
geom_line(size = 0.2) +
scale_color_manual(values = colores_leyenda, labels = labels_leyenda) +
scale_linetype_manual(values = c(crecimiento = "solid", tendencia = "dashed"), labels = labels_leyenda) +
ggtitle("Variable 3:Tasa de crecimiento anual % de la serie Original y la tendencia grafico 13") +
xlab("Tiempo") +
ylab("% de Crecimiento Anual") +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "top",
legend.title = element_blank()
)
# Convertir a gráfico interactivo
ggplotly(grafico_crecimiento_var3)
Este gráfico muestra cómo ha variado el crecimiento anual de la variable 3 en los últimos años. La línea de tendencia revela que, después de una caída fuerte alrededor de 2020, hubo un periodo de crecimiento positivo y luego la tasa volvió a estabilizarse cerca de cero. Analizar la tasa de crecimiento anual ayuda a detectar cambios en el entorno económico que afectan el sector. Se pueden prever crisis o períodos de auge y prepararse para ellos.
División en conjunto de entrenamiento y prueba para la variable 1 que es la elegida para pronosticar
El código siguiente divide una serie temporal (variable1_ts) en dos subconjuntos:
Conjunto de entrenamiento (train): Datos desde enero de 2012 hasta septiembre de 2024. Conjunto de prueba (test): Datos desde octubre de 2024 hasta diciembre de 2024.
Esto se hace para evaluar el desempeño de modelos de predicción en datos no vistos.
# Esta división idealmente podria se 80%-70% de los datos para entrenamiento y 20%-30% para prueba o test
# En este ejemplo el conjunto de entrenamiento es: Enero 2012-Septiembre 2024 y el conjunto de prueba o test: noviembre 2024-diciembre 2024
train_size <- length(variable1_ts) - 3 # Se deja fuera los últimos 3 valores para usarlos como set de prueba.
train_ts <- window(variable1_ts, end = c(2024, 9)) # Entrenamiento hasta septiembre 2024
test_ts <- window(variable1_ts, start = c(2024, 10)) # Prueba inicia desde oct2024
Identificación automática del modelo ARIMA
library(forecast)
# Ajustar un modelo ARIMA automático sin estacionalidad, por eso se pone seasonal=FALSE
auto_arima_model_no_seasonal <- auto.arima(train_ts, seasonal = FALSE)
# Mostrar el modelo seleccionado
summary(auto_arima_model_no_seasonal)
## Series: train_ts
## ARIMA(1,1,1)
##
## Coefficients:
## ar1 ma1
## 0.3336 -0.8940
## s.e. 0.0957 0.0472
##
## sigma^2 = 1.13e+10: log likelihood = -1974.44
## AIC=3954.88 AICc=3955.04 BIC=3963.95
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE
## Training set 10449.67 105259.9 72659.69 -1.566531 8.971488 0.9023438
## ACF1
## Training set -0.03433432
Estimación del modelo identificado automatico y validación de Significancia de coeficientes
library(lmtest)
# Evaluar la significancia estadística de los coeficientes del modelo ARIMA
coeftest(auto_arima_model_no_seasonal)
##
## z test of coefficients:
##
## Estimate Std. Error z value Pr(>|z|)
## ar1 0.333617 0.095680 3.4868 0.0004888 ***
## ma1 -0.893984 0.047222 -18.9315 < 2.2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Ajuste del modelo ARIMA(4,1,2) automático sin parte estacional y crearlo como variable darima_auto para luego poder graficarlo y crear la tabla
darima_auto <- Arima(train_ts,
order = c(4, 1, 2)) # Especificamos directamente (p=4, d=1, q=2)
# Mostrar resumen del modelo ajustado
summary(darima_auto)
## Series: train_ts
## ARIMA(4,1,2)
##
## Coefficients:
## ar1 ar2 ar3 ar4 ma1 ma2
## -0.4183 0.3048 0.0408 -0.0958 -0.1545 -0.6591
## s.e. 0.2134 0.1307 0.1049 0.0893 0.2019 0.1868
##
## sigma^2 = 1.142e+10: log likelihood = -1973.22
## AIC=3960.45 AICc=3961.22 BIC=3981.61
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE
## Training set 10445.8 104401.7 71174.17 -1.533127 8.816225 0.8838954
## ACF1
## Training set -0.009319039
Validación de residuales o errores del modelo
# Diagnóstico del modelo (los residuos deben ser ruido blanco)
checkresiduals(darima_auto) # Verificar si los residuos son aleatorios y no presentan patrones
##
## Ljung-Box test
##
## data: Residuals from ARIMA(4,1,2)
## Q* = 35.097, df = 18, p-value = 0.009189
##
## Model df: 6. Total lags used: 24
Las pruebas muestran que el modelo ARIMA usado para hacer pronósticos todavía deja algunos patrones sin explicar en los datos, ya que los residuos presentan cierta relación entre sí y no son totalmente aleatorios. Esto significa que, aunque el modelo ayuda a entender la tendencia general, sus resultados deben interpretarse con cuidado, porque aún hay comportamientos en la serie que no logra captar del todo.
Pronóstico modelo ARIMA automático dentro de muestra o en el set de prueba
# Generar pronóstico para el conjunto de prueba
forecast_arima_auto <- forecast(darima_auto, h = length(test_ts)) # Predecir los valores futuros
# Crear dataframe para gráfico interactivo del pronóstico
forecast_data_auto <- data.frame(Tiempo = time(forecast_arima_auto$mean),
Pronostico = as.numeric(forecast_arima_auto$mean),
Observado = as.numeric(test_ts))
# Graficar pronóstico junto con los valores observados reales
p4auto <- ggplot(forecast_data_auto, aes(x = Tiempo)) +
geom_line(aes(y = Pronostico, color = "Pronóstico")) +
geom_line(aes(y = Observado, color = "Observado")) +
ggtitle("Pronóstico vs Observado grafico 14")+
xlab("Tiempo") + ylab("variable1")
ggplotly(p4auto) # Convertir el gráfico en interactivo
El análisis y el gráfico 14 muestran que el modelo ARIMA utilizado no es el más adecuado para predecir la variable 1, ya que existe una diferencia notable entre los valores pronosticados y los observados en los datos recientes. Aunque el modelo ayuda a identificar la tendencia general, no logra anticipar los cambios reales que ocurren en la serie. Por esto, se recomienda considerar otros modelos o ajustar el enfoque para obtener pronósticos más precisos y útiles para la toma de decisiones.
Pronóstico automático dentro del set de prueba como tabla
# Cargar librerías necesarias
library(forecast)
library(dplyr)
# Generar pronóstico con el modelo ARIMA identificado
arima_forecast_auto <- forecast(auto_arima_model_no_seasonal, h = length(test_ts))
# Crear un dataframe con los valores observados y pronosticados
forecast_table_auto <- data.frame(
Tiempo = time(arima_forecast_auto$mean), # Extraer las fechas del pronóstico
Observado = as.numeric(test_ts), # Valores reales
Pronosticado = as.numeric(arima_forecast_auto$mean) # Valores pronosticados
)
# Mostrar la tabla
print(forecast_table_auto)
## Tiempo Observado Pronosticado
## 1 2024.750 1180815 1133340
## 2 2024.833 1121219 1141154
## 3 2024.917 1145357 1143761
Ahora pronosticamos con el modelo automatico fuera del periodo de análisis, es decir enero 2025
Es decir, le sumamos al periodo de prueb auna observación más. Es decir, se estan pronosticando 4 observaciones o trimestres.
# Cargar librerías necesarias
library(forecast)
# Hacer un pronóstico para el siguiente trimestre (1 período adicional)
next_forecast_auto <- forecast(auto_arima_model_no_seasonal, h = length(test_ts) + 1)
# Extraer el pronóstico del próximo trimestre
next_month_forecast_auto <- data.frame(
Tiempo = time(next_forecast_auto$mean), # Extraer la fecha del pronóstico
Pronostico = as.numeric(next_forecast_auto$mean) # Valor pronosticado
)
# Mostrar el pronóstico completo
print(next_month_forecast_auto)
## Tiempo Pronostico
## 1 2024.750 1133340
## 2 2024.833 1141154
## 3 2024.917 1143761
## 4 2025.000 1144631
# Extraer solo el valor del trimestre adicional (último de la tabla)
next_month <- tail(next_month_forecast_auto, 1)
print(paste("Pronóstico para enero 2025:", next_month$Tiempo, "=", next_month$Pronostico))
## [1] "Pronóstico para enero 2025: 2025 = 1144630.62871857"
Este modelo podria ser una solución o mejora al modelo arima tradicional ya que recoge el efecto estacional de las variables, es recomendable por tanto para datos que si tienen un componente estacional fuerte.
El modelo ajustado en este ejemplo es un SARIMA(0,1,1)(1,0,0)[12], lo que significa:
(0,1,1): Parte ARIMA no estacional: 0 términos autorregresivos (AR). 1 diferenciación (d), lo que indica que la serie fue diferenciada una vez para hacerla estacionaria. 1 término de media móvil (MA).
(1,0,0)[12]: Parte estacional con periodicidad 12 (mensual si los datos son mensuales): 1 término autorregresivo estacional (SAR). 0 diferenciaciones estacionales. 0 términos de media móvil estacionales (SMA).
El modelo SARIMA(0,1,1)(1,0,0)[12] sugiere que:
Identificación dautomática del modelo SARIMA
# Identificación automática modelo SARIMA
auto_arima_model <- auto.arima(train_ts) # Busca automáticamente los mejores parámetros del modelo ARIMA
print(auto_arima_model)
## Series: train_ts
## ARIMA(1,1,1)(0,0,1)[12]
##
## Coefficients:
## ar1 ma1 sma1
## 0.3748 -0.9184 0.3120
## s.e. 0.0966 0.0477 0.0782
##
## sigma^2 = 1.02e+10: log likelihood = -1966.72
## AIC=3941.45 AICc=3941.72 BIC=3953.54
A continuación, se crea el objeto darima para luegO poder graficar los valores reales y observados:
# Cargar el paquete necesario
library(forecast)
# Ajustar el modelo SARIMA(0,1,1)(1,0,0)[12] #Modelo identificado en el paso anterior
darima <- Arima(train_ts,
order = c(0, 1, 1), # (p,d,q) -> (0,1,1)
seasonal = list(order = c(1, 0, 0), # (P,D,Q) -> (1,0,0)
period = 12)) # Periodicidad estacional de 12 meses
# Mostrar resumen del modelo ajustado
summary(darima)
## Series: train_ts
## ARIMA(0,1,1)(1,0,0)[12]
##
## Coefficients:
## ma1 sar1
## -0.6364 0.3160
## s.e. 0.1061 0.0758
##
## sigma^2 = 1.08e+10: log likelihood = -1971.34
## AIC=3948.69 AICc=3948.85 BIC=3957.76
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE ACF1
## Training set 2856.865 102877.4 64655.76 -2.167637 8.223191 0.8029448 0.1334647
# Diagnóstico del modelo (los residuos deben ser ruido blanco)
checkresiduals(darima) # Verificar si los residuos son aleatorios y no presentan patrones
##
## Ljung-Box test
##
## data: Residuals from ARIMA(0,1,1)(1,0,0)[12]
## Q* = 22.855, df = 22, p-value = 0.41
##
## Model df: 2. Total lags used: 24
El modelo SARIMA ajustado para la serie muestra mejores resultados que el anterior. La prueba de Ljung-Box (p-valor = 0.41) indica que los residuos se comportan como ruido blanco, es decir, no presentan patrones repetitivos ni relación entre sí, lo cual es lo esperado en un buen modelo. Además, los gráficos de los residuos muestran que la mayoría de los valores están centrados cerca de cero y no hay señales claras de problemas. Esto significa que el modelo logra captar adecuadamente el comportamiento de la serie y es más confiable para hacer pronósticos.
Pronóstico con el modelo SARIMA dentro del set de prueba-Gráfico líneas
# Generar pronóstico para el conjunto de prueba
forecast_arima <- forecast(darima, h = length(test_ts)) # Predecir los valores futuros
# Crear dataframe para gráfico interactivo del pronóstico
forecast_data <- data.frame(Tiempo = time(forecast_arima$mean),
Pronostico = as.numeric(forecast_arima$mean),
Observado = as.numeric(test_ts))
# Graficar pronóstico junto con los valores observados reales
p4 <- ggplot(forecast_data, aes(x = Tiempo)) +
geom_line(aes(y = Pronostico, color = "Pronóstico")) +
geom_line(aes(y = Observado, color = "Observado")) +
ggtitle("Pronóstico vs Observado grafico 15") +
xlab("Tiempo") + ylab("Unidad Variable 1")
ggplotly(p4) # Convertir el gráfico en interactivo
El modelo SARIMA muestra un desempeño bastante positivo, ya que logra anticipar correctamente la tendencia general de la variable. Si bien existen diferencias puntuales entre los valores pronosticados y los observados, la trayectoria de ambas líneas es similar, lo que indica que el modelo es útil para orientar la toma de decisiones y planificar a futuro.Este modelo puede seguir mejorando su precisión y apoyar de manera efectiva la gestión del sector.
Pronóstico del modelo automático SARIMA en el set de prueba-Tabla
# Cargar librerías necesarias
library(forecast)
library(dplyr)
# Generar pronóstico con el modelo ARIMA identificado
arima_forecast <- forecast(auto_arima_model, h = length(test_ts))
# Crear un dataframe con los valores observados y pronosticados
forecast_table <- data.frame(
Tiempo = time(arima_forecast$mean), # Extraer las fechas del pronóstico
Observado = as.numeric(test_ts), # Valores reales
Pronosticado = as.numeric(arima_forecast$mean) # Valores pronosticados
)
# Mostrar la tabla
print(forecast_table)
## Tiempo Observado Pronosticado
## 1 2024.750 1180815 1147233
## 2 2024.833 1121219 1117421
## 3 2024.917 1145357 1171977
Pronóstico del modelo automático SARIMA fuera de muestra, es decir, en enero 2025
Es decir, le sumamos al periodo de prueba una observación más. Es decir, se estan pronosticando 4 observaciones o meses.
# Cargar librerías necesarias
library(forecast)
# Hacer un pronóstico para el siguiente mes (1 período adicional)
next_forecast <- forecast(auto_arima_model, h = length(test_ts) + 1)
# Extraer el pronóstico del próximo mes
next_month_forecast <- data.frame(
Tiempo = time(next_forecast$mean), # Extraer la fecha del pronóstico
Pronostico = as.numeric(next_forecast$mean) # Valor pronosticado
)
# Mostrar el pronóstico completo
print(next_month_forecast)
## Tiempo Pronostico
## 1 2024.750 1147233
## 2 2024.833 1117421
## 3 2024.917 1171977
## 4 2025.000 1101935
# Extraer solo el valor del trimestre adicional (último de la tabla)
next_month <- tail(next_month_forecast, 1)
print(paste("Pronóstico para enero 2025:", next_month$Tiempo, "=", next_month$Pronostico))
## [1] "Pronóstico para enero 2025: 2025 = 1101934.82625893"
Discusión de resultados y recomendaciones para CEMEX Colombia
El análisis de las variables clave del sector industrial y de la construcción en Colombia, tomando como referencia la operación de CEMEX, destaca los siguientes puntos:
Tendencias y resiliencia: Las series históricas evidencian la sensibilidad del sector cemento ante choques externos, como la pandemia de 2020, pero también demuestran la capacidad de recuperación de la industria. Desde 2023, el entorno muestra estabilidad con leves signos de desaceleración, lo que plantea retos y oportunidades para CEMEX en la gestión operativa y comercial.
Modelos y pronóstico: Los modelos SARIMA aplicados lograron capturar los patrones principales de la demanda y producción, validando su utilidad para la planeación y gestión de la empresa. Si bien existen diferencias puntuales entre lo pronosticado y lo observado, el modelo es una herramienta sólida para anticipar escenarios y planificar con base en datos.
Perspectivas a futuro: Las proyecciones sugieren un panorama de estabilidad en la demanda de cemento, con tasas de crecimiento moderadas. Las licencias de construcción y el índice de producción industrial regional acompañan esta tendencia, reflejando la importancia de monitorear continuamente el mercado y ajustar estrategias según la dinámica nacional y regional.
Recomendaciones estratégicas para CEMEX Colombia Innovación y sostenibilidad: Ante mercados estables o en desaceleración, es fundamental que CEMEX refuerce la apuesta por productos sostenibles, tecnologías limpias y soluciones innovadoras. Esto permitirá diferenciarse y captar nuevas oportunidades en segmentos exigentes y alineados con tendencias ESG (ambientales, sociales y de gobierno corporativo).
Gestión de riesgos y diversificación: Fortalecer mecanismos de gestión de riesgos ante cambios regulatorios, volatilidad de precios o eventos inesperados. La diversificación de portafolios, tanto en productos como en mercados, será clave para asegurar la resiliencia y el crecimiento a mediano plazo.
Planificación flexible y optimización: Usar los resultados de los modelos para ajustar inventarios, producción y logística según los ciclos estacionales, anticipando picos de demanda y optimizando recursos. La flexibilidad operativa será una ventaja competitiva frente a escenarios inciertos.
Alianzas público-privadas y participación activa: Participar en iniciativas y proyectos de infraestructura promovidos por el gobierno nacional y regional, así como en alianzas público-privadas, permitirá a CEMEX posicionarse como actor clave en la reactivación y el desarrollo del país.
Cultura de analítica y mejora continua: Impulsar la cultura analítica en todos los niveles de la organización, integrando herramientas de pronóstico y monitoreo para apoyar la toma de decisiones ágiles y basadas en evidencia. La actualización permanente de los modelos garantizará decisiones más acertadas y oportunas.
Conclusión:
La adopción de analítica avanzada y modelos estadísticos posiciona a CEMEX Colombia un paso adelante en la gestión estratégica del negocio. Si bien ningún modelo puede prever todos los eventos futuros, su uso sistemático reduce la incertidumbre y permite responder de manera proactiva a los desafíos del mercado. De esta forma, CEMEX no solo fortalece su competitividad, sino que también contribuye activamente al desarrollo sostenible y a la transformación positiva del sector de la construcción en Colombia.