🛠️ Feature Engineering Avanzado: Técnicas que Marcan la Diferencia en Modelos de ML

¡Hola comunidad de Data Science! :bar_chart::robot:

El feature engineering sigue siendo el factor más determinante en el éxito de proyectos de ML. Mientras que las arquitecturas de modelos se estandarizan, la creatividad en la construcción de features es lo que realmente diferencia proyectos promedio de soluciones excepcionales.

:bullseye: Feature Engineering Temporal Avanzado

Características Cíclicas para Variables Temporales

Las variables temporales requieren tratamiento especial para capturar patrones cíclicos:

import numpy as np
import pandas as pd

def crear_features_ciclicos(df, columna_tiempo):
    """Convierte timestamps en features cíclicos"""
    
    # Extraer componentes temporales
    df['hora'] = df[columna_tiempo].dt.hour
    df['dia_semana'] = df[columna_tiempo].dt.dayofweek
    df['mes'] = df[columna_tiempo].dt.month
    df['dia_año'] = df[columna_tiempo].dt.dayofyear
    
    # Transformaciones cíclicas (sin y cos para preservar continuidad)
    df['hora_sin'] = np.sin(2 * np.pi * df['hora'] / 24)
    df['hora_cos'] = np.cos(2 * np.pi * df['hora'] / 24)
    
    df['dia_semana_sin'] = np.sin(2 * np.pi * df['dia_semana'] / 7)
    df['dia_semana_cos'] = np.cos(2 * np.pi * df['dia_semana'] / 7)
    
    df['mes_sin'] = np.sin(2 * np.pi * df['mes'] / 12)
    df['mes_cos'] = np.cos(2 * np.pi * df['mes'] / 12)
    
    # Features de estacionalidad
    df['es_fin_semana'] = (df['dia_semana'] >= 5).astype(int)
    df['es_hora_pico'] = ((df['hora'] >= 7) & (df['hora'] <= 9) | 
                          (df['hora'] >= 17) & (df['hora'] <= 19)).astype(int)
    
    return df

# Ejemplo de uso
df['timestamp'] = pd.to_datetime(df['timestamp'])
df = crear_features_ciclicos(df, 'timestamp')

Rolling Features y Lag Variables

def crear_features_temporales(df, valor_col, ventana=[7, 30, 90]):
    """Crea features basadas en ventanas temporales"""
    
    df = df.sort_values('timestamp')
    
    for v in ventana:
        # Rolling statistics
        df[f'{valor_col}_rolling_mean_{v}d'] = df[valor_col].rolling(window=v).mean()
        df[f'{valor_col}_rolling_std_{v}d'] = df[valor_col].rolling(window=v).std()
        df[f'{valor_col}_rolling_min_{v}d'] = df[valor_col].rolling(window=v).min()
        df[f'{valor_col}_rolling_max_{v}d'] = df[valor_col].rolling(window=v).max()
        
        # Lag features
        df[f'{valor_col}_lag_{v}d'] = df[valor_col].shift(v)
        
        # Rate of change
        df[f'{valor_col}_pct_change_{v}d'] = df[valor_col].pct_change(periods=v)
        
        # Diferencia con media móvil
        df[f'{valor_col}_diff_from_rolling_{v}d'] = (
            df[valor_col] - df[f'{valor_col}_rolling_mean_{v}d']
        )
    
    return df

:chart_increasing: Feature Engineering para Variables Categóricas

Target Encoding Robusto

from sklearn.model_selection import KFold

def target_encoding_robusto(X, y, columna_cat, n_folds=5, smoothing=10):
    """Target encoding con validación cruzada para evitar overfitting"""
    
    kf = KFold(n_splits=n_folds, shuffle=True, random_state=42)
    target_encoded = np.zeros(len(X))
    
    # Media global para regularización
    global_mean = y.mean()
    
    for train_idx, val_idx in kf.split(X):
        # Calcular medias por categoría en conjunto de entrenamiento
        category_means = y.iloc[train_idx].groupby(X[columna_cat].iloc[train_idx]).mean()
        category_counts = X[columna_cat].iloc[train_idx].value_counts()
        
        # Aplicar smoothing
        smoothed_means = (
            (category_means * category_counts + global_mean * smoothing) / 
            (category_counts + smoothing)
        )
        
        # Mapear valores de validación
        target_encoded[val_idx] = X[columna_cat].iloc[val_idx].map(smoothed_means).fillna(global_mean)
    
    return target_encoded

# Ejemplo de uso
X['categoria_target_encoded'] = target_encoding_robusto(X, y, 'categoria')

Frequency Encoding y Rank Features

def crear_features_categoricos_avanzados(df, columna_cat):
    """Crea múltiples tipos de encoding para variables categóricas"""
    
    # Frequency encoding
    freq_map = df[columna_cat].value_counts().to_dict()
    df[f'{columna_cat}_frequency'] = df[columna_cat].map(freq_map)
    
    # Rank encoding (por frecuencia)
    rank_map = df[columna_cat].value_counts().rank(method='dense', ascending=False).to_dict()
    df[f'{columna_cat}_rank'] = df[columna_cat].map(rank_map)
    
    # Rareza (inverso de frecuencia)
    df[f'{columna_cat}_rarity'] = 1 / df[f'{columna_cat}_frequency']
    
    # Encoding por percentiles de frecuencia
    freq_percentiles = df[f'{columna_cat}_frequency'].rank(pct=True)
    df[f'{columna_cat}_freq_percentile'] = freq_percentiles
    
    return df

