数据标准化与归一化

数据分析专题 · 统一的数值尺度

专题:Python数据分析系统学习

关键词:数据分析, 标准化, 归一化, StandardScaler, MinMaxScaler, RobustScaler, Normalizer, Box-Cox

一、为什么需要特征缩放

在机器学习和数据分析中,原始数据的不同特征往往具有不同的量纲和数值范围。例如,年龄的取值范围是0-100,而年收入的取值范围可能是0-10^6,房屋面积则是几十到几百平方米。当这些量纲不一致的特征直接输入模型时,数值范围较大的特征会主导模型的训练过程,导致模型对量纲敏感。

特征缩放(Feature Scaling)正是为了解决这一问题而存在。它通过数学变换将所有特征统一到相似的尺度上,使得每个特征对模型的贡献更加公平。这不仅提升了模型收敛速度(特别是对于梯度下降类算法),还能显著提高模型性能和预测精度。

核心概念:特征缩放主要分为两大类——标准化(Standardization)归一化(Normalization)。标准化将数据变换为均值为0、标准差为1的分布;归一化则将数据缩放到一个固定的数值区间,通常是[0,1]或[-1,1]。两者在数学原理和适用场景上有明显区别,需要根据具体问题选择合适的缩放方法。

提示:需要特征缩放的常见算法包括:SVM、K-Means、KNN、PCA、逻辑回归、神经网络、线性回归等。而基于树的模型(如决策树、随机森林、XGBoost)通常对特征尺度不敏感,可以不进行缩放。

下面我们详细讲解Python中六种最常用的特征缩放方法,包括它们的数学原理、适用场景和完整代码示例。

二、Z-score标准化(StandardScaler)

2.1 数学原理

Z-score标准化是最常用的标准化方法,其核心思想是将原始数据变换为均值为0、标准差为1的分布,转换公式为:

z = (x - μ) / σ

其中 μ 为均值,σ 为标准差

这种变换假设数据近似服从高斯分布(正态分布)。当数据满足这一假设时,标准化后的数据约有68%的值落在[-1,1]区间,约95%的值落在[-2,2]区间,约99.7%的值落在[-3,3]区间。实际应用中对数据分布的假设可以适度放宽,但严重偏态的数据会降低标准化的效果。

2.2 代码实现

from sklearn.preprocessing import StandardScaler import numpy as np # 原始数据:不同量纲的特征 data = np.array([ [25, 50000, 80], # 年龄25,收入50000,身高80kg [30, 80000, 75], [45, 120000, 85], [22, 35000, 65], [35, 95000, 90] ]) # 创建标准化器 scaler = StandardScaler() # 拟合并转换 scaled_data = scaler.fit_transform(data) print("原始数据 均值:", data.mean(axis=0)) print("原始数据 标准差:", data.std(axis=0)) print("标准化后 均值:", scaled_data.mean(axis=0).round(6)) print("标准化后 标准差:", scaled_data.std(axis=0).round(6)) print("\n标准化结果:\n", scaled_data.round(4))

2.3 输出解析

运行上述代码后,标准化后的数据每一列的均值将接近于0(由于浮点精度,实际为10^-15量级),标准差为1。每个数值表示该样本距离均值有多少个标准差。例如,标准化后值为1.5的样本表示该样本的原始值比均值高1.5个标准差。

适用场景:当数据近似服从正态分布时效果最佳;适用于大部分机器学习模型,尤其是SVM、PCA、逻辑回归等;是默认的标准化方法。

注意事项:对异常值敏感(异常值会显著影响均值和标准差的计算),因此如果数据中存在大量异常值,建议使用RobustScaler。

2.4 Gaussian假设的重要性

StandardScaler的Gaussian假设源自于Z-score变换的统计基础。当原始数据服从或近似服从正态分布时,标准化后的数据能够更好地保留原始数据的分布形态,同时使得数据在统计推断中具有更好的性质。如果原始数据严重偏态,可以考虑先进行幂变换(如Box-Cox变换)使其接近正态分布,再进行标准化。

三、MinMax归一化(MinMaxScaler)

3.1 数学原理

