tutoriales.com

Ingeniería de Características en Datos Tabulares con Pandas y NumPy 🛠️

Este tutorial te guiará a través de las técnicas esenciales de ingeniería de características utilizando las bibliotecas Pandas y NumPy. Descubrirás cómo transformar tus datos crudos en variables significativas, mejorando drásticamente el rendimiento de tus modelos de Machine Learning.

Intermedio15 min de lectura15 views23 de marzo de 2026Reportar error

La ingeniería de características es uno de los pasos más críticos y creativos en el pipeline de Machine Learning. No importa cuán sofisticado sea tu algoritmo, si tus características son débiles, tu modelo tendrá dificultades para aprender patrones significativos. En este tutorial, exploraremos cómo utilizar la potencia de Pandas y NumPy para construir características robustas y relevantes a partir de datos tabulares.

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

En esencia, la ingeniería de características es el proceso de transformar datos crudos en características que representen mejor el problema subyacente al modelo predictivo, resultando en un mejor rendimiento del modelo. Implica crear nuevas características a partir de las existentes o transformar las características actuales.

💡 **Consejo:** Un buen ingeniero de características a menudo entiende el dominio del problema profundamente. Este conocimiento es invaluable para identificar las transformaciones más útiles.

La Importancia Crucial de las Características ✨

Las características son los inputs que un modelo de Machine Learning utiliza para aprender y hacer predicciones. Imagina que quieres predecir el precio de una casa. Las características podrían ser el número de habitaciones, el tamaño del jardín, la ubicación, el año de construcción, etc. La forma en que presentas estas características al modelo puede cambiar radicalmente su capacidad para encontrar relaciones.

"Limpiar tus datos, crear buenas características y construir un modelo es el 80% de lo que hace un científico de datos." – Andrew Ng


Configuración del Entorno 🚀

Antes de sumergirnos en la acción, asegúrate de tener las bibliotecas necesarias instaladas. Si aún no las tienes, puedes instalarlas fácilmente usando pip.

pip install pandas numpy scikit-learn matplotlib seaborn

Una vez instaladas, importémoslas en nuestro entorno de trabajo.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# Configuraciones para mejor visualización
sns.set_style("whitegrid")
plt.rcParams['figure.figsize'] = (10, 6)
plt.rcParams['font.size'] = 12

Preparando los Datos de Ejemplo 📊

Para ilustrar los conceptos, crearemos un DataFrame sintético que simule datos de clientes de una tienda online. Este DataFrame contendrá varios tipos de datos que nos permitirán explorar diferentes técnicas de ingeniería de características.

data = {
    'CustomerID': range(1, 101),
    'Age': np.random.randint(18, 70, 100),
    'Gender': np.random.choice(['Male', 'Female'], 100),
    'ProductCategory': np.random.choice(['Electronics', 'Apparel', 'Books', 'Home Goods'], 100),
    'PurchaseAmount': np.random.normal(loc=150, scale=50, size=100).round(2),
    'NumPurchases': np.random.randint(1, 15, 100),
    'DaysSinceLastPurchase': np.random.randint(1, 100, 100),
    'MembershipLevel': np.random.choice(['Bronze', 'Silver', 'Gold'], 100, p=[0.5, 0.3, 0.2]),
    'ReviewScore': np.random.randint(1, 6, 100),
    'IsPromotionClicked': np.random.choice([0, 1], 100, p=[0.7, 0.3])
}
df = pd.DataFrame(data)

# Añadir algunos valores nulos para demostración
for col in ['PurchaseAmount', 'DaysSinceLastPurchase']:
    df.loc[df.sample(frac=0.05).index, col] = np.nan

print(df.head())
Visualización del DataFrame inicial
   CustomerID  Age  Gender ProductCategory  PurchaseAmount  NumPurchases  DaysSinceLastPurchase MembershipLevel  ReviewScore  IsPromotionClicked
