Scikit-learn: Impossible d'obtenir les noms des fonctionnalités après ColumnTransformer

Créé le 6 nov. 2018  ·  13Commentaires  ·  Source: scikit-learn/scikit-learn

Lorsque j'utilise ColumnTransformer pour prétraiter différentes colonnes (y compris numérique, catégorie, texte) avec pipeline, je ne peux pas obtenir les noms de fonctionnalités des données transformées finales, ce qui est difficile pour le débogage.

Voici le code :

titanic_url = ('https://raw.githubusercontent.com/amueller/'
               'scipy-2017-sklearn/091d371/notebooks/datasets/titanic3.csv')

data = pd.read_csv(titanic_url)

target = data.pop('survived')

numeric_columns = ['age','sibsp','parch']
category_columns = ['pclass','sex','embarked']
text_columns = ['name','home.dest']

numeric_transformer = Pipeline(steps=[
    ('impute',SimpleImputer(strategy='median')),
    ('scaler',StandardScaler()
    )
])
category_transformer = Pipeline(steps=[
    ('impute',SimpleImputer(strategy='constant',fill_value='missing')),
    ('ohe',OneHotEncoder(handle_unknown='ignore'))
])
text_transformer = Pipeline(steps=[
    ('cntvec',CountVectorizer())
])

preprocesser = ColumnTransformer(transformers=[
    ('numeric',numeric_transformer,numeric_columns),
    ('category',category_transformer,category_columns),
    ('text',text_transformer,text_columns[0])
])

preprocesser.fit_transform(data)
  1. preprocesser.get_feature_names() obtiendra l'erreur :
    AttributeError: Transformer numeric (type Pipeline) does not provide get_feature_names.
  2. Dans ColumnTransformertext_transformer ne peut traiter qu'une chaîne (par exemple 'Sexe'), mais pas une liste de chaînes comme text_columns

Commentaire le plus utile

Ce n'est pas un problème à propos de ColumnTransformer.

  1. est à propos de Pipeline. Notez que eli5 implémente une fonction de noms de fonctionnalités qui peut prendre en charge Pipeline.

Concernant 2. vous avez peut-être raison de dire qu'il n'est pas convivial que nous n'ayons pas de moyen propre d'appliquer un vectoriseur de texte à chaque colonne. Je ne sais pas comment cela peut être réalisé proprement, à moins que nous commencions simplement à prendre en charge plusieurs colonnes d'entrée dans CountVectorizer, etc.

Merci pour votre aimable réponse!
Comme je le sais, lorsque je prétraite une colonne à l'aide de méthodes qui peuvent changer une colonne en plusieurs colonnes telles que OneHotEncoder , CountVectorizer , je peux obtenir les nouveaux noms de colonnes de données à partir du transformateur de la dernière étape du pipeline en La fonction get_feature_names , lors de l'utilisation de méthodes qui ne créent pas de nouvelles colonnes, peut simplement définir le nom brut des colonnes.

def get_column_names_from_ColumnTransformer(column_transformer):    
    col_name = []
    for transformer_in_columns in column_transformer.transformers_[:-1]:#the last transformer is ColumnTransformer's 'remainder'
        raw_col_name = transformer_in_columns[2]
        if isinstance(transformer_in_columns[1],Pipeline): 
            transformer = transformer_in_columns[1].steps[-1][1]
        else:
            transformer = transformer_in_columns[1]
        try:
            names = transformer.get_feature_names()
        except AttributeError: # if no 'get_feature_names' function, use raw column name
            names = raw_col_name
        if isinstance(names,np.ndarray): # eg.
            col_name += names.tolist()
        elif isinstance(names,list):
            col_name += names    
        elif isinstance(names,str):
            col_name.append(names)
    return col_name

En utilisant le code ci-dessus, je peux obtenir les noms de colonnes de mes preprocesser .
Ces codes résolvent-ils cette question ?
Depuis eli5, je ne trouve pas cette fonction, pouvez-vous me donner un lien pour l'exemple explicite ou une api pour eli5 ?

Tous les 13 commentaires

Ce n'est pas un problème à propos de ColumnTransformer.

  1. est à propos de Pipeline. Notez que eli5 implémente une fonction de noms de fonctionnalités qui peut prendre en charge Pipeline.

