sklearn.compose.ColumnTransformer: fit_transform() 接受了2个位置参数,但实际传入了3个。

17

我正在处理一个使用ColumnTransformerLabelEncoder来预处理著名的泰坦尼克数据集X的示例:

    Age Embarked    Fare    Sex
0   22.0    S      7.2500   male
1   38.0    C      71.2833  female
2   26.0    S      7.9250   female
3   35.0    S      53.1000  female
4   35.0    S      8.0500   male

这样调用转换器:

from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import LabelEncoder
ColumnTransformer(
    transformers=[
        ("label-encode categorical", LabelEncoder(), ["Sex", "Embarked"])
    ]
).fit(X).transform(X)

导致结果如下:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-54-fd5a05b7e47e> in <module>
      4         ("label-encode categorical", LabelEncoder(), ["Sex", "Embarked"])
      5     ]
----> 6 ).fit(X).transform(X)

~/anaconda3/lib/python3.7/site-packages/sklearn/compose/_column_transformer.py in fit(self, X, y)
    418         # we use fit_transform to make sure to set sparse_output_ (for which we
    419         # need the transformed data) to have consistent output type in predict
--> 420         self.fit_transform(X, y=y)
    421         return self
    422 

~/anaconda3/lib/python3.7/site-packages/sklearn/compose/_column_transformer.py in fit_transform(self, X, y)
    447         self._validate_remainder(X)
    448 
--> 449         result = self._fit_transform(X, y, _fit_transform_one)
    450 
    451         if not result:

~/anaconda3/lib/python3.7/site-packages/sklearn/compose/_column_transformer.py in _fit_transform(self, X, y, func, fitted)
    391                               _get_column(X, column), y, weight)
    392                 for _, trans, column, weight in self._iter(
--> 393                     fitted=fitted, replace_strings=True))
    394         except ValueError as e:
    395             if "Expected 2D array, got 1D array instead" in str(e):

~/anaconda3/lib/python3.7/site-packages/sklearn/externals/joblib/parallel.py in __call__(self, iterable)
    915             # remaining jobs.
    916             self._iterating = False
--> 917             if self.dispatch_one_batch(iterator):
    918                 self._iterating = self._original_iterator is not None
    919 

~/anaconda3/lib/python3.7/site-packages/sklearn/externals/joblib/parallel.py in dispatch_one_batch(self, iterator)
    757                 return False
    758             else:
--> 759                 self._dispatch(tasks)
    760                 return True
    761 

~/anaconda3/lib/python3.7/site-packages/sklearn/externals/joblib/parallel.py in _dispatch(self, batch)
    714         with self._lock:
    715             job_idx = len(self._jobs)
--> 716             job = self._backend.apply_async(batch, callback=cb)
    717             # A job can complete so quickly than its callback is
    718             # called before we get here, causing self._jobs to

~/anaconda3/lib/python3.7/site-packages/sklearn/externals/joblib/_parallel_backends.py in apply_async(self, func, callback)
    180     def apply_async(self, func, callback=None):
    181         """Schedule a func to be run"""
--> 182         result = ImmediateResult(func)
    183         if callback:
    184             callback(result)

~/anaconda3/lib/python3.7/site-packages/sklearn/externals/joblib/_parallel_backends.py in __init__(self, batch)
    547         # Don't delay the application, to avoid keeping the input
    548         # arguments in memory
--> 549         self.results = batch()
    550 
    551     def get(self):

~/anaconda3/lib/python3.7/site-packages/sklearn/externals/joblib/parallel.py in __call__(self)
    223         with parallel_backend(self._backend, n_jobs=self._n_jobs):
    224             return [func(*args, **kwargs)
--> 225                     for func, args, kwargs in self.items]
    226 
    227     def __len__(self):

~/anaconda3/lib/python3.7/site-packages/sklearn/externals/joblib/parallel.py in <listcomp>(.0)
    223         with parallel_backend(self._backend, n_jobs=self._n_jobs):
    224             return [func(*args, **kwargs)
--> 225                     for func, args, kwargs in self.items]
    226 
    227     def __len__(self):

~/anaconda3/lib/python3.7/site-packages/sklearn/pipeline.py in _fit_transform_one(transformer, X, y, weight, **fit_params)
    612 def _fit_transform_one(transformer, X, y, weight, **fit_params):
    613     if hasattr(transformer, 'fit_transform'):
--> 614         res = transformer.fit_transform(X, y, **fit_params)
    615     else:
    616         res = transformer.fit(X, y, **fit_params).transform(X)

TypeError: fit_transform() takes 2 positional arguments but 3 were given

这里的**fit_params有什么问题?对我来说,这看起来像是sklearn中的一个bug,或者至少是不兼容的。

我知道有很多解决方法,但我特别寻找一种解决方案,它使用单个管道对象进行整个预处理并应用标签编码。 - clstaudt
3个回答

26

这将不适用于您的目的有两个主要原因。

  1. LabelEncoder() 的设计是为了用于目标变量(y)。因此,当 columnTransformer() 尝试提供 X,y = None,fit_params = {} 时导致位置参数错误。

来自文档

使用介于0和n_classes-1之间的值对标签进行编码。

fit(y)
适合标签编码器

参数:
y:形状为(n_samples,)的类似数组
目标值。

  1. 即使您做一个解决方法来删除空字典,那么LabelEncoder()也无法接受2D数组(基本上是多个特征一次),因为它只取1D y 值。

简而言之 - 我们不应该使用LabelEncoder() 来输入特征。

那么,如何对输入特征进行编码呢?

如果您的特征是序数特征,则使用OrdinalEncoder(),如果是名义特征,则使用OneHotEncoder()

例子:

>>> from sklearn.compose import ColumnTransformer
>>> from sklearn.preprocessing import OrdinalEncoder, OneHotEncoder
>>> X = np.array([[1000., 100., 'apple', 'green'],
...               [1100., 100., 'orange', 'blue']])
>>> ct = ColumnTransformer(
...     [("ordinal", OrdinalEncoder(), [0, 1]),
         ("nominal", OneHotEncoder(), [2, 3])])
>>> ct.fit_transform(X)   
array([[0., 0., 1., 0., 0., 1.],
       [1., 0., 0., 1., 1., 0.]]) 

3
我明白了。我经常看到人们在进行特征编码时误用LabelEncoder,以至于我认为它是为此而设计的。 - clstaudt
2
只是为了明确,空字典实际上是fit_params。如果您浏览代码,您会看到sklearn.pipeline._fit_transform_one被调用,其中包括LabelEncoder实例、数据框(X)、Noney)和{}fit_params)。 - Grr

8
我认为这实际上是与LabelEncoder有关的问题。 LabelEncoder.fit方法只接受selfy作为参数(这很奇怪,因为大多数转换器对象都采用fit(X,y = None,** fit_params)范例)。 无论如何,在管道中调用转换器时,它将使用fit_params,而不考虑您传递了什么。 在这种特定情况下,传递给LabelEncoder.fit的确切参数是X和一个空字典{}。 因此引发错误。

在我看来,这是LabelEncoder中的一个bug,但您应该向sklearn团队提出此问题,因为他们可能有某些原因来实现fit方法的不同之处。


3
这是正确答案。使用LabelEncoder对特征进行编码没有任何问题/不应该有任何问题。OneHotEncoding与标签编码不是同一件事情。在某些情况下,使用大型稀疏结构或序列编码可能不合理。 - Petergavinkin
但是你的解决方案是什么? - Eli Borodach

0
它被称为 标签 编码器,因为它旨在与数据集的 标签,即 y 值一起使用。在我意识到这一点之前,这个类让我感到困惑。
虽然在文献中,我们可以使用 One-Hot 编码或 Label 编码来编码特征,但在这方面 Sklearn 对新手来说不是很友好。
使用 OrdinalEncoder 替代,它被设计用于处理特征。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接