0           1   52    Male     Electronics          208.77            13                   33.0            Gold            1                   1
1           2   44  Female     Electronics          224.77             7                   50.0            Gold            2                   0
2           3   51    Male          Apparel           86.74             2                    NaN          Silver            3                   0
3           4   60    Male     Electronics          142.17             6                   45.0          Bronze            5                   1
4           5   22  Female          Apparel          104.22             8                   12.0            Gold            1                   1

Limpieza y Preprocesamiento Inicial de Datos 🧹

Antes de crear nuevas características, es fundamental asegurar que nuestros datos estén limpios. Esto a menudo implica manejar valores nulos y convertir tipos de datos si es necesario.

Manejo de Valores Nulos 🕵️‍♀️

En nuestro DataFrame de ejemplo, hemos introducido algunos NaN. Una estrategia común para variables numéricas es imputar la media o la mediana.

# Imputar valores nulos con la mediana
df['PurchaseAmount'].fillna(df['PurchaseAmount'].median(), inplace=True)
df['DaysSinceLastPurchase'].fillna(df['DaysSinceLastPurchase'].median(), inplace=True)

print("Valores nulos después de imputación:")
print(df.isnull().sum())

Técnicas de Ingeniería de Características con Pandas y NumPy 💡

Ahora que nuestros datos están limpios, podemos empezar a aplicar diferentes técnicas para crear características más expresivas.

1. Combinación de Características Existentes ➕➖✖️➗

Podemos crear nuevas características combinando dos o más características existentes mediante operaciones aritméticas. Esto a menudo revela relaciones que no eran obvias individualmente.

Ejemplo: Gasto Promedio por Compra (AveragePurchaseValue)

df['AveragePurchaseValue'] = df['PurchaseAmount'] / df['NumPurchases']
print(df[['PurchaseAmount', 'NumPurchases', 'AveragePurchaseValue']].head())
📌 **Nota:** Esta característica podría ser más predictiva que el gasto total o el número de compras por separado, ya que normaliza el gasto por el número de transacciones.

2. Extracción de Información de Fechas y Horas ⏰

Aunque nuestro dataset de ejemplo no contiene fechas, es una técnica fundamental. Imagina una columna TransactionDate. Podrías extraer el día de la semana, el mes, el año, si es fin de semana, etc.

Ejemplo hipotético con fechas ```python # Si tuvieras una columna 'TransactionDate' de tipo datetime # df['TransactionDate'] = pd.to_datetime(df['TransactionDate']) # df['Year'] = df['TransactionDate'].dt.year # df['Month'] = df['TransactionDate'].dt.month # df['DayOfWeek'] = df['TransactionDate'].dt.dayofweek # df['IsWeekend'] = df['TransactionDate'].dt.dayofweek.isin([5, 6]).astype(int) ```

3. Binning o Discretización (Agrupación de Características Numéricas) 📦

Convertir una característica numérica continua en una categórica dividiéndola en 'bins' o rangos. Esto puede ayudar a capturar relaciones no lineales y hacer que las características sean más robustas a los outliers.

Ejemplo: Agrupar la Edad en Categorías (AgeGroup)

bins = [18, 25, 35, 50, 70]
labels = ['Young Adult', 'Adult', 'Middle-Aged', 'Senior']
df['AgeGroup'] = pd.cut(df['Age'], bins=bins, labels=labels, right=False)

print(df[['Age', 'AgeGroup']].head())

Visualización de la Discretización

Distribución de Grupos de Edad 0 100 200 300 400 Conteo de Clientes 280 350 220 110 Young Adult Adult Middle-Aged Senior AgeGroup

4. Codificación de Variables Categóricas 🏷️

Los modelos de Machine Learning generalmente requieren entradas numéricas. Las variables categóricas deben ser transformadas.

a) One-Hot Encoding (OHE) 🌡️

Ideal para variables categóricas nominales (sin orden intrínseco). Crea nuevas columnas binarias para cada categoría.

Ejemplo: Categoría de Producto (ProductCategory)

df_encoded = pd.get_dummies(df, columns=['ProductCategory'], prefix='ProductCat')
print(df_encoded[[col for col in df_encoded.columns if 'ProductCat_' in col]].head())

b) Label Encoding (Codificación de Etiquetas) 🔡

