查看全课大纲(第 16 / 35 节)
- 1.特征工程简介
- 2.特征工程的处理流程
- 3.数据与特征概述
- 4.数据统计、相似度与数据探索
- 5.数据探索工具 Pandas_profiling
- 6.工具应用:《泰坦尼克》分析报告
- 7.构造一个微型链家租房数据集用于演示
- 8.数据预处理综述
- 9.缺失值处理实操
- 10.异常值处理实操
- 11.连续值特征构建
- 12.类别型数据特征构建
- 13.特征幅度缩放
- 14.特征离散化
- 15.特征编码:让类别数据参与建模
- 16.特征变换
- 17.时间特征
- 18.特征衍生
- 19.自动化特征工具
- 20.特征选择综述
- 21.特征选择方法
- 22.过滤式特征选择方法实操
- 23.包裹式特征选择方法
- 24.嵌入式特征选择
- 25.混序法与混合法特征选择
- 26.特征降维综述
- 27.案例实操:特征降维
- 28.特征监控概述与指标
- 29.Iris 数据集特征漂移检测案例
- 30.特征监控实战:用分布对比检测数据漂移
- 31.实时特征监控
- 32.电商销量预测项目背景介绍
- 33.数据预处理和特征工程(一)
- 34.数据预处理和特征工程(二)
- 35.数据建模
特征变换
约 5 分钟
小象实战讲义 · 特征工程精讲与实践
在特征工程中,我们不仅会从原始数据中构造新特征,还会对已有特征的分布进行调整,这就是特征变换。本节将聚焦于对连续值特征进行数学变换,以调整其分布形态,使其更符合模型的假设或提升模型的性能。学完本节,你将掌握对数变换、幂变换、Box-Cox变换等多种常用特征变换方法,并理解它们适用的场景。
💡 核心导读
- 目的:通过数学变换调整连续特征的分布,使其更接近正态分布或减小极端值的影响,从而提升模型效果。
- 常用方法:对数变换、平方根变换、指数变换、Box-Cox变换、分位数变换等。
- 适用场景:处理长尾分布、幅度差异巨大的数据,或在回归问题中对标签进行变换。
- 核心工具:主要依赖
numpy和sklearn.preprocessing中的PowerTransformer、QuantileTransformer。 - 实践要点:变换后需观察分布变化,理解不同变换对数据“压缩”或“拉伸”的效果。
特征变换概述
特征变换(Feature Transformation)是指对连续值字段进行数学上的调整或校正,以改变其数据分布。这种变换的目的通常是为了:
- 减小数据间的幅度差异:当特征中存在极大值和极小值,且分布极不均匀(长尾分布)时,模型可能被少数极端值过度影响。通过变换可以压缩这种差异。
- 使数据分布更接近正态分布:许多统计方法和机器学习模型(如线性回归)在数据服从或近似正态分布时表现更好。
- 稳定方差:对于方差与均值相关的数据,变换可以使其方差更稳定。
与之前介绍的幅度缩放(如标准化、归一化)不同,缩放主要改变数据的尺度和中心位置,但不改变原始数据的相对顺序和分布形状。而特征变换则会改变数据的分布形态。
常用特征变换方法
我们将通过一个简化的泰坦尼克数据集船票价格(Fare)字段来演示各种变换。为了确保代码的独立性和可复现性,我们直接在代码块内构造一个微型数据集。
import pandas as pd
import numpy as np
# 构造一个微型数据集,模拟泰坦尼克号船票价格的长尾分布
# 包含几个较小的值和几个较大的值
data_sample = pd.DataFrame({
'PassengerId': [1, 2, 3, 4, 5, 6],
'Fare': [7.25, 71.28, 7.92, 53.10, 8.05, 512.33] # 包含一个极端大值512.33
})
print("原始数据:")
print(data_sample[['PassengerId', 'Fare']])
print("\n原始Fare字段描述统计:")
print(data_sample['Fare'].describe())原始数据:
PassengerId Fare
0 1 7.25
1 2 71.28
2 3 7.92
3 4 53.10
4 5 8.05
5 6 512.33
原始Fare字段描述统计:
count 6.000000
mean 109.988333
std 198.986416
min 7.250000
25% 7.952500
50% 30.575000
75% 66.735000
max 512.330000
Name: Fare, dtype: float64可以看到,Fare 字段的最小值为7.25,最大值高达512.33,标准差很大,是一个典型的具有较大幅度差异的字段。
对数变换 (Log Transformation)
对数变换是处理正偏态(右偏)长尾数据最常用的方法之一。其公式为:x_new = log(x + 1)。加1是为了防止对0取对数。
import pandas as pd
import numpy as np
# 构造微型数据集
data_sample = pd.DataFrame({
'PassengerId': [1, 2, 3, 4, 5, 6],
'Fare': [7.25, 71.28, 7.92, 53.10, 8.05, 512.33]
})
# 对数变换演示
data_log = data_sample.copy()
# 应用对数变换,加1防止对0取对数
data_log['Fare_log'] = np.log(data_log['Fare'] + 1)
print("对数变换后数据:")
print(data_log[['Fare', 'Fare_log']])
print("\n变换后描述统计:")
print(data_log['Fare_log'].describe())对数变换后数据:
Fare Fare_log
0 7.25 2.110213
1 71.28 4.280547
2 7.92 2.188296
3 53.10 3.990834
4 8.05 2.202765
5 512.33 6.240919
变换后描述统计:
count 6.000000
mean 3.502262
std 1.655154
min 2.110213
25% 2.191913
50% 3.096799
75% 4.208119
max 6.240919
Name: Fare_log, dtype: float64观察结果:原始数据中最大值 512.33 约为最小值 7.25 的 71 倍;对数变换后,最大值 6.24 仅为最小值 2.11 的约 3 倍。对数变换显著压缩了极大值与其余值之间的绝对差距,使数据分布更加紧凑。
幂变换 (Power Transformation)
幂变换通过对数据施加指数运算来调整分布,包括平方根变换和更一般的指数变换。公式为:x_new = x^α。
- 平方根变换 (α=0.5):
x_new = sqrt(x),效果与对数变换类似,但压缩力度稍弱。 - 指数变换:α取其他值,如0.2(相当于开5次方根,压缩),或2(平方,扩大差异)。
- 倒数变换:
x_new = 1/x,同样用于压缩幅度差异,但实际项目中使用较少(原课程也仅作提及)。
import pandas as pd
import numpy as np
# 构造微型数据集
data_sample = pd.DataFrame({
'PassengerId': [1, 2, 3, 4, 5, 6],
'Fare': [7.25, 71.28, 7.92, 53.10, 8.05, 512.33]
})
# 平方根变换和指数变换演示
data_power = data_sample.copy()
data_power['Fare_sqrt'] = np.sqrt(data_power['Fare']) # 平方根变换 (α=0.5)
data_power['Fare_exp_0.2'] = data_power['Fare'] ** 0.2 # 指数变换 (α=0.2,相当于开5次方)
data_power['Fare_square'] = data_power['Fare'] ** 2 # 平方变换 (α=2,扩大差异)
print("幂变换结果对比:")
print(data_power[['Fare', 'Fare_sqrt', 'Fare_exp_0.2', 'Fare_square']])幂变换结果对比:
Fare Fare_sqrt Fare_exp_0.2 Fare_square
0 7.25 2.692582 1.486167 52.5625
1 71.28 8.442748 2.347435 5080.8384
2 7.92 2.814249 1.512673 62.7264
3 53.10 7.286975 2.213191 2819.6100
4 8.05 2.837252 1.517606 64.8025
5 512.33 22.634708 3.482651 262482.0289可以看到:
Fare_sqrt和Fare_exp_0.2都压缩了数据,极端值512.33的影响被减弱。Fare_square则相反,放大了数据间的差异,最大值变得极其巨大。
Box-Cox变换与分位数变换
对于更复杂的分布调整,我们可以使用sklearn提供的自动化变换工具。
Box-Cox变换:要求输入数据必须为正数。它寻找一个最优的λ参数,使得变换后的数据尽可能接近正态分布。其公式为: 当 λ ≠ 0 时, y = (x^λ - 1) / λ 当 λ = 0 时, y = ln(x)
分位数变换:将原始数据的分布映射到指定的目标分布(如标准正态分布)。它基于分位数进行匹配,不依赖于特定的参数族,能更好地将任何分布转换为目标分布。
import pandas as pd
import numpy as np
from sklearn.preprocessing import PowerTransformer, QuantileTransformer
# 构造微型数据集(确保数据为正)
data_sample = pd.DataFrame({
'PassengerId': [1, 2, 3, 4, 5, 6],
'Fare': [7.25, 71.28, 7.92, 53.10, 8.05, 512.33]
})
data_advanced = data_sample.copy()
# Box-Cox变换
pt = PowerTransformer(method='box-cox') # method='box-cox' 要求数据为正
pt.fit(data_advanced[['Fare']])
data_advanced['Fare_boxcox'] = pt.transform(data_advanced[['Fare']])
# 分位数变换(映射到标准正态分布)
qt = QuantileTransformer(output_distribution='normal', random_state=42)
qt.fit(data_advanced[['Fare']])
data_advanced['Fare_quantile'] = qt.transform(data_advanced[['Fare']])
print("高级变换结果:")
print(data_advanced[['Fare', 'Fare_boxcox', 'Fare_quantile']])
print("\nBox-Cox变换后描述统计:")
print(data_advanced['Fare_boxcox'].describe())
print("\n分位数变换后描述统计:")
print(data_advanced['Fare_quantile'].describe())高级变换结果:
Fare Fare_boxcox Fare_quantile
0 7.25 -1.024254 -5.199338
1 71.28 0.748686 0.841621
2 7.92 -0.929755 -0.841621
3 53.10 0.584545 0.253347
4 8.05 -0.912631 -0.253347
5 512.33 1.533408 5.199338
Box-Cox变换后描述统计:
count 6.000000e+00
mean -1.110223e-16
std 1.095445e+00
min -1.024254e+00
25% -9.254740e-01
50% -1.640427e-01
75% 7.076508e-01
max 1.533408e+00
Name: Fare_boxcox, dtype: float64
分位数变换后描述统计:
count 6.000000e+00
mean 1.630755e-11
std 3.335004e+00
min -5.199338e+00
25% -6.945527e-01
50% 1.665335e-16
75% 6.945527e-01
max 5.199338e+00
Name: Fare_quantile, dtype: float64两种变换都试图将数据标准化(均值为0),并调整分布。Box-Cox 变换自带标准化,其输出的总体标准差(ddof=0)恰为 1——上面用 pandas 的 describe 查看时显示 1.095,是因为 pandas 默认按样本标准差(ddof=1)计算。分位数变换后数据被严格映射到标准正态分布的分位数上。注意,由于我们样本量极小,变换效果主要是演示API用法,在实际大数据集上,这些变换能更有效地将长尾分布“拉”向正态分布。
变换方法的选择与应用场景
- 对数变换:简单有效,是处理正偏态、长尾分布的首选尝试方法。也常用于回归问题的标签变换。
- 平方根变换:效果略弱于对数变换,适用于轻度偏态的数据。
- Box-Cox变换:更通用和自动化,能自动寻找最优变换参数,但要求数据严格为正。
- 分位数变换:最强大,可以将任何分布转换为目标分布,且对异常值不敏感,但计算成本相对较高。
重要提示:所有变换都应在训练集上拟合(fit),然后用相同的转换器去转换(transform)验证集和测试集,以确保数据分布的一致性,避免数据泄露。
📝 动手练一练
- 概念辨析:特征幅度缩放(如
StandardScaler)与本节介绍的特征变换(如Log Transformation)最核心的区别是什么?请从数据分布形态改变的角度简要说明。
👉 点击查看参考答案
特征幅度缩放(如标准化、归一化)主要通过线性变换改变数据的尺度和中心位置(例如,标准化使均值为0,标准差为1),但不改变原始数据的分布形状。数据点之间的相对顺序和分布形态(如偏度、峰度)保持不变。
而特征变换(如对数变换、Box-Cox变换)通过非线性数学函数对数据进行映射,其核心目的是改变数据的分布形态,例如将长尾分布调整为更接近正态分布,或压缩极端值带来的幅度差异。变换后,数据点的相对关系(顺序可能不变,但间距)和整体的分布形状都发生了改变。
- 动手实践:假设你有一个代表网站用户浏览时长的特征
view_time,其部分取值为[2, 15, 3, 600, 5, 1800](单位:秒)。其中包含少数极长的会话(如1800秒)。请编写代码,分别使用对数变换和平方根变换处理该数据,并观察变换后最大值与最小值的比值变化,说明哪种变换在此场景下压缩幅度差异的效果更明显。
👉 点击查看参考答案
import numpy as np
import pandas as pd
# 原始数据
view_time = pd.Series([2, 15, 3, 600, 5, 1800])
print("原始数据:", view_time.values)
print("原始极值比 (max/min):", view_time.max() / view_time.min())
# 对数变换 (加1防止对0取对数)
view_time_log = np.log(view_time + 1)
print("\n对数变换后:", np.round(view_time_log, 2))
print("对数变换后极值比:", np.round(view_time_log.max() / view_time_log.min(), 2))
# 平方根变换
view_time_sqrt = np.sqrt(view_time)
print("\n平方根变换后:", np.round(view_time_sqrt, 2))
print("平方根变换后极值比:", np.round(view_time_sqrt.max() / view_time_sqrt.min(), 2))原始数据: [ 2 15 3 600 5 1800]
原始极值比 (max/min): 900.0
对数变换后: 0 1.10
1 2.77
2 1.39
3 6.40
4 1.79
5 7.50
dtype: float64
对数变换后极值比: 6.82
平方根变换后: 0 1.41
1 3.87
2 1.73
3 24.49
4 2.24
5 42.43
dtype: float64
平方根变换后极值比: 30.0分析:原始数据极值比高达900倍。经过对数变换,极值比降至约6.8倍;经过平方根变换,极值比降至约30倍。对数变换在此场景下压缩幅度差异的效果更为显著,更有效地减弱了极端长尾值(1800秒)的影响。
本章小结
本节我们深入学习了特征变换,这是处理连续值特征、优化其分布以适配模型的关键技术。核心在于理解不同非线性变换如何影响数据的分布形态,特别是对长尾、偏态数据的校正作用。
📋 行动清单
- 掌握核心方法:理解对数变换、Box-Cox变换、分位数变换的原理、公式及适用场景。
- 学会观察与选择:面对一个新的连续特征,先观察其分布(如使用直方图),若发现长尾、偏态或幅度差异巨大,则考虑应用特征变换。
- 正确工程实践:记住在
sklearn的Pipeline或工作流中,变换器必须在训练集上fit,再用于转换所有数据集,严防数据泄露。
—— 小象教研组