数据标准化与归一化
数据分析专题 · 统一的数值尺度
专题: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的分布,转换公式为:
这种变换假设数据近似服从高斯分布(正态分布)。当数据满足这一假设时,标准化后的数据约有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]区间内,保留原始数据的分布形状。转换公式为:
当指定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)进行缩放,而不是使用均值和标准差。转换公式为:
由于中位数和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]区间。转换公式为:
这是一种非常简单的缩放方式,它不移动数据中心(不减去均值或中位数),只做除法缩放。对于稀疏数据(如文本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范数:
这种行级归一化使得样本之间的比较侧重于"方向"而非"幅度"(即向量的方向余弦),在文本分类、聚类等任务中非常有用。
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),通过最优参数λ实现变换:
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的扩展,它不要求数据为正,可以处理包含零或负值的数据。这使得它在实际应用中更加灵活。
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自动防止数据泄漏,是实际项目的最佳实践