DEV Community

Jesus Oviedo Riquelme
Jesus Oviedo Riquelme

Posted on

MLZC25-13. Ingeniería de Características y Variables Categóricas: El Arte de Transformar Datos

🎯 Objetivo del Post: Dominarás el arte de crear características poderosas y manejar variables categóricas, habilidades que pueden convertir un modelo mediocre en uno excepcional.

🍳 ¿Qué es la Ingeniería de Características?

La ingeniería de características es el arte y la ciencia de crear nuevas variables a partir de los datos existentes para mejorar el rendimiento de nuestros modelos de Machine Learning. Es como ser un chef experto que toma ingredientes básicos y los transforma en platos deliciosos.

💡 Dato Impactante: La ingeniería de características puede ser la diferencia entre un modelo mediocre y uno excelente. Es donde ocurre la verdadera magia del Machine Learning.

🎯 ¿Por qué es Importante?

  1. 🤖 Los algoritmos solo ven números: No entienden conceptos como "BMW es una marca de lujo"
  2. 🔄 Relaciones no lineales: Podemos capturar patrones complejos que los algoritmos no ven
  3. 📉 Reducción de dimensionalidad: Crear características más informativas y compactas
  4. 🚀 Mejora del rendimiento: Variables bien diseñadas = modelo significativamente mejor

Tipos de Ingeniería de Características

1. Características Derivadas

Crear nuevas variables a partir de las existentes.

2. Transformaciones

Cambiar la escala o distribución de variables.

3. Codificación de Variables Categóricas

Convertir texto a números que el modelo pueda entender.

4. Interacciones

Combinar múltiples variables para crear nuevas relaciones.

Características Derivadas: Ejemplos Prácticos

Ejemplo 1: Edad del Auto

import pandas as pd
import numpy as np

# Datos de ejemplo
df = pd.DataFrame({
    'año': [2020, 2019, 2018, 2015, 2010],
    'precio': [25000, 22000, 20000, 18000, 12000]
})

# Crear edad del auto
año_actual = 2024
df['edad_auto'] = año_actual - df['año']

print("Edad del auto:")
print(df[['año', 'edad_auto', 'precio']])
Enter fullscreen mode Exit fullscreen mode

Ejemplo 2: Kilometraje Anual

# Agregar kilometraje
df['kilometraje'] = [25000, 30000, 35000, 50000, 80000]

# Calcular kilometraje anual promedio
df['km_anuales'] = df['kilometraje'] / df['edad_auto']

print("\nKilometraje anual:")
print(df[['edad_auto', 'kilometraje', 'km_anuales', 'precio']])
Enter fullscreen mode Exit fullscreen mode

Ejemplo 3: Categorías de Uso

# Categorizar por uso del auto
def categorizar_uso(km_anuales):
    if km_anuales < 10000:
        return 'Bajo'
    elif km_anuales < 20000:
        return 'Normal'
    else:
        return 'Alto'

df['categoria_uso'] = df['km_anuales'].apply(categorizar_uso)
print("\nCategorías de uso:")
print(df[['km_anuales', 'categoria_uso', 'precio']])
Enter fullscreen mode Exit fullscreen mode

Variables Categóricas: El Desafío

¿Qué son las Variables Categóricas?

Variables que toman valores de un conjunto finito de categorías:

  • Marca: Toyota, BMW, Honda, Ford
  • Color: Rojo, Azul, Blanco, Negro
  • Transmisión: Manual, Automática
  • Combustible: Gasolina, Diésel, Híbrido

¿Por qué son Problemáticas?

Los algoritmos de Machine Learning solo entienden números. Si les damos "BMW", no saben qué hacer con eso.

Ejemplo del Problema

# Datos con variables categóricas
df_categorico = pd.DataFrame({
    'marca': ['Toyota', 'BMW', 'Honda', 'BMW', 'Toyota'],
    'precio': [20000, 35000, 18000, 40000, 22000]
})

print("Datos categóricos:")
print(df_categorico)

# Intentar calcular correlación (esto fallará)
try:
    correlacion = df_categorico.corr()
    print(correlacion)
except:
    print("Error: No se puede calcular correlación con texto")
Enter fullscreen mode Exit fullscreen mode

Técnicas de Codificación de Variables Categóricas

1. Label Encoding (Codificación de Etiquetas)

Asigna un número único a cada categoría.

from sklearn.preprocessing import LabelEncoder

# Crear datos de ejemplo
marcas = ['Toyota', 'BMW', 'Honda', 'Ford', 'BMW', 'Toyota']
precios = [20000, 35000, 18000, 25000, 40000, 22000]

df = pd.DataFrame({'marca': marcas, 'precio': precios})

