scikit-learn

ScikitLearn

Tutorial ScikitLearn: Pipelines con ColumnTransformer

Aprende a usar `ColumnTransformer` de Scikit-Learn para manejar datos heterogéneos, optimizando tu flujo de trabajo en aprendizaje automático en pipelines.

Aprende ScikitLearn GRATIS y certifícate

Manejo de datos heterogéneos

En el procesamiento de datos para modelos de aprendizaje automático, es común enfrentarse a conjuntos de datos que contienen diferentes tipos de variables, como numéricas, categóricas y de texto. Este tipo de datos se conocen como datos heterogéneos y su manejo eficiente es crucial para la construcción de modelos precisos.

La gestión de datos heterogéneos presenta el desafío de aplicar transformaciones específicas a diferentes tipos de variables. Por ejemplo, es posible que necesitemos escalar las variables numéricas, codificar las categóricas y procesar los textos para extraer características relevantes. Scikit-Learn facilita este proceso mediante el uso de ColumnTransformer, una herramienta que permite aplicar transformaciones distintas a columnas específicas del conjunto de datos.

A continuación, se presenta un ejemplo práctico que ilustra cómo manejar datos heterogéneos utilizando ColumnTransformer. Supongamos que tenemos un conjunto de datos con columnas numéricas, categóricas y de texto:

import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression

# Crear un DataFrame de ejemplo
data = {
    'edad': [25, 32, 47, 51],
    'sexo': ['M', 'F', 'F', 'M'],
    'ingresos': [50000, 60000, 52000, 58000],
    'profesion': ['ingeniero', 'doctora', 'abogada', 'arquitecto'],
    'comentarios': [
        'Excelente servicio y atención',
        'Muy satisfecha con el producto',
        'Experiencia regular, podría mejorar',
        'No estoy contento con el soporte'
    ],
    'compra_realizada': [1, 1, 0, 0]
}

df = pd.DataFrame(data)

En este ejemplo, disponemos de variables numéricas (edad, ingresos), categóricas (sexo, profesion) y de texto (comentarios). Nuestro objetivo es preparar estos datos para entrenar un modelo de clasificación que prediga si un cliente realizará una compra (compra_realizada).

Primero, definimos las columnas y las transformaciones específicas para cada tipo de dato:

# Definición de las columnas según su tipo
columnas_numericas = ['edad', 'ingresos']
columnas_categoricas = ['sexo', 'profesion']
columnas_texto = ['comentarios']

Creamos los transformadores para cada grupo de columnas:

# Transformadores para cada tipo de dato
transformador_numerico = Pipeline(steps=[
    ('escalado', StandardScaler())
], memory='cache_pipelines')

transformador_categorico = Pipeline(steps=[
    ('onehot', OneHotEncoder(handle_unknown='ignore'))
], memory='cache_pipelines')

transformador_texto = Pipeline(steps=[
    ('tfidf', TfidfVectorizer())
], memory='cache_pipelines')

Ahora, utilizamos ColumnTransformer para combinar estos transformadores y aplicarlos a las columnas correspondientes:

# Creación del ColumnTransformer
preprocesador = ColumnTransformer(transformers=[
    ('num', transformador_numerico, columnas_numericas),
    ('cat', transformador_categorico, columnas_categoricas),
    ('txt', transformador_texto, columnas_texto[0])
])

Finalmente, incorporamos el ColumnTransformer en un Pipeline completo que incluye el modelo de aprendizaje automático:

# Creación del Pipeline completo
pipeline = Pipeline(steps=[
    ('preprocesamiento', preprocesador),
    ('clasificador', LogisticRegression())
], memory='cache_pipelines')

Procedemos a dividir el conjunto de datos en características (X) y etiqueta (y):

# División de los datos
X = df.drop('compra_realizada', axis=1)
y = df['compra_realizada']

Entrenamos el modelo utilizando el Pipeline:

# Entrenamiento del modelo
pipeline.fit(X, y)

Con el modelo entrenado, podemos realizar predicciones sobre nuevos datos:

# Datos nuevos para predicción
nuevos_datos = pd.DataFrame({
    'edad': [29],
    'sexo': ['F'],
    'ingresos': [62000],
    'profesion': ['diseñadora'],
    'comentarios': ['El producto es de alta calidad']
})

# Predicción
prediccion = pipeline.predict(nuevos_datos)
print(f'Predicción: {prediccion}')

Este enfoque permite manejar eficientemente datos heterogéneos, aplicando transformaciones adecuadas a cada tipo de variable y facilitando el proceso de modelado. Además, al integrar el ColumnTransformer dentro de un Pipeline, aseguramos la reproducibilidad y la facilidad para realizar validaciones y ajustes de hiperparámetros.

Uso de ColumnTransformer para preprocesamiento selectivo

Una técnica avanzada en el preprocesamiento de datos es aplicar transformaciones específicas a columnas seleccionadas de un conjunto de datos. El ColumnTransformer de Scikit-Learn nos permite realizar este preprocesamiento selectivo, facilitando el manejo eficiente de variables heterogéneas dentro de un mismo pipeline.

El ColumnTransformer nos da la capacidad de asignar diferentes transformadores a subconjuntos de columnas, asegurando que cada transformación se aplique únicamente a las variables relevantes. Esto es especialmente útil cuando trabajamos con conjuntos de datos que incluyen características numéricas y categóricas, o cuando ciertas variables requieren tratamientos únicos.

Por ejemplo, supongamos que tenemos un conjunto de datos de recursos humanos con las siguientes características:

  • Variables numéricas: edad, salario, años_experiencia (algunas con valores faltantes).
  • Variables categóricas: género, departamento.
  • Variable objetivo: abandono (si el empleado ha dejado la empresa).

Creamos un DataFrame con estos datos:

import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.linear_model import LogisticRegression

# Crear un DataFrame de ejemplo
datos = {
    'edad': [28, 34, None, 45, 23],
    'salario': [50000, 65000, 60000, None, 52000],
    'años_experiencia': [5, 10, 7, 15, None],
    'género': ['F', 'M', 'F', 'M', 'F'],
    'departamento': ['Ventas', 'Ingeniería', 'Recursos Humanos', 'Ingeniería', 'Ventas'],
    'abandono': [0, 1, 0, 1, 0]
}