Adecuada para variables categóricas ordinales (con un orden intrínseco). Asigna un número entero a cada categoría.

Ejemplo: Nivel de Membresía (MembershipLevel)

membership_mapping = {'Bronze': 1, 'Silver': 2, 'Gold': 3}
df['MembershipLevel_Encoded'] = df['MembershipLevel'].map(membership_mapping)

print(df[['MembershipLevel', 'MembershipLevel_Encoded']].head())
⚠️ **Advertencia:** Usar Label Encoding para variables nominales puede introducir un orden artificial que el modelo podría interpretar erróneamente. ¡Ten cuidado!

5. Interacciones de Características 🤝

Crear una nueva característica multiplicando o combinando dos características existentes puede capturar interacciones complejas. Por ejemplo, el gasto en un producto puede depender de la edad y la categoría del producto.

Ejemplo: Gasto por Edad y Nivel de Membresía (Purchase_x_Membership)

df['Purchase_x_Membership'] = df['PurchaseAmount'] * df['MembershipLevel_Encoded']

print(df[['PurchaseAmount', 'MembershipLevel_Encoded', 'Purchase_x_Membership']].head())

6. Características Basadas en Frecuencia/Conteo 📊

Una característica puede ser el recuento de ocurrencias de un valor en otra columna. Esto puede ser útil para identificar popularidad o rareza.

Ejemplo: Frecuencia de Categoría de Producto (ProductCategory_Frequency)

category_counts = df['ProductCategory'].value_counts().to_dict()
df['ProductCategory_Frequency'] = df['ProductCategory'].map(category_counts)

print(df[['ProductCategory', 'ProductCategory_Frequency']].head())

7. Transformaciones Numéricas (Log, Raíz Cuadrada, Box-Cox) 📈

Algunas características numéricas pueden tener distribuciones sesgadas. Transformarlas puede ayudar a que se ajusten mejor a los supuestos de ciertos modelos (ej. regresión lineal) o a reducir el impacto de outliers.

Ejemplo: Transformación Logarítmica de PurchaseAmount

# Añadimos un pequeño valor para evitar log(0) si fuera posible
df['PurchaseAmount_Log'] = np.log1p(df['PurchaseAmount'])

# Visualizar la distribución antes y después
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
sns.histplot(df['PurchaseAmount'], bins=30, kde=True, ax=axes[0]).set_title('PurchaseAmount Original')
sns.histplot(df['PurchaseAmount_Log'], bins=30, kde=True, ax=axes[1]).set_title('PurchaseAmount Log-Transformado')
plt.tight_layout()
plt.show()
PurchaseAmount Valor Frecuencia PurchaseAmount_Log Valor (Log) Frecuencia

Pipelines de Ingeniería de Características (Scikit-learn) ⚙️

Cuando trabajamos con múltiples transformaciones, es crucial mantener un flujo de trabajo organizado y reproducible. Scikit-learn ofrece herramientas excelentes para esto, como ColumnTransformer y Pipeline.

🔥 **Importante:** Al usar estas herramientas, evitamos el *data leakage* (fuga de datos) en la validación cruzada y aseguramos que las transformaciones se apliquen consistentemente a los datos de entrenamiento y prueba.

Consideremos un subconjunto de nuestras características para demostrar un pipeline.

from sklearn.preprocessing import StandardScaler, OneHotEncoder, LabelEncoder
from sklearn.impute import SimpleImputer
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline

# Definir las características para el pipeline
features_numeric = ['Age', 'PurchaseAmount', 'NumPurchases', 'DaysSinceLastPurchase']
features_categorical_ohe = ['ProductCategory']
features_categorical_label = ['MembershipLevel'] # Usaremos esto con precaución

# Creamos una copia para el pipeline
df_pipeline = df.copy()

# Primero, codificamos Label Encoding manualmente si es ordinal
membership_mapping_pipeline = {'Bronze': 1, 'Silver': 2, 'Gold': 3}
df_pipeline['MembershipLevel_Encoded'] = df_pipeline['MembershipLevel'].map(membership_mapping_pipeline)
features_numeric.append('MembershipLevel_Encoded') # Añadir al grupo numérico

