比较不同缩放器对包含异常值的数据的影响#

加州住房数据集 的特征 0(街区的中位数收入)和特征 5(平均住房占用率)具有非常不同的尺度,并且包含一些非常大的异常值。这两个特点导致难以可视化数据,更重要的是,它们会降低许多机器学习算法的预测性能。未缩放的数据也会减慢甚至阻止许多基于梯度的估计器的收敛。

事实上,许多估计器是在每个特征取值接近零或更重要的是所有特征在可比尺度上变化的假设下设计的。特别是,基于度量和基于梯度的估计器通常假设近似标准化数据(以单位方差为中心的特征)。一个值得注意的例外是基于决策树的估计器,它们对数据的任意缩放具有鲁棒性。

此示例使用不同的缩放器、转换器和归一化器将数据带入预定义的范围。

缩放器是线性(或更准确地说,仿射)转换器,它们在估计用于平移和缩放每个特征的参数的方式上有所不同。

QuantileTransformer 提供非线性变换,其中边缘异常值和内点的距离被缩小。 PowerTransformer 提供非线性变换,其中数据被映射到正态分布,以稳定方差并最小化偏度。

与之前的变换不同,归一化指的是每个样本的变换,而不是每个特征的变换。

以下代码有点冗长,请随意直接跳到对 结果 的分析。

# Author:  Raghav RV <[email protected]>
#          Guillaume Lemaitre <[email protected]>
#          Thomas Unterthiner
# License: BSD 3 clause

import matplotlib as mpl
import numpy as np
from matplotlib import cm
from matplotlib import pyplot as plt

from sklearn.datasets import fetch_california_housing
from sklearn.preprocessing import (
    MaxAbsScaler,
    MinMaxScaler,
    Normalizer,
    PowerTransformer,
    QuantileTransformer,
    RobustScaler,
    StandardScaler,
    minmax_scale,
)

dataset = fetch_california_housing()
X_full, y_full = dataset.data, dataset.target
feature_names = dataset.feature_names

feature_mapping = {
    "MedInc": "Median income in block",
    "HouseAge": "Median house age in block",
    "AveRooms": "Average number of rooms",
    "AveBedrms": "Average number of bedrooms",
    "Population": "Block population",
    "AveOccup": "Average house occupancy",
    "Latitude": "House block latitude",
    "Longitude": "House block longitude",
}

# Take only 2 features to make visualization easier
# Feature MedInc has a long tail distribution.
# Feature AveOccup has a few but very large outliers.
features = ["MedInc", "AveOccup"]
features_idx = [feature_names.index(feature) for feature in features]
X = X_full[:, features_idx]
distributions = [
    ("Unscaled data", X),
    ("Data after standard scaling", StandardScaler().fit_transform(X)),
    ("Data after min-max scaling", MinMaxScaler().fit_transform(X)),
    ("Data after max-abs scaling", MaxAbsScaler().fit_transform(X)),
    (
        "Data after robust scaling",
        RobustScaler(quantile_range=(25, 75)).fit_transform(X),
    ),
    (
        "Data after power transformation (Yeo-Johnson)",
        PowerTransformer(method="yeo-johnson").fit_transform(X),
    ),
    (
        "Data after power transformation (Box-Cox)",
        PowerTransformer(method="box-cox").fit_transform(X),
    ),
    (
        "Data after quantile transformation (uniform pdf)",
        QuantileTransformer(
            output_distribution="uniform", random_state=42
        ).fit_transform(X),
    ),
    (
        "Data after quantile transformation (gaussian pdf)",
        QuantileTransformer(
            output_distribution="normal", random_state=42
        ).fit_transform(X),
    ),
    ("Data after sample-wise L2 normalizing", Normalizer().fit_transform(X)),
]

# scale the output between 0 and 1 for the colorbar
y = minmax_scale(y_full)

# plasma does not exist in matplotlib < 1.5
cmap = getattr(cm, "plasma_r", cm.hot_r)