df = pd.DataFrame(datos)

Nuestro objetivo es construir un modelo de clasificación que prediga la probabilidad de que un empleado abandone la empresa. Para ello, debemos aplicar diferentes preprocesamientos de manera selectiva:

  • Imputar los valores faltantes en las variables numéricas.
  • Escalar las variables numéricas para normalizar sus distribuciones.
  • Codificar las variables categóricas mediante one-hot encoding.

Definimos las columnas según el tipo de transformación que requieren:

# Definir columnas por tipo de dato
columnas_numericas = ['edad', 'salario', 'años_experiencia']
columnas_categoricas = ['género', 'departamento']

Creamos los pipelines de transformación para cada grupo de columnas:

# Pipeline para variables numéricas
transformador_numerico = Pipeline(steps=[
    ('imputacion', SimpleImputer(strategy='mean')),
    ('escalado', StandardScaler())
], memory='cache_pipelines')

# Pipeline para variables categóricas
transformador_categorico = Pipeline(steps=[
    ('onehot', OneHotEncoder(handle_unknown='ignore'))
], memory='cache_pipelines')

Utilizamos el ColumnTransformer para aplicar estos transformadores de forma selectiva:

# Creación del ColumnTransformer
preprocesador = ColumnTransformer(transformers=[
    ('num', transformador_numerico, columnas_numericas),
    ('cat', transformador_categorico, columnas_categoricas)
], remainder='drop')

En este caso, el parámetro remainder='drop' indica que cualquier columna no especificada será descartada. Si deseamos mantenerlas sin aplicar transformación, podemos usar remainder='passthrough'.

Ahora, integrando el ColumnTransformer en un Pipeline completo junto con el modelo de clasificación:

# Creación del Pipeline completo
pipeline = Pipeline(steps=[
    ('preprocesamiento', preprocesador),
    ('clasificador', LogisticRegression())
], memory='cache_pipelines')

Procedemos a separar las características y la variable objetivo:

# Separación de características y etiqueta
X = df.drop('abandono', axis=1)
y = df['abandono']

Entrenamos el modelo utilizando el pipeline:

# Entrenamiento del modelo
pipeline.fit(X, y)

Podemos realizar predicciones con nuevos datos:

# Nuevos datos para predicción
nuevos_empleados = pd.DataFrame({
    'edad': [30],
    'salario': [58000],
    'años_experiencia': [8],
    'género': ['F'],
    'departamento': ['Marketing']
})

# Predicción
prediccion = pipeline.predict(nuevos_empleados)
print(f'Probabilidad de abandono: {prediccion}')

Este enfoque nos permite aplicar preprocesamiento selectivo de manera eficiente, asegurando que cada variable reciba el tratamiento adecuado sin afectar al resto del conjunto de datos.

Además, el ColumnTransformer ofrece flexibilidad en la selección de columnas:

  • Por nombres de columna: proporcionando una lista de nombres.
  • Por índices: utilizando números de posición.
  • Por selectores personalizados: como funciones que devuelven listas de columnas.

Por ejemplo, podemos seleccionar columnas basadas en su tipo de datos:

from sklearn.compose import make_column_selector

# Selección automática de columnas numéricas y categóricas
preprocesador = ColumnTransformer(transformers=[
    ('num', transformador_numerico, make_column_selector(dtype_include=['float64', 'int64'])),
    ('cat', transformador_categorico, make_column_selector(dtype_include=object))
])

Con make_column_selector, el ColumnTransformer identifica y selecciona automáticamente las columnas que coinciden con los tipos de datos especificados, simplificando la construcción del pipeline.

El uso de ColumnTransformer para preprocesamiento selectivo es una práctica recomendada al trabajar con datasets complejos, ya que permite aplicar transformaciones adecuadas a cada tipo de dato y mantener un código limpio y estructurado.

Combinación de transformaciones para diferentes tipos de datos

En el preprocesamiento de conjuntos de datos complejos, es habitual que necesitemos aplicar múltiples transformaciones a distintas columnas, e incluso a las mismas columnas, dependiendo de las características de los datos. La capacidad de combinar varias transformaciones es esencial para preparar adecuadamente los datos para el modelado de aprendizaje automático.

El ColumnTransformer de Scikit-Learn nos permite orquestar estas transformaciones de manera eficiente. Podemos crear pipelines que combinan transformaciones específicas para diferentes tipos de datos y aplicar varias operaciones en secuencia o en paralelo.

Por ejemplo, supongamos que trabajamos con un conjunto de datos que incluye variables numéricas, categóricas y de texto. Queremos realizar las siguientes transformaciones:

Para las variables numéricas:

  • Imputar valores faltantes con la media.
  • Realizar estandarización.
  • Generar nuevas características polinómicas.

Para las variables categóricas:

  • Imputar valores faltantes con la categoría más frecuente.
  • Codificar las categorías utilizando OneHotEncoder.
  • Aplicar selección de características basada en chi-cuadrado.

Para las variables de texto:

  • Convertir el texto a minúsculas.
  • Eliminar stop words.
  • Vectorizar usando TF-IDF.

Comenzamos definiendo las columnas correspondientes:

import pandas as pd
import numpy as np
from sklearn.pipeline import Pipeline, FeatureUnion
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder, FunctionTransformer, PolynomialFeatures
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.feature_selection import SelectKBest, chi2
from sklearn.linear_model import LogisticRegression

# Supongamos que tenemos un DataFrame 'df' con las siguientes columnas
df = pd.DataFrame({
    'edad': [23, 45, None, 35, 29],
    'ingresos': [50000, 80000, 75000, None, 62000],
    'genero': ['F', 'M', 'F', 'M', None],
    'estado_civil': ['Soltero', 'Casado', 'Soltero', 'Divorciado', 'Casado'],
    'comentarios': [
        'Excelente producto, muy satisfecho',
        'No me gustó, mala calidad',
        'Regular, esperaba más',
        'Fantástico, superó mis expectativas',
        'Malo, llegó dañado'
    ],
    'compra': [1, 0, 0, 1, 0]
})

Definimos las listas de columnas por tipo de dato:

