D
Machine Learning aplicado M3 intermedio 30 min

Capitulo 03

Introducción a Machine Learning y Data Acquisition I

Del ciclo de vida del ML a tu primer pipeline reproducible: lectura, limpieza y train/test

Ciclo de vida del ML, paradigmas supervisado y no supervisado, adquisición de datos heterogéneos con pandas/pathlib y preparación inicial del dataset: duplicados, tipos, nulos y división train/test sin fuga de información.

De la pregunta al modelo, no al revés

Un modelo no empieza con fit(). Empieza cuando puedes formular una pregunta medible, localizar los datos que la responden, verificar su calidad y definir cómo sabrás si la solución funciona. Este capítulo conecta esas cuatro decisiones antes de escribir el primer algoritmo.

ML no es una línea recta: es un ciclo donde cada evaluación genera una nueva pregunta sobre los datos.

— Principio del ciclo de ML

3.1 El ciclo de vida del Machine Learning

Todo proyecto serio de ML recorre el mismo camino, sea para churn, precios o segmentación:

Pregunta de negocio

Problema de datos medible (target + métrica)

Adquisición y comprensión de datos

Preparación y limpieza

Modelado (entrenamiento + validación)

Evaluación con datos no vistos

Despliegue, monitoreo y mejora
        → vuelve a Pregunta

Cada flecha hacia abajo es una transformación; la flecha final hacia arriba es el aprendizaje del equipo. Si la evaluación muestra que el modelo memoriza pero no generaliza, no ajustas hiperparámetros a ciegas: vuelves a los datos.

Las seis etapas con criterio

EtapaPregunta que debe responderEntregable verificable
Definir el problema¿Qué decisión mejora si aciertas?Target con unidad de observación, horizonte y métrica.
Adquisición¿Qué fuentes existen y qué calidad tienen?Inventario de fuentes, fechas de corte y encoding.
Preparación¿Qué filas/columnas son confiables?Dataset limpio, sin duplicados, tipos correctos y contrato.
Modelado¿Qué familia de algoritmos encaja?Baseline + modelo candidato entrenado solo con train.
Evaluación¿Generaliza a datos nuevos?Métrica en test o validación temporal, sin leakage.
Despliegue¿Sigue funcionando en producción?Pipeline versionado, monitoreo de drift y rollback.

Ciclo de vida de Machine Learning

M3

Secuencia iterativa que va de la pregunta de negocio a la evaluación y el despliegue. Cada etapa condiciona a la siguiente y la evaluación puede obligar a volver a los datos.

Ej: Definir churn a 30 días → adquirir historial → limpiar duplicados → entrenar baseline → evaluar recall en test → desplegar y monitorear cancelaciones reales.

#machine-learning #metodologia

Qué documentar antes de codificar

Antes de abrir un notebook, escribe en una línea:

Una fila = un cliente activo al cierre de mes
Target  = cancela en los próximos 30 días (0/1)
Features= compras 90 días, tickets abiertos, saldo pendiente
Horizonte= 30 días
Métrica = F1 (equilibrio entre falsos positivos y falsos negativos)
Baseline= predecir siempre la clase mayoritaria

Si no puedes escribir eso, aún no tienes un problema de ML: tienes una intención.

El costo de una pregunta mal definida

Un modelo perfecto sobre un target ambiguo sigue siendo una mala solución. “Predecir ventas” no es un target. “Predecir unidades por categoría para los próximos 30 días, con datos cerrados al último día del mes y evaluando MAE contra la mediana histórica” sí lo es.

3.2 Aprendizaje supervisado

Aprende de pares (X, y) donde y es la etiqueta conocida. El modelo aproxima la función f: X → y.

Histórico etiquetado              Modelo entrenado         Datos nuevos
[X: compras, tickets, saldo] → f(X) → ŷ (predicción) → decisión

Dos tareas fundamentales

  • Clasificación: y es discreta. ¿El cliente cancela? ¿El mail es spam? ¿Qué segmento asignar?
  • Regresión: y es continua. ¿Cuál será el gasto mensual? ¿El precio de la vivienda? ¿Las unidades vendidas?
PreguntaTipo supervisadoy
¿Cancela en 30 días?Clasificación binaria0 / 1
¿Qué categoría de gasto?Clasificación multiclaseBajo / Medio / Alto
¿Cuánto gastará el próximo mes?Regresión0.0 a ∞