def create_axes(title, figsize=(16, 6)):
    fig = plt.figure(figsize=figsize)
    fig.suptitle(title)

    # define the axis for the first plot
    left, width = 0.1, 0.22
    bottom, height = 0.1, 0.7
    bottom_h = height + 0.15
    left_h = left + width + 0.02

    rect_scatter = [left, bottom, width, height]
    rect_histx = [left, bottom_h, width, 0.1]
    rect_histy = [left_h, bottom, 0.05, height]

    ax_scatter = plt.axes(rect_scatter)
    ax_histx = plt.axes(rect_histx)
    ax_histy = plt.axes(rect_histy)

    # define the axis for the zoomed-in plot
    left = width + left + 0.2
    left_h = left + width + 0.02

    rect_scatter = [left, bottom, width, height]
    rect_histx = [left, bottom_h, width, 0.1]
    rect_histy = [left_h, bottom, 0.05, height]

    ax_scatter_zoom = plt.axes(rect_scatter)
    ax_histx_zoom = plt.axes(rect_histx)
    ax_histy_zoom = plt.axes(rect_histy)

    # define the axis for the colorbar
    left, width = width + left + 0.13, 0.01

    rect_colorbar = [left, bottom, width, height]
    ax_colorbar = plt.axes(rect_colorbar)

    return (
        (ax_scatter, ax_histy, ax_histx),
        (ax_scatter_zoom, ax_histy_zoom, ax_histx_zoom),
        ax_colorbar,
    )


def plot_distribution(axes, X, y, hist_nbins=50, title="", x0_label="", x1_label=""):
    ax, hist_X1, hist_X0 = axes

    ax.set_title(title)
    ax.set_xlabel(x0_label)
    ax.set_ylabel(x1_label)

    # The scatter plot
    colors = cmap(y)
    ax.scatter(X[:, 0], X[:, 1], alpha=0.5, marker="o", s=5, lw=0, c=colors)

    # Removing the top and the right spine for aesthetics
    # make nice axis layout
    ax.spines["top"].set_visible(False)
    ax.spines["right"].set_visible(False)
    ax.get_xaxis().tick_bottom()
    ax.get_yaxis().tick_left()
    ax.spines["left"].set_position(("outward", 10))
    ax.spines["bottom"].set_position(("outward", 10))

    # Histogram for axis X1 (feature 5)
    hist_X1.set_ylim(ax.get_ylim())
    hist_X1.hist(
        X[:, 1], bins=hist_nbins, orientation="horizontal", color="grey", ec="grey"
    )
    hist_X1.axis("off")

    # Histogram for axis X0 (feature 0)
    hist_X0.set_xlim(ax.get_xlim())
    hist_X0.hist(
        X[:, 0], bins=hist_nbins, orientation="vertical", color="grey", ec="grey"
    )
    hist_X0.axis("off")

将为每个缩放器/归一化器/转换器显示两个图。左图将显示完整数据集的散点图,而右图将排除极值,仅考虑 99% 的数据集,排除边缘异常值。此外,每个特征的边缘分布将显示在散点图的侧面。

def make_plot(item_idx):
    title, X = distributions[item_idx]
    ax_zoom_out, ax_zoom_in, ax_colorbar = create_axes(title)
    axarr = (ax_zoom_out, ax_zoom_in)
    plot_distribution(
        axarr[0],
        X,
        y,
        hist_nbins=200,
        x0_label=feature_mapping[features[0]],
        x1_label=feature_mapping[features[1]],
        title="Full data",
    )

    # zoom-in
    zoom_in_percentile_range = (0, 99)
    cutoffs_X0 = np.percentile(X[:, 0], zoom_in_percentile_range)
    cutoffs_X1 = np.percentile(X[:, 1], zoom_in_percentile_range)

    non_outliers_mask = np.all(X > [cutoffs_X0[0], cutoffs_X1[0]], axis=1) & np.all(
        X < [cutoffs_X0[1], cutoffs_X1[1]], axis=1
    )
    plot_distribution(
        axarr[1],
        X[non_outliers_mask],
        y[non_outliers_mask],
        hist_nbins=50,
        x0_label=feature_mapping[features[0]],
        x1_label=feature_mapping[features[1]],
        title="Zoom-in",
    )

    norm = mpl.colors.Normalize(y_full.min(), y_full.max())
    mpl.colorbar.ColorbarBase(
        ax_colorbar,
        cmap=cmap,
        norm=norm,
        orientation="vertical",
        label="Color mapping for values of y",
    )

原始数据#

每个变换都绘制了两个变换后的特征,左图显示了整个数据集,右图放大显示了没有边缘异常值的数据集。绝大多数样本被压缩到特定范围内,中位数收入为 [0, 10],平均住房占用率为 [0, 6]。请注意,存在一些边缘异常值(一些街区的平均占用率超过 1200)。因此,根据应用的不同,特定的预处理可能非常有益。在下文中,我们将介绍这些预处理方法在存在边缘异常值时的某些见解和行为。

make_plot(0)
Unscaled data, Full data, Zoom-in

StandardScaler#

