用于相交列列表的一致 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)
形状的数组。
我仍然在a
和b
列中获得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 forcolumns
)。
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_c
和pipe_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的主要内容,如果未能解决你的问题,请参考以下文章