Cuándo usarlo y con qué algoritmos

Úsalo cuando tienes histórico etiquetado y quieres repetir esa misma predicción a futuro.

Algoritmos habituales por tarea:

  • Clasificación: Regresión logística, árboles, Random Forest, XGBoost, SVM, redes neuronales.
  • Regresión: Regresión lineal/regularizada (Ridge/Lasso), árboles y ensembles, redes.

Aprendizaje supervisado

M3

Paradigma donde el modelo aprende de ejemplos etiquetados (X, y) para predecir la etiqueta de nuevas observaciones.

Ej: Dataset con columna gasto_mensual conocida: X = [id_cliente, segmento] → y = gasto_mensual. El modelo aprende a estimar gasto para clientes nuevos.

#machine-learning #supervisado
# Idea del flujo supervisado con scikit-learn
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier

# X = features, y = target conocido
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
modelo = RandomForestClassifier(random_state=42)
modelo.fit(X_train, y_train)          # aprende de etiquetas
predicciones = modelo.predict(X_test) # aplica a datos no vistos

La validación se hace comparando predicciones contra y_test con una métrica adecuada (accuracy, F1, RMSE, etc.).

3.3 Aprendizaje no supervisado

Solo tienes X, sin y. El objetivo es descubrir estructura.

  • Clustering: agrupar observaciones similares sin clases predefinidas. Ej: segmentar clientes por gasto y frecuencia.
  • Reducción de dimensionalidad: PCA, t-SNE, UMAP para comprimir o visualizar.
  • Reglas de asociación: “quien compra X también compra Y”.
  • Detección de anomalías: transacciones que no encajan con el comportamiento normal.
Pregunta¿Hay y?Enfoque
¿Qué grupos naturales existen?NoClustering (K-Means, DBSCAN)
¿Cómo visualizar 50 features en 2D?NoPCA / t-SNE
¿Qué compra suele ir junta?NoApriori / FP-Growth

Aprendizaje no supervisado

M3

Paradigma que busca patrones, grupos o representaciones en datos sin etiqueta objetivo. No predice un y externo, describe la estructura interna de X.

Ej: Agrupar clientes por gasto y segmento sin columna objetivo, para descubrir perfiles Premium estable vs. Básico volátil.

#machine-learning #no-supervisado
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler

# Sin y: solo X
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X[['gasto_mensual']])

kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
clusters = kmeans.fit_predict(X_scaled)

La evaluación aquí no usa y_test. Se usan métricas internas como inercia o silueta, o validación cualitativa con negocio.

3.4 Cómo elegir: del problema al paradigma

ConceptoDescripcion
Supervisado Pares (X, y) etiquetados Necesita histórico con respuesta.
Supervisado Predecir y para nuevos X Clasificación o regresión.
Supervisado Contra y real (F1, RMSE) Test no visto.
No supervisado Solo X, sin etiqueta Cuando etiquetar es costoso.
No supervisado Descubrir grupos o estructura Clustering, PCA.
No supervisado Métricas internas o negocio Silueta, coherencia.

Regla práctica en una línea:

Si puedes preguntar “dado X, ¿cuánto vale y?”, es supervisado. Si preguntas “¿qué grupos naturales existen en X?”, es no supervisado.

💡 Piensa si tienes la respuesta histórica para aprender.

El error más caro

Etiquetar mal el problema cuesta más que elegir mal el algoritmo. Un clustering no te da una predicción de churn accionable; una clasificación sin histórico etiquetado no tiene de dónde aprender. Define primero el tipo, luego el algoritmo.

3.5 Fuentes de datos y formatos

La adquisición condiciona todo lo que sigue. Clasifica la fuente antes de elegir herramienta.

TipoEstructuraEjemploHerramienta
EstructuradoFilas y columnas con esquema fijoTabla SQL, CSV bien formadoSQL, Pandas
SemiestructuradoJerarquías con claves flexiblesJSON de API, XMLrequests, json, pandas
No estructuradoSin esquema tabularTexto libre, imágenes, audioNLP, visión, embeddings

CSV vs Excel vs JSON vs Parquet