columnas_numericas = ['edad', 'ingresos']
columnas_categoricas = ['genero', 'estado_civil']
columnas_texto = ['comentarios']

# Lista personalizada de palabras vacías en español
stop_words_spanish = [
    'de', 'la', 'que', 'el', 'en', 'y', 'a', 'los', 'del', 'se', 'las', 'por', 'un', 'para',
    'con', 'no', 'una', 'su', 'al', 'lo', 'como', 'más', 'pero', 'sus', 'le', 'ya', 'o',
    'este', 'sí', 'porque', 'esta', 'entre', 'cuando', 'muy', 'sin', 'sobre', 'también',
    'me', 'hasta', 'hay', 'donde', 'quien', 'desde', 'todo', 'nos', 'durante', 'todos',
    'uno', 'les', 'ni', 'contra', 'otros', 'ese', 'eso', 'ante', 'ellos', 'e', 'esto',
    'mí', 'antes', 'algunos', 'qué', 'unos', 'yo', 'otro', 'otras', 'otra', 'él', 'tanto',
    'esa', 'estos', 'mucho', 'quienes', 'nada', 'muchos', 'cual', 'poco', 'ella', 'estar',
    'estas', 'algunas', 'algo', 'nosotros', 'mi', 'mis', 'tú', 'te', 'ti', 'tu', 'tus',
    'ellas', 'nosotras', 'vosotros', 'vosotras', 'os', 'mío', 'mía', 'míos', 'mías',
    'tuyo', 'tuya', 'tuyos', 'tuyas', 'suyo', 'suya', 'suyos', 'suyas', 'nuestro',
    'nuestra', 'nuestros', 'nuestras', 'vuestro', 'vuestra', 'vuestros', 'vuestras',
    'esos', 'esas', 'estoy', 'estás', 'está', 'estamos', 'estáis', 'están', 'esté',
    'estés', 'estemos', 'estéis', 'estén', 'estaré', 'estarás', 'estará', 'estaremos',
    'estaréis', 'estarán', 'estaría', 'estarías', 'estaríamos', 'estaríais', 'estarían'
]

Ahora, creamos los pipelines para cada tipo de dato, combinando varias transformaciones:

# Pipeline para variables numéricas
pipeline_numerico = Pipeline(steps=[
    ('imputacion', SimpleImputer(strategy='mean')),
    ('escalado', StandardScaler()),
    ('polinomio', PolynomialFeatures(degree=2, include_bias=False))
], memory='cache_pipelines')

# Pipeline para variables categóricas
pipeline_categorico = Pipeline(steps=[
    ('imputacion', SimpleImputer(strategy='most_frequent', fill_value='Desconocido')),
    ('onehot', OneHotEncoder(handle_unknown='ignore')),
    ('seleccion', SelectKBest(chi2, k=3))
], memory='cache_pipelines')

# Función para procesamiento de texto
def limpiar_texto(texto):
    return texto.str.lower()

# Pipeline para variables de texto
pipeline_texto = Pipeline(steps=[
    ('limpieza', FunctionTransformer(func=limpiar_texto)),
    ('tfidf', TfidfVectorizer(stop_words=stop_words_spanish))
], memory='cache_pipelines')

En el pipeline numérico, hemos incluido una transformación polinómica que generará características adicionales a partir de las variables originales. En el pipeline categórico, después de la codificación One-Hot, aplicamos una selección de características con SelectKBest utilizando la prueba chi-cuadrado para seleccionar las variables más relevantes.

Para las variables de texto, utilizamos un FunctionTransformer para aplicar una función personalizada que convierte el texto a minúsculas. Posteriormente, vectorizamos el texto usando TF-IDF con stop words en español.

Integramos estos pipelines en un ColumnTransformer:

# Creación del ColumnTransformer
preprocesador = ColumnTransformer(transformers=[
    ('num', pipeline_numerico, columnas_numericas),
    ('cat', pipeline_categorico, columnas_categoricas),
    ('txt', pipeline_texto, columnas_texto[0])
], remainder='drop')

Es importante mencionar que para las variables de texto, especificamos el nombre de la columna directamente (columnas_texto[0]) porque TfidfVectorizer espera una serie de texto, no un DataFrame.

Ahora, creamos el pipeline completo incorporando el modelo que deseamos utilizar:

pipeline = Pipeline(steps=[
    ('preprocesamiento', preprocesador),
    ('clasificador', LogisticRegression())
], memory='cache_pipelines')

Separamos las características y la variable objetivo:

# Separación de características y etiqueta
X = df.drop('compra', axis=1)
y = df['compra']

Entrenamos el modelo:

# Entrenamiento del modelo
pipeline.fit(X, y)

Podemos realizar predicciones con nuevos datos:

# Nuevos datos para predicción
nuevos_datos = pd.DataFrame({
    'edad': [40],
    'ingresos': [70000],
    'genero': ['M'],
    'estado_civil': ['Soltero'],
    'comentarios': ['Me encanta este producto, es el mejor']
})

# Predicción
prediccion = pipeline.predict(nuevos_datos)
print(f'Predicción de compra: {prediccion}')

Este ejemplo demuestra cómo combinar múltiples transformaciones para diferentes tipos de datos en un solo pipeline. Al aplicar transformaciones específicas y secuenciales a cada conjunto de columnas, podemos mejorar la calidad de las características y, por ende, el desempeño del modelo de aprendizaje automático.

Además, podemos incluir transformadores personalizados para adaptar el preprocesamiento a necesidades específicas. Por ejemplo, si necesitamos crear una transformación única para ciertas columnas, podemos implementar un transformador personalizado:

from sklearn.base import TransformerMixin, BaseEstimator

# Transformador personalizado para extraer la longitud del texto
class LongitudTextoTransformer(BaseEstimator, TransformerMixin):
    def fit(self, data, y=None):
        return self
    
    def transform(self, data, y=None):
        return data.apply(len).values.reshape(-1, 1)

# Actualizamos el pipeline de texto para incluir el nuevo transformador
pipeline_texto = Pipeline(steps=[
    ('longitud', LongitudTextoTransformer()),
    ('tfidf', TfidfVectorizer(stop_words='spanish'))
])

