查看全课大纲(第 11 / 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.数据建模
连续值特征构建
约 11 分钟
小象实战讲义 · 特征工程精讲与实践
大家好,欢迎回到小象的特征工程课程。在上一章我们学习了数据与特征预处理,掌握了处理缺失值和异常值的基本方法。本章我们将进入特征工程的核心环节——特征构建。本节我们聚焦于连续值特征的构建方法,学习如何将原始的数值型数据转化为对模型更友好、更有效的特征形式。
💡 核心导读
- 数值型数据的分类:理解连续数据与离散数据的本质区别,这是选择正确处理方法的前提。
- 幅度缩放:学习 Min-Max 缩放和标准化(Z-score)两种核心方法,理解它们在不同模型(如逻辑回归 vs 树模型)中的应用场景。
- 离散化(分箱):掌握将连续值分段处理的技巧,包括等距切分和等频切分,理解其引入非线性表达能力、缓解异常值影响的原理。
- 组合与多项式特征:了解如何通过特征间的四则运算或逻辑组合,构建更具信息量的高阶特征。
数值型数据的分类与理解
在进行特征构建之前,我们首先要正确识别数据的类型。数值型数据并不等同于连续值特征,它内部还有重要的区分:
- 连续数据:代表对象可测量的、在某个区间内可以取任意值的属性。其取值是通过测量或计量得到的。例如:零件的规格尺寸、人的身高体重、房间的长宽等。
- 离散数据:指其取值是不连续的分离值,数据只能在一些特定的点(通常是整数)取值,一般通过计数方法获得。例如:企业职工人数、设备台数、年龄(通常按整数统计)等。
这种区分之所以重要,是因为针对真正的连续值数据,我们有一系列特定的特征工程方法(如幅度缩放、离散化),而离散的数值型数据(如类别编号、计数)则可能更适合采用后续章节会讲到的类别型编码或直接使用。
幅度缩放:统一特征的量纲
幅度缩放是连续值特征工程中最基础也最常用的操作之一。它的核心目的是将不同特征的数值调整到同一数量级。
为什么需要这样做?让我们考虑一个经典的例子:构建一个预测个人信用的模型,特征包含“年龄”(范围0-100)和“年收入”(范围0-1,000,000)。如果我们使用逻辑回归、支持向量机或神经网络这类计算型模型,模型内部会进行加权求和(如 )。此时,收入特征由于其数值巨大,会主导整个计算过程,导致年龄特征的作用被严重削弱,即使年龄可能是一个重要的预测因子。
幅度缩放通过数学变换,将所有特征的值映射到一个大致相当的范围内,从而消除量纲带来的不公平影响。
重要提示:幅度缩放主要对计算型模型(逻辑回归、SVM、神经网络、K近邻、主成分分析等)至关重要。对于树模型(决策树、随机森林、XGBoost、LightGBM等),由于其分裂节点时只关注特征值的排序而非绝对大小,因此通常不需要进行幅度缩放处理。
最大最小缩放 (Min-Max Scaling)
最大最小缩放是最直观的缩放方法,它将原始数据线性变换到 [0, 1] 区间内。其公式为:
其中, 是原始值, 和 分别是该特征在整个数据集中的最小值和最大值。
处理后,原数据中的最小值变为0,最大值变为1,其余值均匀分布在0到1之间。
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
# 构造示例数据:年龄和年收入
data = pd.DataFrame({
'age': [25, 30, 35, 40, 45, 50],
'annual_income': [150000, 180000, 220000, 300000, 350000, 420000]
})
print("原始数据:")
print(data)
print("\n数据统计描述(注意量纲差异):")
print(data.describe())
# 应用最大最小缩放
minmax_scaler = MinMaxScaler()
scaled_data = minmax_scaler.fit_transform(data)
scaled_df = pd.DataFrame(scaled_data, columns=['age_scaled', 'income_scaled'])
print("\n经过最大最小缩放后的数据(0-1范围):")
print(scaled_df)原始数据:
age annual_income
0 25 150000
1 30 180000
2 35 220000
3 40 300000
4 45 350000
5 50 420000
数据统计描述(注意量纲差异):
age annual_income
count 6.000000 6.000000
mean 37.500000 270000.000000
std 9.354143 104690.018626
min 25.000000 150000.000000
25% 31.250000 190000.000000
50% 37.500000 260000.000000
75% 43.750000 337500.000000
max 50.000000 420000.000000
经过最大最小缩放后的数据(0-1范围):
age_scaled income_scaled
0 0.0 0.000000
1 0.2 0.111111
2 0.4 0.259259
3 0.6 0.555556
4 0.8 0.740741
5 1.0 1.000000可以看到,年龄和收入都被压缩到了[0,1]区间,现在它们处于相同的数量级,可以在计算型模型中平等地贡献信息。
标准化 (Standardization / Z-score Scaling)
另一种常见的缩放方法是标准化,也称为Z-score标准化。它基于数据的均值()和标准差()进行调整,其公式为:
标准化处理后,数据的均值变为0,标准差变为1。如果原始数据大致服从正态分布,那么标准化后的数据会近似服从标准正态分布 。与Min-Max缩放不同,标准化不将数据限制在固定区间(如[0,1]),因此对数据中存在极大或极小值(异常值)的情况相对更鲁棒。
import pandas as pd
from sklearn.preprocessing import StandardScaler
# 使用同样的示例数据
data = pd.DataFrame({
'age': [25, 30, 35, 40, 45, 50],
'annual_income': [150000, 180000, 220000, 300000, 350000, 420000]
})
# 应用标准化
standard_scaler = StandardScaler()
standardized_data = standard_scaler.fit_transform(data)
standardized_df = pd.DataFrame(standardized_data, columns=['age_standardized', 'income_standardized'])
print("经过标准化(Z-score)后的数据:")
print(standardized_df)
print("\n标准化后数据的均值和标准差:")
print(f"年龄均值: {standardized_df['age_standardized'].mean():.6f}, 标准差: {standardized_df['age_standardized'].std():.6f}")
print(f"收入均值: {standardized_df['income_standardized'].mean():.6f}, 标准差: {standardized_df['income_standardized'].std():.6f}")经过标准化(Z-score)后的数据:
age_standardized income_standardized
0 -1.46385 -1.255644
1 -0.87831 -0.941733
2 -0.29277 -0.523185
3 0.29277 0.313911
4 0.87831 0.837096
5 1.46385 1.569555
标准化后数据的均值和标准差:
年龄均值: 0.000000, 标准差: 1.095445
收入均值: -0.000000, 标准差: 1.095445💡 口径说明:这里打印出的标准差 1.095445 来自
pandas的.std()(样本标准差,ddof=1),对 6 个样本即 ;而StandardScaler内部按总体标准差(ddof=0)缩放,缩放后数据的总体标准差为 1.0。两数不同是自由度口径差异,不是计算错误。
如何选择?
- Min-Max缩放:当你需要将数据严格限制在一个区间(如后续的神经网络激活函数需要0-1输入),或者数据分布范围已知且稳定时使用。
- 标准化:当数据分布近似正态,或者数据中存在异常值,你希望缩放方法对异常值不那么敏感时使用。这也是许多机器学习算法默认推荐的预处理步骤。
离散化:将连续值分段处理
离散化,也称为分箱或分桶,是另一种处理连续值特征的重要技术。它指的是将连续属性的值域划分为若干个区间(箱),然后将每个原始值映射到它所归属的区间。
为什么要离散化?
- 引入非线性关系:许多模型(如逻辑回归)本质上是线性的,它们假设特征与目标之间存在单调的线性或近似线性关系。但现实中,关系往往是非线性的。例如,在“是否让座”的场景中,对年龄很小(儿童)和年龄很大(老人)的乘客,让座的可能性都高,而对中青年乘客让座的可能性低。这种“两头高,中间低”的关系无法用一个简单的线性系数表达。通过将年龄离散化成“儿童”、“青年”、“中年”、“老年”等箱,模型可以为每个箱学习一个独立的系数,从而捕捉这种非线性模式。
- 增强模型鲁棒性:离散化能够减少异常值和数据噪声的影响。一个异常的年龄值(如200岁)会被归入“老年”箱,而不会对“青年”箱的系数估计产生直接影响。
- 适应某些模型要求:有些算法(如贝叶斯网络、Apriori关联规则)本身只能处理类别型变量。
离散化的方法
主要的离散化策略有两种:
- 等距切分:按照值域的宽度均匀划分。例如,将年龄范围0-100岁,每20岁切一段,得到[0,20), [20,40), [40,60), [60,80), [80,100]五个箱。这种方法简单,但可能因为数据分布不均匀,导致某些箱内样本数极少,而另一些箱内样本数极多。
- 等频切分:按照样本数量均匀划分,使得每个箱内包含大致相同数量的样本。例如,将样本按年龄排序后,平均分成5份,每份占20%的样本。这种方法能保证数据分布均匀,但箱的边界值可能不是整齐的数字。
离散化后,我们需要对每个箱进行编码,常用的编码方式是独热编码,将一个连续特征转化为多个二值特征。
import pandas as pd
from sklearn.preprocessing import KBinsDiscretizer
# 构造示例数据,模拟让座场景:年龄和是否让座(1是,0否)
# 注意:这里构造数据体现非线性关系:低龄和高龄让座率高。
data = pd.DataFrame({
'age': [8, 12, 25, 30, 35, 48, 62, 71, 85, 90],
'give_seat': [1, 1, 0, 0, 0, 0, 1, 1, 1, 1]
})
print("原始数据(年龄与让座标签):")
print(data)
# 1. 等距切分(分成4个箱)
discretizer_eqw = KBinsDiscretizer(n_bins=4, encode='onehot-dense', strategy='uniform')
age_binned_eqw = discretizer_eqw.fit_transform(data[['age']])
bin_edges_eqw = discretizer_eqw.bin_edges_[0]
print(f"\n等距切分边界: {bin_edges_eqw}")
print("等距切分后的One-hot编码(每行代表一个样本,每列代表一个年龄分段):")
print(age_binned_eqw)
# 2. 等频切分(分成4个箱)
discretizer_eqf = KBinsDiscretizer(n_bins=4, encode='onehot-dense', strategy='quantile')
age_binned_eqf = discretizer_eqf.fit_transform(data[['age']])
bin_edges_eqf = discretizer_eqf.bin_edges_[0]
print(f"\n等频切分边界: {bin_edges_eqf}")
print("等频切分后的One-hot编码:")
print(age_binned_eqf)原始数据(年龄与让座标签):
age give_seat
0 8 1
1 12 1
2 25 0
3 30 0
4 35 0
5 48 0
6 62 1
7 71 1
8 85 1
9 90 1
等距切分边界: [ 8. 28.5 49. 69.5 90. ]
等距切分后的One-hot编码(每行代表一个样本,每列代表一个年龄分段):
[[1. 0. 0. 0.]
[1. 0. 0. 0.]
[1. 0. 0. 0.]
[0. 1. 0. 0.]
[0. 1. 0. 0.]
[0. 1. 0. 0.]
[0. 0. 1. 0.]
[0. 0. 0. 1.]
[0. 0. 0. 1.]
[0. 0. 0. 1.]]
等频切分边界: [ 8. 25. 41.5 71. 90. ]
等频切分后的One-hot编码:
[[1. 0. 0. 0.]
[1. 0. 0. 0.]
[0. 1. 0. 0.]
[0. 1. 0. 0.]
[0. 1. 0. 0.]
[0. 0. 1. 0.]
[0. 0. 1. 0.]
[0. 0. 0. 1.]
[0. 0. 0. 1.]
[0. 0. 0. 1.]]观察结果:
- 等距切分:边界是 8, 28.5, 49, 69.5, 90。样本分布不均匀:第一个箱(8
28.5)有 3 个样本,第三个箱(4969.5)只有 1 个样本(62 岁),最后一个箱(69.5~90)有 3 个样本。 - 等频切分:边界是 8, 25, 41.5, 71, 90。目标是每个箱约 2.5 个样本。可以看到,边界值不再是整齐的间隔,但每个箱的样本数更均衡(分别为 2、3、2、3 个)。
组合特征与多项式特征
除了对单个特征进行变换,我们还可以通过组合多个原始特征来构建新的、可能更具预测力的特征。
- 组合特征:通常指对几个条件做“且”的逻辑组合,在类别型特征中更常见(例如“性别=女且城市=北京”)。
- 多项式特征:针对连续值特征,通过四则运算(加、减、乘、除、平方、开方等)构建新的特征。例如,从“身高”和“体重”可以衍生出“身体质量指数(BMI) = 体重/身高^2”。乘法或除法交互项可以帮助线性模型捕捉特征间的交互效应。
import pandas as pd
# 示例:手动构建组合特征(收入年龄比)
data = pd.DataFrame({
'age': [25, 30, 35, 40, 45],
'annual_income': [150000, 180000, 220000, 300000, 350000]
})
# 构建新特征:单位年龄收入(假设业务上认为这可能与消费能力相关)
data['income_per_age'] = data['annual_income'] / data['age']
# 构建交互项:年龄与收入的乘积(有时可代表累积资源)
data['age_times_income'] = data['age'] * data['annual_income']
print("添加组合特征后的数据:")
print(data)添加组合特征后的数据:
age annual_income income_per_age age_times_income
0 25 150000 6000.000000 3750000
1 30 180000 6000.000000 5400000
2 35 220000 6285.714286 7700000
3 40 300000 7500.000000 12000000
4 45 350000 7777.777778 15750000sklearn 提供了 PolynomialFeatures 工具来自动生成多项式特征,它可以生成特征的所有多项式组合(如 ),在需要探索复杂特征关系时非常有用。
📝 动手练一练
- 概念辨析:假设你正在处理一个电商数据集,其中包含“用户浏览商品时长(秒)”和“用户历史订单数”两个特征。从连续值特征工程的角度,你会对它们分别采用哪种处理方式(幅度缩放、离散化、直接使用)?请说明理由。
👉 点击查看参考答案
- 用户浏览商品时长(秒):这是一个典型的连续值测量数据。建议先进行幅度缩放(如标准化),因为其取值可能从几秒到几小时,量纲跨度大,尤其在使用逻辑回归等模型时。如果业务上认为浏览时长与购买意愿存在非单调关系(例如,太短可能没兴趣,太长可能只是闲逛,中等时长最可能购买),也可以考虑离散化(分箱)来捕捉这种非线性。
- 用户历史订单数:这是一个离散的计数数据。虽然它是数值,但取值是非负整数。对于较小的取值范围,可以将其视为类别型特征并进行编码(如序号编码或独热编码),特别是当订单数较少(如0,1,2,3+)且不同订单数代表显著不同的用户阶段时。如果取值范围很大,也可以将其作为连续值进行幅度缩放。通常不建议对计数数据做离散化,因为它本身已经是离散的。
- 动手实践:使用上面“年龄与收入”的示例数据,尝试用
sklearn的PolynomialFeatures(degree=2) 生成多项式特征。观察生成的新特征包含了哪些项?(提示:需要from sklearn.preprocessing import PolynomialFeatures)
👉 点击查看参考答案
import pandas as pd
from sklearn.preprocessing import PolynomialFeatures
data = pd.DataFrame({
'age': [25, 30, 35],
'annual_income': [150000, 180000, 220000]
})
poly = PolynomialFeatures(degree=2, include_bias=False)
poly_features = poly.fit_transform(data)
poly_feature_names = poly.get_feature_names_out(['age', 'annual_income'])
poly_df = pd.DataFrame(poly_features, columns=poly_feature_names)
print("原始特征:")
print(data)
print("\n二次多项式特征(包含交互项和平方项):")
print(poly_df)原始特征:
age annual_income
0 25 150000
1 30 180000
2 35 220000
二次多项式特征(包含交互项和平方项):
age annual_income age^2 age annual_income annual_income^2
0 25.0 150000.0 625.0 3750000.0 2.250000e+10
1 30.0 180000.0 900.0 5400000.0 3.240000e+10
2 35.0 220000.0 1225.0 7700000.0 4.840000e+10运行后你将看到,新特征包括:['age', 'annual_income', 'age^2', 'age annual_income', 'annual_income^2']。这允许模型拟合年龄和收入之间更复杂的曲面关系,而不仅仅是线性平面。
本章小结
本节我们深入学习了连续值特征构建的三大核心武器:幅度缩放、离散化和组合/多项式特征。记住,特征工程没有一成不变的公式,核心在于理解数据背后的业务逻辑以及你所选用模型的特点。
📋 行动清单
- 审视你的数据:打开一个项目中的数据集,区分出真正的连续值特征和离散的数值特征。
- 尝试缩放:对连续值特征分别用
MinMaxScaler和StandardScaler进行变换,观察数据分布的变化。 - 实践分箱:选择一个连续特征,用
KBinsDiscretizer分别尝试等距和等频分箱,并思考哪种方式更符合该特征的业务含义。
—— 小象教研组