ml · temario
2.4tema 4 de 4

Feature Engineering — Encoding, Scaling, Missing Values, Feature Selection

Los datos crudos rara vez son lo que el modelo necesita: transformarlos correctamente define el techo del desempeño.

Feature engineering es el proceso de transformar datos crudos en representaciones que los algoritmos de ML puedan procesar y aprender de manera efectiva. Es la disciplina más subestimada del ciclo de ML: un modelo mediocre con features bien construidas supera a un modelo sofisticado con features mal preparadas. El techo del desempeño lo ponen los datos, no el algoritmo.

Los valores faltantes (missing values) son inevitables en datos reales. La imputación simple más usada: media/mediana para features numéricas, moda o constante para categóricas. Estrategias más sofisticadas incluyen imputación por KNN o imputación con un modelo predictivo. Una práctica valiosa es agregar un feature binario 'was_missing' por columna antes de imputar, para que el modelo pueda aprender si la ausencia misma tiene información.

Las features categóricas deben convertirse a números antes de entrar al modelo. OrdinalEncoder es correcto para variables con orden intrínseco (ej: 'bajo', 'medio', 'alto'). OneHotEncoder es correcto para variables nominales sin orden (ej: 'rojo', 'verde', 'azul'). El error clásico: aplicar OrdinalEncoder a variables nominales, lo que introduce un orden falso que el modelo interpreta como información real.

El escalado (scaling) normaliza la magnitud de las features numéricas. StandardScaler centra en media 0 y desviación estándar 1: adecuado cuando la distribución es aproximadamente normal. MinMaxScaler comprime al rango [0, 1]: útil para redes neuronales o cuando los outliers no son extremos. RobustScaler usa mediana y rango intercuartílico: robusto ante outliers. Los modelos basados en árboles (Random Forest, XGBoost) no requieren escalado; los modelos lineales y SVM sí lo requieren.

La importancia de features permite identificar cuáles contribuyen más a la predicción y cuáles son ruido. Las principales técnicas: feature_importances_ de árboles (rápida, pero sesgada por cardinalidad), permutation importance (más confiable, mide el impacto real en val), y SelectKBest con pruebas estadísticas. Reducir features irrelevantes mejora la generalización y reduce el tiempo de entrenamiento.

El error de leakage en feature engineering es especialmente insidioso: si calculas estadísticas de una feature (media, percentil, embedding) usando todo el dataset antes de dividir, estás filtrando información del test set. La solución: encapsula todas las transformaciones en un Pipeline de sklearn y deja que cross_val_score o el split manual garanticen que cada transformación solo ve datos de entrenamiento.

structure.txt
# Imputación dentro de un Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder, OrdinalEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline

numeric_features = ['age', 'balance', 'duration']
categorical_features = ['job', 'marital', 'education']

numeric_pipe = Pipeline([
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler())
])

categorical_pipe = Pipeline([
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('encoder', OneHotEncoder(handle_unknown='ignore', sparse_output=False))
])

preprocessor = ColumnTransformer([
    ('num', numeric_pipe, numeric_features),
    ('cat', categorical_pipe, categorical_features)
])

full_pipe = Pipeline([
    ('preprocessing', preprocessor),
    ('model', RandomForestClassifier(n_estimators=200, random_state=42))
])

full_pipe.fit(X_train, y_train)

# Feature importance con permutation_importance
from sklearn.inspection import permutation_importance
result = permutation_importance(
    full_pipe, X_val, y_val,
    n_repeats=10, random_state=42, scoring='roc_auc'
)

Debugging lab

Detecta y corrige el error en el código.

0/5 tests passing0%
  1. 2.4.5.1

    from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # X es el dataset completo X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2)

  2. 2.4.5.2

    from sklearn.preprocessing import OrdinalEncoder enc = OrdinalEncoder() df['job_encoded'] = enc.fit_transform(df[['job']]) # 'job' tiene valores: 'admin', 'technician', 'blue-collar', 'entrepreneur'

  3. 2.4.5.3

    imputer = SimpleImputer(strategy='mean') X_imputed = imputer.fit_transform(X_train) # X_train tiene una columna 'income' con distribución muy sesgada a la derecha (outliers)

  4. 2.4.5.4

    preprocessor = ColumnTransformer([('num', StandardScaler(), numeric_cols)]) X_train_prep = preprocessor.fit_transform(X_train) X_test_prep = preprocessor.fit_transform(X_test) # ← bug

  5. 2.4.5.5

    rf.fit(X_train, y_train) importances = rf.feature_importances_ to_drop = [col for col, imp in zip(X_train.columns, importances) if imp < 0.01] X_train_reduced = X_train.drop(columns=to_drop) rf2 = RandomForestClassifier().fit(X_train_reduced, y_train) print(rf2.score(X_test, y_test)) # baja vs rf original