用于相交列列表的一致 ColumnTransformer

Posted

技术标签:

【中文标题】用于相交列列表的一致 ColumnTransformer【英文标题】:Consistent ColumnTransformer for intersecting lists of columns 【发布时间】:2020-09-25 05:51:21 【问题描述】:

我想始终如一地使用sklearn.compose.ColumnTransformer(不是并行的,所以,第二个转换器应该只在第一个转换器之后执行)以这种方式相交的列列表:

log_transformer = p.FunctionTransformer(lambda x: np.log(x))
df = pd.DataFrame('a': [1,2, np.NaN, 4], 'b': [1,np.NaN, 3, 4], 'c': [1 ,2, 3, 4])
compose.ColumnTransformer(n_jobs=1,
                         transformers=[
                             ('num', impute.SimpleImputer() , ['a', 'b']),
                             ('log', log_transformer, ['b', 'c']),
                             ('scale', p.StandardScaler(), ['a', 'b', 'c'])
                         ]).fit_transform(df)

所以,我想将SimpleImputer 用于'a''b',然后将log 用于'b''c',然后将StandardScaler 用于'a'、@9876543331@、@98764 @。

但是:

    我得到(4, 7) 形状的数组。 我仍然在ab 列中获得Nan

那么,如何以Pipeline 的方式将ColumnTransformer 用于不同的列?

UPD:

pipe_1 = pipeline.Pipeline(steps=[
    ('imp', impute.SimpleImputer(strategy='constant', fill_value=42)),
])

pipe_2 = pipeline.Pipeline(steps=[
    ('imp', impute.SimpleImputer(strategy='constant', fill_value=24)),
])

pipe_3 = pipeline.Pipeline(steps=[
    ('scl', p.StandardScaler()),
])

# in the real situation I don't know exactly what cols these arrays contain, so they are not static: 
cols_1 = ['a']
cols_2 = ['b']
cols_3 = ['a', 'b', 'c']

proc = compose.ColumnTransformer(remainder='passthrough', transformers=[
    ('1', pipe_1, cols_1),
    ('2', pipe_2, cols_2),
    ('3', pipe_3, cols_3),
])
proc.fit_transform(df).T

输出:

array([[ 1.        ,  2.        , 42.        ,  4.        ],
       [ 1.        , 24.        ,  3.        ,  4.        ],
       [-1.06904497, -0.26726124,         nan,  1.33630621],
       [-1.33630621,         nan,  0.26726124,  1.06904497],
       [-1.34164079, -0.4472136 ,  0.4472136 ,  1.34164079]])

我明白为什么我有 cols 重复,nans 而不是缩放值,但是当 cols 不是静态的时,如何以正确的方式解决这个问题?

UPD2:

当列改变它们的顺序时可能会出现问题。所以,我想使用FunctionTransformer 进行列选择:

def select_col(X, cols=None):
    return X[cols]

ct1 = compose.make_column_transformer(
    (p.OneHotEncoder(), p.FunctionTransformer(select_col, kw_args=dict(cols=['a', 'b']))),
    remainder='passthrough'
)

ct1.fit(df)

但是得到这个输出:

ValueError:没有有效的列规范。只允许使用所有整数或所有字符串的标量、列表或切片,或布尔掩码

我该如何解决?

【问题讨论】:

在更新中,我不明白你说“我不知道这些数组到底包含什么cols,所以它们不是静态的”是什么意思 @BenReiniger 此列是动态创建的:例如我有偏度测试,所以 col_1 数组(例如)只包含应该去日志转换器的偏斜列。 应用每个转换器的列列表可以用不同的方式给出;如果您的偏度测试可以封装在一个函数中,则可以使用该函数(请参阅文档,callable for columns)。 Re: update2: 这不是 FunctionTransformer 所做的。 【参考方案1】:

ColumnTransformer 的预期用途是并行应用不同的变压器,而不是顺序应用。为了实现您想要的结果,我想到了三种方法:

第一种方法:

pipe_a = Pipeline(steps=[('imp', SimpleImputer()),
                         ('scale', StandardScaler())])
pipe_b = Pipeline(steps=[('imp', SimpleImputer()),
                         ('log', log_transformer),
                         ('scale', StandardScaler())])
pipe_c = Pipeline(steps=[('log', log_transformer),
                         ('scale', StandardScaler())])
proc = ColumnTransformer(transformers=[
    ('a', pipe_a, ['a']),
    ('b', pipe_b, ['b']),
    ('c', pipe_c, ['c'])]
)

