Scikit-learn: Não é possível obter nomes de recursos após ColumnTransformer

Criado em 6 nov. 2018  ·  13Comentários  ·  Fonte: scikit-learn/scikit-learn

Quando eu uso o ColumnTransformer para pré-processar colunas diferentes (incluindo numérico, categoria, texto) com pipeline, não consigo obter os nomes dos recursos dos dados transformados finais, o que é difícil de depurar.

Aqui está o 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() obterá o erro:
    AttributeError: Transformer numeric (type Pipeline) does not provide get_feature_names.
  2. Em ColumnTransformertext_transformer só pode processar uma string (por exemplo, 'Sexo'), mas não uma lista de strings como text_columns

Comentários muito úteis

Este não é um problema sobre o ColumnTransformer.

  1. é sobre Pipeline. Observe que eli5 implementa uma função de nomes de recursos que pode oferecer suporte a Pipeline.

Re 2. talvez você esteja certo ao dizer que não é nada amigável não termos uma maneira limpa de aplicar um vetorizador de texto a cada coluna. Não tenho certeza de como isso pode ser alcançado de forma limpa, a menos que simplesmente comecemos a suportar várias colunas de entrada no CountVectorizer etc.

Obrigado pela sua amável resposta!
Como eu sei, quando pré-processo uma coluna usando métodos que podem alterar uma coluna para várias colunas, como OneHotEncoder , CountVectorizer , posso obter os novos nomes das colunas de dados do transformador da última etapa do pipeline função get_feature_names , ao usar métodos que não criam novas colunas, pode apenas definir o nome das colunas brutas.

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 o código acima, posso obter os nomes das minhas colunas de preprocesser .
Esses códigos resolvem essa questão?
A partir de eli5, não encontro essa função. Você pode me dar um link para o exemplo explícito ou api para eli5?

Todos 13 comentários

Este não é um problema sobre o ColumnTransformer.

  1. é sobre Pipeline. Observe que eli5 implementa uma função de nomes de recursos que pode oferecer suporte a Pipeline.

Re 2. talvez você esteja certo ao dizer que não é nada amigável não termos uma maneira limpa de aplicar um vetorizador de texto a cada coluna. Não tenho certeza de como isso pode ser alcançado de forma limpa, a menos que simplesmente comecemos a suportar várias colunas de entrada no CountVectorizer etc.

Este não é um problema sobre o ColumnTransformer.

  1. é sobre Pipeline. Observe que eli5 implementa uma função de nomes de recursos que pode oferecer suporte a Pipeline.

Re 2. talvez você esteja certo ao dizer que não é nada amigável não termos uma maneira limpa de aplicar um vetorizador de texto a cada coluna. Não tenho certeza de como isso pode ser alcançado de forma limpa, a menos que simplesmente comecemos a suportar várias colunas de entrada no CountVectorizer etc.

Obrigado pela sua amável resposta!
Como eu sei, quando pré-processo uma coluna usando métodos que podem alterar uma coluna para várias colunas, como OneHotEncoder , CountVectorizer , posso obter os novos nomes das colunas de dados do transformador da última etapa do pipeline função get_feature_names , ao usar métodos que não criam novas colunas, pode apenas definir o nome das colunas brutas.

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 o código acima, posso obter os nomes das minhas colunas de preprocesser .
Esses códigos resolvem essa questão?
A partir de eli5, não encontro essa função. Você pode me dar um link para o exemplo explícito ou api para eli5?

Com relação a eli5, consulte transform_feature_names (usado por explain_weights)

1 é uma duplicata de # 6425, certo? Eu quero escrever um slep sobre isso.
Acho que suportar várias colunas de texto é muito fácil com ColumnTransformer . Não é o código mais bonito, mas você pode simplesmente adicionar um CountVectorizer para cada coluna de texto.

E seu snippet não resolve realmente o problema porque nenhum get_feature_names não significa que você pode apenas usar os nomes das colunas.

1 é uma duplicata de # 6425, certo? Eu quero escrever um slep sobre isso.
Acho que suportar várias colunas de texto é muito fácil com ColumnTransformer . Não é o código mais bonito, mas você pode simplesmente adicionar um CountVectorizer para cada coluna de texto.