MinMax归一化通过线性变换将数据缩放到[0,1]区间内,保留原始数据的分布形状。转换公式为:

x_scaled = (x - x_min) / (x_max - x_min)

当指定feature_range=(min, max)时,公式变为:x_scaled = x_std * (max - min) + min。这可以灵活地将数据映射到任意区间,如[-1,1]或[0.5,1.5]等。

3.2 代码实现

from sklearn.preprocessing import MinMaxScaler import numpy as np data = np.array([ [25, 50000, 80], [30, 80000, 75], [45, 120000, 85], [22, 35000, 65], [35, 95000, 90] ]) # 默认缩放到 [0, 1] scaler = MinMaxScaler() scaled_data = scaler.fit_transform(data) print("每个特征的最小值:", scaler.data_min_) print("每个特征的最大值:", scaler.data_max_) print("\nMinMax归一化结果 [0,1]:\n", scaled_data.round(4)) # 缩放到 [-1, 1] scaler2 = MinMaxScaler(feature_range=(-1, 1)) scaled_data2 = scaler2.fit_transform(data) print("\nMinMax归一化结果 [-1,1]:\n", scaled_data2.round(4))

3.3 保留原始分布形状

MinMax归一化是一种线性变换,它完全保留了原始数据的相对大小关系和分布形状。如果原始数据呈偏态分布,归一化后的数据仍然呈相同的偏态分布,只是数值范围被压缩到了[0,1]区间。这一特性使得MinMax归一化特别适用于需要保留原始数据分布形态的场景,如图像处理中的像素值归一化。

适用场景:数据分布有明确边界时效果最佳;适合神经网络输入层(激活函数通常输出[0,1]或[-1,1]);适合计算机视觉中像素值归一化;当算法假设数据在有限区间内时(如图像处理)。

注意事项:对异常值极度敏感——单个极端大值会使其他数据压缩到非常狭窄的区间;当测试集的最大/最小值超出训练集的范围时,测试集的变换会超出[0,1]区间。

四、RobustScaler

4.1 数学原理

RobustScaler使用对异常值更加鲁棒的统计量——中位数(median)和四分位距(IQR = Q3 - Q1)进行缩放,而不是使用均值和标准差。转换公式为:

x_scaled = (x - median) / IQR

其中 IQR = Q3 - Q1(第3四分位数减第1四分位数)

由于中位数和IQR不受极端值的影响(无论异常值多大,中位数和分位数基本不变),因此RobustScaler对异常值具有天然的鲁棒性。即使数据中包含大量异常值,RobustScaler仍能给出稳定的缩放结果。

4.2 代码实现

from sklearn.preprocessing import RobustScaler import numpy as np # 构建包含异常值的数据(最后一行的年龄1000和收入999999是异常值) data_with_outliers = np.array([ [25, 50000, 80], [30, 80000, 75], [45, 120000, 85], [22, 35000, 65], [35, 95000, 90], [1000, 999999, 200] # 异常值 ]) # 对比 StandardScaler 和 RobustScaler from sklearn.preprocessing import StandardScaler ss = StandardScaler() rs = RobustScaler() scaled_ss = ss.fit_transform(data_with_outliers) scaled_rs = rs.fit_transform(data_with_outliers) print("StandardScaler 结果:\n", scaled_ss.round(4)) print("\nRobustScaler 结果:\n", scaled_rs.round(4)) print("\nRobustScaler 中心 (中位数):", rs.center_) print("RobustScaler 缩放 (IQR):", rs.scale_)

核心优势:RobustScaler是处理包含异常值的数据集的最佳选择。它不使用均值(受异常值影响大)而使用中位数,不使用标准差(受异常值影响大)而使用IQR,因此即使数据中存在大量极端值,缩放结果依然稳健。

适用场景:数据中存在明显的异常值;金融数据(如收入分布通常有极端高值);传感器数据(可能存在采集异常);任何不适合直接丢弃异常值的场景。

五、MaxAbsScaler

5.1 数学原理

MaxAbsScaler通过除以每个特征的最大绝对值来将数据缩放到[-1,1]区间。转换公式为:

x_scaled = x / max(|x|)

