Modelos de ML: Por Qué Tu Predicción Es Buena... Hasta Que No Lo Es

Published: (December 27, 2025 at 08:43 PM EST)
7 min read
Source: Dev.to

Source: Dev.to

Imagen del artículo

Una inmersión técnica en el verdadero trabajo de un Data Scientist: Feature Engineering, Pipelines y Métricas de Negocio.

Te paso algo que vivimos todos los días en la industria: hay una abrumadora diferencia entre saber escribir model.fit(X, y) en un Jupyter Notebook y construir un sistema de Machine Learning que realmente genere confianza en una reunión de directores.

La mayoría de los tutoriales te enseñan a predecir el precio de una casa en Boston o clasificar flores de Iris. Son ejercicios académicos. Pero en el mundo real, los datos son sucios, los requerimientos cambian y tu modelo, en producción, falla silenciosamente.

Hoy vamos a dejar de lado los “Hello World”. Vamos a ver por qué tu modelo fallaría mañana si solo entrenas hoy, y cómo usar Scikit‑Learn como un ingeniero de datos profesional, no como un hobbyista.


1️⃣ El Escenario Realista

Imaginemos que somos el equipo de Data de una inmobiliaria en Buenos Aires. Nos piden un modelo para estimar el precio de venta (precio_final) de un departamento.

VariableTipoComentario
m2_totalesNuméricoEl tamaño real.
barrioCategóricoPalermo, Recoleta, etc.
antiguedadNumérico (años)
tiene_piletaBinario (0/1)
fecha_ventaTimestampNo se usará en el ejemplo, pero es típico.

1.1 La Trampa: El Modelo que Vemos en YouTube

Si hacemos lo básico – importamos Pandas, limpiamos un nulo y entrenamos un LinearRegression o RandomForest rápido – nos toparemos con problemas comunes:

  • Si entra un dato nuevo sin escalar, el modelo se vuelve loco.
  • Si olvidamos One‑Hot Encoding en barrio, el modelo se rompe.
  • Si el modelo aprende a predecir “basura” (data leakage), parecerá perfecto en desarrollo, pero perderá plata en la realidad.

2️⃣ La Solución Profesional: Pipelines y Preprocesamiento

En Python Baires enseñamos que la clave del éxito no es el algoritmo (todos usan Random Forest), sino la tubería (Pipeline) que limpia y prepara los datos. Usaremos ColumnTransformer y Pipeline de Scikit‑Learn para automatizar el caos.

2.1 Generación de datos sucios (simulación)

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score

# --- 1. SIMULACIÓN DE DATOS SUCIOS ---
np.random.seed(42)
n_muestras = 1000

data = {
    'm2_totales': np.random.normal(70, 30, n_muestras).astype(float),
    'barrio': np.random.choice(['Palermo', 'Recoleta', 'Caballito', 'Almagro'], n_muestras),
    'antiguedad': np.random.randint(0, 60, n_muestras).astype(float),
    'tiene_pileta': np.random.choice([0, 1], n_muestras, p=[0.7, 0.3]),
}

df = pd.DataFrame(data)

# Introducimos errores humanos (nulos, outliers)
df.loc[5:15, 'm2_totales'] = np.nan
df.loc[20:25, 'antiguedad'] = np.nan
df.loc[30, 'barrio'] = 'sin dato'          # Categoría sucia
df.loc[35, 'antiguedad'] = 150             # Outlier imposible

# Generamos el target (y) añadiendo ruido a una operación lógica
df['precio_final'] = (
    df['m2_totales'].fillna(0) * 2500 +
    df['antiguedad'].fillna(0) * -100 +
    df['tiene_pileta'] * 15000 +
    np.random.normal(0, 20000, n_muestras)
)

# --- 2. SEPARACIÓN Y VALIDACIÓN INICIAL ---
df = df.dropna(subset=['precio_final'])

X = df.drop('precio_final', axis=1)
y = df['precio_final']

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

