Mostrar código
Ocultar código

Análisis de Datos de Ventas en la ILC – SELL IN

Industria de Licores de Caldas | Cuantico | 16 de December de 2025

Diagrama CRISP-DM Aguardiente amarillo

Introducción

La comprensión del comportamiento del mercado y de los factores que influyen en las ventas es fundamental para garantizar la sostenibilidad y el crecimiento de una organización. El estudio sistemático de la demanda permite optimizar la planeación de producción, mejorar la eficiencia logística y fortalecer la relación con los distribuidores, al tiempo que contribuye a una toma de decisiones más informada y alineada con los objetivos estratégicos. Este proceso implica analizar el desempeño histórico de los productos, identificar patrones de consumo y anticipar variaciones estacionales o coyunturales que puedan impactar los niveles de ventas y los ingresos futuros.

En particular, el mercado de licores ocupa un lugar destacado en la economía regional y nacional, pues integra procesos productivos, dinámicas comerciales y regulaciones fiscales que afectan directamente la competitividad de entidades como la Industria de Licores de Caldas (ILC). Productos como el ron, el aguardiente y otros derivados presentan comportamientos de demanda heterogéneos según la zona geográfica, el tipo de distribuidor y los periodos del año, lo que exige un análisis detallado que considere factores comerciales, tributarios y sociales. Por ello, resulta esencial estudiar las condiciones y variaciones que determinan el desempeño del portafolio, con el fin de fortalecer la gestión comercial y asegurar una planeación más robusta y eficiente.

En este contexto, el uso de herramientas analíticas y tecnologías avanzadas se convierte en un componente clave para comprender y predecir el comportamiento del mercado. El análisis de datos históricos y georreferenciados, junto con técnicas modernas de modelado estadístico y aprendizaje automático, facilita la identificación de patrones espacio-temporales, la evaluación del desempeño por zonas y la estimación de la demanda futura. Plataformas como Python y Microsoft Fabric, así como librerías especializadas para el procesamiento, visualización y modelado, permiten construir modelos de pronóstico más precisos y adaptativos. Estas capacidades fortalecen la toma de decisiones estratégicas, optimizan la planificación comercial y reducen la incertidumbre inherente al mercado, apoyando a la ILC en la mejora continua de su eficiencia operativa y competitividad.

Objetivo

Desarrollar, validar y documentar un modelo de pronóstico de ventas para la ILC que integre las prioridades del negocio con el análisis de los datos históricos, garantizando trazabilidad del proceso, calidad de los resultados y capacidad de replicación y escalabilidad para futuros ciclos de planeación comercial.

Metodología CRISP-DM adaptada al proyecto

Diagrama CRISP-DM
Esquema del ciclo CRISP-DM estándar. Fuente: Martínez-Plumed et al., 2021.

La metodología CRISP-DM (Cross-Industry Standard Process for Data Mining), es ampliamente reconocida como un marco robusto y flexible para proyectos de minería y ciencia de datos. Esta metodología se estructura en seis fases iterativas e interdependientes —entendimiento del negocio, entendimiento de los datos, preparación de los datos, modelado, evaluación e implementación— que permiten avanzar y retroceder de forma no lineal, adaptándose a las necesidades del proyecto y favoreciendo la mejora continua. A continuación se detalla su adaptación para el pronóstico de ventas de licores en la ILC.

1. Comprensión del negocio

Objetivo general: Anticipar la evolución de las ventas —en unidades estandarizadas (UR) y en valor (COP)— por departamento y por producto (ron, aguardiente y derivados), con el fin de fortalecer la planeación comercial, la distribución y la producción.

Necesidades del negocio:

  • Identificar patrones de estacionalidad, efectos calendario y variaciones territoriales que influyen en la demanda.
  • Comprender cómo cambia el consumo según zona, distribuidor y composición del portafolio.
  • Explorar si existen grupos de departamentos con comportamientos similares que permitan desarrollar modelos diferenciados y más precisos.

Estas preguntas guían la formulación del problema analítico y la selección de las técnicas de pronóstico adecuadas.

2. Comprensión de los datos

Origen de los datos: Ventas mensuales sell-in desde 2018, con información asociada a departamento, zona, portafolio y distribuidor.

Exploración inicial:

  • Auditoría de calidad: verificación de completitud, revisión de duplicados y consistencia de los datos.
  • Análisis descriptivo para identificar tendencias, estacionalidad y variación regional.