En este caso, el transformador personalizado LongitudTextoTransformer extrae la longitud de los textos y la utiliza como una característica adicional. Esto puede ser útil en ciertos contextos donde la longitud del texto es relevante para el modelo.

Reintegrando el nuevo pipeline de texto en el ColumnTransformer:

# Actualización del ColumnTransformer
preprocesador = ColumnTransformer(transformers=[
    ('num', pipeline_numerico, columnas_numericas),
    ('cat', pipeline_categorico, columnas_categoricas),
    ('txt', pipeline_texto, columnas_texto[0])
])

Con este enfoque, podemos combinar una variedad de transformaciones personalizadas y predefinidas para crear un pipeline robusto y adaptado a las características de nuestros datos.

Por último, es posible que queramos combinar características generadas por diferentes transformadores. Para ello, podemos utilizar FeatureUnion, que concatena las salidas de varios transformadores. Por ejemplo:

# Creamos un FeatureUnion de transformadores numéricos
combinacion_numerica = FeatureUnion(transformer_list=[
    ('polinomio', PolynomialFeatures(degree=2, include_bias=False)),
    ('raiz_cuadrada', FunctionTransformer(func=np.sqrt, validate=False))
])

# Actualizamos el pipeline numérico para incluir la combinación
pipeline_numerico = Pipeline(steps=[
    ('imputacion', SimpleImputer(strategy='mean')),
    ('combinacion', combinacion_numerica),
    ('escalado', StandardScaler())
])

En este ejemplo, la FeatureUnion combina las características polinómicas con las transformadas mediante raíz cuadrada. Esto permite enriquecer el conjunto de características y potencialmente mejorar el rendimiento del modelo.

Al integrar todas estas técnicas, podemos manejar datasets complejos y maximizar la capacidad predictiva de nuestros modelos utilizando Scikit-Learn y sus herramientas avanzadas para el preprocesamiento de datos.

Integración de ColumnTransformer en Pipelines

La integración de ColumnTransformer dentro de un Pipeline es una práctica esencial para estructurar y simplificar el flujo de preprocesamiento y modelado en Scikit-Learn. Al combinar estas herramientas, podemos crear procesos reproducibles, facilitar la validación de modelos y optimizar la búsqueda de hiperparámetros.

Cuando se trabaja con datos heterogéneos, el uso de ColumnTransformer permite aplicar transformaciones específicas a subconjuntos de columnas. Al integrarlo en un Pipeline, encapsulamos todo el proceso de transformación y modelado en un único objeto, lo que mejora la mantenibilidad y facilita la implementación en entornos de producción.

A continuación, exploraremos cómo integrar ColumnTransformer en Pipelines, destacando buenas prácticas y demostrando su aplicación en un ejemplo completo.

Estructura básica de la integración

Un Pipeline en Scikit-Learn es una secuencia de etapas, donde cada etapa es un transformador o un estimador. Al integrar un ColumnTransformer en un Pipeline, éste actúa como una de las etapas de transformación. La estructura general es la siguiente:

from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer

# Definición de transformadores específicos para distintas columnas
preprocesador = ColumnTransformer(transformers=[
    # Transformadores para diferentes tipos de datos
])

# Creación del Pipeline
pipeline = Pipeline(steps=[
    ('preprocesamiento', preprocesador),
    ('modelo', estimador)
], memory='ruta_cache')

El parámetro memory se establece en 'ruta_cache' para habilitar la caché de los resultados intermedios, lo que puede mejorar la eficiencia en procesos repetitivos.

Ejemplo práctico: Preprocesamiento y modelado

Supongamos que tenemos un conjunto de datos sobre pacientes y queremos predecir si padecen una enfermedad en base a varias características:

  • Variables numéricas: edad, presión arterial, colesterol.
  • Variables categóricas: género, fumador (sí/no).
  • Variable objetivo: enfermedad (0: no, 1: sí).

Creamos un DataFrame de ejemplo:

import pandas as pd

# Datos de ejemplo
datos = {
    'edad': [25, 52, 47, 36, 28],
    'presion_arterial': [120, 140, 130, 115, 125],
    'colesterol': [200, 240, 220, 210, 190],
    'genero': ['M', 'F', 'F', 'M', 'F'],
    'fumador': ['No', 'Sí', 'No', 'Sí', 'No'],
    'enfermedad': [0, 1, 1, 0, 0]
}

df = pd.DataFrame(datos)

Definimos las columnas numéricas y categóricas:

# Columnas por tipo de dato
columnas_numericas = ['edad', 'presion_arterial', 'colesterol']
columnas_categoricas = ['genero', 'fumador']

Creamos transformadores para cada tipo de dato:

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

# Pipeline para variables numéricas
pipeline_numerico = Pipeline(steps=[
    ('imputacion', SimpleImputer(strategy='mean')),
    ('escalado', StandardScaler())
], memory='ruta_cache')

# Pipeline para variables categóricas
pipeline_categorico = Pipeline(steps=[
    ('imputacion', SimpleImputer(strategy='most_frequent')),
    ('onehot', OneHotEncoder(
        categories=[['M', 'F'], ['No', 'Sí']],
        drop='first',
        handle_unknown='ignore'
    ))
])

Ahora, construimos el ColumnTransformer:

from sklearn.compose import ColumnTransformer

preprocesador = ColumnTransformer(transformers=[
    ('num', pipeline_numerico, columnas_numericas),
    ('cat', pipeline_categorico, columnas_categoricas)
])

Integramos el ColumnTransformer en un Pipeline junto con el modelo de aprendizaje:

from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline

pipeline = Pipeline(steps=[
    ('preprocesamiento', preprocesador),
    ('clasificador', LogisticRegression())
], memory='cache_pipelines')

Entrenamiento y evaluación del modelo

Separamos las características y la variable objetivo:

# Características y etiqueta
X = df.drop('enfermedad', axis=1)
y = df['enfermedad']

Entrenamos el modelo:

# Entrenamiento
pipeline.fit(X, y)

Realizamos una predicción:

# Datos nuevos
nuevos_pacientes = pd.DataFrame({
    'edad': [30],
    'presion_arterial': [128],
    'colesterol': [210],
    'genero': ['F'],
    'fumador': ['Sí']
})