Concernant 2. vous avez peut-être raison de dire qu'il n'est pas convivial que nous n'ayons pas de moyen propre d'appliquer un vectoriseur de texte à chaque colonne. Je ne sais pas comment cela peut être réalisé proprement, à moins que nous commencions simplement à prendre en charge plusieurs colonnes d'entrée dans CountVectorizer, etc.

Ce n'est pas un problème à propos de ColumnTransformer.

  1. est à propos de Pipeline. Notez que eli5 implémente une fonction de noms de fonctionnalités qui peut prendre en charge Pipeline.

Concernant 2. vous avez peut-être raison de dire qu'il n'est pas convivial que nous n'ayons pas de moyen propre d'appliquer un vectoriseur de texte à chaque colonne. Je ne sais pas comment cela peut être réalisé proprement, à moins que nous commencions simplement à prendre en charge plusieurs colonnes d'entrée dans CountVectorizer, etc.

Merci pour votre aimable réponse!
Comme je le sais, lorsque je prétraite une colonne à l'aide de méthodes qui peuvent changer une colonne en plusieurs colonnes telles que OneHotEncoder , CountVectorizer , je peux obtenir les nouveaux noms de colonnes de données à partir du transformateur de la dernière étape du pipeline en La fonction get_feature_names , lors de l'utilisation de méthodes qui ne créent pas de nouvelles colonnes, peut simplement définir le nom brut des colonnes.

def get_column_names_from_ColumnTransformer(column_transformer):    
    col_name = []
    for transformer_in_columns in column_transformer.transformers_[:-1]:#the last transformer is ColumnTransformer's 'remainder'
        raw_col_name = transformer_in_columns[2]
        if isinstance(transformer_in_columns[1],Pipeline): 
            transformer = transformer_in_columns[1].steps[-1][1]
        else:
            transformer = transformer_in_columns[1]
        try:
            names = transformer.get_feature_names()
        except AttributeError: # if no 'get_feature_names' function, use raw column name
            names = raw_col_name
        if isinstance(names,np.ndarray): # eg.
            col_name += names.tolist()
        elif isinstance(names,list):
            col_name += names    
        elif isinstance(names,str):
            col_name.append(names)
    return col_name

En utilisant le code ci-dessus, je peux obtenir les noms de colonnes de mes preprocesser .
Ces codes résolvent-ils cette question ?
Depuis eli5, je ne trouve pas cette fonction, pouvez-vous me donner un lien pour l'exemple explicite ou une api pour eli5 ?

En ce qui concerne eli5, voir transform_feature_names (utilisé par Explain_weights)

1 est un doublon de #6425, n'est-ce pas ? Je veux écrire un slep là-dessus.
Je pense que la prise en charge de plusieurs colonnes de texte est assez facile avec ColumnTransformer . Ce n'est pas le code le plus joli, mais vous pouvez simplement ajouter un CountVectorizer pour chaque colonne de texte.

Et votre extrait de code ne résout pas vraiment le problème car aucun get_feature_names ne signifie pas que vous pouvez simplement utiliser les noms de colonnes.

1 est un doublon de #6425, n'est-ce pas ? Je veux écrire un slep là-dessus.
Je pense que la prise en charge de plusieurs colonnes de texte est assez facile avec ColumnTransformer . Ce n'est pas le code le plus joli, mais vous pouvez simplement ajouter un CountVectorizer pour chaque colonne de texte.

Et votre extrait de code ne résout pas vraiment le problème car aucun get_feature_names ne signifie pas que vous pouvez simplement utiliser les noms de colonnes.

oui, après qu'un DataFrame pandas alimente un pipeline de prétraitement, il est préférable d'obtenir les noms des fonctionnalités afin de savoir exactement ce qui s'est passé à partir des données générées.

ok, fermeture en double.

Ce n'est pas un problème à propos de ColumnTransformer.

  1. est à propos de Pipeline. Notez que eli5 implémente une fonction de noms de fonctionnalités qui peut prendre en charge Pipeline.

Concernant 2. vous avez peut-être raison de dire qu'il n'est pas convivial que nous n'ayons pas de moyen propre d'appliquer un vectoriseur de texte à chaque colonne. Je ne sais pas comment cela peut être réalisé proprement, à moins que nous commencions simplement à prendre en charge plusieurs colonnes d'entrée dans CountVectorizer, etc.