# Preprocesamiento para características numéricas
numeric_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='median')), # Ya lo hicimos, pero es buena práctica
    ('scaler', StandardScaler()) # Escalado para normalizar las características
])

# Preprocesamiento para características categóricas (One-Hot Encoding)
categorical_transformer = Pipeline(steps=[
    ('onehot', OneHotEncoder(handle_unknown='ignore'))
])

# Crear un ColumnTransformer para aplicar diferentes transformaciones a diferentes columnas
preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, features_numeric),
        ('cat', categorical_transformer, features_categorical_ohe)
    ], 
    remainder='passthrough' # Mantener otras columnas no transformadas
)

# El pipeline final podría incluir un modelo
# from sklearn.linear_model import LogisticRegression
# model_pipeline = Pipeline(steps=[
#     ('preprocessor', preprocessor),
#     ('classifier', LogisticRegression(solver='liblinear'))
# ])

# Aplicar solo el preprocesador para ver las características transformadas
X_transformed = preprocessor.fit_transform(df_pipeline)

print("Forma de los datos transformados:", X_transformed.shape)
print("Primeras 5 filas de los datos transformados (parcial):")
print(X_transformed[:5])
Explicación del `ColumnTransformer` El `ColumnTransformer` es extremadamente útil porque permite aplicar diferentes transformaciones a diferentes columnas de tu DataFrame. Por ejemplo, las columnas numéricas pueden necesitar estandarización e imputación, mientras que las categóricas requieren One-Hot Encoding. `ColumnTransformer` orquesta esto perfectamente.

Ejercicio Práctico: Mejorando un Modelo Sencillo 🎯

Usemos las características que hemos creado para entrenar un modelo sencillo y veamos cómo la ingeniería de características puede impactar el rendimiento.

Supongamos que queremos predecir IsPromotionClicked.

from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report

# Definimos las características ingenierizadas que usaremos
# Excluyendo CustomerID, Gender (ya manejado por OHE en ProductCategory, no incluido directamente), MembershipLevel original
# y las columnas que queremos predecir o las transformadas para OHE
final_features = [
    'Age', 'PurchaseAmount', 'NumPurchases', 'DaysSinceLastPurchase',
    'AveragePurchaseValue', 'MembershipLevel_Encoded', 'ReviewScore',
    'Purchase_x_Membership', 'ProductCategory_Frequency', 'PurchaseAmount_Log'
]

# Añadimos las columnas de One-Hot Encoding
ohe_cols = [col for col in df_encoded.columns if 'ProductCat_' in col]
final_features.extend(ohe_cols)

# Creamos una versión del DataFrame con todas las características relevantes ya preparadas
df_final = df_encoded.copy()
# Aseguramos que todas las columnas usadas en final_features existan en df_final
for col in ['AveragePurchaseValue', 'MembershipLevel_Encoded', 'Purchase_x_Membership', 'ProductCategory_Frequency', 'PurchaseAmount_Log']:
    if col not in df_final.columns:
        df_final[col] = df[col]

y = df_final['IsPromotionClicked']
X = df_final[final_features]

# Manejo de nulos final por si acaso (aunque ya se hizo)
X.fillna(X.median(), inplace=True)

# División de datos
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)

# Escalado de características numéricas (importante para muchos modelos)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# Entrenamiento del modelo con características ingenierizadas
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train_scaled, y_train)

y_pred = model.predict(X_test_scaled)

print("\n--- Rendimiento del Modelo con Características Ingenierizadas ---")
print(f"Accuracy: {accuracy_score(y_test, y_pred):.4f}")
print("Classification Report:\n", classification_report(y_test, y_pred))

# Comparación: Modelo sin ingeniería de características (solo características originales relevantes)
original_features = ['Age', 'PurchaseAmount', 'NumPurchases', 'DaysSinceLastPurchase', 'ReviewScore']
# Añadimos OHE para 'ProductCategory' y Label Encoding para 'MembershipLevel' si fuera el caso, pero simplificamos
# para mostrar el impacto de la ingeniería manual

