Hasta aquí fuimos viendo piezas del proceso por separado: carga de datos, limpieza, tratamiento de variables categóricas, escalado, entrenamiento, evaluación y uso del modelo.
En un proyecto real, esas piezas no aparecen aisladas. Lo normal es construir un flujo completo que resuelva un problema de principio a fin.
En este tema vamos a integrar esas ideas en un caso práctico claro, sencillo y realista.
Imaginemos una empresa que quiere predecir si un cliente terminará comprando o no a partir de algunos datos básicos:
La variable objetivo será compra, donde 0 significa que no compra y 1 significa que sí compra.
Este caso es útil porque mezcla varios elementos muy frecuentes en Machine Learning:
Es decir, no es solo “entrenar un modelo”, sino construir un flujo ordenado y reproducible.
Para las variables numéricas vamos a imputar faltantes con la mediana y luego escalar. Para las variables categóricas vamos a completar faltantes con el valor más frecuente y luego convertir categorías en columnas numéricas con OneHotEncoder.
Después uniremos todo dentro de un ColumnTransformer y finalmente entrenaremos una LogisticRegression dentro de un Pipeline.
Esta estructura es una muy buena base para proyectos reales pequeños y medianos.
El siguiente programa integra preparación, entrenamiento, evaluación y predicción final en un solo flujo.
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
clientes = pd.DataFrame({
"edad": [22, 25, 27, 30, 35, 40, 45, 50, 28, 33, 38, 42],
"ingresos": [300, 350, None, 500, 650, 800, 900, 1100, 420, 560, 700, None],
"canal": ["web", "web", "movil", "tienda", "web", "movil", "tienda", "web", "movil", "tienda", "web", None],
"plan": ["basico", "basico", "basico", "estandar", "estandar", "premium", "premium", "premium", "estandar", "premium", None, "premium"],
"compra": [0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1]
})
X = clientes[["edad", "ingresos", "canal", "plan"]]
y = clientes["compra"]
columnas_numericas = ["edad", "ingresos"]
columnas_categoricas = ["canal", "plan"]
pipeline_numerico = Pipeline([
("imputador", SimpleImputer(strategy="median")),
("escalador", StandardScaler())
])
pipeline_categorico = Pipeline([
("imputador", SimpleImputer(strategy="most_frequent")),
("onehot", OneHotEncoder(handle_unknown="ignore"))
])
preprocesador = ColumnTransformer([
("num", pipeline_numerico, columnas_numericas),
("cat", pipeline_categorico, columnas_categoricas)
])
modelo = Pipeline([
("preprocesamiento", preprocesador),
("clasificador", LogisticRegression(max_iter=1000))
])
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42, stratify=y
)
modelo.fit(X_train, y_train)
y_pred = modelo.predict(X_test)
print("Valores reales:", y_test.values)
print("Predicciones:", y_pred)
print("Exactitud:", accuracy_score(y_test, y_pred))
nuevo_cliente = pd.DataFrame({
"edad": [36],
"ingresos": [720],
"canal": ["web"],
"plan": ["premium"]
})
prediccion = modelo.predict(nuevo_cliente)[0]
probabilidad = modelo.predict_proba(nuevo_cliente)[0, 1]
print("Prediccion para el nuevo cliente:", prediccion)
print(f"Probabilidad de compra: {probabilidad:.3f}")
Salida resumida esperada:
Valores reales: [...]
Predicciones: [...]
Exactitud: ...
Prediccion para el nuevo cliente: ...
Probabilidad de compra: ...
Primero definimos el dataset. Luego separamos las columnas predictoras en X y la variable objetivo en y.
Después armamos dos pipelines diferentes: uno para columnas numéricas y otro para columnas categóricas. Ambos se combinan con ColumnTransformer.
Finalmente, ese preprocesamiento queda conectado con la regresión logística dentro de un único Pipeline. Cuando hacemos fit, el flujo entero se ajusta de manera ordenada.
Sin Pipeline, tendríamos que recordar cada paso por separado:
Eso es propenso a errores. Con un pipeline, el flujo queda definido una sola vez y luego se reutiliza siempre igual.
No todas las columnas necesitan el mismo tratamiento. Las numéricas suelen escalarse y las categóricas suelen codificarse.
ColumnTransformer permite expresar esa idea de manera limpia: “a estas columnas aplícales este pipeline, y a aquellas otras, otro pipeline diferente”.
Es una de las herramientas más importantes de Scikit-learn cuando el dataset mezcla tipos de variables.
La mayoría de los modelos no puede trabajar directamente con valores faltantes. Si dejamos esos huecos en los datos, el entrenamiento fallará.
En este ejemplo usamos estrategias simples pero razonables:
El escalado tiene sentido cuando una variable puede tener magnitudes muy distintas respecto de otra. Eso ocurre con columnas como edad e ingresos.
En cambio, las variables categóricas no se escalan directamente: primero deben codificarse como variables binarias mediante OneHotEncoder.
El modelo devuelve dos cosas útiles:
predict;predict_proba.La clase dice qué decisión toma el modelo. La probabilidad muestra cuánta confianza relativa tiene en esa decisión.
train_test_split(..., stratify=y): divide los datos manteniendo la proporción de clases.SimpleImputer(strategy="median"): completa faltantes numéricos con la mediana.SimpleImputer(strategy="most_frequent"): completa faltantes categóricos con la categoría más común.OneHotEncoder(handle_unknown="ignore"): transforma categorías en columnas binarias sin romperse ante categorías nuevas.StandardScaler(): estandariza columnas numéricas.Pipeline([...]): encadena todo el proceso en un flujo reproducible.LogisticRegression(max_iter=1000): entrena el clasificador final con un margen razonable de iteraciones.ColumnTransformer permite tratar distinto a variables numéricas y categóricas.Pipeline ayuda a que todo el flujo sea reproducible y menos propenso a errores.