FormatoNaturalezaVentajaRiesgo
CSVTexto planoUniversal, portable, legibleSin tipos: todo puede entrar como texto
ExcelBinario de oficinaFórmulas, hojas, formatoLento, metadatos ocultos, no versionable
JSONTexto jerárquicoFlexible, estándar de APIsAnidado: requiere normalización
ParquetBinario columnarComprimido, con schema, lee por columnasRequiere motor que lo entienda (pyarrow)

Data acquisition

M3

Proceso de obtener datos desde una fuente externa (archivo, API, base) y dejarlos disponibles para validar, transformar y analizar dentro del pipeline.

Ej: Leer ventas_mensuales.csv con pandas y configuracion_tienda.json con json.load, registrando origen, fecha y encoding.

#data-acquisition #pipelines

Criterio profesional: registra siempre origen, fecha de extracción, encoding y versión del archivo. Sin eso no puedes reproducir un resultado.

3.6 Lectura robusta en Python: pandas, json y pathlib

Dos errores clásicos rompen pipelines en producción: rutas absolutas hardcodeadas y encoding asumido.

Por qué pathlib.Path

# Frágil: solo funciona en tu máquina
csv_path = "C:\\Users\\denny\\OneDrive\\Documentos\\archivo.csv"

# Robusto: portable entre SO y resiste mover el proyecto
from pathlib import Path
csv_path = Path("data") / "ventas_mensuales.csv"
# Si el script se mueve, usa Path(__file__).parent / "data" / "ventas_mensuales.csv"

Path une con / de forma portable, resuelve .. y evita mezclar \ y /.

Por qué encoding="utf-8"

CSV no declara encoding. Si el archivo tiene tildes, ñ o emojis y lees sin declarar, obtienes UnicodeDecodeError o caracteres mojibake.

import pandas as pd

# Explícito y reproducible
df = pd.read_csv(csv_path, encoding="utf-8")
print(df.head())
print(df.dtypes)  # verifica qué tipos infirió pandas

Por qué with + json.load

import json

# json.load lee desde archivo abierto; json.loads lee desde string
with open(json_path, encoding="utf-8") as f:
    config = json.load(f)  # asegura cierre del archivo incluso si falla

print(config["nombre_tienda"])

with garantiza cierre del descriptor. json.load(f) vs json.loads(texto): la s es de string.

Verifica inmediatamente después de leer

Después de cada lectura imprime shape, head() y dtypes (CSV) o type() y claves (JSON). Si el DataFrame está vacío o el dict no tiene la clave esperada, falla temprano con mensaje claro en lugar de arrastrar el error al modelo.

Ejercicio 1 — Carga de CSV y JSON

Práctica guiada: adquirir dos formatos distintos intermedio

Adquiere un CSV tabular y un JSON de configuración, verificando cada paso.

import pandas as pd
import json
from pathlib import Path

# Define rutas portables (no absolutas)
csv_path = Path("data/ventas_mensuales.csv")
json_path = Path("data/configuracion_tienda.json")

def cargar_datos():
    # --- CSV con encoding explícito ---
    df_ventas = pd.read_csv(csv_path, encoding="utf-8")
    print(df_ventas.head())   # primeras 5 filas
    print(df_ventas.shape)    # filas × columnas
    print(df_ventas.dtypes)   # tipos inferidos

    # --- JSON con with + json.load ---
    with open(json_path, encoding="utf-8") as f:
        data_config = json.load(f)

    print(data_config["nombre_tienda"])
    print(f"Claves del JSON: {list(data_config.keys())}")

    return df_ventas, data_config

if __name__ == "__main__":
    ventas, config = cargar_datos()

Checklist:

  • df_ventas.shape no es (0, 0) y head() muestra las columnas esperadas.
  • data_config es dict y data_config["nombre_tienda"] no lanza KeyError.
  • FileNotFoundError → revisa que csv_path/json_path sean relativos al proyecto o uses Path(__file__).parent.

3.7 Preparación inicial: duplicados, tipos y nulos

Los datos reales llegan sucios. Limpiar no es “estorbo previo”: es parte del modelado.

Duplicados

# Duplicados exactos: filas 100% idénticas
df = df.drop_duplicates()

# Duplicados por clave de negocio
df = df.drop_duplicates(subset=["id_cliente"])
print(f"Duplicados restantes: {df.duplicated().sum()}")

Un duplicado exacto sesga promedios y conteos. Un duplicado por clave (mismo id_cliente dos veces) puede indicar un join mal hecho aguas arriba.

Tipos