这是一种非常简单的缩放方式,它不移动数据中心(不减去均值或中位数),只做除法缩放。对于稀疏数据(如文本TF-IDF矩阵),这种方式不会破坏数据的稀疏结构,因为0值在变换后仍然是0。

5.2 代码实现

from sklearn.preprocessing import MaxAbsScaler import numpy as np from scipy.sparse import csr_matrix data = np.array([ [ 1.0, -2.0, 0.5], [-0.5, 1.5, -1.0], [ 2.0, -0.5, 1.5], [-1.5, 0.0, -0.5] ]) scaler = MaxAbsScaler() scaled_data = scaler.fit_transform(data) print("每个特征的最大绝对值:", scaler.max_abs_) print("\nMaxAbsScaler 结果 [-1,1]:\n", scaled_data) # 对稀疏数据友好 sparse_data = csr_matrix(data) scaled_sparse = scaler.fit_transform(sparse_data) print("\n稀疏矩阵缩放结果:\n", scaled_sparse.toarray())

适用场景:稀疏数据(如文本数据的TF-IDF矩阵、One-Hot编码特征)——不破坏稀疏结构;数据已以0为中心(如已中心化的数据);所有特征均为非负时缩放到[0,1]。

注意事项:不对数据中心化(不调整均值);对异常值敏感(最大绝对值受异常值影响大);如果数据稀疏且包含负值,缩放后仍保持稀疏性。

六、Normalizer(范数归一化)

6.1 数学原理

Normalizer与前面所有方法不同,它是对行(样本)进行归一化而非对列(特征)进行归一化。它将每个样本(行)缩放到具有单位范数(unit norm),即每个样本向量的长度为1。常用的范数有L1范数和L2范数:

L2范数:||x||₂ = sqrt(Σxi²) 缩放后每个样本的L2范数为1

L1范数:||x||₁ = Σ|xi| 缩放后每个样本的L1范数为1

这种行级归一化使得样本之间的比较侧重于"方向"而非"幅度"(即向量的方向余弦),在文本分类、聚类等任务中非常有用。

6.2 代码实现

from sklearn.preprocessing import Normalizer import numpy as np # 行级归一化:每个样本是一个行向量 data = np.array([ [4, 1, 2, 2], # 样本1:L2范数 = sqrt(16+1+4+4) = 5 [1, 3, 9, 3], # 样本2:L2范数 = sqrt(1+9+81+9) = 10 [5, 7, 5, 1] # 样本3:L2范数 = sqrt(25+49+25+1) = 10 ]) # L2范数归一化(默认) l2_normalizer = Normalizer(norm='l2') data_l2 = l2_normalizer.fit_transform(data) print("L2归一化后各行的L2范数:") print(np.linalg.norm(data_l2, axis=1).round(6)) print("\nL2归一化结果:\n", data_l2.round(4)) # L1范数归一化 l1_normalizer = Normalizer(norm='l1') data_l1 = l1_normalizer.fit_transform(data) print("\nL1归一化后各行的L1范数:") print(np.abs(data_l1).sum(axis=1).round(6)) print("\nL1归一化结果:\n", data_l1.round(4))

关键区别:之前的StandardScaler、MinMaxScaler等都是在"列"(特征)上操作,使每个特征的数值范围统一;而Normalizer是在"行"(样本)上操作,使每个样本的向量长度(范数)为1。

适用场景:文本分类/聚类(TF-IDF的余弦相似度);需要比较样本方向而非大小的场景;使用点积或余弦相似度的算法(如KNN、SVM);高维稀疏数据。

七、幂变换(Power Transform)

幂变换是一类更高级的变换方法,它不仅能缩放数据,还能改变数据的分布形态,使其更接近正态分布或均匀分布。这在处理偏态数据时非常有用。

7.1 QuantileTransformer

QuantileTransformer通过分位数变换将数据映射到指定的分布(均匀分布或正态分布)。它将每个特征的值按照分位数映射到目标分布的对应分位数上,因此变换后的数据会严格遵循目标分布的形状。

映射过程:原始值 → 排序 → 分位数排名 → 目标分布的相应分位数值

