Sprint 1–7  ·  I Cuatrimestre 2026

Retail
Inteligente
& AI Forecasting

Predicción de demanda y optimización de inventario

Integrantes
Isaac Ulloa Calvo
Jean Carlo Ramirez Carranza
Jeffrey Jimenez Cordero
UniversidadCUC — Colegio Universitario de Cartago
CursoBig Data
ProfesoraEricka Valverde Navarro
DEMAND AI FORECAST DATA
0
Registros en el dataset
0
Variables / columnas
71%
Precisión modelo ML

El problema que resolvemos

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.

🏪 Contexto

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.

⚠️ El dolor

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.

📉 El impacto

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.

🎯 La solución

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.

¿Qué queremos lograr?

Objetivo General

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.

01

Analizar el comportamiento de los clientes en diferentes fechas del mes para identificar patrones de compra estacionales.

02

Identificar cuáles son los clientes más frecuentes y segmentarlos según su historial de compras.

03

Identificar los productos más vendidos por categoría, región y temporada del año.

04

Predecir la demanda futura de productos usando modelos de machine learning (Prophet + scikit-learn).

Los datos en gráficas

Visualizaciones construidas a partir de los 73,100 registros reales del dataset de Kaggle usando Python + Pandas + Seaborn.

Ventas totales por categoría de producto
Líder: Furniture (2,025,017 un.) — diferencia mínima entre categorías; distribución casi uniforme.
Ventas totales por región geográfica
Región East (2,511,265 un.) lidera. Cada región representa aproximadamente el 25% del total.
Unidades vendidas totales por mes
Pico en Julio (864,547 un.) y mínimo en Febrero. Patrón de estacionalidad visible en verano.
Distribución de unidades vendidas por rango
El 25.5% de transacciones vende entre 0–50 unidades. Distribución sesgada hacia valores bajos.
Demand forecast promedio por categoría
Furniture: 142.8 un. mayor forecast promedio del modelo.
Ventas promedio por temporada
Autumn (137.8 un.) supera a Summer (135.4 un.). Diferencias leves entre estaciones.
Precio promedio por categoría ($)
Electronics: $55.31 precio promedio más alto por categoría.
Dispersión — Precio vs Unidades Vendidas (muestra 60 pts.)
Sin correlación lineal clara. La demanda es inelástica al precio: factores como temporada, promociones e inventario tienen mayor influencia que el precio por sí solo.

Lo que se implementó

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
Fase 7 — Clasificación ML
Fase 3 — EDA
Fase 2 — Data Warehouse SQL
Map_Reduce_Fase6_BigData.pyPython
# ═══════════════════════════════════════════════════════════
# 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)
Fase7ClasificacionProyectoBD.pyPython · scikit-learn
# ═══════════════════════════════════════════════════════════
# 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.
EDA.ipynb — Análisis Exploratorio de DatosPython · Jupyter Notebook
# ═══════════════════════════════════════════════════════════
# 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()
Fase2_DataWarehouse.sql — SQL ServerT-SQL
-- ═══════════════════════════════════════════════════════
-- 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 del Data Warehouse

Consultas analíticas construidas sobre el esquema estrella de RetailDW para responder las preguntas clave del negocio.

Ventas totales por mesEstacionalidad
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;
Resultado esperado (muestra)
2023 · Ene → 856,590 unidades
2023 · Jul → 864,547 unidades (pico)
2023 · Dic → 830,993 unidades
Ventas por categoríaRanking
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;
Resultado esperado
Furniture → 2,025,017 · $55.18 · 142.8
Groceries → 2,000,482 · $55.27 · 141.9
Electronics → 1,960,432 · $55.31 · 140.0
Ventas por regiónGeografía
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;
Resultado esperado
East → 2,511,265 un. · 18,349 transacciones
South → 2,507,799 un. · 18,297 transacciones
West → 2,471,552 un. · 18,226 transacciones
Impacto de feriadosPromociones
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;
Resultado esperado
Feriado + Autumn → prom. 137.8 un.
Feriado + Summer → prom. 135.4 un.
Sin feriado → prom. 136.5 un. (similar)

Clasificación de alta demanda

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.

71%
Accuracy del modelo
7 de cada 10 predicciones son correctas
136.46
Umbral de Alta Demanda
Promedio de Units Sold del dataset
6
Features utilizadas
Variables predictoras del modelo
2
Clases a predecir
Alta demanda (1) · Baja demanda (0)
¿Qué hace el modelo?

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.

Definición del target
alta_demanda = 1 si Units_Sold > 136.46
alta_demanda = 0 si Units_Sold 136.46
Algoritmo utilizado

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.

💰
Price
Precio unitario del producto. Rango: $10–$100. Promedio: $55.14. Incluida para detectar si el precio influye en el volumen de ventas.
📦
Units Ordered
Unidades ordenadas al proveedor. Refleja la expectativa de demanda del comprador. Alta correlación esperada con ventas reales.
🏬
Inventory Level
Nivel de inventario en bodega. Un inventario bajo puede indicar alta rotación del producto, lo que se correlaciona con alta demanda.
🏷️
Discount
Porcentaje de descuento aplicado. Promedio: ~10%. Permite medir si las promociones de precio impulsan el volumen de ventas.
🎉
Holiday / Promotion
Variable binaria (0/1): indica si la transacción ocurrió en un día festivo o en periodo de promoción activa.
🏪
Competitor Pricing
Precio de la competencia para productos similares. Permite capturar el efecto del mercado externo sobre la demanda interna.
Resultado obtenido

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.

🎯 ¿Qué logramos con el modelo?

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.

⚠️ Limitaciones y mejoras futuras

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.

Riesgo de stock por categoría — Tienda S003
Electronics (215 un.) y Toys (210 un.) presentan el mayor volumen de ventas promedio en S003, lo que aumenta el riesgo de quiebre de stock si no se reabastece a tiempo.
⚠️ ¿Por qué S003 es la más crítica?

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.

✅ Acción recomendada

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.

Lambda Architecture

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.

Fuentes
Ventas POS · Inventario · Eventos · Clima
Batch (histórico)
CSV/JSON diario · Pandas
Speed (tiempo real)
Micro-batch · Spark Streaming
Raw / Landing
Data Lake local — CSV/JSON crudo
Procesamiento Batch
PySpark · limpieza · features · agregaciones · entrenamiento
Curated / Processed
Parquet limpio · Data Warehouse SQL Server · Esquema Estrella
Análisis / IA
Prophet · scikit-learn · Regresión Logística · Map-Reduce
Consumo / Visualización
Power BI · Alertas Email / Telegram
Python
Pandas · scikit-learn · Prophet
SQL Server
Star Schema · Views · DW
Power BI
Dashboard · Reportes · KPIs

Lo que aprendimos

📊

Valor del EDA

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.

🗄️

Data Warehouse robusto

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.

🤖

ML aplicado al retail

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.

Map Reduce funcional

La implementación manual MAP → SHUFFLE → REDUCE demostró el paradigma de procesamiento distribuido para agregar millones de registros por región y categoría.

🏗️

Arquitectura Lambda

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.

🎯

Big Data en retail

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.