# Predicción
prediccion = pipeline.predict(nuevos_pacientes)
print(f'Predicción de enfermedad: {prediccion[0]}')

Al integrar el ColumnTransformer en el Pipeline, podemos utilizar las funcionalidades completas de Scikit-Learn, incluyendo la validación cruzada y la búsqueda de hiperparámetros.

Ajuste de hiperparámetros

La integración permite también ajustar hiperparámetros tanto del preprocesador como del modelo usando GridSearchCV o RandomizedSearchCV.

Por ejemplo, podemos buscar el mejor número de componentes principales al incluir una reducción de dimensionalidad con PCA en las variables numéricas.

Primero, actualizamos el pipeline numérico:

from sklearn.decomposition import PCA

pipeline_numerico = Pipeline(steps=[
    ('imputacion', SimpleImputer(strategy='mean')),
    ('escalado', StandardScaler()),
    ('pca', PCA())
])

Actualizamos el ColumnTransformer:

preprocesador = ColumnTransformer(transformers=[
    ('num', pipeline_numerico, columnas_numericas),
    ('cat', pipeline_categorico, columnas_categoricas)
])

Recreamos el Pipeline:

pipeline = Pipeline(steps=[
    ('preprocesamiento', preprocesador),
    ('clasificador', LogisticRegression())
], memory='cache_pipelines')

Ahora, definimos una rejilla de hiperparámetros para buscar el número óptimo de componentes de PCA y el parámetro de regularización de la regresión logística.

from sklearn.model_selection import StratifiedKFold, GridSearchCV

# Definición de la rejilla de hiperparámetros
param_grid = {
    'preprocesamiento__num__pca__n_components': [1, 2], 
    'clasificador__C': [0.1, 1.0, 10.0]
}

# Validación cruzada estratificada
cv = StratifiedKFold(n_splits=2, shuffle=True, random_state=42)

# Configurar GridSearchCV
busqueda = GridSearchCV(pipeline, param_grid, cv=cv, scoring='accuracy', error_score='raise')

# Ejecución de la búsqueda
busqueda.fit(X, y)

# Mejor score y parámetros
print(f'Mejor exactitud: {busqueda.best_score_:.2f}')
print('Mejores parámetros:', busqueda.best_params_)

En este ejemplo, optimizamos hiperparámetros de etapas internas del Pipeline, utilizando la notación __ para acceder a los parámetros de los estimadores anidados.

Beneficios de la integración

La integración de ColumnTransformer en un Pipeline trae múltiples beneficios:

  • Modularidad: separa claramente las etapas de preprocesamiento y modelado.
  • Reproducibilidad: garantiza que las mismas transformaciones se apliquen de manera consistente.
  • Simplificación: reduce la complejidad del código, facilitando su comprensión y mantenimiento.
  • Interoperabilidad: permite el uso de herramientas de validación y búsqueda de hiperparámetros de Scikit-Learn.
  • Prevención de fugas de datos: al encapsular el preprocesamiento, evitamos que información del conjunto de prueba influya en las transformaciones aprendidas durante el entrenamiento.

Consideraciones avanzadas

Al trabajar con Pipelines complejos, es importante considerar:

  • Control eficiente del flujo de datos: asegurarse de que las transformaciones se apliquen en el orden correcto.
  • Manejo adecuado de parámetros: utilizar nombres únicos y claros para evitar conflictos al ajustar hiperparámetros.
  • Optimización del rendimiento: el uso del parámetro memory en el Pipeline mejora la eficiencia al evitar recalcular transformaciones inmutables.

Por ejemplo, si incorporamos tanto transformaciones de datos como selección de características, podemos extender nuestro Pipeline:

from sklearn.feature_selection import SelectKBest, f_classif

# Pipeline numérico con selección de características
pipeline_numerico = Pipeline(steps=[
    ('imputacion', SimpleImputer(strategy='mean')),
    ('escalado', StandardScaler()),
    ('seleccion', SelectKBest(score_func=f_classif))
])

# Re-crear el ColumnTransformer y el Pipeline
preprocesador = ColumnTransformer(transformers=[
    ('num', pipeline_numerico, columnas_numericas),
    ('cat', pipeline_categorico, columnas_categoricas)
])

pipeline = Pipeline(steps=[
    ('preprocesamiento', preprocesador),
    ('clasificador', LogisticRegression())
], memory='cache_pipelines')

Ahora, podemos buscar el mejor número de características:

# Actualizar la rejilla de hiperparámetros
param_grid = {
    'preprocesamiento__num__seleccion__k': [1, 2, 'all'],
    'clasificador__C': [0.1, 1.0, 10.0]
}

# Validación cruzada estratificada
cv = StratifiedKFold(n_splits=2, shuffle=True, random_state=42)

# Configurar GridSearchCV
busqueda = GridSearchCV(pipeline, param_grid, cv=cv, scoring='accuracy', error_score='raise')
busqueda.fit(X, y)

Uso de pipelines con métodos de ensamblado

La integración también se extiende a modelos más complejos, como los métodos de ensemblado. Por ejemplo, podemos utilizar un Random Forest en nuestro Pipeline:

from sklearn.ensemble import RandomForestClassifier

pipeline = Pipeline(steps=[
    ('preprocesamiento', preprocesador),
    ('clasificador', RandomForestClassifier())
], memory='cache_pipelines')

Y ajustar hiperparámetros específicos del modelo:

# Validación cruzada estratificada
cv = StratifiedKFold(n_splits=2, shuffle=True, random_state=42)

# Configurar GridSearchCV con RandomForestClassifier
param_grid = {
    'clasificador__n_estimators': [100, 200],
    'clasificador__max_depth': [None, 5, 10]
}

busqueda = GridSearchCV(pipeline, param_grid, cv=cv, scoring='accuracy', error_score='raise')
busqueda.fit(X, y)

Persistencia del Pipeline

Una vez que hemos entrenado y optimizado nuestro Pipeline, podemos guardarlo para su uso futuro o despliegue en producción.

import joblib

# Guardar el Pipeline
joblib.dump(busqueda.best_estimator_, 'modelo_pipeline.pkl')

