Scikit-learn: No se pueden obtener nombres de funciones después de ColumnTransformer

Creado en 6 nov. 2018  ·  13Comentarios  ·  Fuente: scikit-learn/scikit-learn

Cuando utilizo ColumnTransformer para preprocesar diferentes columnas (incluyen numérico, categoría, texto) con la canalización, no puedo obtener los nombres de las funciones de los datos transformados finales, lo cual es difícil de depurar.

Aquí está el código:

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() obtendrá un error:
    AttributeError: Transformer numeric (type Pipeline) does not provide get_feature_names.
  2. En ColumnTransformertext_transformer solo puede procesar una cadena (por ejemplo, 'Sexo'), pero no una lista de cadenas como text_columns

Comentario más útil

Este no es un problema de ColumnTransformer.

  1. se trata de Pipeline. Tenga en cuenta que eli5 implementa una función de nombres de características que puede admitir Pipeline.

Con respecto a 2. tal vez tenga razón en que no es amigable que no tengamos una forma limpia de aplicar un vectorizador de texto a cada columna. No estoy seguro de cómo se puede lograr de manera limpia, a menos que simplemente comencemos a admitir múltiples columnas de entrada en CountVectorizer, etc.

¡Gracias por su amable respuesta!
Como sé, cuando proceso previamente una columna usando métodos que pueden cambiar una columna a múltiples columnas como OneHotEncoder , CountVectorizer , puedo obtener los nuevos nombres de columna de datos del transformador del último paso de la canalización por función get_feature_names , cuando se utilizan métodos que no crean nuevas columnas, puede simplemente establecer el nombre de las columnas sin formato.

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

Usando el código anterior, puedo obtener los nombres de las columnas de mi preprocesser .
¿Este código resuelve esta pregunta?
A partir de eli5, no encuentro esa función. ¿Me pueden dar un enlace para el ejemplo explícito o una API para eli5?

Todos 13 comentarios

Este no es un problema de ColumnTransformer.

  1. se trata de Pipeline. Tenga en cuenta que eli5 implementa una función de nombres de características que puede admitir Pipeline.

Con respecto a 2. tal vez tenga razón en que no es amigable que no tengamos una forma limpia de aplicar un vectorizador de texto a cada columna. No estoy seguro de cómo se puede lograr de manera limpia, a menos que simplemente comencemos a admitir múltiples columnas de entrada en CountVectorizer, etc.

Este no es un problema de ColumnTransformer.

  1. se trata de Pipeline. Tenga en cuenta que eli5 implementa una función de nombres de características que puede admitir Pipeline.

Con respecto a 2. tal vez tenga razón en que no es amigable que no tengamos una forma limpia de aplicar un vectorizador de texto a cada columna. No estoy seguro de cómo se puede lograr de manera limpia, a menos que simplemente comencemos a admitir múltiples columnas de entrada en CountVectorizer, etc.

¡Gracias por su amable respuesta!
Como sé, cuando proceso previamente una columna usando métodos que pueden cambiar una columna a múltiples columnas como OneHotEncoder , CountVectorizer , puedo obtener los nuevos nombres de columna de datos del transformador del último paso de la canalización por función get_feature_names , cuando se utilizan métodos que no crean nuevas columnas, puede simplemente establecer el nombre de las columnas sin formato.

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

Usando el código anterior, puedo obtener los nombres de las columnas de mi preprocesser .
¿Este código resuelve esta pregunta?
A partir de eli5, no encuentro esa función. ¿Me pueden dar un enlace para el ejemplo explícito o una API para eli5?

Con respecto a eli5, vea transform_feature_names (usado por explica_weights)

1 es un duplicado de # 6425, ¿verdad? Quiero escribir un poco sobre eso.
Creo que admitir varias columnas de texto es bastante fácil con ColumnTransformer . No es el código más bonito, pero puede agregar un CountVectorizer para cada columna de texto.

Y su fragmento no resuelve realmente el problema porque no get_feature_names no significa que pueda usar los nombres de las columnas.

1 es un duplicado de # 6425, ¿verdad? Quiero escribir un poco sobre eso.
Creo que admitir varias columnas de texto es bastante fácil con ColumnTransformer . No es el código más bonito, pero puede agregar un CountVectorizer para cada columna de texto.

