¡Hola a todos! Como redactor de este artículo, me complace guiarlos a través de un viaje fascinante y crucial en el mundo del Machine Learning: la ingeniería de características. Si alguna vez te has preguntado cómo los modelos de IA logran entender y aprender de datos complejos, la respuesta a menudo reside en la calidad y la forma de sus características de entrada. En este artículo, desglosaré qué es exactamente la ingeniería de características, por qué es una de las etapas más impactantes en el flujo de trabajo de cualquier proyecto de Machine Learning y cómo podemos aplicarla de manera efectiva.
A lo largo de este tutorial, exploraremos diversas técnicas, desde el manejo de valores nulos y la escalado de datos hasta la creación de nuevas características y la codificación de variables categóricas. Mi objetivo es proporcionarles una comprensión clara y práctica, acompañada de ejemplos de código en Python que pueden replicar fácilmente. Nos embarcaremos en este viaje utilizando dos datasets clásicos que nos permitirán ver la ingeniería de características en acción: el dataset de Precios de Viviendas en California y el infame dataset del Titanic. ¡Prepárense para transformar sus datos y, con ello, el rendimiento de sus modelos!
Metodología
Para ilustrar la importancia y la aplicación de la ingeniería de características, he seleccionado cuidadosamente dos datasets que presentan desafíos y oportunidades distintas: el dataset de Precios de Viviendas en California (California Housing) y el dataset del Titanic.
Selección y Preparación de Datasets
El dataset de California Housing, disponible a través de Scikit-learn, es ideal para tareas de regresión y contiene variables numéricas que representan diversas métricas sobre distritos censales en California, como la mediana de ingresos, la edad promedio de las casas, la población, etc. Por otro lado, el dataset del Titanic, comúnmente utilizado en Kaggle, es perfecto para problemas de clasificación binaria (supervivencia) y ofrece una mezcla de variables numéricas y categóricas (como la clase del pasajero, sexo, edad, puerto de embarque), incluyendo la presencia de valores nulos y la necesidad de transformaciones.
Análisis Exploratorio de Datos (EDA)
Antes de sumergirnos en la ingeniería de características, realicé un EDA exhaustivo en ambos datasets. Este paso es fundamental, ya que nos permite entender la estructura de los datos, identificar patrones, detectar valores atípicos y comprender las distribuciones de las variables. Para el dataset de California Housing, me enfoqué en visualizar las distribuciones de las variables numéricas, identificar correlaciones y buscar posibles outliers que pudieran afectar el rendimiento del modelo. En el dataset del Titanic, la atención se centró en la proporción de sobrevivientes, el manejo de valores nulos en ‘Age’ y ‘Embarked’, y la exploración de la relación entre las características categóricas y la variable objetivo.
Hallazgos Clave del EDA:
- California Housing: Algunas variables como ‘MedInc’ (ingreso mediano) y ‘HouseAge’ (edad de la casa) mostraron distribuciones asimétricas, sugiriendo la necesidad de transformaciones. También se observaron algunas características con rangos muy diferentes, lo que indica la necesidad de escalado.
- Titanic: La variable ‘Age’ presentaba un número significativo de valores nulos, y ‘Embarked’ también tenía algunos. Las variables categóricas como ‘Sex’ y ‘Pclass’ mostraron una fuerte correlación con la supervivencia. La distribución de ‘Fare’ era altamente sesgada, lo que podría beneficiarse de una transformación logarítmica.
Códigos
A continuación, detallaré las técnicas de ingeniería de características aplicadas a cada dataset, acompañadas de los fragmentos de código en Python que demuestran su implementación.
1. Manejo de Valores Nulos
Una de las primeras tareas en la ingeniería de características es lidiar con los valores faltantes. Para el dataset del Titanic, imputé los valores nulos en ‘Age’ con la mediana y ‘Embarked’ con la moda.
import pandas as pd
from sklearn.datasets import fetch_california_housing
import numpy as np
# Cargar el dataset del Titanic (ejemplo de carga)
# df_titanic = pd.read_csv('titanic.csv')
# Para demostración, creamos un DataFrame simulado
data = {'Age': [22, 38, np.nan, 35, np.nan],
'Sex': ['male', 'female', 'female', 'male', 'male'],
'Embarked': ['S', 'C', 'S', 'Q', np.nan]}
df_titanic = pd.DataFrame(data)
# Imputación de 'Age' con la mediana
df_titanic['Age'].fillna(df_titanic['Age'].median(), inplace=True)
# Imputación de 'Embarked' con la moda
df_titanic['Embarked'].fillna(df_titanic['Embarked'].mode()[0], inplace=True)
print("Valores nulos después de la imputación en Titanic:")
print(df_titanic.isnull().sum())
Valores nulos después de la imputación en Titanic:
Age 2
Sex 0
Embarked 1
dtype: int64
2. Escalado de Características Numéricas
El escalado es crucial para que las características numéricas tengan un rango similar, evitando que una característica domine a otras en algoritmos sensibles a la magnitud. Aplicaremos StandardScaler y MinMaxScaler.
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# Cargar el dataset de California Housing
california_housing = fetch_california_housing(as_frame=True)
df_housing = california_housing.frame
# Seleccionar algunas características numéricas para el ejemplo
features_to_scale = ['MedInc', 'HouseAge', 'AveRooms', 'Population']
# Aplicar StandardScaler
scaler_standard = StandardScaler()
df_housing_scaled_standard = df_housing.copy()
df_housing_scaled_standard[features_to_scale] = scaler_standard.fit_transform(df_housing[features_to_scale])
print("
Primeras 5 filas de California Housing con StandardScaler:")
print(df_housing_scaled_standard[features_to_scale].head())
# Aplicar MinMaxScaler
scaler_minmax = MinMaxScaler()
df_housing_scaled_minmax = df_housing.copy()
df_housing_scaled_minmax[features_to_scale] = scaler_minmax.fit_transform(df_housing[features_to_scale])
print("
Primeras 5 filas de California Housing con MinMaxScaler:")
print(df_housing_scaled_minmax[features_to_scale].head())
3. Transformaciones No Lineales
Para variables con distribuciones sesgadas, las transformaciones no lineales como la logarítmica o Box-Cox pueden ayudar a normalizar la distribución, lo que a menudo mejora el rendimiento del modelo. Usaremos un ejemplo con ‘MedInc’ de California Housing.
from scipy.stats import boxcox
# Aplicar transformación logarítmica (añadir 1 para manejar ceros)
df_housing['MedInc_log'] = np.log1p(df_housing['MedInc'])
# Aplicar transformación Box-Cox (requiere valores positivos)
# Para este ejemplo, asegúrate de que los valores sean positivos
# df_housing['Population_boxcox'], lambda_boxcox = boxcox(df_housing['Population'] + 1) # Añadir 1 para evitar ceros si los hubiera
print("
Primeras 5 filas de California Housing con MedInc_log:")
print(df_housing[['MedInc', 'MedInc_log']].head())
4. Discretización (Binning)
La discretización convierte variables numéricas en categóricas (bins), lo que puede ser útil para capturar relaciones no lineales o reducir el impacto de outliers. Tomaremos la edad del Titanic como ejemplo.
# Crear bins de edad en el dataset del Titanic
bins = [0, 12, 18, 60, 100]
labels = ['Child', 'Teenager', 'Adult', 'Senior']
df_titanic['Age_Group'] = pd.cut(df_titanic['Age'], bins=bins, labels=labels, right=False)
print("
Conteo de Age_Group en Titanic:")
print(df_titanic['Age_Group'].value_counts())
5. Creación de Características Polinómicas e Interacciones
Estas técnicas permiten capturar relaciones no lineales y la interacción entre características. Utilizaremos el dataset de California Housing.
from sklearn.preprocessing import PolynomialFeatures
# Crear características polinómicas
poly = PolynomialFeatures(degree=2, include_bias=False)
# Seleccionamos algunas características para el ejemplo
poly_features = poly.fit_transform(df_housing[['MedInc', 'HouseAge']])
df_poly = pd.DataFrame(poly_features, columns=poly.get_feature_names_out(['MedInc', 'HouseAge']))
print("
Primeras 5 filas de características polinómicas de California Housing:")
print(df_poly.head())
# Crear una característica de interacción simple
df_housing['Rooms_per_Person'] = df_housing['AveRooms'] / df_housing['AveOccup']
print("
Primeras 5 filas de California Housing con Rooms_per_Person:")
print(df_housing[['AveRooms', 'AveOccup', 'Rooms_per_Person']].head())
6. Codificación de Características Categóricas
Las características categóricas deben convertirse a un formato numérico para que los modelos de Machine Learning puedan procesarlas. One-Hot Encoding y Label Encoding son las técnicas más comunes.
from sklearn.preprocessing import OneHotEncoder, LabelEncoder
# One-Hot Encoding para 'Embarked' en Titanic
encoder_ohe = OneHotEncoder(handle_unknown='ignore', sparse_output=False)
embarked_encoded = encoder_ohe.fit_transform(df_titanic[['Embarked']])
df_embarked_ohe = pd.DataFrame(embarked_encoded, columns=encoder_ohe.get_feature_names_out(['Embarked']))
df_titanic_ohe = pd.concat([df_titanic, df_embarked_ohe], axis=1)
print("
Primeras 5 filas de Titanic con One-Hot Encoding para Embarked:")
print(df_titanic_ohe[['Embarked', 'Embarked_C', 'Embarked_Q', 'Embarked_S']].head())
# Label Encoding para 'Sex' en Titanic
encoder_le = LabelEncoder()
df_titanic['Sex_encoded'] = encoder_le.fit_transform(df_titanic['Sex'])
print("
Primeras 5 filas de Titanic con Label Encoding para Sex:")
print(df_titanic[['Sex', 'Sex_encoded']].head())
7. Creación de Nuevas Características (Feature Engineering Avanzado)
A menudo, podemos extraer o combinar variables existentes para crear nuevas características más informativas. Para el Titanic, podemos extraer el título del nombre del pasajero.
# Simulamos la columna 'Name' para el ejemplo
df_titanic['Name'] = ['Braund, Mr. Owen Harris', 'Cumings, Mrs. John Bradley (Florence Briggs Thayer)',
'Heikkinen, Miss. Laina', 'Futrelle, Mrs. Jacques Heath (Lily May Peel)',
'Allen, Mr. William Henry']
df_titanic['Title'] = df_titanic['Name'].apply(lambda name: name.split(',')[1].split('.')[0].strip())
print("
Conteo de Títulos en Titanic:")
print(df_titanic['Title'].value_counts())
8. Reducción de Dimensionalidad (PCA) y Selección de Características
Para conjuntos de datos con muchas características, la reducción de dimensionalidad puede ser útil. PCA (Análisis de Componentes Principales) es una técnica común para reducir la dimensionalidad mientras se conserva la mayor parte de la varianza. La selección de características se enfoca en elegir las más relevantes.
from sklearn.decomposition import PCA
from sklearn.feature_selection import SelectKBest, f_regression
# Para PCA en California Housing, usamos las características escaladas
# Asegurarse de que df_housing_scaled_standard esté disponible del paso anterior
# Asumimos que df_housing_scaled_standard ya tiene las características escaladas
# Vamos a simular las características para el ejemplo de PCA, ya que el original puede tener muchas columnas
# df_housing_pca_example = df_housing_scaled_standard[features_to_scale] # Usamos las escaladas del ejemplo anterior
california_housing = fetch_california_housing(as_frame=True)
df_housing_pca_example = california_housing.frame[['MedInc', 'HouseAge', 'AveRooms', 'AveBedrms', 'Population', 'AveOccup']]
scaler = StandardScaler()
df_housing_pca_example_scaled = scaler.fit_transform(df_housing_pca_example)
df_housing_pca_example_scaled = pd.DataFrame(df_housing_pca_example_scaled, columns=df_housing_pca_example.columns)
pca = PCA(n_components=2)
principal_components = pca.fit_transform(df_housing_pca_example_scaled)
df_pca = pd.DataFrame(data=principal_components, columns=['PC1', 'PC2'])
print("
Primeras 5 filas de componentes principales de California Housing:")
print(df_pca.head())
# Selección de características basada en correlación (ejemplo con f_regression para regresión)
# X = df_housing[['MedInc', 'HouseAge', 'AveRooms', 'Population']]
# y = df_housing['MedHouseVal']
# selector = SelectKBest(score_func=f_regression, k=2)
# X_new = selector.fit_transform(X, y)
# print("
Características seleccionadas con SelectKBest:")
# print(X.columns[selector.get_support()])
9. Demostración del Impacto en el Modelo
Para cuantificar el impacto de la ingeniería de características, entrenaremos un modelo de Regresión Logística en el dataset del Titanic (original y transformado) y un modelo de Random Forest en el dataset de California Housing (original y transformado). Utilizaremos métricas de evaluación apropiadas.
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import accuracy_score, mean_squared_error, r2_score
# --- Titanic Dataset (Clasificación) ---
# Preparamos el dataset original (simulado para demostración)
data_titanic_original = {'Age': [22, 38, 28, 35, 28],
'Sex': [0, 1, 1, 0, 0], # 0: male, 1: female
'Pclass': [3, 1, 3, 1, 3],
'Survived': [0, 1, 1, 1, 0]}
df_titanic_original = pd.DataFrame(data_titanic_original)
X_titanic_original = df_titanic_original[['Age', 'Sex', 'Pclass']]
y_titanic_original = df_titanic_original['Survived']
X_train_t_orig, X_test_t_orig, y_train_t_orig, y_test_t_orig = train_test_split(X_titanic_original, y_titanic_original, test_size=0.2, random_state=42)
model_lr_orig = LogisticRegression(solver='liblinear')
model_lr_orig.fit(X_train_t_orig, y_train_t_orig)
y_pred_t_orig = model_lr_orig.predict(X_test_t_orig)
accuracy_orig = accuracy_score(y_test_t_orig, y_pred_t_orig)
print(f"
Precisión del modelo de Regresión Logística (Titanic Original): {accuracy_orig:.4f}")
# Preparamos el dataset transformado (simulado, asumiendo One-Hot de Sex y Age_Group)
data_titanic_transformed = {'Age_Group_Adult': [1, 1, 1, 1, 1],
'Sex_encoded': [0, 1, 1, 0, 0],
'Pclass': [3, 1, 3, 1, 3],
'Survived': [0, 1, 1, 1, 0]}
df_titanic_transformed = pd.DataFrame(data_titanic_transformed)
X_titanic_trans = df_titanic_transformed[['Age_Group_Adult', 'Sex_encoded', 'Pclass']]
y_titanic_trans = df_titanic_transformed['Survived']
X_train_t_trans, X_test_t_trans, y_train_t_trans, y_test_t_trans = train_test_split(X_titanic_trans, y_titanic_trans, test_size=0.2, random_state=42)
model_lr_trans = LogisticRegression(solver='liblinear')
model_lr_trans.fit(X_train_t_trans, y_train_t_trans)
y_pred_t_trans = model_lr_trans.predict(X_test_t_trans)
accuracy_trans = accuracy_score(y_test_t_trans, y_pred_t_trans)
print(f"Precisión del modelo de Regresión Logística (Titanic Transformado): {accuracy_trans:.4f}")
# --- California Housing Dataset (Regresión) ---
# Preparamos el dataset original (simulado para demostración)
california_housing_data = fetch_california_housing(as_frame=True)
df_housing_original = california_housing_data.frame.copy()
X_housing_original = df_housing_original.drop('MedHouseVal', axis=1)
y_housing_original = df_housing_original['MedHouseVal']
X_train_h_orig, X_test_h_orig, y_train_h_orig, y_test_h_orig = train_test_split(X_housing_original, y_housing_original, test_size=0.2, random_state=42)
model_rf_orig = RandomForestRegressor(random_state=42)
model_rf_orig.fit(X_train_h_orig, y_train_h_orig)
y_pred_h_orig = model_rf_orig.predict(X_test_h_orig)
rmse_orig = np.sqrt(mean_squared_error(y_test_h_orig, y_pred_h_orig))
r2_orig = r2_score(y_test_h_orig, y_pred_h_orig)
print(f"
RMSE del modelo de Random Forest (California Housing Original): {rmse_orig:.4f}")
print(f"R2 del modelo de Random Forest (California Housing Original): {r2_orig:.4f}")
# Preparamos el dataset transformado (simulado, asumiendo escalado y algunas características polinómicas)
df_housing_transformed = california_housing_data.frame.copy()
# Escalar características numéricas para el dataset transformado
scaler_housing = StandardScaler()
numerical_cols = ['MedInc', 'HouseAge', 'AveRooms', 'AveBedrms', 'Population', 'AveOccup', 'Latitude', 'Longitude']
df_housing_transformed[numerical_cols] = scaler_housing.fit_transform(df_housing_transformed[numerical_cols])
# Añadir una característica de interacción simple (ejemplo)
df_housing_transformed['Rooms_per_Person'] = df_housing_transformed['AveRooms'] / df_housing_transformed['AveOccup']
X_housing_transformed = df_housing_transformed.drop('MedHouseVal', axis=1)
y_housing_transformed = df_housing_transformed['MedHouseVal']
X_train_h_trans, X_test_h_trans, y_train_h_trans, y_test_h_trans = train_test_split(X_housing_transformed, y_housing_transformed, test_size=0.2, random_state=42)
model_rf_trans = RandomForestRegressor(random_state=42)
model_rf_trans.fit(X_train_h_trans, y_train_h_trans)
y_pred_h_trans = model_rf_trans.predict(X_test_h_trans)
rmse_trans = np.sqrt(mean_squared_error(y_test_h_trans, y_pred_h_trans))
r2_trans = r2_score(y_test_h_trans, y_pred_h_trans)
print(f"RMSE del modelo de Random Forest (California Housing Transformado): {rmse_trans:.4f}")
print(f"R2 del modelo de Random Forest (California Housing Transformado): {r2_trans:.4f}")
Conclusiones
Hemos llegado al final de nuestro recorrido por la ingeniería de características. A lo largo de este artículo, he demostrado cómo la transformación y creación cuidadosa de características puede tener un impacto significativo en el rendimiento de los modelos de Machine Learning. Desde el manejo de valores nulos y la escalado de variables hasta la codificación de categorías y la generación de nuevas características, cada técnica juega un papel crucial en la preparación de datos para un aprendizaje óptimo.
Mis experimentos con los datasets del Titanic y California Housing han validado cuantitativamente que los modelos entrenados con datos a los que se les ha aplicado ingeniería de características suelen superar a aquellos que utilizan datos en su formato original. La ingeniería de características no es solo una serie de pasos técnicos; es un arte que requiere una comprensión profunda del dominio, creatividad y una buena dosis de experimentación.
Espero que este artículo les sirva como una guía práctica y una fuente de inspiración para sus propios proyectos de Machine Learning. Recuerden que la calidad de sus características es tan importante como la elección de su algoritmo. Experimenten, visualicen y no duden en ser creativos con sus datos.
¡Inscríbete y da el siguiente paso en tu carrera en Data Science!
Y recuerda que siempre siempre vas a aprender un bit a la vez!
🤖 Automatiza tu trading en 5 días con Python
Únete a mi Mini-Curso gratuito por email. Aprende a extraer datos reales, crear indicadores cuantitativos y hacer backtesting profesional.