# Cargar el Pipeline
modelo_cargado = joblib.load('modelo_pipeline.pkl')

Al persistir el Pipeline completo, incluimos todas las transformaciones y el modelo, lo que simplifica la predicción sobre nuevos datos sin necesidad de recrear el flujo de preprocesamiento.

Caso práctico con datos mixtos

En esta sección, abordaremos un caso práctico donde aplicaremos los conceptos de ColumnTransformer y Pipelines para procesar un conjunto de datos mixtos y entrenar un modelo de aprendizaje automático. Este ejemplo ilustrará cómo manejar eficientemente variables numéricas, categóricas y de texto en un flujo de trabajo completo.

Descripción del problema

Supongamos que trabajamos para una empresa de telecomunicaciones que desea predecir si un cliente abandonará el servicio (churn). Disponemos de un conjunto de datos que incluye:

Variables numéricas:

  • edad
  • ingresos
  • antigüedad_en_meses

Variables categóricas:

  • genero
  • contrato
  • forma_pago

Variables de texto:

  • comentarios_cliente (comentarios escritos por el cliente sobre el servicio)

Variable objetivo:

  • abandono (1 si el cliente ha abandonado, 0 en caso contrario)

Nuestro objetivo es construir un modelo de clasificación que prediga la probabilidad de abandono de un cliente, utilizando un pipeline que combine diversas transformaciones adecuadas para cada tipo de dato.

Carga y exploración de datos

Primero, importamos las librerías necesarias y creamos un DataFrame de ejemplo:

import pandas as pd
import numpy as np

# Creación del DataFrame de ejemplo
datos = {
    'edad': [34, 42, np.nan, 29, 54, 46, 31, np.nan, 40, 37],
    'ingresos': [50000, 60000, 55000, 45000, np.nan, 52000, 48000, 51000, np.nan, 58000],
    'antigüedad_en_meses': [12, 24, 6, 18, 36, np.nan, 9, 15, 27, 21],
    'genero': ['F', 'M', 'F', 'M', 'F', 'M', np.nan, 'F', 'M', 'F'],
    'contrato': ['Mensual', 'Anual', 'Mensual', 'Anual', 'Mensual', 'Anual', 'Mensual', 'Anual', np.nan, 'Mensual'],
    'forma_pago': ['Tarjeta', np.nan, 'Transferencia', 'Tarjeta', 'Efectivo', 'Transferencia', 'Efectivo', 'Tarjeta', 'Efectivo', 'Transferencia'],
    'comentarios_cliente': [
        'El servicio es excelente',
        'Tengo problemas frecuentes de conexión',
        'Atención al cliente deficiente',
        'Precio razonable y buena cobertura',
        'Estoy considerando cambiar de proveedor',
        'Muy satisfecho con la velocidad de internet',
        'Las facturas no son claras',
        'El soporte técnico es lento',
        'Buen servicio en general',
        'Demasiadas interrupciones en el servicio'
    ],
    'abandono': [0, 1, 1, 0, 1, 0, 1, 1, 0, 1]
}

df = pd.DataFrame(datos)

Observamos que el conjunto de datos contiene valores faltantes (np.nan) en algunas columnas, lo que es común en datos reales. Ahora, procederemos a preparar estos datos para el modelado.

Definición de las columnas y transformaciones

Identificamos las columnas por tipo de dato:

# Columnas numéricas
columnas_numericas = ['edad', 'ingresos', 'antigüedad_en_meses']

# Columnas categóricas
columnas_categoricas = ['genero', 'contrato', 'forma_pago']

# Columna de texto
columna_texto = 'comentarios_cliente'

Para cada tipo de dato, definiremos un pipeline de transformación adecuado.

Pipeline para variables numéricas

Aplicaremos las siguientes transformaciones a las variables numéricas:

  • Imputación de valores faltantes usando la media
  • Escalado utilizando StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler

pipeline_numerico = Pipeline(steps=[
    ('imputacion', SimpleImputer(strategy='mean')),
    ('escalado', StandardScaler())
], memory='cache_pipelines')

Pipeline para variables categóricas

Para las variables categóricas, realizaremos:

  • Imputación de valores faltantes con la categoría más frecuente
  • Codificación mediante OneHotEncoder
from sklearn.preprocessing import OneHotEncoder

pipeline_categorico = Pipeline(steps=[
    ('imputacion', SimpleImputer(strategy='most_frequent')),
    ('onehot', OneHotEncoder(handle_unknown='ignore'))
], memory='cache_pipelines')

Pipeline para variables de texto

En la variable de texto, ejecutaremos:

  • Conversión a minúsculas
  • Eliminación de caracteres especiales
  • Vectorización utilizando TfidfVectorizer con eliminación de stop words

Primero, creamos una función personalizada para limpiar el texto:

import re
from sklearn.base import BaseEstimator, TransformerMixin

class LimpiezaTexto(BaseEstimator, TransformerMixin):
    def fit(self, X, y=None):
        return self
    def transform(self, X, y=None):
        return X.apply(self._limpiar_texto)
    def _limpiar_texto(self, texto):
        texto = texto.lower()
        texto = re.sub(r'[\W_]+', ' ', texto)
        return texto

Luego, definimos el pipeline de texto:

from sklearn.feature_extraction.text import TfidfVectorizer

