5
votes

AttributeError lors de l'utilisation de ColumnTransformer dans un pipeline

C'est mon premier projet d'apprentissage automatique et la première fois que j'utilise ColumnTransformer. Mon objectif est d'effectuer deux étapes de prétraitement des données, et d'utiliser ColumnTransformer pour chacune d'elles.

Dans la première étape, je souhaite remplacer les valeurs manquantes dans mon dataframe par la chaîne 'missing_value' pour certaines fonctionnalités, et valeur fréquente pour les fonctionnalités restantes. Par conséquent, je combine ces deux opérations en utilisant ColumnTransformer et en lui passant les colonnes correspondantes de mon dataframe.

Dans la deuxième étape, je souhaite utiliser les données juste prétraitées et appliquer OrdinalEncoder ou OneHotEncoder en fonction des fonctionnalités. Pour cela, j'utilise à nouveau ColumnTransformer.

Je combine ensuite les deux étapes en un seul pipeline.

J'utilise l'ensemble de données Kaggle Houses Price, j'ai la version 0.20 de scikit-learn et ceci est une version simplifiée de mon code:

enc_one = ColumnTransformer([
        ('onehot', OneHotEncoder(), cat_columns_onehot),
        ('pass_ord', 'passthrough', cat_columns_ord)
])

new_cat_pipeline = Pipeline([
        ('imp_cat', imputer_cat_pipeline),
        ('onehot_encoder', enc_one),
])

Malheureusement, lorsque je l'applique à Housing_cat, le sous-ensemble de mon dataframe ne comprend que des fonctionnalités catégorielles,

new_cat_pipeline = Pipeline([
        ('imp_cat', imputer_cat_pipeline),
        ('onehot', OneHotEncoder()),
])

J'obtiens l'erreur:

AttributeError: l'objet 'numpy.ndarray' n'a pas d'attribut 'colonnes'

Lors de la gestion de l'exception ci-dessus, une autre exception s'est produite:

...

ValueError: la spécification des colonnes à l'aide de chaînes n'est prise en charge que pour les pandas DataFrames

J'ai essayé ce pipeline simplifié et il fonctionne correctement:

cat_pipeline.fit_transform(housing_cat)

Cependant, si j'essaye:

cat_columns_fill_miss = ['PoolQC', 'Alley']
cat_columns_fill_freq = ['Street', 'MSZoning', 'LandContour']
cat_columns_ord = ['Street', 'Alley', 'PoolQC']
ord_mapping = [['Pave', 'Grvl'],                          # Street
               ['missing_value', 'Pave', 'Grvl'],         # Alley
               ['missing_value', 'Fa', 'TA', 'Gd', 'Ex']  # PoolQC
]
cat_columns_onehot = ['MSZoning', 'LandContour']


imputer_cat_pipeline = ColumnTransformer([
        ('imp_miss', SimpleImputer(strategy='constant'), cat_columns_fill_miss),  # fill_value='missing_value' by default
        ('imp_freq', SimpleImputer(strategy='most_frequent'), cat_columns_fill_freq),
])

encoder_cat_pipeline = ColumnTransformer([
        ('ordinal', OrdinalEncoder(categories=ord_mapping), cat_columns_ord),
        ('pass_ord', OneHotEncoder(), cat_columns_onehot),
])

cat_pipeline = Pipeline([
        ('imp_cat', imputer_cat_pipeline),
        ('cat_encoder', encoder_cat_pipeline),
])


0 commentaires

3 Réponses :


4
votes

ColumnTransformer renvoie numpy.array , il ne peut donc pas avoir d'attribut de colonne (comme indiqué par votre erreur).

Si je peux suggérer une solution différente, utilisez pandas pour vos deux tâches, ce sera plus facile.

Étape 1 - remplacement des valeurs manquantes

Pour remplacer la valeur manquante dans un sous-ensemble de colonnes par missing_value utilisez ceci:

pd.DataFrame(data=your_array, index=np.arange(len(your_array)), columns=["A", "B"])

Pour le reste (imputation avec la moyenne de chaque colonne), cela fonctionnera parfaitement:

encoded = pd.get_dummies(dataframe[['MSZoning', 'LandContour']], drop_first=True)
pd.dropna(['MSZoning', 'LandContour'], axis=columns, inplace=True)
dataframe = dataframe.join(encoded)

Étape 2 - un encodage à chaud et des variables catégorielles

pandas fournit get_dummies , qui renvoie pandas Dataframe, contrairement à ColumnTransfomer , le code pour cela serait:

dataframe[["Street", "MSZoning", "LandContour"]].fillna(
    dataframe[["Street", "MSZoning", "LandContour"]].mean(), inplace=True
)

Pour les variables ordinales et leur codage, je vous suggère de regarder à cette réponse SO (malheureusement, un mappage manuel serait nécessaire dans ce cas).

Si vous souhaitez quand même utiliser le transformateur