from sklearn.preprocessing import QuantileTransformer import numpy as np # 生成偏态数据(指数分布) np.random.seed(42) skewed_data = np.random.exponential(scale=2, size=(1000, 1)) # 变换为均匀分布 qt_uniform = QuantileTransformer( n_quantiles=100, output_distribution='uniform', random_state=42 ) data_uniform = qt_uniform.fit_transform(skewed_data) # 变换为正态分布 qt_normal = QuantileTransformer( n_quantiles=100, output_distribution='normal', random_state=42 ) data_normal = qt_normal.fit_transform(skewed_data) print("原始数据 - 均值: {:.4f}, 标准差: {:.4f}".format( skewed_data.mean(), skewed_data.std())) print("均匀变换后 - 均值: {:.4f}, 标准差: {:.4f}".format( data_uniform.mean(), data_uniform.std())) print("正态变换后 - 均值: {:.4f}, 标准差: {:.4f}".format( data_normal.mean(), data_normal.std()))

特点:可以处理任意分布的数据;变换后的数据严格服从目标分布(均匀或正态);对异常值有一定的鲁棒性(分位数不受极端值影响);但会改变特征之间的线性相关关系。

7.2 Box-Cox变换

Box-Cox是一种参数化的幂变换方法,用于将数据变换到接近正态分布。它适用于严格为正的数据(x > 0),通过最优参数λ实现变换:

Box-Cox 变换公式:

如果 λ ≠ 0: y = (x^λ - 1) / λ

如果 λ = 0: y = ln(x)

λ 通过最大似然估计自动确定最优值

from sklearn.preprocessing import PowerTransformer import numpy as np # Box-Cox变换(要求数据严格为正) data_positive = np.array([ 1, 2, 3, 4, 5, 10, 20, 50, 100, 200, 500, 1000 ]).reshape(-1, 1) boxcox = PowerTransformer(method='box-cox') data_boxcox = boxcox.fit_transform(data_positive) print("Box-Cox 最优 λ:", boxcox.lambdas_[0].round(4)) print("变换后 均值: {:.4f}, 标准差: {:.4f}".format( data_boxcox.mean(), data_boxcox.std()))

7.3 Yeo-Johnson变换

Yeo-Johnson变换是Box-Cox的扩展,它不要求数据为正,可以处理包含零或负值的数据。这使得它在实际应用中更加灵活。

Yeo-Johnson 变换(分段定义):

对于 x >= 0 和 x < 0 分别有不同的变换公式

λ同样通过最大似然估计自动确定

from sklearn.preprocessing import PowerTransformer import numpy as np # Yeo-Johnson可以处理负值 data_with_negatives = np.array([ -10, -5, -1, 0, 1, 2, 3, 5, 10, 20, 50 ]).reshape(-1, 1) yeojohnson = PowerTransformer(method='yeo-johnson') data_yj = yeojohnson.fit_transform(data_with_negatives) print("Yeo-Johnson 最优 λ:", yeojohnson.lambdas_[0].round(4)) print("变换后 均值: {:.4f}, 标准差: {:.4f}".format( data_yj.mean(), data_yj.std())) print("\n原始数据:", data_with_negatives.flatten()) print("变换后数据:", data_yj.flatten().round(4))

Box-Cox vs Yeo-Johnson 选择指南:

  • Box-Cox:数据严格为正(x > 0)时优先选择;统计性质更明确;解释性更好。
  • Yeo-Johnson:数据包含零或负值时的最佳选择;适用范围更广;变换效果与Box-Cox相当。

两者都能自动搜索最优变换参数λ,使变换后的数据尽可能接近正态分布。

八、标准化 vs 归一化:选择指南

标准化和归一化是特征缩放的两大流派,选择哪种方法取决于数据的特性和模型的要求。下表给出了详细的对比和选择建议:

比较维度 标准化(Standardization) 归一化(Normalization)
输出范围 无固定范围(通常约[-3,3]) 固定范围(通常[0,1]或[-1,1])
分布形态 均值为0,标准差为1 保留原始分布形状
对异常值敏感度 敏感(StandardScaler) 敏感(MinMaxScaler)
鲁棒选项 RobustScaler(中位数+IQR) 分位数变换
适用算法 SVM、PCA、逻辑回归、线性回归、神经网络 神经网络(图像)、KNN、K-Means、距离计算
稀疏数据 不适用(破坏稀疏性) MaxAbsScaler(保持稀疏性)
分布假设 建议数据近似正态分布 无分布假设