# Lista de palabras vacías en español
stop_words_spanish = [
    'de', 'la', 'que', 'el', 'en', 'y', 'a', 'los', 'del', 'se', 'las', 'por', 'un', 'para',
    'con', 'no', 'una', 'su', 'al', 'lo', 'como', 'más', 'pero', 'sus', 'le', 'ya', 'o',
    'este', 'sí', 'porque', 'esta', 'entre', 'cuando', 'muy', 'sin', 'sobre', 'también',
    'me', 'hasta', 'hay', 'donde', 'quien', 'desde', 'todo', 'nos', 'durante', 'todos',
    'uno', 'les', 'ni', 'contra', 'otros', 'ese', 'eso', 'ante', 'ellos', 'e', 'esto',
    'mí', 'antes', 'algunos', 'qué', 'unos', 'yo', 'otro', 'otras', 'otra', 'él', 'tanto',
    'esa', 'estos', 'mucho', 'quienes', 'nada', 'muchos', 'cual', 'poco', 'ella', 'estar',
    'estas', 'algunas', 'algo', 'nosotros', 'mi', 'mis', 'tú', 'te', 'ti', 'tu', 'tus',
    'ellas', 'nosotras', 'vosotros', 'vosotras', 'os', 'mío', 'mía', 'míos', 'mías',
    'tuyo', 'tuya', 'tuyos', 'tuyas', 'suyo', 'suya', 'suyos', 'suyas', 'nuestro',
    'nuestra', 'nuestros', 'nuestras', 'vuestro', 'vuestra', 'vuestros', 'vuestras',
    'esos', 'esas', 'estoy', 'estás', 'está', 'estamos', 'estáis', 'están', 'esté',
    'estés', 'estemos', 'estéis', 'estén', 'estaré', 'estarás', 'estará', 'estaremos',
    'estaréis', 'estarán', 'estaría', 'estarías', 'estaríamos', 'estaríais', 'estarían'
]

# Función de limpieza
def limpieza_texto(texto):
    texto = texto.lower()
    texto = re.sub(r'[\W_]+', ' ', texto)
    return texto

# Pipeline de texto
pipeline_texto = Pipeline(steps=[
    ('tfidf', TfidfVectorizer(preprocessor=limpieza_texto, stop_words=stop_words_spanish))
], memory='cache_pipelines')

Creación del ColumnTransformer

Ahora, combinamos los pipelines anteriores utilizando ColumnTransformer:

from sklearn.compose import ColumnTransformer

preprocesador = ColumnTransformer(transformers=[
    ('num', pipeline_numerico, columnas_numericas),
    ('cat', pipeline_categorico, columnas_categoricas),
    ('txt', pipeline_texto, columna_texto)
], remainder='drop')

El parámetro remainder='drop' indica que omitiremos cualquier columna no especificada.

Construcción del Pipeline completo

Integramos el ColumnTransformer en un Pipeline que incluye el modelo de clasificación. En este caso, utilizaremos un Random Forest:

from sklearn.ensemble import RandomForestClassifier

pipeline_completo = Pipeline(steps=[
    ('preprocesamiento', preprocesador),
    ('clasificador', RandomForestClassifier(random_state=42))
], memory='cache_pipelines')

División de los datos en entrenamiento y prueba

Separamos las características y la variable objetivo, y luego dividimos en conjuntos de entrenamiento y prueba:

from sklearn.model_selection import train_test_split

# Separación de características y etiqueta
X = df.drop('abandono', axis=1)
y = df['abandono']

# División en entrenamiento y prueba
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

Entrenamiento del modelo

Entrenamos el modelo utilizando el conjunto de entrenamiento:

# Entrenamiento del modelo
pipeline_completo.fit(X_train, y_train)

Evaluación del modelo

Evaluamos el desempeño del modelo en el conjunto de prueba:

from sklearn.metrics import classification_report, confusion_matrix

# Predicciones en el conjunto de prueba
y_pred = pipeline_completo.predict(X_test)

# Reporte de clasificación
print(classification_report(y_test, y_pred))

# Matriz de confusión
print("Matriz de confusión:")
print(confusion_matrix(y_test, y_pred))

Obtendremos métricas como precisión, exhaustividad y F1-score, que nos ayudarán a entender el rendimiento del modelo.

Predicción con nuevos datos

Podemos utilizar el pipeline para predecir el abandono en nuevos clientes:

# Nuevos datos de clientes
nuevos_clientes = pd.DataFrame({
    'edad': [38],
    'ingresos': [57000],
    'antigüedad_en_meses': [16],
    'genero': ['F'],
    'contrato': ['Anual'],
    'forma_pago': ['Tarjeta'],
    'comentarios_cliente': ['El servicio ha mejorado en los últimos meses']
})

# Predicción
prediccion_abandono = pipeline_completo.predict(nuevos_clientes)
probabilidad_abandono = pipeline_completo.predict_proba(nuevos_clientes)[:,1]

print(f'Predicción de abandono: {prediccion_abandono[0]}')
print(f'Probabilidad de abandono: {probabilidad_abandono[0]:.2f}')

Esto nos permite estimar la probabilidad de que un cliente específico abandone el servicio, lo cual es valioso para tomar acciones preventivas.

Optimización del modelo mediante GridSearchCV

Podemos mejorar el modelo ajustando los hiperparámetros utilizando GridSearchCV. Definimos una rejilla de parámetros:

from sklearn.model_selection import GridSearchCV, StratifiedKFold

# Definición de la rejilla de hiperparámetros
param_grid = {
    'clasificador__n_estimators': [100, 200],
    'clasificador__max_depth': [None, 5, 10],
    'preprocesamiento__num__imputacion__strategy': ['mean', 'median'],
    'preprocesamiento__cat__onehot__drop': [None, 'first']
}

cv = StratifiedKFold(n_splits=3, shuffle=True, random_state=42)

# Configuración de GridSearchCV 
grid_search = GridSearchCV(pipeline_completo, param_grid, cv=cv, scoring='accuracy')

# Ejecución de la búsqueda
grid_search.fit(X_train, y_train)

# Mejor score y parámetros
print(f'Mejor exactitud: {grid_search.best_score_:.2f}')
print('Mejores parámetros:', grid_search.best_params_)

Esto nos ayuda a encontrar la combinación óptima de hiperparámetros para mejorar el rendimiento del modelo.

Visualización de la importancia de las características

Podemos analizar cuáles son las características más relevantes para el modelo:

import matplotlib.pyplot as plt

# Obtener las características después del preprocesamiento
caracteristicas = grid_search.best_estimator_.named_steps['preprocesamiento'].get_feature_names_out()

# Importancias de las características
importancias = grid_search.best_estimator_.named_steps['clasificador'].feature_importances_

# Crear un DataFrame con las importancias
df_importancias = pd.DataFrame({'Característica': caracteristicas, 'Importancia': importancias})

# Ordenar de mayor a menor
df_importancias = df_importancias.sort_values('Importancia', ascending=False)