Esta fase permite conocer la estructura de los datos, detectar inconsistencias y evaluar su calidad para el modelado.

3. Preparación de los datos

Limpieza y estandarización:

  • Conversión de ventas a una unidad común (botellas equivalentes de 750 ml).
  • Tratamiento de duplicados, valores faltantes e inconsistencias.
  • Agregación al nivel de análisis requerido (departamento × tipo de producto).
  • Cálculo de composiciones y participaciones del portafolio.
  • Construcción del calendario de referencia (mes, trimestre y año).

El resultado es una base consolidada, depurada y lista para la fase de modelado.

4. Modelado

Estrategia general: Se adopta un enfoque basado en clústeres para capturar la heterogeneidad territorial y los diferentes perfiles de consumo.

Agrupamiento en dos niveles:

  • Clustering de departamentos según sus patrones de estacionalidad.
  • Subagrupamiento por magnitud de ventas y composición del portafolio.

Selección y ajuste de modelos por clúster/serie:

  • Se emplea una combinación de métodos tradicionales y modernos para series temporales, priorizando modelos que equilibren precisión e interpretabilidad.

Validación:

  • Backtesting con ventanas rodantes.
  • Comparación mediante métricas de desempeño.
  • Selección jerárquica (serie → clúster → modelo global), penalizando la complejidad innecesaria.

5. Evaluación

Revisión técnica:

  • Análisis del desempeño por horizonte, tipo de producto y región.
  • Evaluación de errores (sesgo, varianza y estacionalidad residual).

Criterios de liberación:

  • Cumplimiento de umbrales de error por familia de producto.
  • Estabilidad del modelo entre ciclos de actualización.

6. Despliegue y monitoreo

Implementación operativa:

  • Publicación de resultados en tableros de Power BI con pronósticos, bandas de incertidumbre y comparación frente a metas (en construcción).
  • Integración con sistemas fuente (Fabric) y definición del ciclo de actualización (mensual o según necesidad comercial).

Monitoreo continuo:

  • Alertas ante degradación de las métricas de desempeño.
  • Programación de retraining periódico o condicionado a cambios relevantes del portafolio, la demanda o las reglas de distribución.

Configuración de Python

Código Python
# =============================================================================
# Librerías estándar de Python
# =============================================================================
import sys
import os
import math
import re
import glob
import unicodedata
from datetime import datetime
import warnings
warnings.filterwarnings("ignore")

# =============================================================================
# Procesamiento y manipulación de datos
# =============================================================================
import numpy as np
import pandas as pd

# =============================================================================
# Funciones propias del proyecto
# =============================================================================
import importlib
import funciones_ilc as f
importlib.reload(f)

# =============================================================================
# Visualización
# =============================================================================
import matplotlib.pyplot as plt
import missingno as msno

import ipywidgets as w
from ipywidgets import interact, widgets

from IPython.display import display, clear_output, HTML

import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import plotly.io as pio
pio.renderers.default = "vscode"

# =============================================================================
# Modelado y aprendizaje automático
# =============================================================================
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
import lightgbm as lgb
from statsmodels.tsa.holtwinters import ExponentialSmoothing
from statsmodels.tools.sm_exceptions import ConvergenceWarning

Extracción, Transformación y Carga (ETL)

ETL
Proceso ETL: Extracción, transformación y carga de datos.

El ETL (Extract, Transform, Load) es el proceso que permite consolidar datos provenientes de múltiples fuentes en un repositorio confiable para análisis. Primero extrae información de sistemas transaccionales, archivos, APIs o bases de datos; luego la transforma limpiando, estandarizando, enriqueciendo y aplicando reglas de negocio para garantizar calidad y consistencia; y finalmente la carga en un destino como un data warehouse o lakehouse. Un buen ETL incorpora validaciones, manejo de errores, control de versiones y metadatos (linaje) para asegurar trazabilidad y reproducibilidad. Al automatizarse y escalar, reduce tiempos operativos, minimiza riesgos por datos inconsistentes y habilita insights oportunos que soportan decisiones basadas en evidencia.