Récupérez np.array à partir du dataframe en utilisant l'attribut values ​​, passez-le dans le pipeline et recréez les colonnes et des indices du tableau comme ceci:

dataframe[["PoolQC", "Alley"]].fillna("missing_value", inplace=True)

Il y a cependant une mise en garde à cette approche; vous ne connaîtrez pas les noms des colonnes personnalisées à encodage à chaud (le pipeline ne le fera pas pour vous).

De plus, vous pouvez obtenir les noms des colonnes à partir des objets de transformation de sklearn (par exemple en utilisant categories_ attribute), mais je pense que cela briserait le pipeline (quelqu'un me corrige si je me trompe).


3 commentaires

Salut, merci pour votre réponse rapide. Ce que vous suggérez est en effet ce que j'ai fait avant: utiliser fillna pour les valeurs manquantes et get_dummies / replace pour les variables nominales / ordinales. Cependant, je pensais qu'il aurait été plus agréable de créer un pipeline, de sorte que tout le prétraitement des données ne soit pas manuel et puisse être facilement effectué à nouveau sur un ensemble de données différent avec les mêmes variables. En revanche, il est vrai que ne pas avoir accès aux colonnes one-hot-encodées est ennuyeux ...


Je suis d'accord que ce serait mieux, même si je ne vois pas comment / je ne sais pas si cela pourrait être fait de manière aussi succincte.


Ok, donc si je comprends bien, le problème n'est pas dans ColumnTransformer en soi, mais dans le fait que le transformateur SimpleImputer donne en sortie np.array . Ainsi, à l'étape de codage suivante, je ne peux pas indiquer les colonnes avec leur nom. Est-ce exact? Je dois utiliser les indéces correspondantes, n'est-ce pas?



4
votes

Option 2

utilisez la fonction make_pipeline

(J'ai eu la même erreur, j'ai trouvé cette réponse, que j'ai trouvé ceci: Présentation du ColumnTransformer )

imputer_cat_pipeline = make_column_transformer(
    (make_pipeline(SimpleImputer(strategy='constant'), cat_columns_fill_miss),
)
imputer_cat_pipeline = make_column_transformer(
    (make_pipeline(SimpleImputer(strategy='constant'), cat_columns_fill_miss),
    (make_pipeline(SimpleImputer(strategy='most_frequent'), cat_columns_fill_freq),
)

encoder_cat_pipeline = make_column_transformer(
    (OrdinalEncoder(categories=ord_mapping), cat_columns_ord),
    (OneHotEncoder(), cat_columns_onehot),
)

cat_pipeline = Pipeline([
    ('imp_cat', imputer_cat_pipeline),
    ('cat_encoder', encoder_cat_pipeline),
])
cat_columns_fill_miss = ['PoolQC', 'Alley']
cat_columns_fill_freq = ['Street', 'MSZoning', 'LandContour']
cat_columns_ord = ['Street', 'Alley', 'PoolQC']
ord_mapping = [['Pave', 'Grvl'],                          # Street
               ['missing_value', 'Pave', 'Grvl'],         # Alley
               ['missing_value', 'Fa', 'TA', 'Gd', 'Ex']  # PoolQC
               ]
cat_columns_onehot = ['MSZoning', 'LandContour']

Dans mes propres pipelines, je n'ai pas de prétraitement qui se chevauchent dans l'espace de colonne. Je ne suis donc pas sûr du fonctionnement de la transformation et du "pipeline externe".

Cependant, la partie importante est d'utiliser make_pipeline autour de SimpleImputer pour l'utiliser dans un pipeline correctement:

from sklearn.compose import make_column_transformer
from sklearn.pipeline import make_pipeline


0 commentaires

3
votes

Juste pour ajouter aux autres réponses ici. Je ne suis pas un expert en Python ou en science des données, mais vous pouvez passer un autre pipeline à ColumnTransformer afin de faire ce dont vous avez besoin en ajoutant plus d'un transformateur à une colonne. Je suis venu ici à la recherche d'une réponse à la même question et j'ai trouvé cette solution.

Tout faire via des pipelines vous permet de contrôler beaucoup plus facilement les données de test / train pour éviter les fuites, et ouvre également plus de possibilités de Grid Search. Personnellement, je ne suis pas fan de l'approche pandas dans une autre réponse pour ces raisons, mais cela fonctionnerait encore.

encoder_cat_pipeline = Pipeline([
    ('ordinal', OrdinalEncoder(categories=ord_mapping)),
    ('pass_ord', OneHotEncoder()),
])

imputer_cat_pipeline = ColumnTransformer([
    ('imp_miss', SimpleImputer(strategy='constant'), cat_columns_fill_miss),
    ('new_pipeline', encoder_cat_pipeline, cat_columns_fill_freq)
])

cat_pipeline = Pipeline([
    ('imp_cat', imputer_cat_pipeline),
])


0 commentaires