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.
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
| Etapa | Pregunta que debe responder | Entregable 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
M3Secuencia 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.
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.
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:
yes discreta. ¿El cliente cancela? ¿El mail es spam? ¿Qué segmento asignar? - Regresión:
yes continua. ¿Cuál será el gasto mensual? ¿El precio de la vivienda? ¿Las unidades vendidas?
| Pregunta | Tipo supervisado | y |
|---|---|---|
| ¿Cancela en 30 días? | Clasificación binaria | 0 / 1 |
| ¿Qué categoría de gasto? | Clasificación multiclase | Bajo / Medio / Alto |
| ¿Cuánto gastará el próximo mes? | Regresión | 0.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
M3Paradigma 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.
# 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? | No | Clustering (K-Means, DBSCAN) |
| ¿Cómo visualizar 50 features en 2D? | No | PCA / t-SNE |
| ¿Qué compra suele ir junta? | No | Apriori / FP-Growth |
Aprendizaje no supervisado
M3Paradigma 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.
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
| Concepto | Descripcion |
|---|---|
| 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.
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.
| Tipo | Estructura | Ejemplo | Herramienta |
|---|---|---|---|
| Estructurado | Filas y columnas con esquema fijo | Tabla SQL, CSV bien formado | SQL, Pandas |
| Semiestructurado | Jerarquías con claves flexibles | JSON de API, XML | requests, json, pandas |
| No estructurado | Sin esquema tabular | Texto libre, imágenes, audio | NLP, visión, embeddings |
CSV vs Excel vs JSON vs Parquet
| Formato | Naturaleza | Ventaja | Riesgo |
|---|---|---|---|
| CSV | Texto plano | Universal, portable, legible | Sin tipos: todo puede entrar como texto |
| Excel | Binario de oficina | Fórmulas, hojas, formato | Lento, metadatos ocultos, no versionable |
| JSON | Texto jerárquico | Flexible, estándar de APIs | Anidado: requiere normalización |
| Parquet | Binario columnar | Comprimido, con schema, lee por columnas | Requiere motor que lo entienda (pyarrow) |
Data acquisition
M3Proceso 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.
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.
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
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.shapeno es(0, 0)yhead()muestra las columnas esperadas.data_configesdictydata_config["nombre_tienda"]no lanzaKeyError.FileNotFoundError→ revisa quecsv_path/json_pathsean relativos al proyecto o usesPath(__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_mensual→float(numérico continuo)segmento→str/category(categórica)id_cliente→intostrsegú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
| Columna | Tipo | Estrategia | Por qué |
|---|---|---|---|
gasto_mensual | Numérica | Mediana | Robusta a outliers; la media se infla con un gasto de $50.000 |
segmento | Categó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
M3Reemplazo 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.
fillnano modifica in-place por defecto.df["col"].fillna(v)sin asignar no cambiadf. Usadf["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
M3Fuga 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.
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.
- 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
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 dedupReto: 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.
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.