print("Datos listos. Observa la columna 'barrio' y los NaN en m2:")
print(X_train.head())

3️⃣ Diseñando el Flujo de Trabajo (Workflow)

En lugar de limpiar el DataFrame manualmente, creamos un objeto que encapsula toda la lógica. Nuestro flujo debe hacer tres cosas:

  1. Imputar valores numéricos (p. ej., llenar los NaN de m2_totales con la mediana).
  2. Escalar valores numéricos (standardization para que antiguedad no domine por su magnitud).
  3. Procesar categóricos (convertir barrio en variables dummy).
# 1. Definimos qué columnas son numéricas y cuáles categóricas
numeric_features = ['m2_totales', 'antiguedad', 'tiene_pileta']
categorical_features = ['barrio']

# 2. Creamos los "tubos" de procesamiento
numeric_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler())
])

categorical_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('encoder', OneHotEncoder(handle_unknown='ignore'))
])

# 3. Juntamos todo en un ColumnTransformer
preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, numeric_features),
        ('cat', categorical_transformer, categorical_features)
    ]
)

# 4. Construimos el Pipeline final (preprocesador + modelo)
full_pipeline = Pipeline(steps=[
    ('preprocessor', preprocessor),
    ('model', RandomForestRegressor(
        n_estimators=200,
        random_state=42,
        n_jobs=-1
    ))
])

# 5. Entrenamos
full_pipeline.fit(X_train, y_train)

# 6. Predicción y métricas
y_pred = full_pipeline.predict(X_test)

mae  = mean_absolute_error(y_test, y_pred)
rmse = mean_squared_error(y_test, y_pred, squared=False)
r2   = r2_score(y_test, y_pred)

print(f"MAE : ${mae:,.0f}")
print(f"RMSE: ${rmse:,.0f}")
print(f"R²  : {r2:.3f}")

3.1 Qué estamos midiendo

MétricaPor qué importa en el negocio
MAE (Mean Absolute Error)Nos dice, en promedio, cuántos pesos (pesos argentinos) nos equivocamos. Fácil de comunicar a directivos.
RMSE (Root Mean Squared Error)Penaliza fuertemente los errores grandes; útil cuando un outlier cuesta mucho.
Indica la proporción de varianza explicada; sirve para comparar modelos rápidamente.

4️⃣ Llevar el Pipeline a Producción

  1. Serializar con joblib o pickle.
  2. Versionar el modelo y el pipeline (por ejemplo, con DVC o MLflow).
  3. Crear una API (FastAPI, Flask) que reciba un JSON con los campos y devuelva la predicción.
  4. Monitorear drift de datos y degradación de métricas; re‑entrenar cuando sea necesario.
import joblib

# Guardar
joblib.dump(full_pipeline, 'pipeline_rf.pkl')

# Cargar en producción
pipeline_prod = joblib.load('pipeline_rf.pkl')

# Ejemplo de predicción en producción
sample = pd.DataFrame([{
    'm2_totales': 85,
    'barrio': 'Palermo',
    'antiguedad': 10,
    'tiene_pileta': 1
}])
print(pipeline_prod.predict(sample))

5️⃣ Conclusión

  • No basta con “entrenar y listo”.
  • El pipeline garantiza reproducibilidad, evita data leakage y simplifica el paso a producción.
  • Las métricas de negocio (MAE en pesos, por ejemplo) son la lingua franca con la que convencerás a la gerencia.

Si quieres que tu modelo sobreviva al día siguiente, invierte tiempo en Feature Engineering, Pipelines y Métricas alineadas al negocio. ¡Eso es lo que diferencia a un Data Scientist profesional de un entusiasta del notebook!

from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
import numpy as np
import pandas as pd

# Supongamos que ya tienes X_train, X_test, y_train, y_test definidos
# y que las columnas categóricas son 'barrio' y la numérica es 'm2_totales'