StandardScaler 去除均值并将数据缩放到单位方差。缩放会缩小特征值的范围,如以下左图所示。但是,异常值在计算经验均值和标准差时会产生影响。特别要注意,由于每个特征上的异常值具有不同的幅度,因此变换后数据的每个特征的分布非常不同:大多数数据位于变换后的中位数收入特征的 [-2, 4] 范围内,而相同的数据被压缩到变换后的平均住房占用率的较小范围 [-0.2, 0.2] 内。

因此,StandardScaler 无法保证在存在异常值的情况下特征尺度平衡。

make_plot(1)
Data after standard scaling, Full data, Zoom-in

MinMaxScaler#

MinMaxScaler 对数据集进行重新缩放,使所有特征值都在 [0, 1] 范围内,如以下右图所示。但是,这种缩放会将所有内点压缩到变换后的平均住房占用率的狭窄范围 [0, 0.005] 内。

StandardScalerMinMaxScaler 对异常值的存在非常敏感。

make_plot(2)
Data after min-max scaling, Full data, Zoom-in

MaxAbsScaler#

MaxAbsScalerMinMaxScaler 类似,只是值被映射到多个范围内,具体取决于是否存在负值或正值。如果只存在正值,则范围为 [0, 1]。如果只存在负值,则范围为 [-1, 0]。如果同时存在负值和正值,则范围为 [-1, 1]。在仅包含正值的数据上,MinMaxScalerMaxAbsScaler 的行为类似。因此,MaxAbsScaler 也受到大型异常值的影响。

make_plot(3)
Data after max-abs scaling, Full data, Zoom-in

RobustScaler#

与之前的缩放器不同,RobustScaler 的中心化和缩放统计数据基于百分位数,因此不受少量极端离群值的影响。因此,转换后的特征值的范围比之前的缩放器更大,更重要的是,它们大致相似:对于这两个特征,大多数转换后的值都位于 [-2, 3] 范围内,如放大图所示。请注意,离群值本身仍然存在于转换后的数据中。如果需要单独的离群值裁剪,则需要非线性变换(见下文)。

make_plot(4)
Data after robust scaling, Full data, Zoom-in

PowerTransformer#

PowerTransformer 对每个特征应用幂变换,使数据更接近高斯分布,以稳定方差并最小化偏度。目前支持 Yeo-Johnson 和 Box-Cox 变换,两种方法都通过最大似然估计确定最佳缩放因子。默认情况下,PowerTransformer 应用零均值、单位方差归一化。请注意,Box-Cox 只能应用于严格正数数据。收入和平均房屋占用率恰好是严格正数,但如果存在负值,则首选 Yeo-Johnson 变换。

make_plot(5)
make_plot(6)
  • Data after power transformation (Yeo-Johnson), Full data, Zoom-in
  • Data after power transformation (Box-Cox), Full data, Zoom-in

QuantileTransformer(均匀输出)#

QuantileTransformer 应用非线性变换,使每个特征的概率密度函数映射到均匀或高斯分布。在这种情况下,所有数据(包括离群值)都将映射到范围为 [0, 1] 的均匀分布,使离群值与内点无法区分。

RobustScalerQuantileTransformer 对离群值具有鲁棒性,因为在训练集中添加或删除离群值将产生大致相同的变换。但与 RobustScaler 相反,QuantileTransformer 还会自动将任何离群值压缩到预定义的范围边界(0 和 1)。这可能会导致极端值的饱和伪影。

make_plot(7)
Data after quantile transformation (uniform pdf), Full data, Zoom-in

QuantileTransformer(高斯输出)#

要映射到高斯分布,请设置参数 output_distribution='normal'

make_plot(8)
Data after quantile transformation (gaussian pdf), Full data, Zoom-in

Normalizer#

Normalizer 对每个样本的向量进行重新缩放,使其具有单位范数,独立于样本的分布。这可以在下面的两个图中看到,其中所有样本都映射到单位圆上。在我们的示例中,两个选定的特征只有正值;因此,转换后的数据只位于正象限。如果某些原始特征具有正值和负值的混合,则情况并非如此。

make_plot(9)

plt.show()
Data after sample-wise L2 normalizing, Full data, Zoom-in

脚本的总运行时间:(0 分钟 9.245 秒)

相关示例

将数据映射到正态分布

将数据映射到正态分布

离群值检测估计器的评估

离群值检测估计器的评估

Theil-Sen 回归

Theil-Sen 回归

特征缩放的重要性

特征缩放的重要性

由 Sphinx-Gallery 生成的图库