Antes de modelar, asegura que cada columna tenga el tipo que su semántica exige:

  • gasto_mensualfloat (numérico continuo)
  • segmentostr / category (categórica)
  • id_clienteint o str según si es identificador o feature
df["gasto_mensual"] = pd.to_numeric(df["gasto_mensual"], errors="coerce")
df["segmento"] = df["segmento"].astype("string")

errors="coerce" convierte lo no numérico en NaN en lugar de romper el pipeline: luego decides si imputas o descartas.

Nulos: mediana vs media vs etiqueta

ColumnaTipoEstrategiaPor qué
gasto_mensualNuméricaMedianaRobusta a outliers; la media se infla con un gasto de $50.000
segmentoCategórica"Sin Info"Etiqueta explícita: preserva la fila y permite auditarla
import numpy as np

mediana = df["gasto_mensual"].median()  # ignora NaN por defecto
df["gasto_mensual"] = df["gasto_mensual"].fillna(mediana)

df["segmento"] = df["segmento"].fillna("Sin Info")

print(df.isna().sum())  # debe ser 0 en ambas columnas

Imputación por mediana

M3

Reemplazo de valores faltantes numéricos por la mediana de la columna. Es robusta a valores extremos, a diferencia de la media.

Ej: gasto_mensual con valores [150, 250, 300, 400, 50000] → mediana 300, media 10220. Imputar con 300 preserva la distribución central.

#limpieza #nulos

fillna no modifica in-place por defecto. df["col"].fillna(v) sin asignar no cambia df. Usa df["col"] = df["col"].fillna(v).

3.8 División train/test y fuga de información

Entrenar y evaluar sobre los mismos datos es autoengaño. Separa:

from sklearn.model_selection import train_test_split

# X = features, y = target
X = df[["id_cliente", "segmento"]]  # o df.drop(columns=["gasto_mensual"])
y = df["gasto_mensual"]

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=123
)
print(f"Train: {len(X_train)} filas | Test: {len(X_test)} filas")
  • test_size=0.2 → 80% entrena, 20% evalúa. Estándar en datasets pequeños/medianos. Con 1.000 filas: 800 train / 200 test.
  • random_state=123 → semilla fija: el mismo split reproducible en cada ejecución. Sin ella, cada run baraja distinto y no puedes comparar experimentos.
  • stratify=y (opcional, en clasificación) → preserva proporción de clases en train y test.

El principio que no puedes violar: sin leakage

Data leakage

M3

Fuga de información del conjunto de test o del futuro hacia el entrenamiento. Produce métricas artificialmente optimistas que colapsan en producción.

Ej: Calcular la mediana sobre todo el dataset antes de separar train/test, o hacer scaler.fit sobre df completo. La mediana/scaler debe aprenderse solo de train.

#validacion #riesgos

En este capítulo simplificamos y calculamos la mediana sobre el dataset completo para enfocarnos en la mecánica. En un pipeline profesional:

# Correcto en producción: mediana aprendida solo de train
mediana_train = X_train["gasto_mensual"].median() if "gasto_mensual" in X_train else df_train["gasto_mensual"].median()
X_train["gasto_mensual"] = X_train["gasto_mensual"].fillna(mediana_train)
X_test["gasto_mensual"]  = X_test["gasto_mensual"].fillna(mediana_train)  # reutiliza la de train

La misma regla aplica a StandardScaler, OneHotEncoder y cualquier transformador: fit en train, transform en test.

Orden correcto del pipeline
  1. Limpia duplicados → 2. Separa train/test → 3. Aprende imputación/scaling solo de train → 4. Aplica a test → 5. Entrena → 6. Evalúa. Alterar el orden filtra información y falsea la métrica.

Ejercicio 2 — Limpieza, imputación y división

Práctica guiada: de dataset sucio a train/test intermedio

Dataset con duplicados y nulos, como llega del mundo real.

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split

data = {
    "id_cliente": [101, 102, 102, 103, 104, 105],
    "gasto_mensual": [250.0, 300.5, 300.5, np.nan, 150.0, 400.0],
    "segmento": ["Premium", "Básico", "Básico", "Premium", np.nan, "Básico"],
}
df = pd.DataFrame(data)

# 1. Duplicados exactos
df = df.drop_duplicates()
print(f"Filas tras dedup: {len(df)}")  # 5, no 6