# Visualizar las principales características
plt.figure(figsize=(10, 6))
plt.barh(df_importancias['Característica'][:10], df_importancias['Importancia'][:10])
plt.xlabel('Importancia')
plt.ylabel('Característica')
plt.title('Principales características según su importancia')
plt.gca().invert_yaxis()
plt.show()

Esta visualización nos proporciona insights sobre qué variables influyen más en la predicción del abandono.

Guardado y carga del Pipeline

Para utilizar el modelo en producción, es necesario guardarlo:

import joblib

# Guardar el pipeline entrenado
joblib.dump(grid_search.best_estimator_, 'modelo_abandono.pkl')

Posteriormente, podemos cargar el modelo y usarlo para predicciones:

# Cargar el pipeline entrenado
modelo_cargado = joblib.load('modelo_abandono.pkl')

# Predicción con el modelo cargado
prediccion_nueva = modelo_cargado.predict(nuevos_clientes)
print(f'Predicción con el modelo cargado: {prediccion_nueva[0]}')

Beneficios de este enfoque

Este caso práctico demuestra cómo utilizar ColumnTransformer y Pipelines para manejar datos mixtos de forma efectiva. Al estructurar el flujo de preprocesamiento y modelado en un pipeline:

  • Simplificamos el código y mejoramos su legibilidad.
  • Evitamos fugas de datos al asegurar que las transformaciones se aplican adecuadamente.
  • Facilitamos la validación y optimización de hiperparámetros.
  • Mejoramos la reproducibilidad y facilitamos el despliegue en producción.

Además, al incorporar el parámetro memory en el pipeline, optimizamos el rendimiento al reutilizar resultados intermedios.

Aprende ScikitLearn GRATIS online

Ejercicios de esta lección Pipelines con ColumnTransformer

Evalúa tus conocimientos de esta lección Pipelines con ColumnTransformer con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Todas las lecciones de ScikitLearn

Accede a todas las lecciones de ScikitLearn y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Aprendizaje Automático

scikit-learn

Introducción Y Entorno

Introducción E Instalación

scikit-learn

Introducción Y Entorno

Introducción Al Preprocesamiento De Datos

scikit-learn

Preprocesamiento De Datos

Identificación Y Tratamiento De Valores Faltantes

scikit-learn

Preprocesamiento De Datos

Escalado De Datos

scikit-learn

Preprocesamiento De Datos

Normalización De Datos

scikit-learn

Preprocesamiento De Datos

Codificación De Variables Categóricas

scikit-learn

Preprocesamiento De Datos

Ingeniería De Características

scikit-learn

Preprocesamiento De Datos

Selección De Características

scikit-learn

Preprocesamiento De Datos

Extracción De Características

scikit-learn

Preprocesamiento De Datos

Particionamiento De Datos

scikit-learn

Preprocesamiento De Datos

Preprocesamiento De Datos Desbalanceados

scikit-learn

Preprocesamiento De Datos

Introducción A La Regresión

scikit-learn

Regresión

Regresión Lineal

scikit-learn

Regresión

Regresión Knn Kneighborsregressor

scikit-learn

Regresión

Regresión Svm Con Svr

scikit-learn

Regresión

Regresión Con Árboles Decisiontreeregressor

scikit-learn

Regresión

Regresión Con Algoritmos De Conjunto

scikit-learn

Regresión

Introducción A La Clasificación

scikit-learn

Clasificación

Clasificación Con Regresión Logística

scikit-learn

Clasificación

Clasificación Knn Kneighborsclassifier

scikit-learn

Clasificación

Clasificación Svm Con Svc

scikit-learn

Clasificación

Clasificación Con Árboles Decisiontreeclassifier

scikit-learn

Clasificación

Clasificación Con Algoritmos De Conjunto

scikit-learn

Clasificación

Reducción De La Dimensionalidad Con Pca

scikit-learn

Aprendizaje No Supervisado

Clustering Con Kmeans

scikit-learn

Aprendizaje No Supervisado

Clustering Jerárquico

scikit-learn

Aprendizaje No Supervisado

Clustering De Densidad Con Dbscan

scikit-learn

Aprendizaje No Supervisado

Preprocesamiento De Textos Para Nlp

scikit-learn

Nlp

Representación De Texto Y Extracción De Características

scikit-learn

Nlp

Clasificación De Texto Con Scikit Learn

scikit-learn

Nlp

Análisis De Sentimiento

scikit-learn

Nlp

Técnicas Avanzadas De Extracción De Características

scikit-learn

Nlp

Introducción Al Análisis De Series Temporales

scikit-learn

Series Temporales

Preprocesamiento De Datos De Series Temporales

scikit-learn

Series Temporales

Ingeniería De Características Para Series Temporales

scikit-learn

Series Temporales

Transformación Y Escalado De Series Temporales

scikit-learn

Series Temporales

Validación Y Evaluación De Modelos En Series Temporales

scikit-learn

Series Temporales

Validación Y Evaluación De Modelos

scikit-learn

Validación De Modelos

Técnicas De Validación Cruzada

scikit-learn

Validación De Modelos

Métricas De Regresión

scikit-learn

Validación De Modelos

Métricas De Clasificación

scikit-learn

Validación De Modelos

Ajuste De Hiperparámetros

scikit-learn

Validación De Modelos

Introducción A Pipelines

scikit-learn

Pipelines Y Despliegue

Creación De Pipelines Básicos

scikit-learn

Pipelines Y Despliegue

Preprocesamiento De Datos Con Pipelines

scikit-learn

Pipelines Y Despliegue

Pipelines Y Validación Cruzada

scikit-learn

Pipelines Y Despliegue

Pipelines Con Columntransformer

scikit-learn

Pipelines Y Despliegue

Exportar E Importar Pipelines

scikit-learn

Pipelines Y Despliegue

Accede GRATIS a ScikitLearn y certifícate

Objetivos de aprendizaje de esta lección

  • Comprender el concepto de datos heterogéneos en el contexto de aprendizaje automático.
  • Aprender a aplicar transformaciones específicas a tipos de variables numéricas, categóricas y de texto.
  • Integrar ColumnTransformer en un Pipeline de Scikit-Learn.
  • Desarrollar un modelo de clasificación con datos preprocesados.
  • Implementar técnicas eficientes para mejorar la precisión del modelo.