第二个实际上不起作用,因为ColumnTransformer 将重新排列列并忘记名称*,这样后面的列就会失败或应用于错误的列。当 sklearn 最终确定如何传递数据帧或特征名称时,这可能会被挽救,或者您现在可以针对您的特定用例进行调整。 (* ColumnTransformer 确实已经有一个get_feature_names,但是通过管道传递的实际数据没有那个信息。)

imp_tfm = ColumnTransformer(
    transformers=[('num', impute.SimpleImputer() , ['a', 'b'])],
    remainder='passthrough'
    )
log_tfm = ColumnTransformer(
    transformers=[('log', log_transformer, ['b', 'c'])],
    remainder='passthrough'
    )
scl_tfm = ColumnTransformer(
    transformers=[('scale', StandardScaler(), ['a', 'b', 'c'])
    )
proc = Pipeline(steps=[
    ('imp', imp_tfm),
    ('log', log_tfm),
    ('scale', scl_tfm)]
)

第三,可能有一种方法可以使用Pipeline 切片功能来拥有一个“主”管道,您可以为每个功能减少一个“主”管道......这与第一种方法大致相同,在较大的管道​​的情况下可能会节省一些编码,但似乎有点 hacky。例如,您可以在这里:

pipe_a = clone(pipe_b)[1:]
pipe_c = clone(pipe_b)
pipe_c.steps[1] = ('nolog', 'passthrough')

(如果不克隆或以其他方式深度复制pipe_b,最后一行将同时更改pipe_cpipe_b。切片机制返回一个副本,因此pipe_a 并不严格需要被克隆,但是我把它留在里面是为了感觉更安全。不幸的是,你不能提供一个不连续的切片,所以pipe_c = pipe_b[0,2] 不起作用,但你可以像我上面所做的那样设置单个切片"passthrough" 禁用它们。)

【讨论】:

感谢您的回答。我已经考虑过类似的事情。不幸的是,第一种方法在功能方面不是非常可扩展和自主的 - 当有很多功能时,手动使用所有这些将非常困难。另外,您能否更详细地描述您的最后一句话“......可能有一种方法可以使用管道切片功能来为每个功能削减一个“主”管道......”? 当然,在第一种方法中,您不需要为每个功能使用单独的管道;仅针对您要应用的每个唯一转换列表。 (例如,将功能 c 也发送到 pipe_b。) 请检查我更新的问题,我试图解释我的意思。 我发现了我们如何做到这一点,您的第二个选择是起点。 More info.【参考方案2】:

我们可以使用 little columns_name_to_index hack 将列名转换为索引,然后我们可以像这样将数据帧传递给管道:

def columns_name_to_index(arr_of_names, df):
    return [df.columns.get_loc(c) for c in arr_of_names if c in df]

cols_1 = ['a']
cols_2 = ['b']
cols_3 = ['a', 'b', 'c']

ct1 = compose.ColumnTransformer(remainder='passthrough', transformers=[
    (impute.SimpleImputer(strategy='constant', fill_value=42), columns_name_to_index(cols_1, df)),
    (impute.SimpleImputer(strategy='constant', fill_value=24), columns_name_to_index(cols_2, df)),
])

ct2 = compose.ColumnTransformer(remainder='passthrough', transformers=[
    (p.StandardScaler(), columns_name_to_index(cols_3, df)),
])

pipe = pipeline.Pipeline(steps=[
    ('ct1', ct1),
    ('ct2', ct2),
])

pipe.fit_transform(df).T

【讨论】:

我很高兴这可以满足您的需求;我想这是我对第二个选项的描述中的“您可以针对您的特定用例进行调整”的部分。但请注意,重申一下:“ColumnTransformer 将重新排列列......”因此,如果您的ct2 没有在整个框架上运行,并且ct1 的转换器以不同的顺序排列(或包含添加/删除列的转换器),这将失败,因为您的 columns_name_to_index 引用原始 df 中的索引。 @BenReiniger 然后我想例如OneHotEncoder 会产生问题。你能提出解决办法吗? 在我所有的项目中,我只有少数几个不同的管道可以应用,所以我的第一个选项是最适用的。如果您的数据不是这种情况,也许如果您提供一个更具代表性的示例,我们可以一起破解一些东西。 @BenReiniger 我尝试使用FunctionTransformer但出现错误,请检查问题中的UPD2

以上是关于用于相交列列表的一致 ColumnTransformer的主要内容,如果未能解决你的问题,请参考以下文章

增强几何返回相交和相交的不一致结果

算法总结之 两个单链表相交的一些列问题

比较子集和超集 CSV 的列,并在 Pandas 中输出相交列 + 超集 CSV 的另一列

相交列表

如何一致地重播事件列表

PHP相交与MySQL相交