Scikit-learn: 在 ColumnTransformer 之后无法获取功能名称

创建于 2018-11-06  ·  13评论  ·  资料来源: scikit-learn/scikit-learn

当我使用ColumnTransformer对不同的列(包括数字、类别、文本)进行管道预处理时,我无法获得最终转换数据的特征名称,这很难调试。

这是代码:

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()会报错:
    AttributeError: Transformer numeric (type Pipeline) does not provide get_feature_names.
  2. ColumnTransformertext_transformer只能处理一个字符串(例如 'Sex'),而不能像text_columns那样处理一个字符串列表

最有用的评论

这不是 ColumnTransformer 的问题。

  1. 是关于管道的。 请注意, eli5实现了一个可以支持 Pipeline 的特性名称函数。

Re 2. 也许你是对的,我们没有一种干净的方法将文本向量化器应用于每一列是不友好的。 我不确定如何完全实现,除非我们只是开始在 CountVectorizer 等中支持多列输入。

感谢您的回复!
据我所知,当我使用可以将一列更改为多列的方法(例如OneHotEncoderCountVectorizer对列进行预处理时,我可以通过管道最后一步的转换器获取新的数据列名称函数get_feature_names ,当使用不创建新列的方法时,可以只设置原始列名。

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

使用上面的代码,我可以得到我的preprocesser的列名。
这些代码能解决这个问题吗?
从 eli5 开始,我没有找到那个函数,你能给我一个 eli5 的显式示例或 api 的链接吗?

所有13条评论

这不是 ColumnTransformer 的问题。

  1. 是关于管道的。 请注意, eli5实现了一个可以支持 Pipeline 的特性名称函数。

Re 2. 也许你是对的,我们没有一种干净的方法将文本向量化器应用于每一列是不友好的。 我不确定如何完全实现,除非我们只是开始在 CountVectorizer 等中支持多列输入。

这不是 ColumnTransformer 的问题。

  1. 是关于管道的。 请注意, eli5实现了一个可以支持 Pipeline 的特性名称函数。

Re 2. 也许你是对的,我们没有一种干净的方法将文本向量化器应用于每一列是不友好的。 我不确定如何完全实现,除非我们只是开始在 CountVectorizer 等中支持多列输入。

感谢您的回复!
据我所知,当我使用可以将一列更改为多列的方法(例如OneHotEncoderCountVectorizer对列进行预处理时,我可以通过管道最后一步的转换器获取新的数据列名称函数get_feature_names ,当使用不创建新列的方法时,可以只设置原始列名。

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

使用上面的代码,我可以得到我的preprocesser的列名。
这些代码能解决这个问题吗?
从 eli5 开始,我没有找到那个函数,你能给我一个 eli5 的显式示例或 api 的链接吗?

关于 eli5,请参阅 transform_feature_names(由解释权重使用)

1 是#6425 的副本,对吗? 我想写一篇关于它的睡眠。
我认为使用ColumnTransformer支持多个文本列非常容易。 这不是最漂亮的代码,但您可以为每个文本列添加一个 CountVectorizer。

而且您的代码段并没有真正解决问题,因为没有get_feature_names并不意味着您可以只使用列名。

1 是#6425 的副本,对吗? 我想写一篇关于它的睡眠。
我认为使用ColumnTransformer支持多个文本列非常容易。 这不是最漂亮的代码,但您可以为每个文本列添加一个 CountVectorizer。

而且您的代码段并没有真正解决问题,因为没有get_feature_names并不意味着您可以只使用列名。

是的,在预处理管道中的 Pandas DataFrame 馈送之后,最好获取特征名称,以便可以仅从生成的数据中确切地知道发生了什么。

好的,关闭重复。

这不是 ColumnTransformer 的问题。

  1. 是关于管道的。 请注意, eli5实现了一个可以支持 Pipeline 的特性名称函数。

Re 2. 也许你是对的,我们没有一种干净的方法将文本向量化器应用于每一列是不友好的。 我不确定如何完全实现,除非我们只是开始在 CountVectorizer 等中支持多列输入。

感谢您的回复!
据我所知,当我使用可以将一列更改为多列的方法(例如OneHotEncoderCountVectorizer对列进行预处理时,我可以通过管道最后一步的转换器获取新的数据列名称函数get_feature_names ,当使用不创建新列的方法时,可以只设置原始列名。

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

使用上面的代码,我可以得到我的preprocesser的列名。
这些代码能解决这个问题吗?
从 eli5 开始,我没有找到那个函数,你能给我一个 eli5 的显式示例或 api 的链接吗?

我做了一个微小的改进,以恢复 onehot 表单的 rawname_value 之类的名称:

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

这不是 ColumnTransformer 的问题。

  1. 是关于管道的。 请注意, eli5实现了一个可以支持 Pipeline 的特性名称函数。

Re 2. 也许你是对的,我们没有一种干净的方法将文本向量化器应用于每一列是不友好的。 我不确定如何完全实现,除非我们只是开始在 CountVectorizer 等中支持多列输入。

感谢您的回复!
据我所知,当我使用可以将一列更改为多列的方法(例如OneHotEncoderCountVectorizer对列进行预处理时,我可以通过管道最后一步的转换器获取新的数据列名称函数get_feature_names ,当使用不创建新列的方法时,可以只设置原始列名。

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

使用上面的代码,我可以得到我的preprocesser的列名。
这些代码能解决这个问题吗?
从 eli5 开始,我没有找到那个函数,你能给我一个 eli5 的显式示例或 api 的链接吗?

如果您在管道中应用带有 add_indicator 的 simpleimputer 呢? 这种方法行不通。

如果您在管道中应用带有 add_indicator 的 simpleimputer 呢? 这种方法行不通。

为这个配置有一个 get_feature_names 方法会很好。

如果您在管道中应用带有 add_indicator 的 simpleimputer 呢? 这种方法行不通。

这是我对短期解决方案的贡献。 它将所有不同的数组类型强制转换为列表,并处理 SimpleImputer(add_indicate=True) 的情况。 它也有点冗长。

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

仅供参考,我写了一些代码和一篇关于如何从复杂的管道和列转换器中提取特征名称的博客。 该代码是对我之前的帖子的改进。 https://towardsdatascience.com/extracting-plotting-feature-names-importance-from-scikit-learn-pipelines-eb5bfa6a31f4

@kylegilde很棒的文章,感谢您的代码。 奇迹般有效。 对于全局解释,我已经与KernelSHAP 和 alibi搏斗了几个小时,但没有handle_unkown='ignore'就没有让我的 onehot 变压器工作

这是@pjgao片段的另一个版本,其中包含提醒中的列:

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

您如何看待在核心代码库中添加类似功能?

此页面是否有帮助?
0 / 5 - 0 等级