E seu snippet não resolve realmente o problema porque nenhum get_feature_names não significa que você pode apenas usar os nomes das colunas.

sim, depois que um DataFrame do pandas for alimentado em um pipeline de pré-processo, é melhor obter nomes de recursos para que possamos saber exatamente o que aconteceu apenas a partir dos dados gerados.

ok, fechando como duplicata.

Este não é um problema sobre o ColumnTransformer.

  1. é sobre Pipeline. Observe que eli5 implementa uma função de nomes de recursos que pode oferecer suporte a Pipeline.

Re 2. talvez você esteja certo ao dizer que não é nada amigável não termos uma maneira limpa de aplicar um vetorizador de texto a cada coluna. Não tenho certeza de como isso pode ser alcançado de forma limpa, a menos que simplesmente comecemos a suportar várias colunas de entrada no CountVectorizer etc.

Obrigado pela sua amável resposta!
Como eu sei, quando pré-processo uma coluna usando métodos que podem alterar uma coluna para várias colunas, como OneHotEncoder , CountVectorizer , posso obter os novos nomes das colunas de dados do transformador da última etapa do pipeline função get_feature_names , ao usar métodos que não criam novas colunas, pode apenas definir o nome das colunas brutas.

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 o código acima, posso obter os nomes das minhas colunas de preprocesser .
Esses códigos resolvem essa questão?
A partir de eli5, não encontro essa função. Você pode me dar um link para o exemplo explícito ou api para eli5?

Fiz um pequeno aprimoramento para recuperar o nome como rawname_value para formulários 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 não é um problema sobre o ColumnTransformer.

  1. é sobre Pipeline. Observe que eli5 implementa uma função de nomes de recursos que pode oferecer suporte a Pipeline.

Re 2. talvez você esteja certo ao dizer que não é nada amigável não termos uma maneira limpa de aplicar um vetorizador de texto a cada coluna. Não tenho certeza de como isso pode ser alcançado de forma limpa, a menos que simplesmente comecemos a suportar várias colunas de entrada no CountVectorizer etc.

Obrigado pela sua amável resposta!
Como eu sei, quando pré-processo uma coluna usando métodos que podem alterar uma coluna para várias colunas, como OneHotEncoder , CountVectorizer , posso obter os novos nomes das colunas de dados do transformador da última etapa do pipeline função get_feature_names , ao usar métodos que não criam novas colunas, pode apenas definir o nome das colunas brutas.

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 o código acima, posso obter os nomes das minhas colunas de preprocesser .
Esses códigos resolvem essa questão?
A partir de eli5, não encontro essa função. Você pode me dar um link para o exemplo explícito ou api para eli5?

E se você aplicar simpleimputer com add_indicator em um pipeline? Essa abordagem não funcionará.

E se você aplicar simpleimputer com add_indicator em um pipeline? Essa abordagem não funcionará.

Seria bom ter um método get_feature_names para esta configuração.

E se você aplicar simpleimputer com add_indicator em um pipeline? Essa abordagem não funcionará.

Aqui está minha contribuição para a solução de curto prazo. Ele força todos os diferentes tipos de array a listas e trata o caso de SimpleImputer (add_indicate = True). Também é um pouco mais prolixo.

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 sua informação, escrevi um código e um blog sobre como extrair os nomes dos recursos de Pipelines e Transformadores de Coluna complexos. O código é uma melhoria em relação ao meu post anterior. https://towardsdatascience.com/extracting-plotting-feature-names-importance-from-scikit-learn-pipelines-eb5bfa6a31f4

@kylegilde Excelente artigo e obrigado pelo código. Funciona como um encanto. Para explicações globais, eu estava lutando com KernelSHAP e álibi por algumas horas, mas não consegui fazer meu transformador onehot funcionar sem handle_unkown='ignore'

Aqui está outra versão do snippet de @pjgao que inclui colunas do lembrete:

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

O que você acha de adicionar uma função semelhante à base de código principal?

Esta página foi útil?
0 / 5 - 0 avaliações