Merci pour votre aimable réponse!
Comme je le sais, lorsque je prétraite une colonne à l'aide de méthodes qui peuvent changer une colonne en plusieurs colonnes telles que OneHotEncoder , CountVectorizer , je peux obtenir les nouveaux noms de colonnes de données à partir du transformateur de la dernière étape du pipeline en La fonction get_feature_names , lors de l'utilisation de méthodes qui ne créent pas de nouvelles colonnes, peut simplement définir le nom brut des colonnes.

def get_column_names_from_ColumnTransformer(column_transformer):    
    col_name = []
    for transformer_in_columns in column_transformer.transformers_[:-1]:#the last transformer is ColumnTransformer's 'remainder'
        raw_col_name = transformer_in_columns[2]
        if isinstance(transformer_in_columns[1],Pipeline): 
            transformer = transformer_in_columns[1].steps[-1][1]
        else:
            transformer = transformer_in_columns[1]
        try:
            names = transformer.get_feature_names()
        except AttributeError: # if no 'get_feature_names' function, use raw column name
            names = raw_col_name
        if isinstance(names,np.ndarray): # eg.
            col_name += names.tolist()
        elif isinstance(names,list):
            col_name += names    
        elif isinstance(names,str):
            col_name.append(names)
    return col_name

En utilisant le code ci-dessus, je peux obtenir les noms de colonnes de mes preprocesser .
Ces codes résolvent-ils cette question ?
Depuis eli5, je ne trouve pas cette fonction, pouvez-vous me donner un lien pour l'exemple explicite ou une api pour eli5 ?

J'ai fait une petite amélioration pour récupérer le nom comme rawname_value pour les formulaires onehot :

def get_column_names_from_ColumnTransformer(column_transformer):    
    col_name = []
    for transformer_in_columns in column_transformer.transformers_[:-1]:#the last transformer is ColumnTransformer's 'remainder'
        raw_col_name = transformer_in_columns[2]
        raw_col_name_reverse = raw_col_name[::-1]
        if isinstance(transformer_in_columns[1],Pipeline): 
            transformer = transformer_in_columns[1].steps[-1][1]
        else:
            transformer = transformer_in_columns[1]
        try:
            names = transformer.get_feature_names()
            exchange_name = [(_.split("_")) for _ in preprocessor.transformers_[:-1][0][1].steps[-1][1].get_feature_names()]
            last_pre_name = ""
            last_raw_name = ""
            for pre_name,value in exchange_name:
                if pre_name==last_pre_name:
                    col_name.append(last_raw_name+"_"+value)
                if pre_name!=last_pre_name:
                    last_pre_name=pre_name
                    last_raw_name=raw_col_name_reverse.pop()
                    col_name.append(last_raw_name+"_"+value)
        except AttributeError: # if no 'get_feature_names' function, use raw column name
            names = raw_col_name
        if isinstance(names,np.ndarray): # eg.
            col_name += names.tolist()
        elif isinstance(names,list):
            col_name += names    
        elif isinstance(names,str):
            col_name.append(names)
    return col_name

Ce n'est pas un problème à propos de ColumnTransformer.

  1. est à propos de Pipeline. Notez que eli5 implémente une fonction de noms de fonctionnalités qui peut prendre en charge Pipeline.

Concernant 2. vous avez peut-être raison de dire qu'il n'est pas convivial que nous n'ayons pas de moyen propre d'appliquer un vectoriseur de texte à chaque colonne. Je ne sais pas comment cela peut être réalisé proprement, à moins que nous commencions simplement à prendre en charge plusieurs colonnes d'entrée dans CountVectorizer, etc.

Merci pour votre aimable réponse!
Comme je le sais, lorsque je prétraite une colonne à l'aide de méthodes qui peuvent changer une colonne en plusieurs colonnes telles que OneHotEncoder , CountVectorizer , je peux obtenir les nouveaux noms de colonnes de données à partir du transformateur de la dernière étape du pipeline en La fonction get_feature_names , lors de l'utilisation de méthodes qui ne créent pas de nouvelles colonnes, peut simplement définir le nom brut des colonnes.