:1234: Feature Engineering Numérico Avanzado

Transformaciones Matemáticas Inteligentes

def transformaciones_numericas_avanzadas(df, columnas_numericas):
    """Aplica transformaciones matemáticas que capturan relaciones no lineales"""
    
    for col in columnas_numericas:
        # Transformaciones básicas
        df[f'{col}_log'] = np.log1p(df[col].clip(lower=0))
        df[f'{col}_sqrt'] = np.sqrt(df[col].clip(lower=0))
        df[f'{col}_squared'] = df[col] ** 2
        df[f'{col}_cubed'] = df[col] ** 3
        
        # Transformaciones de normalización
        df[f'{col}_standardized'] = (df[col] - df[col].mean()) / df[col].std()
        df[f'{col}_min_max_scaled'] = (df[col] - df[col].min()) / (df[col].max() - df[col].min())
        
        # Binning inteligente
        df[f'{col}_quantile_bin'] = pd.qcut(df[col], q=10, labels=False, duplicates='drop')
        
        # Outlier indicators
        Q1 = df[col].quantile(0.25)
        Q3 = df[col].quantile(0.75)
        IQR = Q3 - Q1
        df[f'{col}_is_outlier'] = (
            (df[col] < Q1 - 1.5 * IQR) | (df[col] > Q3 + 1.5 * IQR)
        ).astype(int)
    
    return df

Features de Interacción Automática

from itertools import combinations

def crear_features_interaccion(df, columnas_numericas, max_interacciones=20):
    """Crea features de interacción entre variables numéricas"""
    
    interaction_features = []
    
    # Combinaciones de 2 variables
    for col1, col2 in combinations(columnas_numericas, 2):
        # Multiplicación
        feature_name = f'{col1}_x_{col2}'
        df[feature_name] = df[col1] * df[col2]
        interaction_features.append(feature_name)
        
        # División (evitando división por cero)
        feature_name = f'{col1}_div_{col2}'
        df[feature_name] = df[col1] / (df[col2] + 1e-8)
        interaction_features.append(feature_name)
        
        # Diferencia
        feature_name = f'{col1}_minus_{col2}'
        df[feature_name] = df[col1] - df[col2]
        interaction_features.append(feature_name)
        
        # Suma
        feature_name = f'{col1}_plus_{col2}'
        df[feature_name] = df[col1] + df[col2]
        interaction_features.append(feature_name)
        
        if len(interaction_features) >= max_interacciones:
            break
    
    return df, interaction_features

:bullseye: Feature Selection Inteligente

Selección por Importancia y Correlación

import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.feature_selection import mutual_info_regression
from sklearn.ensemble import RandomForestRegressor

def seleccion_features_avanzada(X, y, umbral_correlacion=0.95):
    """Selecciona features usando múltiples criterios"""
    
    # 1. Eliminar features con baja varianza
    from sklearn.feature_selection import VarianceThreshold
    selector_var = VarianceThreshold(threshold=0.01)
    X_var_filtered = pd.DataFrame(
        selector_var.fit_transform(X), 
        columns=X.columns[selector_var.get_support()]
    )
    
    # 2. Eliminar features altamente correlacionadas
    corr_matrix = X_var_filtered.corr().abs()
    upper_triangle = corr_matrix.where(
        np.triu(np.ones_like(corr_matrix, dtype=bool), k=1)
    )
    
    features_to_drop = [
        column for column in upper_triangle.columns 
        if any(upper_triangle[column] > umbral_correlacion)
    ]
    
    X_corr_filtered = X_var_filtered.drop(columns=features_to_drop)
    
    # 3. Selección por mutual information
    mi_scores = mutual_info_regression(X_corr_filtered, y)
    mi_scores = pd.Series(mi_scores, index=X_corr_filtered.columns)
    
    # 4. Importancia con Random Forest
    rf = RandomForestRegressor(n_estimators=100, random_state=42)
    rf.fit(X_corr_filtered, y)
    rf_importance = pd.Series(rf.feature_importances_, index=X_corr_filtered.columns)
    
    # 5. Combinar scores
    feature_scores = pd.DataFrame({
        'mutual_info': mi_scores,
        'rf_importance': rf_importance,
        'combined_score': mi_scores * rf_importance
    }).sort_values('combined_score', ascending=False)
    
    return feature_scores, X_corr_filtered

def plot_feature_importance(feature_scores, top_n=20):
    """Visualiza importancia de features"""
    top_features = feature_scores.head(top_n)
    
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
    
    # Mutual Information
    top_features['mutual_info'].plot(kind='barh', ax=ax1, color='skyblue')
    ax1.set_title('Top Features - Mutual Information')
    ax1.set_xlabel('Mutual Information Score')
    
    # Random Forest Importance
    top_features['rf_importance'].plot(kind='barh', ax=ax2, color='lightcoral')
    ax2.set_title('Top Features - Random Forest Importance')
    ax2.set_xlabel('Feature Importance')
    
    plt.tight_layout()
    plt.show()