8.1 推荐选择策略

选择标准化的情况:

  • 算法对数据的分布形态有假设(如线性模型的残差正态性)
  • 不知道数据的具体分布时,标准化是安全的默认选择
  • 数据中存在异常值时,选择RobustScaler
  • 需要进行PCA分析(PCA要求数据以0为中心)

选择归一化的情况:

  • 数据有明确的上下界(如图像像素值0-255)
  • 算法要求输入在固定区间内(如神经网络的Sigmoid/Tanh激活函数)
  • 稀疏数据(选择MaxAbsScaler)
  • 需要保留原始数据的稀疏结构

重要原则:始终在训练集上fit缩放器,然后用同一个缩放器transform训练集和测试集。绝对不要在整个数据集上fit后再划分,这会造成数据泄漏(data leakage),导致模型评估结果过于乐观。

九、Pipeline标准化流程

在实际项目中,特征缩放通常是整个机器学习流程中的一个环节。使用Pipeline可以优雅地将预处理步骤与模型训练串联起来,避免数据泄漏,并简化代码维护。

9.1 基本Pipeline

from sklearn.preprocessing import StandardScaler from sklearn.svm import SVC from sklearn.pipeline import Pipeline from sklearn.model_selection import train_test_split, cross_val_score from sklearn.datasets import make_classification import numpy as np # 生成示例数据 X, y = make_classification( n_samples=1000, n_features=20, n_informative=15, random_state=42 ) # 划分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) # 创建Pipeline:标准化 → SVM pipeline = Pipeline([ ('scaler', StandardScaler()), # 第一步:特征缩放 ('svm', SVC(kernel='rbf')) # 第二步:分类模型 ]) # 训练Pipeline pipeline.fit(X_train, y_train) # 评估 train_score = pipeline.score(X_train, y_train) test_score = pipeline.score(X_test, y_test) print("训练集准确率: {:.4f}".format(train_score)) print("测试集准确率: {:.4f}".format(test_score))

9.2 防止数据泄漏

数据泄漏(Data Leakage)是机器学习中一个非常隐蔽但后果严重的问题。当缩放器使用测试集的信息(如均值和标准差)来变换训练集时,就发生了数据泄漏。这会使模型评估结果过于乐观,模型在实际部署中表现远低于预期。

正确的做法(通过Pipeline自动实现):

  • 特征缩放器只从训练集学习参数(fit)
  • 用训练集学到的参数去变换训练集和测试集
  • 在交叉验证中,每一折都独立进行fit/transform

Pipeline自动保证了每一折交叉验证中,缩放器只使用当前折的训练部分进行fit,然后transform验证部分,完美避免了数据泄漏。

9.3 交叉验证中的Pipeline

from sklearn.preprocessing import StandardScaler from sklearn.svm import SVC from sklearn.pipeline import Pipeline from sklearn.model_selection import cross_val_score pipeline = Pipeline([ ('scaler', StandardScaler()), ('svm', SVC(kernel='rbf')) ]) # 5折交叉验证 - Pipeline自动防止数据泄漏 scores = cross_val_score( pipeline, X_train, y_train, cv=5, scoring='accuracy' ) print("交叉验证准确率: {:.4f} (+/- {:.4f})".format( scores.mean(), scores.std() * 2 ))

9.4 包含多种预处理的复杂Pipeline