def get_column_names_from_ColumnTransformer(column_transformer):    
    col_name = []
    for transformer_in_columns in column_transformer.transformers_[:-1]:#the last transformer is ColumnTransformer's 'remainder'
        raw_col_name = transformer_in_columns[2]
        if isinstance(transformer_in_columns[1],Pipeline): 
            transformer = transformer_in_columns[1].steps[-1][1]
        else:
            transformer = transformer_in_columns[1]
        try:
            names = transformer.get_feature_names()
        except AttributeError: # if no 'get_feature_names' function, use raw column name
            names = raw_col_name
        if isinstance(names,np.ndarray): # eg.
            col_name += names.tolist()
        elif isinstance(names,list):
            col_name += names    
        elif isinstance(names,str):
            col_name.append(names)
    return col_name

En utilisant le code ci-dessus, je peux obtenir les noms de colonnes de mes preprocesser .
Ces codes résolvent-ils cette question ?
Depuis eli5, je ne trouve pas cette fonction, pouvez-vous me donner un lien pour l'exemple explicite ou une api pour eli5 ?

Qu'en est-il si vous appliquez simpleimputer avec add_indicator dans un pipeline ? Cette approche ne fonctionnera pas.

Qu'en est-il si vous appliquez simpleimputer avec add_indicator dans un pipeline ? Cette approche ne fonctionnera pas.

Ce serait bien d'avoir une méthode get_feature_names pour cette configuration.

Qu'en est-il si vous appliquez simpleimputer avec add_indicator dans un pipeline ? Cette approche ne fonctionnera pas.

Voici ma contribution à la solution à court terme. Il contraint tous les différents types de tableaux à des listes et gère le cas de SimpleImputer(add_indicate=True). C'est aussi un peu plus verbeux.

def get_column_names_from_ColumnTransformer(column_transformer):    
    col_name = []

    for transformer_in_columns in column_transformer.transformers_[:-1]: #the last transformer is ColumnTransformer's 'remainder'
        print('\n\ntransformer: ', transformer_in_columns[0])

        raw_col_name = list(transformer_in_columns[2])

        if isinstance(transformer_in_columns[1], Pipeline): 
            # if pipeline, get the last transformer
            transformer = transformer_in_columns[1].steps[-1][1]
        else:
            transformer = transformer_in_columns[1]

        try:
          if isinstance(transformer, OneHotEncoder):
            names = list(transformer.get_feature_names(raw_col_name))

          elif isinstance(transformer, SimpleImputer) and transformer.add_indicator:
            missing_indicator_indices = transformer.indicator_.features_
            missing_indicators = [raw_col_name[idx] + '_missing_flag' for idx in missing_indicator_indices]

            names = raw_col_name + missing_indicators

          else:
            names = list(transformer.get_feature_names())

        except AttributeError as error:
          names = raw_col_name

        print(names)    

        col_name.extend(names)

    return col_name

Pour info, j'ai écrit du code et un blog sur la façon d'extraire les noms de fonctionnalités à partir de Pipelines & ColumnTransformers complexes. Le code est une amélioration par rapport à mon post précédent. https://towardsdatascience.com/extracting-plotting-feature-names-importance-from-scikit-learn-pipelines-eb5bfa6a31f4

@kylegilde Super article et merci pour le code. Fonctionne comme un charme. Pour des explications globales, j'ai lutté avec KernelSHAP et alibi pendant quelques heures, mais je n'ai pas réussi à faire fonctionner mon transformateur onehot sans handle_unkown='ignore'

Voici une autre version de l' extrait de

def get_columns_from_transformer(column_transformer, input_colums):    
    col_name = []

    for transformer_in_columns in column_transformer.transformers_[:-1]: #the last transformer is ColumnTransformer's 'remainder'
        raw_col_name = transformer_in_columns[2]
        if isinstance(transformer_in_columns[1],Pipeline): 
            transformer = transformer_in_columns[1].steps[-1][1]
        else:
            transformer = transformer_in_columns[1]
        try:
            names = transformer.get_feature_names(raw_col_name)
        except AttributeError: # if no 'get_feature_names' function, use raw column name
            names = raw_col_name
        if isinstance(names,np.ndarray): # eg.
            col_name += names.tolist()
        elif isinstance(names,list):
            col_name += names    
        elif isinstance(names,str):
            col_name.append(names)

    [_, _, reminder_columns] = column_transformer.transformers_[-1]

    for col_idx in reminder_columns:
        col_name.append(input_colums[col_idx])

    return col_name

Que pensez-vous de l'ajout d'une fonction similaire à la base de code principale ?

Cette page vous a été utile?
0 / 5 - 0 notes