Y su fragmento no resuelve realmente el problema porque no get_feature_names no significa que pueda usar los nombres de las columnas.

sí, después de que un DataFrame de pandas se alimenta en una tubería de preproceso, es mejor obtener los nombres de las funciones para que puedan saber exactamente qué sucedió solo a partir de los datos generados.

ok, cerrando como duplicado.

Este no es un problema de ColumnTransformer.

  1. se trata de Pipeline. Tenga en cuenta que eli5 implementa una función de nombres de características que puede admitir Pipeline.

Con respecto a 2. tal vez tenga razón en que no es amigable que no tengamos una forma limpia de aplicar un vectorizador de texto a cada columna. No estoy seguro de cómo se puede lograr de manera limpia, a menos que simplemente comencemos a admitir múltiples columnas de entrada en CountVectorizer, etc.

¡Gracias por su amable respuesta!
Como sé, cuando proceso previamente una columna usando métodos que pueden cambiar una columna a múltiples columnas como OneHotEncoder , CountVectorizer , puedo obtener los nuevos nombres de columna de datos del transformador del último paso de la canalización por función get_feature_names , cuando se utilizan métodos que no crean nuevas columnas, puede simplemente establecer el nombre de las columnas sin formato.

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

Usando el código anterior, puedo obtener los nombres de las columnas de mi preprocesser .
¿Este código resuelve esta pregunta?
A partir de eli5, no encuentro esa función. ¿Me pueden dar un enlace para el ejemplo explícito o una API para eli5?

Hice una pequeña mejora para recuperar el nombre como rawname_value para formularios 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

Este no es un problema de ColumnTransformer.

  1. se trata de Pipeline. Tenga en cuenta que eli5 implementa una función de nombres de características que puede admitir Pipeline.

Con respecto a 2. tal vez tenga razón en que no es amigable que no tengamos una forma limpia de aplicar un vectorizador de texto a cada columna. No estoy seguro de cómo se puede lograr de manera limpia, a menos que simplemente comencemos a admitir múltiples columnas de entrada en CountVectorizer, etc.

¡Gracias por su amable respuesta!
Como sé, cuando proceso previamente una columna usando métodos que pueden cambiar una columna a múltiples columnas como OneHotEncoder , CountVectorizer , puedo obtener los nuevos nombres de columna de datos del transformador del último paso de la canalización por función get_feature_names , cuando se utilizan métodos que no crean nuevas columnas, puede simplemente establecer el nombre de las columnas sin formato.

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

Usando el código anterior, puedo obtener los nombres de las columnas de mi preprocesser .
¿Este código resuelve esta pregunta?
A partir de eli5, no encuentro esa función. ¿Me pueden dar un enlace para el ejemplo explícito o una API para eli5?

¿Qué pasa si aplica simpleimputer con add_indicator en una canalización? Este enfoque no funcionará.

¿Qué pasa si aplica simpleimputer con add_indicator en una canalización? Este enfoque no funcionará.

Sería bueno tener un método get_feature_names para esta configuración.

¿Qué pasa si aplica simpleimputer con add_indicator en una canalización? Este enfoque no funcionará.

Aquí está mi contribución a la solución a corto plazo. Coacciona todos los diferentes tipos de arreglos a listas y maneja el caso de SimpleImputer (add_indicate = True). También es un poco más detallado.

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

Para su información, escribí un código y un blog sobre cómo extraer los nombres de las funciones de Pipelines & ColumnTransformers complejos. El código es una mejora con respecto a mi publicación anterior. https://towardsdatascience.com/extracting-plotting-feature-names-importance-from-scikit-learn-pipelines-eb5bfa6a31f4

@kylegilde Gran artículo y gracias por el código. Funciona de maravilla. Para obtener explicaciones globales, había estado luchando con KernelSHAP y coartada durante algunas horas, pero no conseguí que mi transformador onehot funcionara sin handle_unkown='ignore'

Aquí hay otra versión del fragmento de @pjgao que incluye columnas del recordatorio:

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

¿Qué piensas acerca de agregar una función similar al código base central?

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

jorisvandenbossche picture jorisvandenbossche  ·  63Comentarios

yedtoss picture yedtoss  ·  68Comentarios

amueller picture amueller  ·  64Comentarios

thomasjpfan picture thomasjpfan  ·  60Comentarios

naught101 picture naught101  ·  59Comentarios