Transformador de columna con tipos mixtos

Este ejemplo ilustra cómo aplicar diferentes pipelines de preprocesamiento y extracción de características a diferentes subconjuntos de características, utilizando ColumnTransformer. Esto es particularmente útil para el caso de conjuntos de datos que contienen tipos de datos heterogéneos, ya que podemos querer escalar las características numéricas y codificar de una sola vez las categóricas.

En este ejemplo, los datos numéricos se ajustan a la escala estándar después de la imputación de la media, mientras que los datos categóricos se codifican de una sola vez después de imputar los valores faltantes con una nueva categoría ('missing').

Además, mostramos dos formas diferentes de enviar las columnas al preprocesador particular: por nombres de columna y por tipos de datos de columna.

Por último, el pipeline de preprocesamiento se integra en un pipeline de predicción completo utilizando Pipeline, junto con un modelo de clasificación simple.

# Author: Pedro Morales <part.morales@gmail.com>
#
# License: BSD 3 clause

import numpy as np

from sklearn.compose import ColumnTransformer
from sklearn.datasets import fetch_openml
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split, GridSearchCV

np.random.seed(0)

# Load data from https://www.openml.org/d/40945
X, y = fetch_openml("titanic", version=1, as_frame=True, return_X_y=True)

# Alternatively X and y can be obtained directly from the frame attribute:
# X = titanic.frame.drop('survived', axis=1)
# y = titanic.frame['survived']

Usa ColumnTransformer seleccionando columna por nombres

Entrenaremos a nuestro clasificador con las siguientes características:

Características numéricas:

  • age: float;

  • fare: float.

Características categóricas:

  • embarked: categorías codificadas como cadenas {'C', 'S', 'Q'};

  • sex: categorías codificadas como cadenas {'female', 'male'};

  • pclass: enteros ordinales {1, 2, 3}.

Creamos pipelines de preprocesamiento para datos numéricos y categóricos. Tenga en cuenta que pclass puede ser tratada como una característica categórica o numérica.

numeric_features = ['age', 'fare']
numeric_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler())])

categorical_features = ['embarked', 'sex', 'pclass']
categorical_transformer = OneHotEncoder(handle_unknown='ignore')

preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, numeric_features),
        ('cat', categorical_transformer, categorical_features)])

# Append classifier to preprocessing pipeline.
# Now we have a full prediction pipeline.
clf = Pipeline(steps=[('preprocessor', preprocessor),
                      ('classifier', LogisticRegression())])

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,
                                                    random_state=0)

clf.fit(X_train, y_train)
print("model score: %.3f" % clf.score(X_test, y_test))

Out:

model score: 0.790

Representación HTML de Pipeline

Cuando el Pipeline se imprime en un Cuaderno de Jupyter se muestra una representación HTML del estimador como la siguiente:

from sklearn import set_config

set_config(display='diagram')
clf
Pipeline(steps=[('preprocessor',
                 ColumnTransformer(transformers=[('num',
                                                  Pipeline(steps=[('imputer',
                                                                   SimpleImputer(strategy='median')),
                                                                  ('scaler',
                                                                   StandardScaler())]),
                                                  ['age', 'fare']),
                                                 ('cat',
                                                  OneHotEncoder(handle_unknown='ignore'),
                                                  ['embarked', 'sex',
                                                   'pclass'])])),
                ('classifier', LogisticRegression())])
ColumnTransformer(transformers=[('num',
                                 Pipeline(steps=[('imputer',
                                                  SimpleImputer(strategy='median')),
                                                 ('scaler', StandardScaler())]),
                                 ['age', 'fare']),
                                ('cat', OneHotEncoder(handle_unknown='ignore'),
                                 ['embarked', 'sex', 'pclass'])])
['age', 'fare']
SimpleImputer(strategy='median')
StandardScaler()
['embarked', 'sex', 'pclass']
OneHotEncoder(handle_unknown='ignore')
LogisticRegression()


Utiliza ColumnTransformer seleccionando la columna por tipos de datos

Cuando se trata de un conjunto de datos limpio, el preprocesamiento puede ser automático utilizando los tipos de datos de la columna para decidir si se trata una columna como característica numérica o categórica. sklearn.compose.make_column_selector ofrece esta posibilidad. Primero, vamos a seleccionar sólo un subconjunto de columnas para simplificar nuestro ejemplo.

subset_feature = ['embarked', 'sex', 'pclass', 'age', 'fare']
X_train, X_test = X_train[subset_feature], X_test[subset_feature]

A continuación, introspeccionamos la información relativa a cada tipo de dato de la columna.

X_train.info()

Out:

<class 'pandas.core.frame.DataFrame'>
Int64Index: 1047 entries, 1118 to 684
Data columns (total 5 columns):
 #   Column    Non-Null Count  Dtype
---  ------    --------------  -----
 0   embarked  1045 non-null   category
 1   sex       1047 non-null   category
 2   pclass    1047 non-null   float64
 3   age       841 non-null    float64
 4   fare      1046 non-null   float64
dtypes: category(2), float64(3)
memory usage: 35.0 KB

Podemos observar que las columnas embarked y sex fueron etiquetadas como columnas de categoría al cargar los datos con fetch_openml. Por tanto, podemos utilizar esta información para enviar las columnas categóricas al categorical_transformer y el resto de columnas al numerical_transformer.

Nota

En la práctica, deberás manejar tú mismo el tipo de datos de la columna. Si quieres que algunas columnas sean consideradas como categoría, tendrás que convertirlas en columnas categóricas. Si utilizas pandas, puede consultar su documentación sobre Datos categóricos.

from sklearn.compose import make_column_selector as selector

preprocessor = ColumnTransformer(transformers=[
    ('num', numeric_transformer, selector(dtype_exclude="category")),
    ('cat', categorical_transformer, selector(dtype_include="category"))
])
clf = Pipeline(steps=[('preprocessor', preprocessor),
                      ('classifier', LogisticRegression())])


clf.fit(X_train, y_train)
print("model score: %.3f" % clf.score(X_test, y_test))

Out:

model score: 0.794

La puntuación resultante no es exactamente la misma que la del pipeline anterior porque el selector basado en dtype trata las columnas pclass como características numéricas en lugar de categóricas como anteriormente:

selector(dtype_exclude="category")(X_train)

Out:

['pclass', 'age', 'fare']
selector(dtype_include="category")(X_train)

Out:

['embarked', 'sex']