Existen diversas operaciones que se pueden realizar durante la transformación de datos para asegurar su calidad y coherencia. Algunas de las operaciones más comunes son:

  • Limpieza de datos: se eliminan los caracteres especiales, espacios en blanco innecesarios, valores nulos o incorrectos.
  • Filtrado de datos: se seleccionan los registros que cumplen ciertas condiciones o criterios establecidos.
  • Normalización de datos: se ajustan los valores de los datos para que estén dentro de un rango específico o se expresen en una misma unidad.
  • Validación de datos: se comprueba la integridad y consistencia de los datos, verificando que cumplan con ciertas reglas o restricciones.
  • Transformación de datos: se realizan cálculos o manipulaciones para obtener nuevos valores o derivar información adicional.

Extracción

Código Python
# Importar base de datos
df0 = pd.read_excel('data/Ventas_historico_V2_nov2025.xlsx', sheet_name='Ventas',
                   usecols="A:AC", engine="openpyxl")

df0.info()

RangeIndex: 87850 entries, 0 to 87849
Data columns (total 29 columns):
 #   Column              Non-Null Count  Dtype  
---  ------              --------------  -----  
 0   Concepto            87850 non-null  object 
 1   Zona                86641 non-null  object 
 2   NIT                 87850 non-null  object 
 3   Nombre de cliente   87850 non-null  object 
 4   Fecha de factura    87850 non-null  object 
 5   Factura             87850 non-null  object 
 6   Código de producto  87850 non-null  object 
 7   Nombre de producto  87849 non-null  object 
 8   Presentación        87850 non-null  int64  
 9   Unidad              87557 non-null  object 
 10  Cantidad facturada  87850 non-null  float64
 11  Valor licor         87850 non-null  float64
 12  Valor Licor COP     87850 non-null  float64
 13  Cantidad UR         87850 non-null  float64
 14  Iva cliente         87647 non-null  float64
 15  IVA ILC             86433 non-null  object 
 16  Especifico cliente  87522 non-null  float64
 17  Especifico ILC      86433 non-null  object 
 18  Ad Valorem cliente  87499 non-null  float64
 19  Ad Valorem ILC      86433 non-null  object 
 20  Retención           87700 non-null  float64
 21  Pedido de venta     87850 non-null  object 
 22  Contrato            86629 non-null  object 
 23  Negocio             87502 non-null  object 
 24  Tipo                87135 non-null  object 
 25  Marca               86951 non-null  object 
 26  Submarca            86926 non-null  object 
 27  Costos de Ventas    87850 non-null  object 
 28  Tipo de Producto    87850 non-null  int64  
dtypes: float64(8), int64(2), object(19)
memory usage: 19.4+ MB

Transformación

Ajuste de Tipo de datos

Se convierte la columna Fecha de factura a formato datetime, manejando errores como valores nulos. Luego se transforman varias columnas numéricas a enteros (Int64) o flotantes (float), asegurando que cualquier valor no válido se convierta en NaN. Finalmente, convierte a tipo categórico (category) aquellas columnas relacionadas con conceptos, zonas, clientes y productos, optimizando memoria y facilitando su uso en análisis posteriores.

Código Python
# Fecha
df0["Fecha de factura"] = pd.to_datetime(df0["Fecha de factura"], errors="coerce")

# Enteros
int_cols = ["Presentación", "Cantidad facturada", "Tipo de Producto"]
for c in int_cols:
    if c in df0.columns:
    # Convertir a numérico, redondear y luego a Int64 (maneja NaN)
        df0[c] = pd.to_numeric(df0[c], errors="coerce").round(0).astype("Int64")

# Flotantes (monetarios/cantidades continuas)
float_cols = [
    "Cantidad UR", "Valor licor", "Valor Licor COP", "Iva cliente",
    "IVA ILC", "Especifico cliente", "Especifico ILC", "Ad Valorem cliente",
    "Ad Valorem ILC", "Retención", "Costos de Ventas"
    ]
for c in float_cols:
    if c in df0.columns:
        df0[c] = pd.to_numeric(df0[c], errors="coerce")

# Categóricas
cat_cols = [
    "Concepto", "Zona", "NIT", "Nombre de cliente", "Factura", "Código de producto",
    "Nombre de producto", "Unidad", "Pedido de venta", "Contrato", "Negocio",
    "Tipo", "Marca", "Submarca"
    ]
for c in cat_cols:
    if c in df0.columns:
        df0[c] = df0[c].astype("category")

Filtros