from sklearn.preprocessing import ( StandardScaler, MinMaxScaler, RobustScaler, PowerTransformer ) from sklearn.pipeline import Pipeline from sklearn.svm import SVC from sklearn.model_selection import GridSearchCV # 构建包含多种缩放选项的Pipeline pipeline = Pipeline([ ('scaler', StandardScaler()), # 默认使用StandardScaler ('clf', SVC(kernel='rbf')) ]) # 在网格搜索中比较不同缩放器的效果 param_grid = [ { 'scaler': [StandardScaler()], 'clf__C': [0.1, 1, 10], 'clf__gamma': [0.01, 0.1, 1] }, { 'scaler': [MinMaxScaler()], 'clf__C': [0.1, 1, 10], 'clf__gamma': [0.01, 0.1, 1] }, { 'scaler': [RobustScaler()], 'clf__C': [0.1, 1, 10], 'clf__gamma': [0.01, 0.1, 1] }, { 'scaler': [PowerTransformer(method='yeo-johnson')], 'clf__C': [0.1, 1, 10], 'clf__gamma': [0.01, 0.1, 1] } ] grid = GridSearchCV( pipeline, param_grid, cv=5, scoring='accuracy', n_jobs=-1 ) grid.fit(X_train, y_train) print("最佳参数:", grid.best_params_) print("最佳CV准确率: {:.4f}".format(grid.best_score_)) print("测试集准确率: {:.4f}".format(grid.score(X_test, y_test)))

Pipeline的优势:

  • 防止数据泄漏:自动确保每一折独立进行fit/transform
  • 代码整洁:将多个步骤封装为一个估计器,fit/predict/predict_proba一站完成
  • 便于调参:在GridSearchCV中统一搜索预处理参数和模型参数
  • 便于部署:将整个流程保存为单个模型对象(joblib/pickle),部署时无需单独维护缩放器

十、逆变换还原

在实际应用中,我们经常需要将缩放后的数据还原回原始尺度。例如,当我们使用标准化后的数据训练回归模型,预测得到的结果是标准化后的值,我们需要将其逆变换回原始量纲才能解读。sklearn的缩放器都提供了inverse_transform方法用于还原。

10.1 单一步骤逆变换

from sklearn.preprocessing import StandardScaler, MinMaxScaler import numpy as np # 原始数据 original = np.array([[25, 50000], [30, 80000], [45, 120000]]) # 标准化 scaler = StandardScaler() scaled = scaler.fit_transform(original) # 还原 recovered = scaler.inverse_transform(scaled) print("原始数据:\n", original) print("\n标准化后:\n", scaled.round(4)) print("\n逆变换还原:\n", recovered.round(0)) # 确认还原正确 print("\n还原与原值一致:", np.allclose(original, recovered))

10.2 Pipeline中的逆变换

from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LinearRegression from sklearn.pipeline import Pipeline import numpy as np # 示例:房价预测中的逆变换 # 假设目标是房屋价格(原始量纲:万元) X = np.array([[80], [100], [120], [150], [180]]) # 面积(平方米) y = np.array([[200], [280], [350], [450], [520]]) # 价格(万元) # 构建Pipeline:标准化 → 线性回归 pipeline = Pipeline([ ('scaler', StandardScaler()), ('reg', LinearRegression()) ]) pipeline.fit(X, y) # 预测新样本(预测结果仍处于标准化后的尺度) new_X = np.array([[130], [160]]) pred_scaled = pipeline.predict(new_X) # 从Pipeline中获取缩放器进行逆变换 scaler = pipeline.named_steps['scaler'] pred_original = scaler.inverse_transform(pred_scaled) print("预测的标准化值:\n", pred_scaled.round(4)) print("逆变换为原始量纲 (万元):\n", pred_original.round(2))

10.3 自定义封装器的逆变换

from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LinearRegression from sklearn.pipeline import Pipeline # 如果需要对预测目标y也进行缩放 y_scaler = StandardScaler() y_scaled = y_scaler.fit_transform(y) pipeline = Pipeline([ ('scaler', StandardScaler()), ('reg', LinearRegression()) ]) pipeline.fit(X, y_scaled) # 预测并还原 new_X = np.array([[130], [160]]) pred_scaled = pipeline.predict(new_X) pred_original = y_scaler.inverse_transform(pred_scaled) print("预测价格 (万元):", pred_original.flatten().round(2))

逆变换的应用场景:

  • 回归预测:将标准化后的预测值还原为原始量纲(如房价万元、温度摄氏度等)
  • 数据可视化:在原始尺度上展示聚类结果或降维结果
  • 异常检测:将异常分数还原为原始值以提供业务可理解的告警阈值
  • 模型解释:将特征重要性或SHAP值显示的原始特征尺度恢复,便于业务解释