# Aplicar Label Encoding
le = LabelEncoder()
df['marca_encoded'] = le.fit_transform(df['marca'])

print("Label Encoding:")
print(df[['marca', 'marca_encoded', 'precio']])
print(f"\nMapeo: {dict(zip(le.classes_, le.transform(le.classes_)))}")
Enter fullscreen mode Exit fullscreen mode

Problema: Implica un orden que puede no existir (BMW = 0, Ford = 1, Honda = 2, Toyota = 3).

2. One-Hot Encoding (Codificación One-Hot)

Crea una columna binaria para cada categoría.

from sklearn.preprocessing import OneHotEncoder

# One-Hot Encoding con pandas
df_onehot = pd.get_dummies(df, columns=['marca'], prefix='marca')

print("One-Hot Encoding:")
print(df_onehot)

# One-Hot Encoding con scikit-learn
ohe = OneHotEncoder(sparse_output=False)
marca_encoded = ohe.fit_transform(df[['marca']])
marca_df = pd.DataFrame(marca_encoded, columns=ohe.get_feature_names_out(['marca']))

print("\nOne-Hot Encoding con scikit-learn:")
print(marca_df)
Enter fullscreen mode Exit fullscreen mode

Ventajas:

  • No implica orden entre categorías
  • Cada categoría es independiente

Desventajas:

  • Crea muchas columnas (curse of dimensionality)
  • Puede causar overfitting con muchas categorías

3. Target Encoding (Codificación por Objetivo)

Usa el valor promedio del objetivo para cada categoría.

# Target Encoding manual
target_encoding = df.groupby('marca')['precio'].mean()
print("Target Encoding:")
print(target_encoding)

# Aplicar target encoding
df['marca_target_encoded'] = df['marca'].map(target_encoding)
print("\nDatos con Target Encoding:")
print(df[['marca', 'precio', 'marca_target_encoded']])
Enter fullscreen mode Exit fullscreen mode

Ventajas:

  • Captura la relación con el objetivo
  • No aumenta la dimensionalidad

Desventajas:

  • Puede causar overfitting
  • Necesita validación cruzada para evitar data leakage

Implementación Completa con Nuestro Dataset

Paso 1: Cargar y Preparar Datos

# Cargar datos
df = pd.read_csv('car_data_limpio.csv')

print("Información del dataset:")
print(df.info())
print(f"\nVariables categóricas:")
categorical_cols = df.select_dtypes(include=['object']).columns
print(categorical_cols.tolist())
Enter fullscreen mode Exit fullscreen mode

Paso 2: Análisis de Variables Categóricas

# Análisis de marcas
print("Distribución de marcas:")
marca_counts = df['marca'].value_counts()
print(marca_counts)

# Visualización
import matplotlib.pyplot as plt

plt.figure(figsize=(12, 6))
marca_counts.head(10).plot(kind='bar', color='skyblue')
plt.title('Top 10 Marcas por Frecuencia')
plt.xlabel('Marca')
plt.ylabel('Cantidad de Autos')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

# Precio promedio por marca
precio_por_marca = df.groupby('marca')['precio'].agg(['mean', 'count']).round(2)
precio_por_marca = precio_por_marca[precio_por_marca['count'] >= 10]  # Solo marcas con al menos 10 autos
precio_por_marca = precio_por_marca.sort_values('mean', ascending=False)

print("\nPrecio promedio por marca (mínimo 10 autos):")
print(precio_por_marca.head(10))
Enter fullscreen mode Exit fullscreen mode

Paso 3: Estrategia de Codificación

def estrategia_codificacion(df, columna, min_freq=10):
    """Estrategia inteligente de codificación"""
    # Contar frecuencias
    frecuencias = df[columna].value_counts()

    # Identificar categorías frecuentes
    categorias_frecuentes = frecuencias[frecuencias >= min_freq].index.tolist()

    # Crear nueva columna
    df[f'{columna}_encoded'] = df[columna].apply(
        lambda x: x if x in categorias_frecuentes else 'Otros'
    )

    return df, categorias_frecuentes

# Aplicar estrategia a marca
df, marcas_frecuentes = estrategia_codificacion(df, 'marca', min_freq=20)
print(f"Marcas frecuentes: {marcas_frecuentes}")
print(f"Distribución después de agrupar:")
print(df['marca_encoded'].value_counts())
Enter fullscreen mode Exit fullscreen mode

Paso 4: One-Hot Encoding Optimizado

# One-Hot Encoding solo para categorías frecuentes
df_encoded = pd.get_dummies(df, columns=['marca_encoded'], prefix='marca')

print("Columnas después de One-Hot Encoding:")
print([col for col in df_encoded.columns if col.startswith('marca_')])