Se filtra la base de datos para quedarse solo con las facturas que cumplen dos condiciones: que el Concepto esté dentro de una lista específica (COMERCIAL, CONVENIO INTERADMM, FABRI. PT .TERCERO, OTRAS – MARCAS, VENTAS -INTERNACIONA) y que el Tipo de Producto sea igual a 4 (Producto terminado).

Código Python
# Filtrado
concepto_filtro = ["COMERCIAL", "CONVENIO INTERADMM", "FABRI. PT .TERCERO",
                   "OTRAS - MARCAS", "VENTAS -INTERNACIONA"]
tipo_producto_filtro = 4

df_filter = df0[(df0["Concepto"].isin(concepto_filtro)) &
                (df0["Tipo de Producto"] == tipo_producto_filtro)
                ]

print(f"Base original: {df0.shape[0]} filas")
print(f"Base filtrada: {df_filter.shape[0]} filas")
Base original: 87850 filas
Base filtrada: 50126 filas

Faltantes

Identificar y cuantificar los valores faltantes que tiene cada columna del DataFrame filtrado.

Código Python
# Verificación de datos faltantes
def contar_faltantes_por_columna(df: pd.DataFrame) -> pd.Series:
    """
    Retorna un Series con la cantidad de valores faltantes (NaN) por columna.
    """
    return df.isna().sum()

faltantes = contar_faltantes_por_columna(df_filter)

# Mostrar solo las columnas que tienen al menos un valor faltante
faltantes_con_datos = faltantes[faltantes > 0].sort_values(ascending=False)

print(faltantes_con_datos)
IVA ILC               1379
Ad Valorem ILC        1379
Especifico ILC        1379
Costos de Ventas       677
Ad Valorem cliente     204
Especifico cliente     181
Iva cliente             57
Retención               29
Negocio                  1
dtype: int64

Consistencia de los datos

Revisar y ordenar las presentaciones disponibles en la base de datos, con el fin de identificar valores fuera del rango esperado —mínimo 50 ml y máximo 2000 ml—. En caso de encontrar presentaciones diferentes, como 29 ml, estas deben verificarse y corregirse para garantizar la consistencia y validez de los datos antes de continuar con el análisis.

Código Python
# Mostrar presentaciones
presentaciones = df_filter["Presentación"].dropna().unique().tolist()
presentaciones = sorted(presentaciones) 
print("Presentaciones (ml):", presentaciones)

# Seleccionar columnas
pd.set_option("display.max_colwidth", None)
columnas_interes = ["Nombre de producto", "Presentación"]

# Filtrar Presentación de 29 ml
print("\nPresentaciones con 29 ml:")
display(df_filter.loc[df_filter["Presentación"] == 29, columnas_interes])

# Reemplazar valores
df_filter.loc[df_filter["Presentación"] == 29, "Presentación"] = 1750
Presentaciones (ml): [29, 50, 250, 295, 375, 700, 750, 1000, 1500, 1750, 2000]

Presentaciones con 29 ml:
Nombre de producto Presentación
82633 AGUARDIENTE DEL PUTUMAYO SIN AZÚCAR 1750 VIDRIO 29° NP 29
86214 AGUARDIENTE DEL PUTUMAYO SIN AZÚCAR 1750 VIDRIO 29° NP 29
87398 AGUARDIENTE DEL PUTUMAYO SIN AZÚCAR 1750 VIDRIO 29° NP 29

Reclasificación de datos

Reclasificar permite alinear la información con las reglas de negocio y la estructura real de la operación comercial. Corregir estas inconsistencias evita que registros equivalentes queden dispersos en categorías distintas, mejora la coherencia de los análisis posteriores y reduce el riesgo de sesgos en indicadores de ventas.

Código Python
# Zonas de interés
zonas_objetivo = ["CHOCO-PLA", "CAQUETA-EX", "NAR- AGTE", "DEG-CALDAS"]

# Filtrar filas de zonas y ver en qué Concepto están
df_zonas = df_filter.loc[df_filter["Zona"].isin(zonas_objetivo), ["Zona", "Concepto"]]

# Ver combinaciones únicas Zona–Concepto
df_zonas_unique = df_zonas.drop_duplicates()
display(df_zonas_unique)

# Mapeo de Zona -> Concepto destino
reclasifica = {
    'CHOCO-PLA' : 'OTRAS - MARCAS',
    'CAQUETA-EX': 'OTRAS - MARCAS',
    'NAR- AGTE' : 'CONVENIO INTERADMM',
    'DEG-CALDAS' : 'DEG CON IMPUESTOS'
}

