En este informe asumimos el rol de la Gerencia de Estrategia de “Café de Colombia Export S.A.S.”, una empresa líder en la exportación de café verde y procesado. Nuestro objetivo es utilizar la ciencia de datos para anticipar los ciclos del mercado cafetero, optimizar nuestras coberturas cambiarias y maximizar los ingresos por exportación.
Cargamos las librerías necesarias para el análisis de series de tiempo y visualización interactiva.
library(readxl)
library(tidyverse)
library(lubridate)
library(tseries)
library(forecast)
library(ggplot2)
library(plotly)
library(kableExtra)
Leemos la base de datos maestra Base Caso2.xlsx.
# Cargar datos
data_raw <- read_excel("Base Caso2.xlsx")
# Selección de nuestras 5 variables de interés + Fecha
df_cafe <- data_raw %>%
select(FECHA, XCAF, PNCAFE, PECAFE, TRM, ISE) %>%
mutate(FECHA = as.Date(FECHA)) # Asegurar formato fecha
# Mostrar primeras filas
df_cafe %>%
head() %>%
kbl() %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"))
| FECHA | XCAF | PNCAFE | PECAFE | TRM | ISE |
|---|---|---|---|---|---|
| 2012-01-01 | 197295.8 | 535 | 256.1168 | 1847.516 | 81.64043 |
| 2012-02-01 | 186693.3 | 571 | 246.3010 | 1786.543 | 84.46710 |
| 2012-03-01 | 203636.0 | 576 | 226.0661 | 1767.793 | 87.79398 |
| 2012-04-01 | 121442.5 | 580 | 215.2930 | 1773.893 | 84.04757 |
| 2012-05-01 | 167643.9 | 689 | 209.5097 | 1795.907 | 87.93170 |
| 2012-06-01 | 162433.1 | 714 | 185.8843 | 1788.877 | 87.83290 |
#Executive Dashboard (Resumen Gerencial)
Estado de los KPIs al cierre de Diciembre 2024
| Indicador | Valor.Actual | Var..Anual…. |
|---|---|---|
| Exportaciones (XCAF) | 462,940.1 | 50.97 |
| Producción (PNCAFE) | 1,798.231 | 47.40 |
| Tasa de Cambio (TRM) | 4,386.39 | 11.10 |
| Precio Externo (PECAFE) | 340.5167 | 64.56 |
Nota Estratégica: Los colores en la columna de variación anual indican la dirección del crecimiento. Para una exportadora, un aumento en la TRM es positivo (verde), mientras que una caída en la producción es una señal de alerta (rojo).
ts)Transformamos cada variable en un objeto de serie de tiempo mensual (frecuencia = 12), iniciando en Enero de 2012.
# Definir inicio y frecuencia
start_date <- c(2012, 1)
freq <- 12
# Crear objetos ts
ts_xcaf <- ts(df_cafe$XCAF, start = start_date, frequency = freq)
ts_pncafe <- ts(df_cafe$PNCAFE, start = start_date, frequency = freq)
ts_pecafe <- ts(df_cafe$PECAFE, start = start_date, frequency = freq)
ts_trm <- ts(df_cafe$TRM, start = start_date, frequency = freq)
ts_ise <- ts(df_cafe$ISE, start = start_date, frequency = freq)
# Verificar estructura de una serie (ej. Exportaciones)
print(ts_xcaf)
## Jan Feb Mar Apr May Jun Jul Aug
## 2012 197295.8 186693.3 203636.0 121442.5 167643.9 162433.1 151832.1 180244.0
## 2013 177150.5 171569.2 127181.2 152040.1 168982.2 138026.6 159759.0 169039.9
## 2014 142633.8 202636.1 153516.9 224406.6 213123.9 161247.5 229475.0 218584.5
## 2015 288671.5 251209.4 211129.0 182545.4 194717.5 160584.9 239676.4 218630.5
## 2016 163588.4 184225.8 179113.8 281209.6 152586.3 176288.8 128997.8 169424.1
## 2017 232188.4 214883.5 295930.9 217795.8 196637.3 137977.0 230431.8 211174.9
## 2018 255058.1 187220.6 201578.7 205465.6 190580.9 147559.5 166218.3 213792.4
## 2019 260699.1 221412.3 193807.5 186052.9 143552.4 147796.7 205010.7 218999.2
## 2020 249836.3 215605.6 170321.5 157384.0 141864.0 194805.8 270277.4 201786.8
## 2021 255455.2 267999.8 278224.9 243363.4 124318.7 115655.4 309123.1 322175.0
## 2022 332201.4 383477.5 412235.8 327940.2 275625.7 369007.8 394622.5 323525.4
## 2023 250131.8 261153.3 292938.5 212748.0 251607.5 225461.4 227622.0 226253.7
## 2024 231800.6 258106.5 238506.5 261112.5 268005.6 285915.9 291114.4 318115.4
## Sep Oct Nov Dec
## 2012 123549.6 159793.2 144269.2 166159.5
## 2013 152688.2 152603.0 173174.7 191779.4
## 2014 216896.0 252496.3 267402.2 244052.0
## 2015 250151.3 179340.6 191276.3 217993.9
## 2016 196074.9 193648.9 219217.4 429058.8
## 2017 240608.9 215414.3 198250.7 203778.0
## 2018 179237.1 194640.1 191052.0 216342.7
## 2019 167456.2 195326.7 193952.8 242374.7
## 2020 217897.4 173371.9 227635.8 316675.7
## 2021 268316.6 327953.2 316349.7 376919.2
## 2022 339103.7 323641.1 293141.5 348771.0
## 2023 234930.5 188621.9 251091.2 306637.2
## 2024 284093.6 338376.9 325234.9 462940.1
Una primera mirada a la evolución histórica de nuestras variables clave.
XCAF) vs Producción
(PNCAFE)Se identifican los choques externos históricos que han distorsionado la serie: el inicio de la Pandemia (Marzo 2020) y el Paro Nacional (Abril-Mayo 2021), este último con un impacto logístico severo en la salida de café por el Puerto de Buenaventura.
# Gráfico interactivo con Shocks Históricos
p1 <- df_cafe %>%
ggplot(aes(x = FECHA)) +
geom_line(aes(y = XCAF, color = "Exportaciones (USD)"), size = 0.8) +
geom_line(aes(y = PNCAFE * 10, color = "Producción (Sacos x10)"), size = 0.8, linetype = "dashed") +
# Anotaciones de Shocks
geom_vline(xintercept = as.numeric(as.Date("2020-03-01")), linetype="dotted", color="#e74c3c", size=1) +
annotate("text", x=as.Date("2020-03-01"), y=1500000, label="Pandemia", angle=90, vjust=-0.5, color="#c0392b") +
geom_vline(xintercept = as.numeric(as.Date("2021-05-01")), linetype="dotted", color="#e74c3c", size=1) +
annotate("text", x=as.Date("2021-05-01"), y=1500000, label="Paro Nacional", angle=90, vjust=-0.5, color="#c0392b") +
scale_y_continuous(sec.axis = sec_axis(~./10, name = "Producción (Miles de Sacos)")) +
labs(title = "XCAF vs PNCAFE: Evolución Histórica con Shocks",
y = "Exportaciones (Miles USD)", x = "Tiempo", color = "Variable") +
theme_minimal() +
scale_color_manual(values = c("Exportaciones (USD)" = "#2c3e50", "Producción (Sacos x10)" = "#e67e22"))
ggplotly(p1)
Interpretación Preliminar: Observamos una clara estacionalidad en la producción (línea naranja discontinua), con picos y valles marcados cada año, lo cual es típico de los ciclos de cosecha cafetera. Las exportaciones (línea azul) parecen seguir una tendencia relacionada, pero con variaciones de amplitud diferentes, probablemente influenciadas por el precio internacional y la TRM.
Para una empresa exportadora, es vital separar el “ruido” de corto plazo y los ciclos estacionales de la verdadera tendencia de mercado. Utilizaremos la técnica STL (Seasonal and Trend decomposition using Loess).
Aplicamos STL a nuestras 5 series temporales para extraer la Tendencia (Trend), la Estacionalidad (Seasonal) y los Residuos (Remainder).
# Función para realizar STL y graficar interactivamente
analizar_stl <- function(ts_data, titulo, color_pal) {
decomp <- stl(ts_data, s.window = "periodic")
df_stl <- data.frame(
Time = rep(time(ts_data), 4),
Value = c(decomp$time.series[, "seasonal"],
decomp$time.series[, "trend"],
decomp$time.series[, "remainder"],
ts_data),
Componente = rep(c("Estacionalidad", "Tendencia", "Residuo", "Serie Original"), each = length(ts_data))
)
p <- ggplot(df_stl, aes(x = Time, y = Value, color = Componente)) +
geom_line() +
facet_wrap(~Componente, scales = "free_y", ncol = 1) +
theme_minimal() +
labs(title = paste("Descomposición STL:", titulo), x = "Tiempo", y = "Valor") +
scale_color_manual(values = color_pal)
return(list(plot = ggplotly(p), decomp = decomp))
}
# Definir paleta de colores profesional
paleta <- c("Estacionalidad" = "#16a085", "Tendencia" = "#2980b9", "Residuo" = "#7f8c8d", "Serie Original" = "#2c3e50")
# Ejecutar para variables críticas
res_xcaf <- analizar_stl(ts_xcaf, "Exportaciones de Café (USD)", paleta)
res_pncafe <- analizar_stl(ts_pncafe, "Producción de Café (Sacos)", paleta)
res_pecafe <- analizar_stl(ts_pecafe, "Precio Externo del Café (USD)", paleta)
res_trm <- analizar_stl(ts_trm, "Tasa de Cambio (TRM)", paleta)
res_ise <- analizar_stl(ts_ise, "Índice Seguimiento Economía (ISE)", paleta)
XCAF)res_xcaf$plot
PECAFE)res_pecafe$plot
PNCAFE)res_pncafe$plot
TRM)res_trm$plot
ISE)res_ise$plot
Para nuestra exportadora, la Serie Ajustada por Estacionalidad (SA) es la señal más importante, ya que permite ver si estamos creciendo “realmente” independientemente de la época del año.
# Función general para graficar Original vs Ajustada vs Tendencia
graficar_sa_trend <- function(ts_orig, res_obj, titulo, unidades, fechas) {
# Calcular Serie Ajustada (Original - Estacionalidad)
sa <- ts_orig - res_obj$decomp$time.series[, "seasonal"]
df_comp <- data.frame(
Fecha = fechas,
Original = as.numeric(ts_orig),
Ajustada = as.numeric(sa),
Tendencia = as.numeric(res_obj$decomp$time.series[, "trend"])
)
p <- df_comp %>%
ggplot(aes(x = Fecha)) +
geom_line(aes(y = Original, color = "Original"), alpha = 0.4) +
geom_line(aes(y = Ajustada, color = "Ajustada por Estacionalidad"), size = 0.8) +
geom_line(aes(y = Tendencia, color = "Tendencia de Largo Plazo"), size = 1, linetype = "dashed") +
labs(title = paste(titulo, ": Señal 'Limpia'"),
y = unidades, color = "Señal") +
theme_minimal() +
scale_color_manual(values = c("Original" = "grey",
"Ajustada por Estacionalidad" = "#27ae60",
"Tendencia de Largo Plazo" = "#c0392b"))
return(ggplotly(p))
}
# 1. Extraer Series Ajustadas (SA) para uso posterior si es necesario
xcaf_sa <- ts_xcaf - res_xcaf$decomp$time.series[, "seasonal"]
pncafe_sa <- ts_pncafe - res_pncafe$decomp$time.series[, "seasonal"]
pecafe_sa <- ts_pecafe - res_pecafe$decomp$time.series[, "seasonal"]
trm_sa <- ts_trm - res_trm$decomp$time.series[, "seasonal"]
ise_sa <- ts_ise - res_ise$decomp$time.series[, "seasonal"]
# 2. Generar Gráficos para las 5 variables
graficar_sa_trend(ts_xcaf, res_xcaf, "XCAF (Exportaciones)", "Miles USD", df_cafe$FECHA)
graficar_sa_trend(ts_pncafe, res_pncafe, "PNCAFE (Producción)", "Sacos", df_cafe$FECHA)
graficar_sa_trend(ts_pecafe, res_pecafe, "PECAFE (Precio)", "USD", df_cafe$FECHA)
graficar_sa_trend(ts_trm, res_trm, "TRM (Tasa Cambio)", "COP/USD", df_cafe$FECHA)
graficar_sa_trend(ts_ise, res_ise, "ISE (Economía)", "Índice", df_cafe$FECHA)
Interpretación de la Señal de Ingresos: Al eliminar la estacionalidad (línea verde), vemos que las exportaciones han tenido ciclos de volatilidad estructural. La Tendencia (línea roja) nos muestra un crecimiento sostenido desde 2012 hasta aproximadamente 2022, seguido de una meseta o leve corrección. Esto sugiere que el sector alcanzó un pico de madurez y ahora enfrenta retos competitivos o de precios.
Para una visión estratégica de largo plazo, calculamos la tasa de crecimiento anual de la Tendencia (Ciclo-Tendencia) de todas nuestras variables. Esto nos permite ver si el crecimiento es estructural o simplemente un rebote estacional.
# Función para calcular tasa de crecimiento anual (t / t-12 - 1) * 100
calcular_tasa_anual <- function(ts_data) {
tasa <- (ts_data[13:length(ts_data)] / ts_data[1:(length(ts_data) - 12)] - 1) * 100
return(tasa)
}
# Calcular tasas para todas las variables
df_tasas_all <- data.frame(
Fecha = df_cafe$FECHA[13:nrow(df_cafe)],
XCAF = calcular_tasa_anual(res_xcaf$decomp$time.series[, "trend"]),
PNCAFE = calcular_tasa_anual(res_pncafe$decomp$time.series[, "trend"]),
PECAFE = calcular_tasa_anual(res_pecafe$decomp$time.series[, "trend"]),
TRM = calcular_tasa_anual(res_trm$decomp$time.series[, "trend"]),
ISE = calcular_tasa_anual(res_ise$decomp$time.series[, "trend"])
) %>%
pivot_longer(-Fecha, names_to = "Variable", values_to = "Tasa")
# Gráfico comparativo de Tasas de Crecimiento
p_tasas_comp <- df_tasas_all %>%
ggplot(aes(x = Fecha, y = Tasa, color = Variable)) +
geom_line(size = 0.8) +
geom_hline(yintercept = 0, linetype = "dashed", color = "black", alpha = 0.5) +
facet_wrap(~Variable, scales = "free_y") +
labs(title = "Crecimiento Anual de la Tendencia por Variable (%)",
subtitle = "Señales estructurales libres de estacionalidad y ruido",
y = "% Variación Anual", x = "Año") +
theme_minimal() +
theme(legend.position = "none")
ggplotly(p_tasas_comp)
Insight Estratégico: La tasa de crecimiento de la tendencia ha fluctuado significativamente. Los periodos por debajo de la línea roja (crecimiento negativo) indican momentos de contracción estructural del mercado, vitales para que la gerencia de Café de Colombia Export S.A.S. tome medidas de austeridad o diversificación.
En esta sección, evaluamos cómo las variables externas y de oferta
impactan nuestras exportaciones. Como “Café de Colombia Export
S.A.S.”, necesitamos saber si nuestros ingresos dependen más
del Precio Internacional (PECAFE) o de la
Tasa de Cambio (TRM).
Para identificar los verdaderos impulsores del negocio, analizamos la correlación entre las Series Ajustadas (SA). Al eliminar la estacionalidad, evitamos “correlaciones espurias” (falsos positivos causados porque ambas variables suben en la misma época del año, como en la cosecha).
library(ggcorrplot)
# Crear dataframe con las series ajustadas (Seasonally Adjusted)
df_sa <- data.frame(
XCAF = as.numeric(xcaf_sa),
PNCAFE = as.numeric(pncafe_sa),
PECAFE = as.numeric(pecafe_sa),
TRM = as.numeric(trm_sa),
ISE = as.numeric(ise_sa)
)
# Calcular matriz de correlación de Pearson sobre señales limpias
cor_matrix_sa <- cor(df_sa, use = "complete.obs")
# Graficar Heatmap
ggcorrplot(cor_matrix_sa,
hc.order = TRUE,
type = "lower",
lab = TRUE,
colors = c("#e74c3c", "white", "#27ae60"),
title = "Matriz de Correlación: Señales Limpias (SA)")
Hallazgos Clave: - TRM vs XCAF: Observamos una correlación positiva moderada/fuerte. Esto sugiere que un dólar más caro incentiva mayores niveles de exportación o aumenta el valor exportado en términos nominales. - PNCAFE vs XCAF: La producción tiene una relación directa con las exportaciones, lo cual es lógico: mayor oferta disponible permite mayores volúmenes de venta al exterior. - ISE vs Otros: La correlación con el indicador de la economía general nos muestra qué tanto el sector cafetero se mueve al ritmo del país.
Analizamos la relación entre el Precio Externo
(PECAFE) y las Exportaciones
(XCAF) utilizando las series ajustadas por
estacionalidad. Esto nos permite ver la sensibilidad “pura” de nuestros
ingresos ante cambios en el precio internacional, eliminando el efecto
de los picos de cosecha.
# Gráfico de dispersión interactivo con datos ajustados
p_scatter <- df_sa %>%
ggplot(aes(x = PECAFE, y = XCAF)) +
geom_point(aes(color = TRM), alpha = 0.6) +
geom_smooth(method = "lm", color = "#c0392b", fill = "#e74c3c") +
scale_color_gradient(low = "#3498db", high = "#e67e22") +
labs(title = "Relación Estructural: Precio Externo vs Exportaciones (SA)",
subtitle = "Datos ajustados por estacionalidad - Color por TRM",
x = "Precio Externo Ajustado (USD)",
y = "Exportaciones Ajustadas (Miles USD)") +
theme_minimal()
ggplotly(p_scatter)
Insight de Estrategia: La pendiente positiva confirma que mayores precios internacionales atraen mayores divisas. Sin embargo, los puntos más altos (mayores exportaciones) tienden a ocurrir cuando la TRM (color naranja) también es elevada. Nuestra “Tormenta Perfecta” de rentabilidad ocurre cuando el Precio Externo > 200 y la TRM > 4000.
¿Un aumento en el precio hoy se refleja hoy mismo en las exportaciones o hay un retraso? Analizamos la Correlación Cruzada (CCF) entre el Precio Externo y las Exportaciones.
# Análisis de correlación cruzada (Serie de exportaciones vs Rezagos de precio externo)
# Usamos las series ajustadas por estacionalidad para evitar correlaciones espurias
ccf_res <- ccf(as.numeric(pecafe_sa), as.numeric(xcaf_sa), lag.max = 12, plot = FALSE)
# Convertir a dataframe para graficar
df_ccf <- data.frame(Lag = ccf_res$lag, ACF = ccf_res$acf)
p_ccf <- df_ccf %>%
ggplot(aes(x = Lag, y = ACF)) +
geom_bar(stat = "identity", fill = "#34495e", width = 0.5) +
geom_hline(yintercept = c(-1.96/sqrt(length(xcaf_sa)), 1.96/sqrt(length(xcaf_sa))),
color = "red", linetype = "dashed") +
labs(title = "Correlación Cruzada: PECAFE vs XCAF (Lags)",
subtitle = "Lags negativos indican que el Precio precede a las Exportaciones",
x = "Rezago (Meses)", y = "Correlación") +
theme_minimal()
ggplotly(p_ccf)
Análisis de Rezagos: Si observamos una barra significativa en un lag negativo (ej. Lag -1 o -2), significa que el Precio de hoy impacta las Exportaciones de dentro de 1 o 2 meses. Esto es fundamental para la planeación logística y de inventarios de nuestra empresa.
Para la gestión de riesgos de “Café de Colombia Export S.A.S.”, es crucial entender cuánta de la variabilidad de nuestras métricas es predecible (estacional) y cuánta es riesgo puro (incertidumbre).
# Función para calcular coeficiente de variación (Desviación / Media)
# El CV permite comparar volatilidad entre variables con diferentes unidades
cv <- function(x) (sd(x, na.rm=T) / mean(x, na.rm=T)) * 100
volatilidad <- data.frame(
Variable = c("XCAF", "PNCAFE", "PECAFE", "TRM", "ISE"),
Vol_Original = c(cv(ts_xcaf), cv(ts_pncafe), cv(ts_pecafe), cv(ts_trm), cv(ts_ise)),
Vol_Ajustada = c(cv(xcaf_sa), cv(pncafe_sa), cv(pecafe_sa), cv(trm_sa), cv(ise_sa))
) %>%
mutate(Reduccion_Ruido = Vol_Original - Vol_Ajustada)
volatilidad %>%
kbl(caption = "Comparativa de Volatilidad (Coeficiente de Variación %)") %>%
kable_styling(bootstrap_options = c("striped", "hover"))
| Variable | Vol_Original | Vol_Ajustada | Reduccion_Ruido |
|---|---|---|---|
| XCAF | 29.72552 | 27.835021 | 1.8905011 |
| PNCAFE | 23.60270 | 19.043308 | 4.5593963 |
| PECAFE | 27.04873 | 27.041533 | 0.0071975 |
| TRM | 27.58491 | 27.515505 | 0.0694063 |
| ISE | 11.22141 | 9.952956 | 1.2684556 |
Insight de Gestión de Riesgos: - PNCAFE (Producción): Es la variable donde el ajuste estacional reduce más la volatilidad. Esto confirma que la oferta de café es altamente predecible según el calendario de cosechas. - PECAFE y TRM: La reducción de volatilidad es mínima, lo que indica que estos son riesgos de mercado puros que no dependen de la época del año. Aquí es donde la empresa debe enfocar sus instrumentos de cobertura (Derivados/Forward).
Teóricamente, las exportaciones de nuestra empresa están “topadas” por la disponibilidad de inventario nacional.
Análisis Estratégico: Al observar la correlación
positiva entre PNCAFE_sa y XCAF_sa,
confirmamos que en periodos de baja producción estructural (como los
años de exceso de lluvia por fenómeno de La Niña), nuestra capacidad de
cumplir contratos internacionales se ve comprometida, independientemente
de si el precio externo es favorable. Esto resalta la importancia de la
diversificación de orígenes o la gestión de inventarios
de seguridad.
Como paso previo al modelado predictivo, es indispensable determinar si nuestras series son estacionarias. Un modelo de pronóstico confiable requiere que la media y la varianza de los datos sean constantes en el tiempo.
Para variables con alta volatilidad como la TRM y las Exportaciones, aplicamos una transformación logarítmica para estabilizar la varianza (heterocedasticidad).
# Aplicar logaritmo
log_xcaf <- log(ts_xcaf)
log_trm <- log(ts_trm)
# Comparativa visual
par(mfrow = c(1, 2))
plot(ts_xcaf, main = "Original XCAF", col = "#2980b9")
plot(log_xcaf, main = "Log(XCAF)", col = "#c0392b")
El test ADF nos dirá científicamente cuántas diferenciaciones necesita la serie antes de entrar al modelo ARIMA.
library(tseries)
# Realizar test ADF en niveles
adf_xcaf_original <- adf.test(ts_xcaf)
adf_xcaf_log <- adf.test(log_xcaf)
# Mostrar resultados en una tabla elegante
adf_results <- data.frame(
Variable = c("XCAF (Original)", "XCAF (Log)"),
`P-Value` = c(adf_xcaf_original$p.value, adf_xcaf_log$p.value),
`¿Es Estacionaria?` = c(ifelse(adf_xcaf_original$p.value < 0.05, "SI", "NO"),
ifelse(adf_xcaf_log$p.value < 0.05, "SI", "NO"))
)
adf_results %>%
kbl() %>%
kable_styling(full_width = F)
| Variable | P.Value | X.Es.Estacionaria. |
|---|---|---|
| XCAF (Original) | 0.1499997 | NO |
| XCAF (Log) | 0.0560125 | NO |
Interpretación Técnica: Si el p-value es mayor a 0.05 (NO), la serie necesita una primera diferencia (\(d=1\)). Esto es clave para configurar nuestro modelo ARIMA en la siguiente etapa. La transformación logarítmica ha ayudado a que la serie sea más tratable estadísticamente.