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ícateManejo 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.
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
Introducción Y Entorno
Introducción E Instalación
Introducción Y Entorno
Introducción Al Preprocesamiento De Datos
Preprocesamiento De Datos
Identificación Y Tratamiento De Valores Faltantes
Preprocesamiento De Datos
Escalado De Datos
Preprocesamiento De Datos
Normalización De Datos
Preprocesamiento De Datos
Codificación De Variables Categóricas
Preprocesamiento De Datos
Ingeniería De Características
Preprocesamiento De Datos
Selección De Características
Preprocesamiento De Datos
Extracción De Características
Preprocesamiento De Datos
Particionamiento De Datos
Preprocesamiento De Datos
Preprocesamiento De Datos Desbalanceados
Preprocesamiento De Datos
Introducción A La Regresión
Regresión
Regresión Lineal
Regresión
Regresión Knn Kneighborsregressor
Regresión
Regresión Svm Con Svr
Regresión
Regresión Con Árboles Decisiontreeregressor
Regresión
Regresión Con Algoritmos De Conjunto
Regresión
Introducción A La Clasificación
Clasificación
Clasificación Con Regresión Logística
Clasificación
Clasificación Knn Kneighborsclassifier
Clasificación
Clasificación Svm Con Svc
Clasificación
Clasificación Con Árboles Decisiontreeclassifier
Clasificación
Clasificación Con Algoritmos De Conjunto
Clasificación
Reducción De La Dimensionalidad Con Pca
Aprendizaje No Supervisado
Clustering Con Kmeans
Aprendizaje No Supervisado
Clustering Jerárquico
Aprendizaje No Supervisado
Clustering De Densidad Con Dbscan
Aprendizaje No Supervisado
Preprocesamiento De Textos Para Nlp
Nlp
Representación De Texto Y Extracción De Características
Nlp
Clasificación De Texto Con Scikit Learn
Nlp
Análisis De Sentimiento
Nlp
Técnicas Avanzadas De Extracción De Características
Nlp
Introducción Al Análisis De Series Temporales
Series Temporales
Preprocesamiento De Datos De Series Temporales
Series Temporales
Ingeniería De Características Para Series Temporales
Series Temporales
Transformación Y Escalado De Series Temporales
Series Temporales
Validación Y Evaluación De Modelos En Series Temporales
Series Temporales
Validación Y Evaluación De Modelos
Validación De Modelos
Técnicas De Validación Cruzada
Validación De Modelos
Métricas De Regresión
Validación De Modelos
Métricas De Clasificación
Validación De Modelos
Ajuste De Hiperparámetros
Validación De Modelos
Introducción A Pipelines
Pipelines Y Despliegue
Creación De Pipelines Básicos
Pipelines Y Despliegue
Preprocesamiento De Datos Con Pipelines
Pipelines Y Despliegue
Pipelines Y Validación Cruzada
Pipelines Y Despliegue
Pipelines Con Columntransformer
Pipelines Y Despliegue
Exportar E Importar Pipelines
Pipelines Y Despliegue
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 unPipeline
de Scikit-Learn. - Desarrollar un modelo de clasificación con datos preprocesados.
- Implementar técnicas eficientes para mejorar la precisión del modelo.