# Aplicar reclasificación
mask = df_filter['Zona'].isin(reclasifica.keys())
df_filter.loc[mask, 'Concepto'] = df_filter.loc[mask, 'Zona'].map(reclasifica)
Zona Concepto
0 CHOCO-PLA OTRAS – MARCAS
2 CHOCO-PLA COMERCIAL
40 CAQUETA-EX COMERCIAL
41 CAQUETA-EX OTRAS – MARCAS
112 NAR- AGTE COMERCIAL
113 NAR- AGTE CONVENIO INTERADMM
265 DEG-CALDAS COMERCIAL

Crear características

La creación de nuevas características a partir de variables existentes es una etapa clave en la preparación de datos, pues permite enriquecer la información utilizada en los análisis y modelos. En este caso, a partir de la columna Fecha de factura se generan las variables Mes y Año, que facilitan el estudio de patrones temporales como estacionalidad, tendencias y variaciones interanuales. Estas nuevas características no solo simplifican la segmentación temporal de las ventas, sino que también permiten agrupar, comparar y modelar el comportamiento comercial con mayor precisión, mejorando la interpretación y la capacidad predictiva de los modelos posteriores.

Código Python
 # Crear columnas Mes y Año a partir de la fecha

df_filter = df_filter.copy()
df_filter["Mes"] = df_filter["Fecha de factura"].dt.month
df_filter["Año"] = df_filter["Fecha de factura"].dt.year

Carga

La etapa de Carga (Load) es la fase final de un proceso ETL (Extracción, Transformación y Carga) y consiste en almacenar los datos que ya fueron depurados, transformados y estructurados en un destino definitivo. Este destino puede ser un archivo (CSV, Parquet, Excel), una base de datos transaccional, un almacén analítico (Data Warehouse), un Data Lake o cualquier sistema donde la información vaya a ser consumida por aplicaciones, analistas o modelos. Su propósito es garantizar que los datos estén disponibles, organizados y accesibles para procesos posteriores de análisis, visualización o modelado.

Código Python
# Exportar a CSV

df_clean = df_filter.copy()
df_clean.to_csv('data/df_clean.csv', index=False, encoding='utf-8-sig')

Análisis Exploratorio de Datos (EDA)

ETL
EDA: Análisis exploratorio de datos.

El EDA (Exploratory Data Analysis) es la fase en la que se entiende el conjunto de datos antes de modelar: se describen distribuciones y medidas resumen, se validan y ajustan tipos de variables, se detectan y tratan valores faltantes y atípicos, y se exploran relaciones entre variables mediante tablas, gráficos y métricas de dependencia. Este proceso revela sesgos, errores de captura y patrones temporales o espaciales, ayuda a formular hipótesis, elegir transformaciones y seleccionar características relevantes. Un buen EDA deja datos consistentes, documenta supuestos y criterios de limpieza, y orienta decisiones posteriores (modelos, segmentaciones o pruebas estadísticas) con evidencia y contexto.

La siguiente gráfica muestra la serie temporal mensual de ventas agregadas (suma) en Cantidad UR —expresada en botellas equivalentes de 750 ml— para el periodo enero 2018 hasta noviembre 2025. Se observa un comportamiento estacional, con picos recurrentes en algunos meses del año y caídas pronunciadas en otros, lo que evidencia una alta variabilidad mensual. A lo largo del horizonte analizado se aprecia además un crecimiento en los niveles máximos, ya que los picos más recientes alcanzan valores superiores a los de los primeros años, destacándose un repunte fuerte hacia el final del periodo (con máximos por arriba de las 7 millones de botellas 750 ml). En conjunto, la serie combina tendencia creciente, estacionalidad y fluctuaciones mensuales que sugieren la influencia de ciclos comerciales y eventos específicos de mitad y fin de año.

Código Python
# Serie temporal agregada
df_sum_m, fig = f.time_series_line(df_clean,
                                 period="mensual", #'mensual','anual'
                                 stat="suma", # 'sum','mean','median','min','max','sd'
                                 date_col="Fecha de factura",
                                 value_col="Cantidad UR",
                                 y_label="Cantidad UR (botellas 750 ml)",
                                 y_max=8000000,
                                 width=1000, height=550)