Predicción de demanda y optimización de inventario
El sector retail enfrenta pérdidas millonarias por mala gestión del inventario: sobrestock, quiebres de producto y predicciones imprecisas. La solución está en los datos.
Empresas minoristas como Walmart usan IA para analizar historiales de pedidos, patrones de navegación y programas de fidelización, logrando gestión de inventario completamente automatizada y experiencia del cliente personalizada.
Muchas organizaciones no aprovechan el valor de su data. Hay empresas que aún gestionan ventas en papel, perdiendo oportunidades por falta de implementación tecnológica o por temas de costo para innovar.
Las malas predicciones generan sobrestock, liquidaciones masivas, pérdida de clientes y deterioro de márgenes. El problema afecta directamente la rentabilidad y obliga a tener capital inmovilizado.
Aplicar Big Data e IA sobre datos históricos de ventas para detectar patrones, generar KPIs y construir modelos de predicción de demanda basados en evidencia real de 73,100 transacciones.
Mejorar el retail de empresas mediante el análisis de datos para tomar mejores decisiones basadas en evidencia, optimizando el inventario y anticipando la demanda futura con modelos de inteligencia artificial.
Analizar el comportamiento de los clientes en diferentes fechas del mes para identificar patrones de compra estacionales.
Identificar cuáles son los clientes más frecuentes y segmentarlos según su historial de compras.
Identificar los productos más vendidos por categoría, región y temporada del año.
Predecir la demanda futura de productos usando modelos de machine learning (Prophet + scikit-learn).
Visualizaciones construidas a partir de los 73,100 registros reales del dataset de Kaggle usando Python + Pandas + Seaborn.
Scripts desarrollados en Python para Map Reduce, clasificación con Machine Learning, análisis exploratorio y el modelo de Data Warehouse en SQL Server.
# ═══════════════════════════════════════════════════════════ # FASE 6 — Map Reduce # Calcula ventas totales por Región y Categoría # Paradigma: MAP → SHUFFLE → REDUCE # ═══════════════════════════════════════════════════════════ import pandas as pd from collections import defaultdict df = pd.read_csv(r"retail_store_inventory.csv") # ─── ETAPA 1: MAP ──────────────────────────────────────── # Para cada fila genera una tupla (clave, monto_de_venta) mapped_region = [] mapped_category = [] for _, row in df.iterrows(): region = row.get("Region") category = row.get("Category") units_sold = row.get("Units Sold") price = row.get("Price") # Validar nulos y valores no positivos if pd.isna(region) or pd.isna(category): continue if units_sold <= 0 or price <= 0: continue monto = units_sold * price mapped_region.append((region, monto)) mapped_category.append((category, monto)) # ─── ETAPA 2: SHUFFLE ─────────────────────────────────── # Agrupa los valores por clave def agrupar(mapped_data): groups = defaultdict(list) for k, v in mapped_data: groups[k].append(v) return groups groups_region = agrupar(mapped_region) groups_category = agrupar(mapped_category) # ─── ETAPA 3: REDUCE ──────────────────────────────────── # Suma todos los montos de cada grupo reduced_region = {k: sum(vs) for k, vs in groups_region.items()} reduced_category = {k: sum(vs) for k, vs in groups_category.items()} print("\nVentas por región:", reduced_region) print("\nVentas por categoría:", reduced_category)
# ═══════════════════════════════════════════════════════════ # FASE 7 — Clasificación de Alta/Baja Demanda # Modelo: Regresión Logística | Accuracy: ~71% # ═══════════════════════════════════════════════════════════ import pandas as pd from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score # ─── Cargar datos ──────────────────────────────────────── df = pd.read_csv(r"retail_store_inventory.csv") df.columns = df.columns.str.strip() # ─── Variable objetivo (target) ───────────────────────── # 1 = Alta demanda (Units Sold > promedio) # 0 = Baja demanda (Units Sold ≤ promedio) promedio_ventas = df['Units Sold'].mean() # 136.46 df['alta_demanda'] = (df['Units Sold'] > promedio_ventas).astype(int) # ─── Features (6 variables predictoras) ───────────────── X = df[['Price', 'Units Ordered', 'Inventory Level', 'Discount', 'Holiday/Promotion', 'Competitor Pricing']] y = df['alta_demanda'] # ─── Entrenamiento ─────────────────────────────────────── model = LogisticRegression(max_iter=1000) model.fit(X, y) # ─── Predicciones y evaluación ─────────────────────────── predicciones = model.predict(X) accuracy = accuracy_score(y, predicciones) print(f"Accuracy: {accuracy:.2%}") # → ~71% print(f"Total de registros: {len(df)}") print(f"Promedio de ventas: {promedio_ventas:.2f}") # Resultado: 71% de accuracy. Variables más relevantes: # Inventory Level y Units Ordered. # El modelo es útil para apoyar decisiones operativas en retail.
# ═══════════════════════════════════════════════════════════ # FASE 3 — EDA (Exploratory Data Analysis) # Librerías: Pandas, NumPy, Matplotlib, Seaborn # ═══════════════════════════════════════════════════════════ import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns # ─── 1. Carga y exploración inicial ───────────────────── df = pd.read_csv("retail_store_inventory.csv") print("Shape:", df.shape) # (73100, 15) print(df.describe(include="all")) print(df.isnull().sum()) # ─── 2. Normalización de columnas ─────────────────────── df["Category"] = df["Category"].astype("category") df["Region"] = df["Region"].astype("category") cat_cols = df.select_dtypes(include=["object"]).columns df[cat_cols] = df[cat_cols].apply(lambda c: c.astype("string").str.strip().str.lower()) # ─── 3. Frecuencia por categoría ──────────────────────── cat_counts = df['Category'].value_counts() plt.figure(figsize=(10, 6)) sns.barplot(x=cat_counts.index, y=cat_counts.values) plt.title("Frecuencia de productos por categoría") plt.show() # ─── 4. Densidad Precio vs Unidades Vendidas ──────────── plt.hexbin(df['Price'], df['Units Sold'], gridsize=50, cmap='Blues') plt.colorbar() plt.title("Densidad: Precio vs Unidades Vendidas") plt.show() # ─── 5. Mapa de calor: Región × Clima ─────────────────── pivot = (df.groupby(["Region", "Weather Condition"]) .size().reset_index(name="conteo") .pivot(index="Region", columns="Weather Condition", values="conteo") .fillna(0)) sns.heatmap(pivot, annot=True, fmt=".0f", cmap="Blues") plt.title("Transacciones por Región y Clima") plt.show() # ─── 6. Matriz de correlación numérica ────────────────── df_num = df.select_dtypes(include=["number"]) sns.heatmap(df_num.corr(), annot=True, fmt=".2f", cmap="coolwarm") plt.title("Matriz de Correlación") plt.show()
-- ═══════════════════════════════════════════════════════ -- FASE 2 — Data Warehouse · Esquema Estrella -- Base de datos: RetailDW | Motor: SQL Server -- ═══════════════════════════════════════════════════════ CREATE DATABASE RetailDW; USE RetailDW; -- ─── Dimensión Tiempo ──────────────────────────────── CREATE TABLE dim_tiempo ( id_tiempo INT IDENTITY(1,1) PRIMARY KEY, fecha DATE NOT NULL, anio INT, mes INT, dia INT, seasonality VARCHAR(20), holiday BIT ); -- ─── Dimensión Tienda ──────────────────────────────── CREATE TABLE dim_tienda ( id_tienda INT IDENTITY(1,1) PRIMARY KEY, store_id VARCHAR(10) NOT NULL, region VARCHAR(50) ); -- ─── Dimensión Producto ────────────────────────────── CREATE TABLE dim_producto ( id_producto INT IDENTITY(1,1) PRIMARY KEY, product_id VARCHAR(10) NOT NULL, category VARCHAR(50) ); -- ─── Dimensión Clima ───────────────────────────────── CREATE TABLE dim_clima ( id_clima INT IDENTITY(1,1) PRIMARY KEY, weather_condition VARCHAR(30) ); -- ─── Tabla de Hechos (Fact Table) ──────────────────── CREATE TABLE fact_ventas_inventario ( fk_tiempo INT NOT NULL, fk_tienda INT NOT NULL, fk_producto INT NOT NULL, fk_clima INT NOT NULL, units_sold INT, units_ordered INT, inventory_level INT, demand_forecast DECIMAL(10,2), price DECIMAL(10,2), discount DECIMAL(5,2), competitor_pricing DECIMAL(10,2), PRIMARY KEY (fk_tiempo, fk_tienda, fk_producto, fk_clima), FOREIGN KEY (fk_tiempo) REFERENCES dim_tiempo(id_tiempo), FOREIGN KEY (fk_tienda) REFERENCES dim_tienda(id_tienda), FOREIGN KEY (fk_producto) REFERENCES dim_producto(id_producto), FOREIGN KEY (fk_clima) REFERENCES dim_clima(id_clima) ); -- ─── Carga masiva desde CSV ────────────────────────── BULK INSERT staging_retail FROM 'C:\csv retail\retail_store_inventory.csv' WITH (FIRSTROW=2, FIELDTERMINATOR=',', ROWTERMINATOR='\n', TABLOCK);
Consultas analíticas construidas sobre el esquema estrella de RetailDW para responder las preguntas clave del negocio.
SELECT t.anio, t.mes, SUM(f.units_sold) AS total_ventas FROM fact_ventas_inventario f JOIN dim_tiempo t ON f.fk_tiempo = t.id_tiempo GROUP BY t.anio, t.mes ORDER BY t.anio, t.mes;
SELECT p.category, SUM(f.units_sold) AS total_ventas, AVG(f.price) AS precio_promedio, AVG(f.demand_forecast) AS forecast_promedio FROM fact_ventas_inventario f JOIN dim_producto p ON f.fk_producto = p.id_producto GROUP BY p.category ORDER BY total_ventas DESC;
SELECT ti.region, SUM(f.units_sold) AS total_ventas, AVG(f.inventory_level) AS inventario_prom, COUNT(*) AS total_transacciones FROM fact_ventas_inventario f JOIN dim_tienda ti ON f.fk_tienda = ti.id_tienda GROUP BY ti.region ORDER BY total_ventas DESC;
SELECT t.holiday, t.seasonality, SUM(f.units_sold) AS total_ventas, AVG(f.units_sold) AS venta_promedio, AVG(f.discount) AS descuento_prom FROM fact_ventas_inventario f JOIN dim_tiempo t ON f.fk_tiempo = t.id_tiempo GROUP BY t.holiday, t.seasonality ORDER BY t.holiday DESC;
Aplicamos un modelo de Regresión Logística (scikit-learn) sobre los 73,100 registros para predecir si un producto tendrá alta o baja demanda, con el objetivo de anticipar el reabastecimiento y evitar quiebres de stock.
El modelo analiza cada transacción del dataset y le asigna una etiqueta binaria: 1 = Alta Demanda si las unidades vendidas superan el promedio histórico de 136.46 unidades, o 0 = Baja Demanda si está por debajo.
Regresión Logística de scikit-learn. Ideal para clasificación binaria: es interpretable, eficiente con grandes volúmenes de datos y permite entender el peso de cada variable sobre la predicción.
El modelo clasifica correctamente el 71% de los registros. El 29% restante representa casos donde los factores externos (clima, eventos) generan demanda difícil de predecir sólo con estas 6 variables.
Identificar productos con alta probabilidad de agotarse antes del próximo reabastecimiento.
Priorizar qué SKUs deben reordenarse primero, optimizando el capital invertido en inventario.
Generar alertas automáticas para el gerente de tienda cuando un producto entra en zona de alta demanda.
Reducir el sobrestock en categorías de baja demanda, liberando espacio en bodega y capital inmovilizado.
El 71% podría mejorar incluyendo variables de temporada, clima y categoría como features adicionales.
Se podría dividir en train/test para obtener métricas de generalización más rigurosas.
Modelos como Random Forest o XGBoost podrían capturar relaciones no lineales entre variables.
Al revisar los niveles de inventario predichos por el modelo, la Tienda S003 es la que presenta el ratio ventas/stock más alto entre todas las sucursales analizadas.
Categorías como Electronics y Toys muestran promedios de venta superiores a 210 unidades, muy por encima del umbral de alta demanda de 136.46 unidades.
Sin la predicción del modelo, este riesgo no sería evidente hasta que el stock ya estuviera agotado, generando pérdidas de ventas y mala experiencia al cliente.
Priorizar el reabastecimiento de Electronics y Toys en S003 antes del próximo ciclo de ventas.
Generar una alerta automática en Power BI para el gerente de S003 cuando el inventario de estas categorías baje del umbral crítico.
Revisar si Groceries (173 un.) tiene menor rotación o si la demanda está siendo cubierta correctamente con el stock actual.
Arquitectura Lambda elegida por su capacidad de combinar procesamiento batch de grandes volúmenes históricos con análisis speed de datos recientes para predicciones precisas.
Las 5 categorías tienen distribución casi uniforme (~14,600 c/u). Clima y festivos influyen en la demanda, aunque el precio por sí solo no determina el volumen de ventas.
El esquema estrella con 4 dimensiones habilitó consultas analíticas eficientes. La carga de 73,100 registros con BULK INSERT validó la arquitectura de almacenamiento.
Regresión logística alcanzó 71% de accuracy. Inventory Level y Units Ordered fueron las variables más relevantes para clasificar alta demanda en el entorno retail.
La implementación manual MAP → SHUFFLE → REDUCE demostró el paradigma de procesamiento distribuido para agregar millones de registros por región y categoría.
La separación batch / speed cubrió los requerimientos analíticos e históricos. Power BI como capa de visualización conecta el pipeline técnico con las decisiones del negocio.
Con datos estructurados, herramientas open source y modelos de IA es posible mejorar la gestión de inventario y anticipar demanda, reduciendo sobrestock y pérdidas.