# 2. Mediana para numérico (robusta)
mediana = df["gasto_mensual"].median()
df["gasto_mensual"] = df["gasto_mensual"].fillna(mediana)
print(f"Mediana imputada: {mediana}")

# 3. Etiqueta para categórico
df["segmento"] = df["segmento"].fillna("Sin Info")

# 4. División 80/20 reproducible
X = df[["id_cliente", "segmento"]]
y = df["gasto_mensual"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=123)

print("Train:\n", X_train)
print("Nulos por columna:\n", df.isna().sum())

Verificación automática:

assert df.duplicated().sum() == 0, "Quedan duplicados"
assert df["gasto_mensual"].isna().sum() == 0
assert df["segmento"].isna().sum() == 0
assert len(X_train) == 4 and len(X_test) == 1  # 80/20 de 5 filas tras dedup

Reto: convierte segmento a numérico con pd.get_dummies(df["segmento"], dtype=int) y explica por qué LinearRegression no acepta strings directos.

3.9 Integración: pipeline reproducible mínimo

Un pipeline no es un script largo: es una secuencia con contrato y validación.

import pandas as pd
from pathlib import Path

def cargar_datos(csv_path: Path) -> pd.DataFrame:
    df = pd.read_csv(csv_path, encoding="utf-8")
    return df

def validar_contrato(df: pd.DataFrame) -> pd.DataFrame:
    requeridas = {"id_cliente", "gasto_mensual", "segmento"}
    faltantes = requeridas - set(df.columns)
    if faltantes:
        raise ValueError(f"Faltan columnas: {faltantes}")
    if (df["gasto_mensual"] < 0).any():
        raise ValueError("gasto_mensual no puede ser negativo")
    return df

def limpiar(df: pd.DataFrame) -> pd.DataFrame:
    df = df.drop_duplicates()
    mediana = df["gasto_mensual"].median()
    df["gasto_mensual"] = df["gasto_mensual"].fillna(mediana)
    df["segmento"] = df["segmento"].fillna("Sin Info")
    return df

# Uso
df = cargar_datos(Path("data/ventas.csv"))
df = validar_contrato(df)
df = limpiar(df)

Tres evidencias de un pipeline profesional:

  • Contrato: columnas, tipos y rangos esperados (falla temprano si el origen cambia).
  • Linaje: de qué archivo vino cada columna y qué transformaciones recibió.
  • Idempotencia: dos ejecuciones con la misma entrada producen el mismo resultado, sin duplicar.
Ruta absoluta hardcodeada
✗ pd.read_csv("C:\\Users\\denny\\ventas.csv")
✓ Path("data/ventas.csv") o Path(__file__).parent / "data" / "ventas.csv"
fillna sin asignar
✗ df["gasto"].fillna(mediana)
✓ df["gasto"] = df["gasto"].fillna(mediana)
Mediana sobre todo el dataset
✗ mediana = df_completo.median() antes de train_test_split
✓ mediana = df_train.median(); aplicar a train y test
Evaluar sobre train
✗ modelo.score(X_train, y_train)
✓ modelo.score(X_test, y_test) con datos no vistos

3.10 Preguntas de entrevista

Q: ¿Por qué imputar gasto_mensual con mediana y no con media?

La mediana es robusta a outliers. Un gasto de $50.000 infla la media pero apenas mueve la mediana. En distribuciones sesgadas, la mediana preserva el centro real.

🪤 Decir que la media siempre es mejor porque usa todos los datos.

Q: ¿Por qué separar train/test antes de calcular la mediana o el scaler?

Para evitar leakage. Si usas test para calcular estadísticas, el modelo ve indirectamente información del futuro y la métrica se vuelve optimista. Fit solo en train, transform en ambos.

🪤 Calcular todo sobre el dataset completo porque es más cómodo.

💡 Piensa en reproducibilidad entre ejecuciones.

3.11 Recursos

Libro: Hands-On Machine Learning with Scikit-Learn, Keras & TensorFlow — Aurélien Géron, caps. 1-2: ciclo de vida, tipos de aprendizaje y primer pipeline.
Libro: Python for Data Analysis — Wes McKinney, cap. 6: lectura de CSV, JSON y manejo de archivos con pandas/pathlib.
Libro: Introduction to Statistical Learning — James et al., cap. 2: fundamentos de supervisado/no supervisado (gratuito en statlearning.com).