注意:对于QuantileTransformer等非线性变换,逆变换时不具备完美的可逆性——当原始数据中有重复值时,分位数映射会引入一定的信息损失。但对于StandardScaler、MinMaxScaler等线性变换,逆变换是完美的数学可逆过程。

十一、实战案例:完整的数据预处理流程

以下是一个完整的实战案例,将本章所学的各种缩放方法综合应用到一个典型的数据分析任务中。

import pandas as pd import numpy as np from sklearn.preprocessing import ( StandardScaler, MinMaxScaler, RobustScaler, MaxAbsScaler, Normalizer, PowerTransformer ) from sklearn.pipeline import Pipeline from sklearn.ensemble import RandomForestRegressor from sklearn.linear_model import LinearRegression from sklearn.model_selection import train_test_split, cross_val_score from sklearn.svm import SVR from sklearn.compose import TransformedTargetRegressor from sklearn.metrics import mean_absolute_error, r2_score # 创建示例数据集 np.random.seed(42) n = 500 df = pd.DataFrame({ 'age': np.random.randint(18, 80, n), # 年龄 18-80 'income': np.random.exponential(50, n) * 1000, # 收入(偏态分布) 'education': np.random.randint(0, 5, n), # 学历等级 0-4 'experience': np.random.randint(0, 40, n), # 工作经验 0-40 'city_tier': np.random.choice([1, 2, 3], n), # 城市等级 1-3 'score': np.random.normal(70, 15, n) # 测试分数(正态分布) }) # 目标变量:消费金额(偏态分布,包含异常高值) y = ( 2000 + 50 * df['age'] + 0.5 * df['income'] + 3000 * df['education'] + 200 * df['experience'] + 1000 * df['city_tier'] + 100 * df['score'] + np.random.exponential(5000, n) # 添加噪声和异常值 ) # 划分数据集 X_train, X_test, y_train, y_test = train_test_split( df, y, test_size=0.2, random_state=42 ) # 方法一:对特征和目标都进行标准化 pipeline_standard = Pipeline([ ('scaler', StandardScaler()), ('reg', LinearRegression()) ]) pipeline_standard.fit(X_train, y_train) pred_standard = pipeline_standard.predict(X_test) # 方法二:RobustScaler + SVR(对异常值更鲁棒) pipeline_robust = Pipeline([ ('scaler', RobustScaler()), ('reg', SVR(kernel='rbf')) ]) pipeline_robust.fit(X_train, y_train) pred_robust = pipeline_robust.predict(X_test) # 方法三:对目标变量进行幂变换 pipeline_power = TransformedTargetRegressor( regressor=Pipeline([ ('scaler', StandardScaler()), ('reg', LinearRegression()) ]), transformer=PowerTransformer(method='yeo-johnson') ) pipeline_power.fit(X_train, y_train) pred_power = pipeline_power.predict(X_test) # 评估各方法 methods = [ ('StandardScaler + Linear', pred_standard), ('RobustScaler + SVR', pred_robust), ('Linear + Yeo-Johnson Target', pred_power) ] print("\n模型性能对比:") print("-" * 50) for name, pred in methods: mae = mean_absolute_error(y_test, pred) r2 = r2_score(y_test, pred) print(f"{name:>30s}: MAE={mae:>10.2f}, R2={r2:.4f}") print("\n结论: 不同缩放方法对模型性能有显著影响,") print("选择合适的缩放策略是数据预处理的关键步骤。")

本章核心要点:

  • 特征缩放是数据预处理中不可或缺的一步,尤其是对于基于距离和梯度的算法
  • StandardScaler是通用默认选择,但对异常值敏感
  • MinMaxScaler保留分布形状,适合有界数据
  • RobustScaler是数据含异常值时的最佳选择
  • MaxAbsScaler不破坏稀疏数据结构
  • Normalizer对行进行单位范数归一化,适合方向比较
  • 幂变换(Box-Cox/Yeo-Johnson)可将偏态数据变换为正态分布
  • Pipeline自动防止数据泄漏,是实际项目的最佳实践