Scikit-learn: Feature-Namen nach ColumnTransformer können nicht abgerufen werden

Erstellt am 6. Nov. 2018  ·  13Kommentare  ·  Quelle: scikit-learn/scikit-learn

Wenn ich ColumnTransformer verwende, um verschiedene Spalten (einschließlich numerisch, Kategorie, Text) mit Pipeline vorzuverarbeiten, kann ich die Feature-Namen der endgültigen transformierten Daten nicht abrufen, was für das Debuggen schwierig ist.

Hier ist der 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() wird einen Fehler erhalten:
    AttributeError: Transformer numeric (type Pipeline) does not provide get_feature_names.
  2. In ColumnTransformertext_transformer kann nur einen String verarbeiten (zB 'Sex'), aber keine Liste von Strings wie text_columns

Hilfreichster Kommentar

Dies ist bei ColumnTransformer kein Problem.

  1. geht es um Pipeline. Beachten Sie, dass eli5 eine Feature-Namensfunktion implementiert, die Pipeline unterstützen kann.

Zu 2. Vielleicht haben Sie Recht, dass es unfreundlich ist, dass wir keinen sauberen Weg haben, einen Textvektorisierer auf jede Spalte anzuwenden. Ich bin mir nicht sicher, wie das sauber erreicht werden kann, es sei denn, wir unterstützen einfach mehrere Eingabespalten in CountVectorizer usw.

Danke für ihre nette Antwort!
Wie ich weiß, kann ich, wenn ich eine Spalte mit Methoden vorverarbeite, die eine Spalte in mehrspaltig ändern können, wie OneHotEncoder , CountVectorizer , die neuen Datenspaltennamen aus dem Transformer des letzten Schrittes der Pipeline abrufen Funktion get_feature_names , wenn Methoden verwendet werden, die keine neuen Spalten erstellen, kann nur der Name der Rohspalte gesetzt werden.

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

Mit dem obigen Code kann ich die Spaltennamen meiner preprocesser abrufen.
Ist dieser Code diese Frage lösen?
Ab eli5 finde ich diese Funktion nicht, kannst du mir einen Link für das explizite Beispiel oder die API für eli5 geben?

Alle 13 Kommentare

Dies ist bei ColumnTransformer kein Problem.

  1. geht es um Pipeline. Beachten Sie, dass eli5 eine Feature-Namensfunktion implementiert, die Pipeline unterstützen kann.

Zu 2. Vielleicht haben Sie Recht, dass es unfreundlich ist, dass wir keinen sauberen Weg haben, einen Textvektorisierer auf jede Spalte anzuwenden. Ich bin mir nicht sicher, wie das sauber erreicht werden kann, es sei denn, wir unterstützen einfach mehrere Eingabespalten in CountVectorizer usw.

Dies ist bei ColumnTransformer kein Problem.

  1. geht es um Pipeline. Beachten Sie, dass eli5 eine Feature-Namensfunktion implementiert, die Pipeline unterstützen kann.

Zu 2. Vielleicht haben Sie Recht, dass es unfreundlich ist, dass wir keinen sauberen Weg haben, einen Textvektorisierer auf jede Spalte anzuwenden. Ich bin mir nicht sicher, wie das sauber erreicht werden kann, es sei denn, wir unterstützen einfach mehrere Eingabespalten in CountVectorizer usw.

Danke für ihre nette Antwort!
Wie ich weiß, kann ich, wenn ich eine Spalte mit Methoden vorverarbeite, die eine Spalte in mehrspaltig ändern können, wie OneHotEncoder , CountVectorizer , die neuen Datenspaltennamen aus dem Transformer des letzten Schrittes der Pipeline abrufen Funktion get_feature_names , wenn Methoden verwendet werden, die keine neuen Spalten erstellen, kann nur der Name der Rohspalte gesetzt werden.

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

Mit dem obigen Code kann ich die Spaltennamen meiner preprocesser abrufen.
Ist dieser Code diese Frage lösen?
Ab eli5 finde ich diese Funktion nicht, kannst du mir einen Link für das explizite Beispiel oder die API für eli5 geben?

In Bezug auf eli5 siehe transform_feature_names (verwendet vonexplain_weights)

1 ist ein Duplikat von #6425, oder? Darüber möchte ich einen Schlaf schreiben.
Ich denke, die Unterstützung mehrerer Textspalten ist mit ColumnTransformer ziemlich einfach. Es ist nicht der schönste Code, aber Sie könnten einfach einen CountVectorizer für jede Textspalte hinzufügen.

Und Ihr Snippet löst das Problem nicht wirklich, denn kein get_feature_names bedeutet nicht, dass Sie nur die Spaltennamen verwenden können.

1 ist ein Duplikat von #6425, oder? Darüber möchte ich einen Schlaf schreiben.
Ich denke, die Unterstützung mehrerer Textspalten ist mit ColumnTransformer ziemlich einfach. Es ist nicht der schönste Code, aber Sie könnten einfach einen CountVectorizer für jede Textspalte hinzufügen.