# Hacer lo mismo para transmisión
df_encoded = pd.get_dummies(df_encoded, columns=['transmision'], prefix='transmision')

print(f"\nForma del dataset: {df_encoded.shape}")
Enter fullscreen mode Exit fullscreen mode

Paso 5: Crear Características Derivadas

# Características derivadas más sofisticadas
def crear_caracteristicas_derivadas(df):
    """Crear características derivadas útiles"""
    df = df.copy()

    # 1. Edad del auto
    df['edad_auto'] = 2024 - df['año']

    # 2. Kilometraje anual promedio
    df['km_anuales'] = df['kilometraje'] / df['edad_auto']

    # 3. Categoría de uso basada en kilometraje anual
    df['uso_bajo'] = (df['km_anuales'] < 10000).astype(int)
    df['uso_normal'] = ((df['km_anuales'] >= 10000) & (df['km_anuales'] < 20000)).astype(int)
    df['uso_alto'] = (df['km_anuales'] >= 20000).astype(int)

    # 4. Auto nuevo (menos de 2 años)
    df['auto_nuevo'] = (df['edad_auto'] <= 2).astype(int)

    # 5. Auto viejo (más de 10 años)
    df['auto_viejo'] = (df['edad_auto'] > 10).astype(int)

    # 6. Kilometraje alto (más de 100,000 km)
    df['km_alto'] = (df['kilometraje'] > 100000).astype(int)

    # 7. Relación precio/edad (depreciación)
    df['precio_por_año'] = df['precio'] / df['edad_auto']

    return df

# Aplicar transformaciones
df_final = crear_caracteristicas_derivadas(df_encoded)

print("Nuevas características creadas:")
nuevas_caracteristicas = ['edad_auto', 'km_anuales', 'uso_bajo', 'uso_normal', 'uso_alto', 
                         'auto_nuevo', 'auto_viejo', 'km_alto', 'precio_por_año']
print(nuevas_caracteristicas)
Enter fullscreen mode Exit fullscreen mode

Paso 6: Selección de Características

# Seleccionar características para el modelo
caracteristicas_numericas = ['año', 'kilometraje', 'edad_auto', 'km_anuales']
caracteristicas_binarias = ['uso_bajo', 'uso_normal', 'uso_alto', 'auto_nuevo', 'auto_viejo', 'km_alto']
caracteristicas_categoricas = [col for col in df_final.columns if col.startswith(('marca_', 'transmision_'))]

# Combinar todas las características
todas_las_caracteristicas = caracteristicas_numericas + caracteristicas_binarias + caracteristicas_categoricas

print(f"Total de características: {len(todas_las_caracteristicas)}")
print(f"Características numéricas: {len(caracteristicas_numericas)}")
print(f"Características binarias: {len(caracteristicas_binarias)}")
print(f"Características categóricas (one-hot): {len(caracteristicas_categoricas)}")

# Preparar datos para el modelo
X = df_final[todas_las_caracteristicas]
y = df_final['precio']

print(f"\nForma final de X: {X.shape}")
Enter fullscreen mode Exit fullscreen mode

Evaluación del Impacto de Feature Engineering

Comparar Modelos

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
import numpy as np

# Dividir datos
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)

# Modelo 1: Solo características básicas
X_basico = X_train[['año', 'kilometraje']]
X_val_basico = X_val[['año', 'kilometraje']]

modelo_basico = LinearRegression()
modelo_basico.fit(X_basico, y_train)
y_pred_basico = modelo_basico.predict(X_val_basico)

# Modelo 2: Con feature engineering
modelo_completo = LinearRegression()
modelo_completo.fit(X_train, y_train)
y_pred_completo = modelo_completo.predict(X_val)

# Comparar resultados
def comparar_modelos(y_real, y_pred, nombre):
    rmse = np.sqrt(mean_squared_error(y_real, y_pred))
    r2 = r2_score(y_real, y_pred)
    print(f"{nombre}:")
    print(f"  RMSE: ${rmse:,.2f}")
    print(f"  R²:   {r2:.3f}")
    return rmse, r2

print("Comparación de Modelos:")
rmse_basico, r2_basico = comparar_modelos(y_val, y_pred_basico, "Modelo Básico")
rmse_completo, r2_completo = comparar_modelos(y_val, y_pred_completo, "Modelo con Feature Engineering")

# Calcular mejora
mejora_rmse = ((rmse_basico - rmse_completo) / rmse_basico) * 100
mejora_r2 = ((r2_completo - r2_basico) / r2_basico) * 100

print(f"\nMejoras:")
print(f"RMSE: {mejora_rmse:.1f}% mejor")
print(f"R²:   {mejora_r2:.1f}% mejor")
Enter fullscreen mode Exit fullscreen mode

Análisis de Importancia de Características

# Analizar coeficientes del modelo
coef_df = pd.DataFrame({
    'caracteristica': X.columns,
    'coeficiente': modelo_completo.coef_,
    'abs_coeficiente': np.abs(modelo_completo.coef_)
})

# Ordenar por importancia
coef_df = coef_df.sort_values('abs_coeficiente', ascending=False)

print("Top 10 características más importantes:")
print(coef_df.head(10))

# Visualizar importancia
plt.figure(figsize=(12, 8))
top_features = coef_df.head(15)
plt.barh(range(len(top_features)), top_features['abs_coeficiente'])
plt.yticks(range(len(top_features)), top_features['caracteristica'])
plt.xlabel('Importancia (Valor Absoluto del Coeficiente)')
plt.title('Importancia de Características')
plt.gca().invert_yaxis()
plt.tight_layout()
plt.show()
Enter fullscreen mode Exit fullscreen mode

Mejores Prácticas

1. Evitar Data Leakage

# MAL: Calcular estadísticas usando todos los datos
precio_promedio_global = df['precio'].mean()

# BIEN: Calcular estadísticas solo en datos de entrenamiento
precio_promedio_train = X_train['precio'].mean()
Enter fullscreen mode Exit fullscreen mode

2. Manejar Categorías Raras

# Agrupar categorías con pocas muestras
def agrupar_categorias_raras(serie, min_freq=5):
    frecuencias = serie.value_counts()
    categorias_raras = frecuencias[frecuencias < min_freq].index
    return serie.replace(categorias_raras, 'Otros')
Enter fullscreen mode Exit fullscreen mode

3. Validación Cruzada para Target Encoding

from sklearn.model_selection import KFold

def target_encoding_cv(X, y, columna, cv=5):
    """Target encoding con validación cruzada"""
    kf = KFold(n_splits=cv, shuffle=True, random_state=42)
    X_encoded = X.copy()

    for train_idx, val_idx in kf.split(X):
        # Calcular encoding solo con datos de entrenamiento
        encoding_map = X.iloc[train_idx].groupby(columna)[y.iloc[train_idx]].mean()
        # Aplicar a datos de validación
        X_encoded.iloc[val_idx, X_encoded.columns.get_loc(columna)] = \
            X.iloc[val_idx, X.columns.get_loc(columna)].map(encoding_map)

    return X_encoded
Enter fullscreen mode Exit fullscreen mode

4. Documentar Transformaciones

# Crear pipeline de transformaciones
transformaciones = {
    'fecha': '2024-01-15',
    'caracteristicas_derivadas': [
        'edad_auto = 2024 - año',
        'km_anuales = kilometraje / edad_auto',
        'auto_nuevo = edad_auto <= 2',
        'auto_viejo = edad_auto > 10'
    ],
    'codificaciones': [
        'marca: one-hot encoding (mínimo 20 muestras)',
        'transmision: one-hot encoding'
    ],
    'caracteristicas_finales': len(todas_las_caracteristicas)
}

print("Documentación de transformaciones:")
for key, value in transformaciones.items():
    print(f"{key}: {value}")
Enter fullscreen mode Exit fullscreen mode

Errores Comunes

1. Curse of Dimensionality

  • Crear demasiadas características puede causar overfitting
  • Solución: Selección de características, regularización

2. Data Leakage

  • Usar información del futuro para predecir el pasado
  • Solución: Validación cruzada, separación temporal

3. Ignorar Categorías Raras

  • No agrupar categorías con pocas muestras
  • Solución: Agrupar en "Otros" o usar técnicas especializadas

4. No Validar Transformaciones

  • Aplicar transformaciones sin verificar su impacto
  • Solución: Comparar modelos antes y después

Conclusión

La ingeniería de características y el manejo de variables categóricas son habilidades fundamentales en Machine Learning. Pueden transformar un modelo mediocre en uno excelente.

Puntos clave:

  • La ingeniería de características puede ser más importante que el algoritmo
  • Las variables categóricas necesitan codificación especial
  • One-Hot Encoding es simple pero puede crear muchas dimensiones
  • Target Encoding es poderoso pero requiere cuidado con data leakage
  • Siempre valida el impacto de tus transformaciones

Próximos pasos:

  • Experimentar con diferentes técnicas de codificación
  • Crear características de interacción
  • Aplicar selección de características
  • Usar validación cruzada para evitar overfitting

En el siguiente post, exploraremos la regularización y el tuning de modelos para mejorar aún más nuestro rendimiento.


¿Qué características derivadas crees que serían más útiles para predecir precios de autos? ¿Has trabajado con variables categóricas antes?

Top comments (0)