:brain: Feature Engineering con Embeddings

Embeddings para Variables Categóricas de Alta Cardinalidad

from sklearn.decomposition import TruncatedSVD
from sklearn.feature_extraction.text import TfidfVectorizer

def crear_embeddings_categoricos(df, columna_cat, dim_embedding=50):
    """Crea embeddings para variables categóricas usando co-ocurrencia"""
    
    # Crear documentos basados en co-ocurrencia
    documentos = df.groupby('id')[columna_cat].apply(lambda x: ' '.join(x.astype(str))).values
    
    # TF-IDF Vectorization
    vectorizer = TfidfVectorizer(max_features=1000, ngram_range=(1, 2))
    tfidf_matrix = vectorizer.fit_transform(documentos)
    
    # Reducción dimensional con SVD
    svd = TruncatedSVD(n_components=dim_embedding, random_state=42)
    embeddings = svd.fit_transform(tfidf_matrix)
    
    # Crear mapeo de categoría a embedding
    categorias_unicas = df[columna_cat].unique()
    categoria_to_embedding = {}
    
    for categoria in categorias_unicas:
        # Promedio de embeddings donde aparece la categoría
        indices = df[df[columna_cat] == categoria]['id'].unique()
        embedding_promedio = embeddings[indices].mean(axis=0)
        categoria_to_embedding[categoria] = embedding_promedio
    
    # Agregar embeddings como features
    for i in range(dim_embedding):
        df[f'{columna_cat}_embed_{i}'] = df[columna_cat].map(
            lambda x: categoria_to_embedding[x][i]
        )
    
    return df, categoria_to_embedding

:wrench: Pipeline de Feature Engineering Automatizado

Clase para Automatizar el Proceso

class FeatureEngineeringPipeline:
    def __init__(self):
        self.target_encoders = {}
        self.scalers = {}
        self.feature_names = []
    
    def fit_transform(self, X, y):
        """Ajusta y transforma features en un pipeline"""
        X_processed = X.copy()
        
        # Identificar tipos de columnas
        numeric_cols = X_processed.select_dtypes(include=[np.number]).columns
        categorical_cols = X_processed.select_dtypes(include=['object', 'category']).columns
        datetime_cols = X_processed.select_dtypes(include=['datetime64']).columns
        
        # Feature engineering temporal
        for col in datetime_cols:
            X_processed = crear_features_ciclicos(X_processed, col)
        
        # Feature engineering numérico
        if len(numeric_cols) > 0:
            X_processed = transformaciones_numericas_avanzadas(X_processed, numeric_cols)
            X_processed, _ = crear_features_interaccion(X_processed, numeric_cols[:5])  # Limitar interacciones
        
        # Feature engineering categórico
        for col in categorical_cols:
            X_processed = crear_features_categoricos_avanzados(X_processed, col)
            X_processed[f'{col}_target_encoded'] = target_encoding_robusto(X_processed, y, col)
        
        # Selección de features
        feature_scores, X_processed = seleccion_features_avanzada(X_processed, y)
        top_features = feature_scores.head(100).index.tolist()  # Top 100 features
        X_processed = X_processed[top_features]
        
        self.feature_names = X_processed.columns.tolist()
        return X_processed
    
    def transform(self, X):
        """Transforma nuevos datos usando parámetros ajustados"""
        # Implementar transformación usando parámetros guardados
        pass

:light_bulb: Tips Prácticos para Implementación

1. Validación de Features

  • Cross-validation: Siempre valida nuevos features con CV para evitar overfitting
  • Feature importance: Usa SHAP values para entender el impacto real de cada feature
  • Temporal validation: Para datos con componente temporal, usa validación temporal en lugar de aleatoria

2. Optimización de Performance

  • Lazy evaluation: Calcula features costosos solo cuando sea necesario
  • Caching: Guarda features intermedios para reutilizar en experimentos
  • Paralelización: Usa joblib o multiprocessing para crear features en paralelo

3. Monitoreo en Producción

  • Feature drift: Monitorea distribuciones de features en producción
  • Data quality: Implementa checks automáticos para valores faltantes y outliers
  • Performance tracking: Rastrea cómo nuevos features afectan métricas de negocio

:bullseye: Consideraciones Clave

El feature engineering efectivo requiere:

  • Conocimiento del dominio: Las mejores features vienen de entender el problema
  • Experimentación sistemática: Documenta qué funciona y qué no
  • Balance: Más features no siempre es mejor; calidad sobre cantidad
  • Reproducibilidad: Usa pipelines que puedan recrear features consistentemente

¿Qué técnicas de feature engineering han tenido mayor impacto en sus proyectos? ¿Algún approach innovador que hayan desarrollado para dominios específicos?

#FeatureEngineering machinelearning #DataScience mlops #DataPreprocessing