Und Ihr Snippet löst das Problem nicht wirklich, denn kein get_feature_names bedeutet nicht, dass Sie nur die Spaltennamen verwenden können.

Ja, nachdem ein Pandas-DataFrame in eine Vorverarbeitungspipeline eingespeist wurde, ist es besser, Featurenamen zu erhalten, damit Sie anhand der generierten Daten genau wissen können, was passiert ist.

ok, wird als Duplikat geschlossen.

Dies ist bei ColumnTransformer kein Problem.

  1. geht es um Pipeline. Beachten Sie, dass eli5 eine Feature-Namensfunktion implementiert, die Pipeline unterstützen kann.

Zu 2. Vielleicht haben Sie Recht, dass es unfreundlich ist, dass wir keinen sauberen Weg haben, einen Textvektorisierer auf jede Spalte anzuwenden. Ich bin mir nicht sicher, wie das sauber erreicht werden kann, es sei denn, wir unterstützen einfach mehrere Eingabespalten in CountVectorizer usw.

Danke für ihre nette Antwort!
Wie ich weiß, kann ich, wenn ich eine Spalte mit Methoden vorverarbeite, die eine Spalte in mehrspaltig ändern können, wie OneHotEncoder , CountVectorizer , die neuen Datenspaltennamen aus dem Transformer des letzten Schrittes der Pipeline abrufen Funktion get_feature_names , wenn Methoden verwendet werden, die keine neuen Spalten erstellen, kann nur der Name der Rohspalte gesetzt werden.

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

Mit dem obigen Code kann ich die Spaltennamen meiner preprocesser abrufen.
Ist dieser Code diese Frage lösen?
Ab eli5 finde ich diese Funktion nicht, kannst du mir einen Link für das explizite Beispiel oder die API für eli5 geben?

Ich habe eine kleine Verbesserung vorgenommen, um den Namen wie rawname_value für Onehot-Formulare zurückzubekommen:

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

Dies ist bei ColumnTransformer kein Problem.

  1. geht es um Pipeline. Beachten Sie, dass eli5 eine Feature-Namensfunktion implementiert, die Pipeline unterstützen kann.

Zu 2. Vielleicht haben Sie Recht, dass es unfreundlich ist, dass wir keinen sauberen Weg haben, einen Textvektorisierer auf jede Spalte anzuwenden. Ich bin mir nicht sicher, wie das sauber erreicht werden kann, es sei denn, wir unterstützen einfach mehrere Eingabespalten in CountVectorizer usw.

Danke für ihre nette Antwort!
Wie ich weiß, kann ich, wenn ich eine Spalte mit Methoden vorverarbeite, die eine Spalte in mehrspaltig ändern können, wie OneHotEncoder , CountVectorizer , die neuen Datenspaltennamen aus dem Transformer des letzten Schrittes der Pipeline abrufen Funktion get_feature_names , wenn Methoden verwendet werden, die keine neuen Spalten erstellen, kann nur der Name der Rohspalte gesetzt werden.

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

Mit dem obigen Code kann ich die Spaltennamen meiner preprocesser abrufen.
Ist dieser Code diese Frage lösen?
Ab eli5 finde ich diese Funktion nicht, kannst du mir einen Link für das explizite Beispiel oder die API für eli5 geben?

Was ist, wenn Sie simpleimputer mit add_indicator in einer Pipeline anwenden? Dieser Ansatz wird nicht funktionieren.

Was ist, wenn Sie simpleimputer mit add_indicator in einer Pipeline anwenden? Dieser Ansatz wird nicht funktionieren.

Es wäre schön, eine get_feature_names-Methode für diese Konfiguration zu haben.

Was ist, wenn Sie simpleimputer mit add_indicator in einer Pipeline anwenden? Dieser Ansatz wird nicht funktionieren.

Hier ist mein Beitrag zur kurzfristigen Lösung. Es zwingt alle verschiedenen Array-Typen in Listen und behandelt den Fall von SimpleImputer(add_indicate=True). Es ist auch etwas ausführlicher.

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

Zu Ihrer Information, ich habe Code und einen Blog darüber geschrieben, wie man die Featurenamen aus komplexen Pipelines & ColumnTransformers extrahiert. Der Code ist eine Verbesserung gegenüber meinem vorherigen Beitrag. https://towardsdatascience.com/extracting-plotting-feature-names-importance-from-scikit-learn-pipelines-eb5bfa6a31f4

@kylegilde Toller Artikel und danke für den Code. Klappt wunderbar. Für globale Erklärungen hatte ich einige Stunden mit KernelSHAP und Alibi gerungen , aber meinen Onehot-Transformator nicht zum Laufen gebracht ohne handle_unkown='ignore'

Hier ist eine weitere Version des Snippets von @pjgao , die Spalten aus der Erinnerung enthält:

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

Was halten Sie davon, der Kerncodebasis eine ähnliche Funktion hinzuzufügen?

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen