7.3. 预处理数据#

sklearn.preprocessing 该包提供了几个常见的效用函数和Transformer类,以将原始特征载体更改为更适合下游估计器的表示。

一般来说,线性模型等许多学习算法受益于数据集的标准化(请参阅 特征缩放的重要性 ).如果集中存在一些离群值,则鲁棒缩放器或其他转换器可能更合适。包含边缘离群值的数据集中的不同缩放器、转换器和规范器的行为在中突出显示 比较不同缩放器对数据的影响与离群值 .

7.3.1. 标准化,或均值去除和方差缩放#

Standardization 数据集是一个 common requirement for many machine learning estimators 在scikit-learn中实现;如果单个特征看起来或多或少不像标准的正态分布数据,则它们可能表现得很糟糕: zero mean and unit variance .

在实践中,我们经常忽略分布的形状,只是通过删除每个特征的平均值来转换数据以使其居中,然后通过将非恒定特征除以其标准差来扩展其规模。

例如,学习算法的目标函数中使用的许多元素(例如支持向量机的RBS核或线性模型的l1和l2正规化器)可以假设所有特征都以零为中心或具有相同顺序的方差。如果一个特征的方差比其他特征大几个数量级,它可能会主导目标函数,并使估计器无法像预期的那样正确地从其他特征中学习。

preprocessing 模块提供 StandardScaler 实用程序类,这是对类数组数据集执行以下操作的一种快速简单的方法:

>>> from sklearn import preprocessing
>>> import numpy as np
>>> X_train = np.array([[ 1., -1.,  2.],
...                     [ 2.,  0.,  0.],
...                     [ 0.,  1., -1.]])
>>> scaler = preprocessing.StandardScaler().fit(X_train)
>>> scaler
StandardScaler()

>>> scaler.mean_
array([1., 0., 0.33])

>>> scaler.scale_
array([0.81, 0.81, 1.24])

>>> X_scaled = scaler.transform(X_train)
>>> X_scaled
array([[ 0.  , -1.22,  1.33 ],
       [ 1.22,  0.  , -0.267],
       [-1.22,  1.22, -1.06 ]])

缩放数据的均值和单位方差为零::

>>> X_scaled.mean(axis=0)
array([0., 0., 0.])

>>> X_scaled.std(axis=0)
array([1., 1., 1.])

此类实现 Transformer 计算训练集的均值和标准差的API,以便以后能够对测试集重新应用相同的转换。因此,此类适合在 Pipeline

>>> from sklearn.datasets import make_classification
>>> from sklearn.linear_model import LogisticRegression
>>> from sklearn.model_selection import train_test_split
>>> from sklearn.pipeline import make_pipeline
>>> from sklearn.preprocessing import StandardScaler

>>> X, y = make_classification(random_state=42)
>>> X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
>>> pipe = make_pipeline(StandardScaler(), LogisticRegression())
>>> pipe.fit(X_train, y_train)  # apply scaling on training data
Pipeline(steps=[('standardscaler', StandardScaler()),
                ('logisticregression', LogisticRegression())])

>>> pipe.score(X_test, y_test)  # apply scaling on testing data, without leaking training data.
0.96

可以通过任何一种方式禁用定心或缩放 with_mean=Falsewith_std=False 致的构造者 StandardScaler .

7.3.1.1. 将功能扩展到一定范围#

另一种标准化是将特征缩放到给定的最小值和最大值之间,通常在零和一之间,或者使每个特征的最大绝对值缩放到单位大小。这可以通过使用来实现 MinMaxScalerMaxAbsScaler ,分别。

使用这种缩放的动机包括对非常小的特征标准差的鲁棒性以及在稀疏数据中保留零条目。

以下是将玩具数据矩阵扩展到 [0, 1] 范围::

>>> X_train = np.array([[ 1., -1.,  2.],
...                     [ 2.,  0.,  0.],
...                     [ 0.,  1., -1.]])
...
>>> min_max_scaler = preprocessing.MinMaxScaler()
>>> X_train_minmax = min_max_scaler.fit_transform(X_train)
>>> X_train_minmax
array([[0.5       , 0.        , 1.        ],
       [1.        , 0.5       , 0.33333333],
       [0.        , 1.        , 0.        ]])

然后,可以将Transformer的相同实例应用于拟合调用期间未看到的一些新测试数据:将应用相同的缩放和移位操作,以与对训练数据执行的转换一致:

>>> X_test = np.array([[-3., -1.,  4.]])
>>> X_test_minmax = min_max_scaler.transform(X_test)
>>> X_test_minmax
array([[-1.5       ,  0.        ,  1.66666667]])

可以内省缩放器属性,以找到在训练数据上学到的转换的确切性质::

>>> min_max_scaler.scale_
array([0.5       , 0.5       , 0.33])

>>> min_max_scaler.min_
array([0.        , 0.5       , 0.33])

如果 MinMaxScaler 给出了一个明确的 feature_range=(min, max) 完整的公式是::

X_std = (X - X.min(axis=0)) / (X.max(axis=0) - X.min(axis=0))

X_scaled = X_std * (max - min) + min

MaxAbsScaler 其工作方式非常相似,但以训练数据位于范围内的方式扩展 [-1, 1] 通过除以每个特征中的最大最大值。它适用于已经以零为中心的数据或稀疏数据。

以下是如何在此缩放器中使用上一示例中的玩具数据::

>>> X_train = np.array([[ 1., -1.,  2.],
...                     [ 2.,  0.,  0.],
...                     [ 0.,  1., -1.]])
...
>>> max_abs_scaler = preprocessing.MaxAbsScaler()
>>> X_train_maxabs = max_abs_scaler.fit_transform(X_train)
>>> X_train_maxabs
array([[ 0.5, -1. ,  1. ],
       [ 1. ,  0. ,  0. ],
       [ 0. ,  1. , -0.5]])
>>> X_test = np.array([[ -3., -1.,  4.]])
>>> X_test_maxabs = max_abs_scaler.transform(X_test)
>>> X_test_maxabs
array([[-1.5, -1. ,  2. ]])
>>> max_abs_scaler.scale_
array([2.,  1.,  2.])

7.3.1.2. 缩放稀疏数据#

将稀疏数据置于中心会破坏数据中的稀疏结构,因此很少是一件明智的事情。然而,缩放稀疏输入是有意义的,尤其是当特征处于不同的比例时。

MaxAbsScaler 专为扩展稀疏数据而设计,并且是推荐的实现这一目标的方法。然而, StandardScaler 可以接受 scipy.sparse 矩阵作为输入,只要 with_mean=False 显式传递给构造函数。否则 ValueError 将被提出,因为无声地居中会打破稀疏性,并且经常会因无意中分配过多的内存而导致执行崩溃。 RobustScaler 无法适合稀疏输入,但您可以使用 transform 稀疏输入上的方法。

请注意,缩放器同时接受压缩稀疏列和压缩稀疏列格式(请参阅 scipy.sparse.csr_matrixscipy.sparse.csc_matrix ).任何其他稀疏输入都将是 converted to the Compressed Sparse Rows representation . 为了避免不必要的内存副本,建议选择上游的CSR或CSC表示。

最后,如果预计中心数据足够小,则使用 toarray 稀疏矩阵方法是另一种选择。

7.3.1.3. 使用异常值缩放数据#

如果您的数据包含许多异常值,则使用数据的均值和方差进行缩放可能不会很好地发挥作用。在这些情况下,您可以使用 RobustScaler 作为临时替代品。它对数据的中心和范围使用更可靠的估计。

引用#

有关对数据进行中心化和缩放的重要性的进一步讨论,请参阅此常见问题解答: Should I normalize/standardize/rescale the data?

脱毛与美白#

有时,独立地集中和缩放特征是不够的,因为下游模型可以进一步对特征的线性独立性做出一些假设。

要解决此问题,您可以使用 PCAwhiten=True 以进一步消除特征之间的线性相关性。

7.3.1.4. 定心核心矩阵#

如果你有一个核矩阵 \(K\) 计算由函数定义的特征空间(可能隐式)中的点积 \(\phi(\cdot)\) 、a KernelCenterer 可以变换核矩阵,使其包含由 \(\phi\) 然后删除该空间中的平均值。换句话说, KernelCenterer 计算与正半定核相关的中心Gram矩阵 \(K\) .

数学公式#

既然我们有了直觉,我们就可以看看数学公式了。让 \(K\) 成为形状的核心矩阵 (n_samples, n_samples) 计算出 \(X\) ,形状的数据矩阵 (n_samples, n_features) ,在 fit\(K\) 限定

\[K(X,X)= \phy(X)。\ph(X)'{T}\]

\(\phi(X)\) 是的函数映射 \(X\) 到一个希尔伯特空间。中心核 \(\tilde{K}\) 定义为:

\[\tilde{K}(X, X) = \tilde{\phi}(X) . \tilde{\phi}(X)^{T}\]

哪里 \(\tilde{\phi}(X)\) 中心的结果 \(\phi(X)\) 在希尔伯特空间中。

Thus, one could compute \(\tilde{K}\) by mapping \(X\) using the function \(\phi(\cdot)\) and center the data in this new space. However, kernels are often used because they allow some algebra calculations that avoid computing explicitly this mapping using \(\phi(\cdot)\). Indeed, one can implicitly center as shown in Appendix B in [Scholkopf1998]:

\[\tilde{K} = K - 1_{\text{n}_{samples}} K - K 1_{\text{n}_{samples}} + 1_{\text{n}_{samples}} K 1_{\text{n}_{samples}}\]

\(1_{\text{n}_{samples}}\) 是一个 (n_samples, n_samples) 其中所有条目都等于 \(\frac{1}{\text{n}_{samples}}\) .在 transform 步骤,内核变成 \(K_{test}(X, Y)\) 定义为:

\[K_{Test}(X,Y)= \phy(Y)。\ph(X)'{T}\]

\(Y\) 是形状的测试数据集 (n_samples_test, n_features) 并且因此 \(K_{test}\) 形状 (n_samples_test, n_samples) .在这种情况下,对中 \(K_{test}\) 如下所示:

\[\tilde{K}_{test}(X, Y) = K_{test} - 1'_{\text{n}_{samples}} K - K_{test} 1_{\text{n}_{samples}} + 1'_{\text{n}_{samples}} K 1_{\text{n}_{samples}}\]

\(1'_{\text{n}_{samples}}\) 是形状矩阵 (n_samples_test, n_samples) 其中所有条目都等于 \(\frac{1}{\text{n}_{samples}}\) .

引用

[Scholkopf1998]

B.肖尔科普夫,A.斯莫拉和KR穆勒, "Nonlinear component analysis as a kernel eigenvalue problem." 神经计算10.5(1998):1299-1319。

7.3.2. 非线性变换#

有两种类型的转换可用:分位数转换和功率转换。分位数和功率变换都基于特征的单调变换,因此保留了每个特征的值的排序。

分位数变换根据公式将所有要素放入相同的所需分布中 \(G^{-1}(F(X))\) 哪里 \(F\) 是特征的累积分布函数, \(G^{-1}\)quantile function 所需的输出分布 \(G\) .该公式使用以下两个事实:(i)如果 \(X\) 是具有连续累积分布函数的随机变量 \(F\) 然后 \(F(X)\) 均匀分布在 \([0,1]\) ;(ii)如果 \(U\) 是均匀分布的随机变量 \([0,1]\) 然后 \(G^{-1}(U)\) 具有分配 \(G\) .通过执行等级变换,分位数变换可以平滑不寻常的分布,并且比缩放方法受异常值的影响较小。然而,它确实扭曲了特征内部和之间的相关性和距离。

功率变换是一系列参数变换,旨在将数据从任何分布映射到尽可能接近高斯分布。

7.3.2.1. 映射到均匀分布#

QuantileTransformer 提供非参数转换以将数据映射到值介于0和1之间的均匀分布::

>>> from sklearn.datasets import load_iris
>>> from sklearn.model_selection import train_test_split
>>> X, y = load_iris(return_X_y=True)
>>> X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
>>> quantile_transformer = preprocessing.QuantileTransformer(random_state=0)
>>> X_train_trans = quantile_transformer.fit_transform(X_train)
>>> X_test_trans = quantile_transformer.transform(X_test)
>>> np.percentile(X_train[:, 0], [0, 25, 50, 75, 100])
array([ 4.3,  5.1,  5.8,  6.5,  7.9])

该特征对应于以厘米为单位的花瓣长度。应用分位数变换后,这些地标将接近之前定义的百分位数::

>>> np.percentile(X_train_trans[:, 0], [0, 25, 50, 75, 100])
...
array([ 0.00 ,  0.24,  0.49,  0.73,  0.99 ])

这可以在独立测试集上得到证实,并带有类似的注释::

>>> np.percentile(X_test[:, 0], [0, 25, 50, 75, 100])
...
array([ 4.4  ,  5.125,  5.75 ,  6.175,  7.3  ])
>>> np.percentile(X_test_trans[:, 0], [0, 25, 50, 75, 100])
...
array([ 0.01,  0.25,  0.46,  0.60 ,  0.94])

7.3.2.2. 映射到高斯分布#

在许多建模场景中,数据集中特征的正态性是可取的。功率变换是一系列参数单调变换,旨在将数据从任何分布映射到尽可能接近高斯分布,以稳定方差并最大限度地减少偏度。

PowerTransformer 目前提供两种这样的权力转换:Yeo-Johnson转换和Box-Cox转换。

约-约翰逊转变#
\[\begin{split}x_i^{(\lambda)} = \begin{cases} [(x_i + 1)^\lambda - 1] / \lambda & \text{if } \lambda \neq 0, x_i \geq 0, \\[8pt] \ln{(x_i + 1)} & \text{if } \lambda = 0, x_i \geq 0 \\[8pt] -[(-x_i + 1)^{2 - \lambda} - 1] / (2 - \lambda) & \text{if } \lambda \neq 2, x_i < 0, \\[8pt] - \ln (- x_i + 1) & \text{if } \lambda = 2, x_i < 0 \end{cases}\end{split}\]
博克斯-考克斯变换#
\[\begin{split}x_i^{(\lambda)} = \begin{cases} \dfrac{x_i^\lambda - 1}{\lambda} & \text{if } \lambda \neq 0, \\[8pt] \ln{(x_i)} & \text{if } \lambda = 0, \end{cases}\end{split}\]

Box-Cox仅适用于严格的正数据。在这两种方法中,转换都由以下参数化 \(\lambda\) ,这是通过最大似然估计确定的。以下是使用Box-Cox将从log正态分布提取的样本映射到正态分布的示例::

>>> pt = preprocessing.PowerTransformer(method='box-cox', standardize=False)
>>> X_lognormal = np.random.RandomState(616).lognormal(size=(3, 3))
>>> X_lognormal
array([[1.28, 1.18 , 0.84 ],
       [0.94, 1.60 , 0.388],
       [1.35, 0.217, 1.09 ]])
>>> pt.fit_transform(X_lognormal)
array([[ 0.49 ,  0.179, -0.156],
       [-0.051,  0.589, -0.576],
       [ 0.69 , -0.849,  0.101]])

虽然上面的例子设置了 standardize 选项 False , PowerTransformer 默认情况下,将对转换后的输出应用零均值、单位方差正规化。

以下是应用于各种概率分布的Box-Cox和Yeo-Johnson的示例。 请注意,当应用于某些分布时,功率变换会实现非常类似高斯的结果,但对于其他分布,它们是无效的。这凸显了转换前后可视化数据的重要性。

../_images/sphx_glr_plot_map_data_to_normal_001.png

还可以使用将数据映射到正态分布 QuantileTransformer 通过设置 output_distribution='normal' .使用iris数据集的早期示例::

>>> quantile_transformer = preprocessing.QuantileTransformer(
...     output_distribution='normal', random_state=0)
>>> X_trans = quantile_transformer.fit_transform(X)
>>> quantile_transformer.quantiles_
array([[4.3, 2. , 1. , 0.1],
       [4.4, 2.2, 1.1, 0.1],
       [4.4, 2.2, 1.2, 0.1],
       ...,
       [7.7, 4.1, 6.7, 2.5],
       [7.7, 4.2, 6.7, 2.5],
       [7.9, 4.4, 6.9, 2.5]])

因此,输入的中位数变成了输出的平均值,以0为中心。正常输出被剪裁,以便输入的最小值和最大值(分别对应于1 e-7和1 - 1 e-7分位数)在变换下不会变得无限。

7.3.3. 正常化#

Normalization 的过程是 scaling individual samples to have unit norm .如果您计划使用二次形式(例如点积或任何其他核)来量化任何样本对的相似性,则此过程可能很有用。

这个假设是 Vector Space Model 通常用于文本分类和集群上下文。

功能 normalize 提供了一种快速简单的方法来对单个类似阵列的数据集执行此操作,可以使用 l1 , l2 ,或者 max 规范::

>>> X = [[ 1., -1.,  2.],
...      [ 2.,  0.,  0.],
...      [ 0.,  1., -1.]]
>>> X_normalized = preprocessing.normalize(X, norm='l2')

>>> X_normalized
array([[ 0.408, -0.408,  0.812],
       [ 1.   ,  0.   ,  0.   ],
       [ 0.   ,  0.707, -0.707]])

preprocessing 模块进一步提供实用程序类 Normalizer 使用 Transformer API(即使 fit 在这种情况下,方法是无用的:该类是无状态的,因为该操作独立处理样本)。

因此,此类适合在 Pipeline

>>> normalizer = preprocessing.Normalizer().fit(X)  # fit does nothing
>>> normalizer
Normalizer()

然后,规范器实例可以作为任何Transformer在样本载体上使用::

>>> normalizer.transform(X)
array([[ 0.408, -0.408,  0.812],
       [ 1.   ,  0.   ,  0.   ],
       [ 0.   ,  0.707, -0.707]])

>>> normalizer.transform([[-1.,  1., 0.]])
array([[-0.707,  0.707,  0.]])

注:L2正规化也称为空间符号预处理。

稀疏输入#

normalizeNormalizer 接受 both dense array-like and sparse matrices from scipy.sparse as input .

对于稀疏输入,数据为 converted to the Compressed Sparse Rows representation (见 scipy.sparse.csr_matrix )然后再被喂食到高效的Cython例程。为了避免不必要的内存副本,建议选择上游的CSR表示。

7.3.4. 编码类别特征#

特征通常不是作为连续值给出的,而是分类的。例如,一个人可能有特征 ["male", "female"] , ["from Europe", "from US", "from Asia"] , ["uses Firefox", "uses Chrome", "uses Safari", "uses Internet Explorer"] .例如,此类特征可以被有效地编码为整数 ["male", "from US", "uses Internet Explorer"] 可以表示为 [0, 1, 3]["female", "from Asia", "uses Chrome"] 将是 [1, 2, 1] .

要将分类要素转换为此类整元代码,我们可以使用 OrdinalEncoder .此估计器将每个类别特征转换为一个新的整特征(0到n_category- 1)::

>>> enc = preprocessing.OrdinalEncoder()
>>> X = [['male', 'from US', 'uses Safari'], ['female', 'from Europe', 'uses Firefox']]
>>> enc.fit(X)
OrdinalEncoder()
>>> enc.transform([['female', 'from US', 'uses Safari']])
array([[0., 1., 1.]])

然而,这种整表示不能直接与所有scikit-learn估计器一起使用,因为这些估计器期望连续输入,并且会将类别解释为有序,这通常是不希望的(即浏览器集是任意排序的)。

默认情况下, OrdinalEncoder 还将传递由以下指示的缺失值 np.nan .

>>> enc = preprocessing.OrdinalEncoder()
>>> X = [['male'], ['female'], [np.nan], ['female']]
>>> enc.fit_transform(X)
array([[ 1.],
       [ 0.],
       [nan],
       [ 0.]])

OrdinalEncoder 提供参数 encoded_missing_value 对缺失的值进行编码,而无需创建管道并使用 SimpleImputer .

>>> enc = preprocessing.OrdinalEncoder(encoded_missing_value=-1)
>>> X = [['male'], ['female'], [np.nan], ['female']]
>>> enc.fit_transform(X)
array([[ 1.],
       [ 0.],
       [-1.],
       [ 0.]])

上述处理相当于以下管道::

>>> from sklearn.pipeline import Pipeline
>>> from sklearn.impute import SimpleImputer
>>> enc = Pipeline(steps=[
...     ("encoder", preprocessing.OrdinalEncoder()),
...     ("imputer", SimpleImputer(strategy="constant", fill_value=-1)),
... ])
>>> enc.fit_transform(X)
array([[ 1.],
       [ 0.],
       [-1.],
       [ 0.]])

将类别特征转换为可与scikit-learn估计器一起使用的特征的另一种可能性是使用K之一,也称为一热编码或哑编码。这种类型的编码可以通过 OneHotEncoder ,它将每个类别特征转换为 n_categories 可能的价值观 n_categories 二进制特征,其中一个为1,所有其他为0。

继续上面的例子::

>>> enc = preprocessing.OneHotEncoder()
>>> X = [['male', 'from US', 'uses Safari'], ['female', 'from Europe', 'uses Firefox']]
>>> enc.fit(X)
OneHotEncoder()
>>> enc.transform([['female', 'from US', 'uses Safari'],
...                ['male', 'from Europe', 'uses Safari']]).toarray()
array([[1., 0., 0., 1., 0., 1.],
       [0., 1., 1., 0., 0., 1.]])

默认情况下,每个要素可以采用的值是从数据集自动推断的,并可以在 categories_ 属性::

>>> enc.categories_
[array(['female', 'male'], dtype=object), array(['from Europe', 'from US'], dtype=object), array(['uses Firefox', 'uses Safari'], dtype=object)]

可以使用参数显式指定这一点 categories .我们的数据集中有两种性别、四个可能的大洲和四个网络浏览器::

>>> genders = ['female', 'male']
>>> locations = ['from Africa', 'from Asia', 'from Europe', 'from US']
>>> browsers = ['uses Chrome', 'uses Firefox', 'uses IE', 'uses Safari']
>>> enc = preprocessing.OneHotEncoder(categories=[genders, locations, browsers])
>>> # Note that for there are missing categorical values for the 2nd and 3rd
>>> # feature
>>> X = [['male', 'from US', 'uses Safari'], ['female', 'from Europe', 'uses Firefox']]
>>> enc.fit(X)
OneHotEncoder(categories=[['female', 'male'],
                          ['from Africa', 'from Asia', 'from Europe',
                           'from US'],
                          ['uses Chrome', 'uses Firefox', 'uses IE',
                           'uses Safari']])
>>> enc.transform([['female', 'from Asia', 'uses Chrome']]).toarray()
array([[1., 0., 0., 1., 0., 0., 1., 0., 0., 0.]])

如果训练数据可能缺少分类特征,通常最好指定 handle_unknown='infrequent_if_exist' 而不是设置 categories 如上所述手动。当 handle_unknown='infrequent_if_exist' 指定并且在转换过程中遇到未知类别,不会引发错误,但此功能的生成的一热编码列将全为零或被视为罕见类别(如果启用)。 (handle_unknown='infrequent_if_exist' 仅支持一热编码)::

>>> enc = preprocessing.OneHotEncoder(handle_unknown='infrequent_if_exist')
>>> X = [['male', 'from US', 'uses Safari'], ['female', 'from Europe', 'uses Firefox']]
>>> enc.fit(X)
OneHotEncoder(handle_unknown='infrequent_if_exist')
>>> enc.transform([['female', 'from Asia', 'uses Chrome']]).toarray()
array([[1., 0., 0., 0., 0., 0.]])

还可以将每列编码为 n_categories - 1 列,而不是 n_categories 列通过使用 drop 参数.此参数允许用户为要删除的每个功能指定类别。这对于避免某些分类器中的输入矩阵中的共线性很有用。例如,当使用非正规化回归时,此类功能很有用 (LinearRegression ),因为共线性会导致协方差矩阵不可逆:

>>> X = [['male', 'from US', 'uses Safari'],
...      ['female', 'from Europe', 'uses Firefox']]
>>> drop_enc = preprocessing.OneHotEncoder(drop='first').fit(X)
>>> drop_enc.categories_
[array(['female', 'male'], dtype=object), array(['from Europe', 'from US'], dtype=object),
 array(['uses Firefox', 'uses Safari'], dtype=object)]
>>> drop_enc.transform(X).toarray()
array([[1., 1., 1.],
       [0., 0., 0.]])

人们可能只想删除具有2个类别的功能的两列中的一列。在这种情况下,您可以设置参数 drop='if_binary' .

>>> X = [['male', 'US', 'Safari'],
...      ['female', 'Europe', 'Firefox'],
...      ['female', 'Asia', 'Chrome']]
>>> drop_enc = preprocessing.OneHotEncoder(drop='if_binary').fit(X)
>>> drop_enc.categories_
[array(['female', 'male'], dtype=object), array(['Asia', 'Europe', 'US'], dtype=object),
 array(['Chrome', 'Firefox', 'Safari'], dtype=object)]
>>> drop_enc.transform(X).toarray()
array([[1., 0., 0., 1., 0., 0., 1.],
       [0., 0., 1., 0., 0., 1., 0.],
       [0., 1., 0., 0., 1., 0., 0.]])

在转化的 X ,第一列是具有类别“男性”/“女性”的特征的编码,而其余6列是具有各自3个类别的2个特征的编码。

handle_unknown='ignore'drop 不为None,则未知类别将被编码为全零::

>>> drop_enc = preprocessing.OneHotEncoder(drop='first',
...                                        handle_unknown='ignore').fit(X)
>>> X_test = [['unknown', 'America', 'IE']]
>>> drop_enc.transform(X_test).toarray()
array([[0., 0., 0., 0., 0.]])

所有类别在 X_test 在变换期间未知,并将映射到全零。这意味着未知类别将与删除的类别具有相同的映射。 OneHotEncoder.inverse_transform 如果类别被删除,则将所有零映射到删除的类别,并且 None 如果未删除类别::

>>> drop_enc = preprocessing.OneHotEncoder(drop='if_binary', sparse_output=False,
...                                        handle_unknown='ignore').fit(X)
>>> X_test = [['unknown', 'America', 'IE']]
>>> X_trans = drop_enc.transform(X_test)
>>> X_trans
array([[0., 0., 0., 0., 0., 0., 0.]])
>>> drop_enc.inverse_transform(X_trans)
array([['female', None, None]], dtype=object)
支持具有缺失值的分类特征#

OneHotEncoder 通过将缺失值视为额外类别来支持具有缺失值的分类功能::

>>> X = [['male', 'Safari'],
...      ['female', None],
...      [np.nan, 'Firefox']]
>>> enc = preprocessing.OneHotEncoder(handle_unknown='error').fit(X)
>>> enc.categories_
[array(['female', 'male', nan], dtype=object),
array(['Firefox', 'Safari', None], dtype=object)]
>>> enc.transform(X).toarray()
array([[0., 1., 0., 0., 1., 0.],
      [1., 0., 0., 0., 0., 1.],
      [0., 0., 1., 1., 0., 0.]])

如果要素同时包含两者 np.nanNone ,它们将被视为单独的类别::

>>> X = [['Safari'], [None], [np.nan], ['Firefox']]
>>> enc = preprocessing.OneHotEncoder(handle_unknown='error').fit(X)
>>> enc.categories_
[array(['Firefox', 'Safari', None, nan], dtype=object)]
>>> enc.transform(X).toarray()
array([[0., 1., 0., 0.],
      [0., 0., 1., 0.],
      [0., 0., 0., 1.],
      [1., 0., 0., 0.]])

看到 从文本加载功能 对于被表示为dict而不是纯量的分类特征。

7.3.4.1. 不常见类别#

OneHotEncoderOrdinalEncoder 支持将不常见的类别聚合到每个功能的单个输出中。支持收集不常见类别的参数包括 min_frequencymax_categories .

  1. min_frequency 是大于或等于1的整数,或者是区间中的浮点数 (0.0, 1.0) .如果 min_frequency 是一个整数,基数小于 min_frequency 将被认为不常见。如果 min_frequency 是浮动的,基数小于样本总数的这一部分的类别将被视为不常见。默认值为1,这意味着每个类别都单独编码。

  2. max_categories 要么是 None 或任何大于1的整数。此参数为每个输入要素的输出要素数量设置上限。 max_categories 包括组合不常见类别的功能。

在下面的例子中, OrdinalEncoder 分类 'dog''snake' 被认为不常见::

>>> X = np.array([['dog'] * 5 + ['cat'] * 20 + ['rabbit'] * 10 +
...               ['snake'] * 3], dtype=object).T
>>> enc = preprocessing.OrdinalEncoder(min_frequency=6).fit(X)
>>> enc.infrequent_categories_
[array(['dog', 'snake'], dtype=object)]
>>> enc.transform(np.array([['dog'], ['cat'], ['rabbit'], ['snake']]))
array([[2.],
       [0.],
       [1.],
       [2.]])

OrdinalEncodermax_categoriesnot 考虑到缺失或未知的类别。设置 unknown_valueencoded_missing_value 将唯一的整数代码增加一个。这可能会导致高达 max_categories + 2 integer代码。在下面的示例中,“a”和“d”被认为不常见并被分组到单个类别中,“b”和“c”是它们自己的类别,未知值编码为3,缺失值编码为4。

>>> X_train = np.array(
...     [["a"] * 5 + ["b"] * 20 + ["c"] * 10 + ["d"] * 3 + [np.nan]],
...     dtype=object).T
>>> enc = preprocessing.OrdinalEncoder(
...     handle_unknown="use_encoded_value", unknown_value=3,
...     max_categories=3, encoded_missing_value=4)
>>> _ = enc.fit(X_train)
>>> X_test = np.array([["a"], ["b"], ["c"], ["d"], ["e"], [np.nan]], dtype=object)
>>> enc.transform(X_test)
array([[2.],
       [0.],
       [1.],
       [2.],
       [3.],
       [4.]])

同样, OneHotEncoder 可以配置为将不常见的类别分组在一起::

>>> enc = preprocessing.OneHotEncoder(min_frequency=6, sparse_output=False).fit(X)
>>> enc.infrequent_categories_
[array(['dog', 'snake'], dtype=object)]
>>> enc.transform(np.array([['dog'], ['cat'], ['rabbit'], ['snake']]))
array([[0., 0., 1.],
       [1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])

通过将handle_unknown设置为 'infrequent_if_exist' ,未知类别将被视为不常见::

>>> enc = preprocessing.OneHotEncoder(
...    handle_unknown='infrequent_if_exist', sparse_output=False, min_frequency=6)
>>> enc = enc.fit(X)
>>> enc.transform(np.array([['dragon']]))
array([[0., 0., 1.]])

OneHotEncoder.get_feature_names_out 使用“不频繁”作为不频繁功能名称::

>>> enc.get_feature_names_out()
array(['x0_cat', 'x0_rabbit', 'x0_infrequent_sklearn'], dtype=object)

'handle_unknown' 设置为 'infrequent_if_exist' 变形中遇到未知类别:

  1. 如果未配置非频繁类别支持或训练期间不存在非频繁类别,则此功能的生成一次性编码列将全为零。在逆变换中,未知类别将被表示为 None .

  2. 如果训练期间存在不常见的类别,则未知类别将被视为不常见。在逆变换中,“infrequent_sklearn”将用于表示不频繁类别。

不常见的类别也可以使用配置 max_categories .在下面的例子中,我们设置 max_categories=2 以限制输出中的要素数量。这将导致除了 'cat' 类别被认为不常见,导致两个功能,一个是 'cat' 一个用于不常见的类别-这是所有其他类别::

>>> enc = preprocessing.OneHotEncoder(max_categories=2, sparse_output=False)
>>> enc = enc.fit(X)
>>> enc.transform([['dog'], ['cat'], ['rabbit'], ['snake']])
array([[0., 1.],
       [1., 0.],
       [0., 1.],
       [0., 1.]])

If both max_categories and min_frequency are non-default values, then categories are selected based on min_frequency first and max_categories categories are kept. In the following example, min_frequency=4 considers only snake to be infrequent, but max_categories=3, forces dog to also be infrequent:

>>> enc = preprocessing.OneHotEncoder(min_frequency=4, max_categories=3, sparse_output=False)
>>> enc = enc.fit(X)
>>> enc.transform([['dog'], ['cat'], ['rabbit'], ['snake']])
array([[0., 0., 1.],
       [1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])

如果在截止点有相同基数的罕见类别 max_categories ,然后是第一个 max_categories 是根据词典顺序进行的。在下面的示例中,“b”、“c”和“d”具有相同的基数并且具有 max_categories=2 、“b”和“c”不常见,因为它们具有更高的词典顺序。

>>> X = np.asarray([["a"] * 20 + ["b"] * 10 + ["c"] * 10 + ["d"] * 10], dtype=object).T
>>> enc = preprocessing.OneHotEncoder(max_categories=3).fit(X)
>>> enc.infrequent_categories_
[array(['b', 'c'], dtype=object)]

7.3.4.2. 目标编码器#

TargetEncoder 使用以类别特征为条件的目标平均值来编码无序类别,即名义类别 [PAR] [MIC] .这种编码方案对于具有高基数的分类特征很有用,其中一热编码会膨胀特征空间,使下游模型的处理成本更高。高基数类别的典型例子是基于位置的,例如邮政编码或地区。

二元分类目标#

对于二进制分类目标,目标编码由下式给出:

\[S_i = \lambda_i\frac{n_{iY}}{n_i} + (1 - \lambda_i)\frac{n_Y}{n}\]

哪里 \(S_i\) 是类别的编码 \(i\) , \(n_{iY}\) 是观察次数 \(Y=1\) 和类别 \(i\) , \(n_i\) 是具有类别的观察数 \(i\) , \(n_Y\) 是观察次数 \(Y=1\) , \(n\) 是观察次数,并且 \(\lambda_i\) 是类别的萎缩因素 \(i\) .收缩系数由下式给出:

\[\拉姆达_i = \fRAC{n_i}{m + n_i}\]

where \(m\) is a smoothing factor, which is controlled with the smooth parameter in TargetEncoder. Large smoothing factors will put more weight on the global mean. When smooth="auto", the smoothing factor is computed as an empirical Bayes estimate: \(m=\sigma_i^2/\tau^2\), where \(\sigma_i^2\) is the variance of y with category \(i\) and \(\tau^2\) is the global variance of y.

多类分类目标#

对于多类分类目标,公式类似于二元分类:

\[S_{ij} = \lambda_i\frac{n_{iY_j}}{n_i} + (1 - \lambda_i)\frac{n_{Y_j}}{n}\]

哪里 \(S_{ij}\) 是类别的编码 \(i\) 和阶级 \(j\) , \(n_{iY_j}\) 是观察次数 \(Y=j\) 和类别 \(i\) , \(n_i\) 是具有类别的观察数 \(i\) , \(n_{Y_j}\) 是观察次数 \(Y=j\) , \(n\) 是观察次数,并且 \(\lambda_i\) 是类别的萎缩因素 \(i\) .

持续目标#

对于连续目标,公式类似于二元分类:

\[S_i = \lambda_i\frac{\sum_{k\in L_i}Y_k}{n_i} + (1 - \lambda_i)\frac{\sum_{k=1}^{n}Y_k}{n}\]

哪里 \(L_i\) 是具有类别的观测集 \(i\)\(n_i\) 是具有类别的观察数 \(i\) .

fit_transform 内部依赖于 cross fitting 防止目标信息泄露到训练时表示中的计划,特别是对于非信息性高基数的分类变量,并帮助防止下游模型过度匹配虚假相关性。请注意,因此, fit(X, y).transform(X) 不等于 fit_transform(X, y) .在 fit_transform ,训练数据被分成 k 褶皱(由 cv 参数),并且每个折叠都使用另一个学习的编码进行编码 k-1 折叠。下图显示 cross fitting 方案 fit_transform 使用默认 cv=5 :

../_images/target_encoder_cross_validation.svg

fit_transform 还使用整个训练集学习“全数据”编码。这从未用于 fit_transform 但保存到属性 encodings_ ,用于当 transform 被称为。请注意,在 cross fitting 方案不会保存到属性。

fit 方法并 not 使用任何 cross fitting 在整个训练集中规划并学习一种编码,用于对类别进行编码 transform .此编码与中学习的“完整数据”编码相同 fit_transform .

备注

TargetEncoder 考虑缺失的值,例如 np.nanNone ,作为另一个类别,并像任何其他类别一样对它们进行编码。期间未看到的类别 fit 用目标均值编码,即 target_mean_ .

示例

引用

7.3.5. 离散化#

Discretization (也称为量化或分类)提供了一种将连续特征划分为离散值的方法。某些具有连续特征的数据集可能会受益于离散化,因为离散化可以将连续属性的数据集转换为仅具有名义属性的数据集。

独热编码的离散化特征可以使模型更具表达力,同时保持可解释性。例如,使用离散化器进行预处理可能会将非线性引入线性模型。有关更高级的可能性,特别是平滑的可能性,请参见 生成多项特征 进一步在下面。

7.3.5.1. K箱离散化#

KBinsDiscretizer 将要素离散化为 k bins::

>>> X = np.array([[ -3., 5., 15 ],
...               [  0., 6., 14 ],
...               [  6., 3., 11 ]])
>>> est = preprocessing.KBinsDiscretizer(n_bins=[3, 2, 2], encode='ordinal').fit(X)

默认情况下,输出被单热编码为稀疏矩阵(请参阅 编码类别特征 )并且可以使用 encode 参数.对于每个要素,在 fit 它们将与箱的数量一起定义间隔。因此,对于当前示例,这些间隔定义为:

  • 特征1: \({[-\infty, -1), [-1, 2), [2, \infty)}\)

  • 特征二: \({[-\infty, 5), [5, \infty)}\)

  • 特征三: \({[-\infty, 14), [14, \infty)}\)

根据这些垃圾箱间隔, X 转换如下:

>>> est.transform(X)
array([[ 0., 1., 1.],
       [ 1., 1., 1.],
       [ 2., 0., 0.]])

生成的数据集包含序数属性,这些属性可进一步用于 Pipeline .

离散化类似于为连续数据构建直方图。然而,直方图侧重于对落入特定箱的特征进行计数,而离散化侧重于将特征值分配给这些箱。

KBinsDiscretizer 实现不同的分类策略,可以使用 strategy 参数.“均匀”策略使用恒定宽度的箱。“分位数”策略使用分位数值在每个要素中具有相等填充的箱。“kmeans”策略基于对每个特征独立执行的k-means集群过程定义了箱。

请注意,可以通过将定义离散化策略的可调用传递给来指定自定义箱 FunctionTransformer .例如,我们可以使用Pandas函数 pandas.cut

>>> import pandas as pd
>>> import numpy as np
>>> from sklearn import preprocessing
>>>
>>> bins = [0, 1, 13, 20, 60, np.inf]
>>> labels = ['infant', 'kid', 'teen', 'adult', 'senior citizen']
>>> transformer = preprocessing.FunctionTransformer(
...     pd.cut, kw_args={'bins': bins, 'labels': labels, 'retbins': False}
... )
>>> X = np.array([0.2, 2, 15, 25, 97])
>>> transformer.fit_transform(X)
['infant', 'kid', 'teen', 'adult', 'senior citizen']
Categories (5, object): ['infant' < 'kid' < 'teen' < 'adult' < 'senior citizen']

示例

  • sphx_glr_auto_examples_preprocessing_plot_discretization.py

  • sphx_glr_auto_examples_preprocessing_plot_discretization_classification.py

  • sphx_glr_auto_examples_preprocessing_plot_discretization_strategies.py

7.3.5.2. 特征二进制化#

Feature binarization 的过程是 thresholding numerical features to get boolean values .这对于假设输入数据根据多变量分布的下游概率估计器来说可能很有用 Bernoulli distribution .例如, BernoulliRBM .

文本处理社区中使用二进制特征值(可能是为了简化概率推理)也很常见,即使规范化计数(又名术语频率)或TF-IDF值的特征在实践中通常表现得稍好一些。

至于 Normalizer 、公用事业类 Binarizer 旨在在早期阶段使用 Pipeline .的 fit 方法不起任何作用,因为每个样本都独立于其他样本处理::

>>> X = [[ 1., -1.,  2.],
...      [ 2.,  0.,  0.],
...      [ 0.,  1., -1.]]

>>> binarizer = preprocessing.Binarizer().fit(X)  # fit does nothing
>>> binarizer
Binarizer()

>>> binarizer.transform(X)
array([[1., 0., 1.],
       [1., 0., 0.],
       [0., 1., 0.]])

可以调整二进制化器的阈值::

>>> binarizer = preprocessing.Binarizer(threshold=1.1)
>>> binarizer.transform(X)
array([[0., 0., 1.],
       [1., 0., 0.],
       [0., 0., 0.]])

至于 Normalizer 类中,预处理模块提供了伴随函数 binarize 当不需要Transformer API时使用。

注意到 Binarizer 类似于 KBinsDiscretizerk = 2 ,以及当垃圾箱边缘处于该值时 threshold .

7.3.6. 插补缺失值#

用于估算缺失值的工具在 插补缺失值 .

7.3.7. 生成多项特征#

通常,通过考虑输入数据的非线性特征来增加模型的复杂性是有用的。我们展示了两种都基于多边形的可能性:第一种使用纯多边形,第二种使用样条线,即分段多边形。

7.3.7.1. 多项式特征#

一种简单且常用的方法是多项特征,它可以获取特征的高级项和交互项。执行这项要求的具体 PolynomialFeatures

>>> import numpy as np
>>> from sklearn.preprocessing import PolynomialFeatures
>>> X = np.arange(6).reshape(3, 2)
>>> X
array([[0, 1],
       [2, 3],
       [4, 5]])
>>> poly = PolynomialFeatures(2)
>>> poly.fit_transform(X)
array([[ 1.,  0.,  1.,  0.,  0.,  1.],
       [ 1.,  2.,  3.,  4.,  6.,  9.],
       [ 1.,  4.,  5., 16., 20., 25.]])

X的功能已从 \((X_1, X_2)\)\((1, X_1, X_2, X_1^2, X_1X_2, X_2^2)\) .

在某些情况下,仅需要要素之间的交互项,并且可以通过设置获得 interaction_only=True

>>> X = np.arange(9).reshape(3, 3)
>>> X
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])
>>> poly = PolynomialFeatures(degree=3, interaction_only=True)
>>> poly.fit_transform(X)
array([[  1.,   0.,   1.,   2.,   0.,   0.,   2.,   0.],
       [  1.,   3.,   4.,   5.,  12.,  15.,  20.,  60.],
       [  1.,   6.,   7.,   8.,  42.,  48.,  56., 336.]])

X的功能已从 \((X_1, X_2, X_3)\)\((1, X_1, X_2, X_3, X_1X_2, X_1X_3, X_2X_3, X_1X_2X_3)\) .

请注意,在 kernel methods (e.g., SVC , KernelPCA )使用多项时 核函数 .

看到 多项和样条插值 使用创建的多项特征进行岭回归。

7.3.7.2. 样条Transformer#

添加非线性项而不是纯特征项的另一种方法是用 SplineTransformer .样条是分段多项式,由其多项式次数和节点位置参数化。的 SplineTransformer 实现B样条基,参见下面的参考。

备注

SplineTransformer 单独对待每个功能,即它不会为您提供交互条款。

样条函数相对于多项式的一些优点是:

  • 如果保持固定的低度数(通常为3)并节俭地调整结的数量,B样条线非常灵活和稳健。多项运算需要更高的度数,这就引出了下一个问题。

  • B样条在边界处不具有像多项那样的振荡行为(次数越高,越差)。这被称为 Runge's phenomenon .

  • B样条为超出边界(即超出匹配值范围)的外推提供了良好的选择。看看选项 extrapolation .

  • B样条生成具有带状结构的特征矩阵。对于单个要素,每一行仅包含 degree + 1 非零元素,连续出现,甚至是积极的。这导致具有良好数值特性的矩阵,例如低条件数,与多项式矩阵形成鲜明对比,多项式矩阵的名称为 Vandermonde matrix .低条件数对于线性模型的稳定算法很重要。

下面的代码片段显示了样条线的作用::

>>> import numpy as np
>>> from sklearn.preprocessing import SplineTransformer
>>> X = np.arange(5).reshape(5, 1)
>>> X
array([[0],
       [1],
       [2],
       [3],
       [4]])
>>> spline = SplineTransformer(degree=2, n_knots=3)
>>> spline.fit_transform(X)
array([[0.5  , 0.5  , 0.   , 0.   ],
       [0.125, 0.75 , 0.125, 0.   ],
       [0.   , 0.5  , 0.5  , 0.   ],
       [0.   , 0.125, 0.75 , 0.125],
       [0.   , 0.   , 0.5  , 0.5  ]])

X 排序后,可以很容易地看到带状矩阵输出。只有三条中间对角线为非零 degree=2 .度数越高,样条线的重叠越多。

有趣的是, SplineTransformerdegree=0 是一样的 KBinsDiscretizerencode='onehot-dense'n_bins = n_knots - 1 如果 knots = strategy .

示例

引用#

7.3.8. 定制变压器#

通常,您会想要将现有的Python函数转换为Transformer,以协助数据清理或处理。您可以从任意函数实现Transformer FunctionTransformer .例如,要构建在管道中应用日志转换的Transformer,请执行::

>>> import numpy as np
>>> from sklearn.preprocessing import FunctionTransformer
>>> transformer = FunctionTransformer(np.log1p, validate=True)
>>> X = np.array([[0, 1], [2, 3]])
>>> # Since FunctionTransformer is no-op during fit, we can call transform directly
>>> transformer.transform(X)
array([[0.        , 0.69314718],
       [1.09861229, 1.38629436]])

您可以确保 funcinverse_func 通过设置彼此相反 check_inverse=True 和调用 fit 之前 transform .请注意,系统会发出警告,并可能会通过 filterwarnings

>>> import warnings
>>> warnings.filterwarnings("error", message=".*check_inverse*.",
...                         category=UserWarning, append=False)

有关演示使用 FunctionTransformer 要从文本数据中提取要素,请参阅 具有异类数据源的列Transformer与时间相关的特征工程 .