# 1️⃣ Pre‑procesamiento de columnas
preprocess = ColumnTransformer(
    transformers=[
        ('cat', OneHotEncoder(handle_unknown='ignore'), ['barrio']),
        ('num', StandardScaler(), ['m2_totales'])
    ]
)

# 2️⃣ Pipeline completa (pre‑procesamiento + modelo)
full_pipeline = Pipeline(steps=[
    ('preprocess', preprocess),
    ('regressor', RandomForestRegressor(
        n_estimators=100,
        random_state=42,
        n_jobs=-1
    ))
])

# Entrenamos (esto transforma y entrena en un solo paso)
full_pipeline.fit(X_train, y_train)

# Predicciones
y_pred = full_pipeline.predict(X_test)

No Engañes al Negocio: Evaluación Correcta

En un tutorial básico te dirían solo el R2. En la industria, nos importa el error en plata.

  • MAE (Error Absoluto Medio): “En promedio, nuestro modelo se equivoca en X pesos.”
  • RMSE (Error Cuadrático Medio): Penaliza mucho más los errores grandes (fundamental si no quieres que el modelo se equivoque grotescamente en casos raros).
mae = mean_absolute_error(y_test, y_pred)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
r2 = r2_score(y_test, y_pred)

print("\n--- REPORTE DE NEGOCIO ---")
print(f"R2 Score: {r2:.3f} (Explica el {r2*100:.1f}% de la varianza del precio)")
print(f"MAE: ${mae:,.0f} (Error promedio absoluto)")
print(f"RMSE: ${rmse:,.0f} (Desviación del error)")

# Demostración de uso final
print("\n--- PREDICCIÓN DE EJEMPLO ---")
ejemplo = pd.DataFrame({
    'm2_totales': [85.0],
    'barrio': ['Palermo'],
    'antiguedad': [10.0],
    'tiene_pileta': [1]
})
precio_estimado = full_pipeline.predict(ejemplo)[0]
print(f"Un depto en Palermo de 85 m² con pileta se estima en: ${precio_estimado:,.0f}")

¿Qué significa esto para tu jefe?

  • Si el MAE es 15 000 pesos, significa que, en promedio, el modelo se equivoca ese monto.
  • Si el RMSE es 50 000, indica que hay casos donde el error es muchísimo mayor (quizás departamentos de lujo donde el modelo no aprendió bien).

La Brecha de Conocimiento (Y por qué estás leyendo esto)

Lo que acabamos de ver es el estándar mínimo de calidad para un modelo que entre en producción. Pero…

  • No vimos Optimización de Hiperparámetros: (GridSearch, RandomizedSearch) para tunear el modelo.
  • No vimos Despliegue (Deployment): (¿Cómo expones esto como una API REST con FastAPI o Flask para que la inmobiliaria lo use en su web?).
  • No vimos Deep Learning: (Cuando los problemas tienen patrones mucho más complejos que una regresión lineal).

Estos son los saltos que separan a un Programador Junior de Python de un Senior Data Scientist.


El Siguiente Paso

Copiar código de un blog es fácil. Entender por qué ese código falla cuando cambias el tamaño del dataset o cuando entran datos nuevos de una fuente distinta… eso es lo que define tu carrera.

Si querés dejar de ser el “chico de Python” que arregla scripts y empezar a diseñar soluciones de IA que transforman empresas, en Python Baires tenemos el curso de Machine Learning más completo del mercado local.

No es solo aprender a codear. Es aprender a resolver.

Mirá el programa completo y reservá tu lugar:

👉 https://www.python-baires.ar/Curso-ml.html

Back to Blog

Related posts

Read more »

Feature Engineering

What is Feature Engineering? - A feature is just a column of data e.g., age, salary, number of purchases. - Feature engineering means creating, modifying, or s...

The End of the Train-Test Split

Article URL: https://folio.benguzovsky.com/train-test Comments URL: https://news.ycombinator.com/item?id=46149740 Points: 7 Comments: 1...