X_original = df_final[original_features]
X_original.fillna(X_original.median(), inplace=True)

X_train_orig, X_test_orig, y_train_orig, y_test_orig = train_test_split(X_original, y, test_size=0.3, random_state=42, stratify=y)

scaler_orig = StandardScaler()
X_train_orig_scaled = scaler_orig.fit_transform(X_train_orig)
X_test_orig_scaled = scaler_orig.transform(X_test_orig)

model_orig = RandomForestClassifier(n_estimators=100, random_state=42)
model_orig.fit(X_train_orig_scaled, y_train_orig)

y_pred_orig = model_orig.predict(X_test_orig_scaled)

print("\n--- Rendimiento del Modelo SIN Ingeniería de Características --- ")
print(f"Accuracy: {accuracy_score(y_test_orig, y_pred_orig):.4f}")
print("Classification Report:\n", classification_report(y_test_orig, y_pred_orig))
¡Casi hemos terminado!

Consideraciones Adicionales y Mejores Prácticas ✅

  • Conocimiento del Dominio: Siempre es tu mejor herramienta. Entender el negocio o el fenómeno detrás de los datos te ayudará a crear características más inteligentes.
  • Exploración de Datos (EDA): Realiza un EDA exhaustivo antes y durante la ingeniería de características. Los gráficos de dispersión, histogramas y correlaciones pueden revelar relaciones ocultas.
  • Selección de Características: No todas las características ingenierizadas serán útiles. Usa técnicas de selección de características (Filter, Wrapper, Embedded methods) para elegir las más relevantes y evitar la sobrecarga del modelo.
  • Iteración: La ingeniería de características es un proceso iterativo. Crea, prueba, evalúa y refina.
  • Evitar Data Leakage: Asegúrate de que las transformaciones (como la imputación de la media o el escalado) se calculen solo con los datos de entrenamiento y luego se apliquen a los datos de prueba y nuevos datos.
⚠️ **Advertencia:** Demasiadas características, especialmente si son redundantes o irrelevantes, pueden llevar a un sobreajuste y a un mayor tiempo de entrenamiento.

Resumen de Técnicas Vistas 📖

TécnicaDescripciónCuándo Usarla
Combinación AritméticaCrear nuevas características combinando existentes (suma, resta, etc.)Cuando hay una relación lógica entre características que se puede expresar aritméticamente.
Discretización (Binning)Convertir numéricas continuas en categóricas por rangosPara capturar no linealidades, reducir el impacto de outliers, o cuando un rango tiene más significado que un valor exacto.
One-Hot Encoding (OHE)Convertir categóricas nominales en binariasPara variables categóricas sin orden intrínseco.
Label EncodingConvertir categóricas ordinales en enterosPara variables categóricas con un orden claro (ej. niveles).
Interacciones de Caract.Combinar dos características para capturar efectos sinérgicosCuando el efecto de una característica depende de otra.
Frecuencia/ConteoUsar la frecuencia de aparición de un valor como característicaPara capturar la popularidad o rareza de una categoría.
Transformaciones NuméricasAplicar funciones (log, raíz) para cambiar la distribución de una variableCuando una variable está sesgada o tiene outliers para normalizar su distribución.
Paso 1: Entender los Datos - Realizar un EDA a fondo.
Paso 2: Generar Hipótesis - Pensar en posibles relaciones y transformaciones.
Paso 3: Implementar Características - Usar Pandas y NumPy para crear nuevas variables.
Paso 4: Evaluar y Refinar - Probar las nuevas características con un modelo y ajustar.

Conclusión 🎉

La ingeniería de características es tanto un arte como una ciencia. Requiere creatividad, conocimiento del dominio y un buen dominio de herramientas como Pandas y NumPy. Al aplicar las técnicas que hemos explorado en este tutorial, estás un paso más cerca de desbloquear el verdadero potencial de tus datos y construir modelos de Machine Learning más robustos y precisos.

¡Sigue experimentando y mejorando tus habilidades en este fascinante campo!

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!