特征工程系统方法论:编码、分箱、交互特征与特征选择

文章目录

    • 一、为什么特征工程比换算法更重要
    • 二、类别编码全策略:遇到类别数据该用什么方法
      • [2.1 编码策略选择决策树](#2.1 编码策略选择决策树)
      • [2.2 五种编码方法详解](#2.2 五种编码方法详解)
    • 三、数值特征变换:让分布更适合模型
      • [3.1 数值变换方法选择流程](#3.1 数值变换方法选择流程)
      • [3.2 常用变换方法](#3.2 常用变换方法)
    • [四、交互特征与派生特征:让 1+1 > 2](#四、交互特征与派生特征:让 1+1 > 2)
      • [4.1 交互特征构造策略](#4.1 交互特征构造策略)
      • [4.2 时间特征提取](#4.2 时间特征提取)
      • [4.3 文本特征的简单处理](#4.3 文本特征的简单处理)
    • [五、特征选择四大方法:从 200+ 特征到核心特征](#五、特征选择四大方法:从 200+ 特征到核心特征)
      • [5.1 四种特征选择方法对比](#5.1 四种特征选择方法对比)
    • [六、特征工程 Pipeline 化:从脚本到可复现工作流](#六、特征工程 Pipeline 化:从脚本到可复现工作流)
    • 七、实战:房价预测的特征工程全流程
      • [7.1 特征工程 Pipeline 架构](#7.1 特征工程 Pipeline 架构)
      • [7.2 完整代码](#7.2 完整代码)
      • [7.3 特征工程效果对比](#7.3 特征工程效果对比)
    • 八、小结

一、为什么特征工程比换算法更重要

在 Kaggle 的房价预测竞赛中,排名靠前的方案大多使用梯度提升树(XGBoost/LightGBM),算法本身并无显著差异。真正拉开差距的是特征工程------同样的数据,精心构造的特征能让 AUC 从 0.75 提升到 0.85,而换一个更复杂的模型只能从 0.75 提升到 0.76。特征工程的本质,是将原始数据中对预测目标有用的信号提取、放大并规范化,同时抑制噪声。

特征工程的好坏,直接决定了模型性能的天花板。算法的作用是逼近这个天花板,但天花板的高度由特征决定。本文将从编码策略、数值变换、交互特征、时间特征、文本特征、特征选择六个维度,建立一套系统化的特征工程决策框架,并以房价预测为实战场景,演示从 79 个原始特征到 200+ 特征,再经选择压缩至 35 个核心特征的完整过程。

二、类别编码全策略:遇到类别数据该用什么方法

类别型变量是特征工程中最常见的挑战。机器学习模型本质上处理的是数值,类别数据必须经过编码才能进入模型。不同编码方式对模型性能的影响差异巨大,选择不当会导致维度灾难、信息损失或数据泄漏。

2.1 编码策略选择决策树

#mermaid-svg-b6ltY3Z37ja9u0lA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-b6ltY3Z37ja9u0lA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-b6ltY3Z37ja9u0lA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-b6ltY3Z37ja9u0lA .error-icon{fill:#552222;}#mermaid-svg-b6ltY3Z37ja9u0lA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-b6ltY3Z37ja9u0lA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-b6ltY3Z37ja9u0lA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-b6ltY3Z37ja9u0lA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-b6ltY3Z37ja9u0lA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-b6ltY3Z37ja9u0lA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-b6ltY3Z37ja9u0lA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-b6ltY3Z37ja9u0lA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-b6ltY3Z37ja9u0lA .marker.cross{stroke:#333333;}#mermaid-svg-b6ltY3Z37ja9u0lA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-b6ltY3Z37ja9u0lA p{margin:0;}#mermaid-svg-b6ltY3Z37ja9u0lA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-b6ltY3Z37ja9u0lA .cluster-label text{fill:#333;}#mermaid-svg-b6ltY3Z37ja9u0lA .cluster-label span{color:#333;}#mermaid-svg-b6ltY3Z37ja9u0lA .cluster-label span p{background-color:transparent;}#mermaid-svg-b6ltY3Z37ja9u0lA .label text,#mermaid-svg-b6ltY3Z37ja9u0lA span{fill:#333;color:#333;}#mermaid-svg-b6ltY3Z37ja9u0lA .node rect,#mermaid-svg-b6ltY3Z37ja9u0lA .node circle,#mermaid-svg-b6ltY3Z37ja9u0lA .node ellipse,#mermaid-svg-b6ltY3Z37ja9u0lA .node polygon,#mermaid-svg-b6ltY3Z37ja9u0lA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-b6ltY3Z37ja9u0lA .rough-node .label text,#mermaid-svg-b6ltY3Z37ja9u0lA .node .label text,#mermaid-svg-b6ltY3Z37ja9u0lA .image-shape .label,#mermaid-svg-b6ltY3Z37ja9u0lA .icon-shape .label{text-anchor:middle;}#mermaid-svg-b6ltY3Z37ja9u0lA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-b6ltY3Z37ja9u0lA .rough-node .label,#mermaid-svg-b6ltY3Z37ja9u0lA .node .label,#mermaid-svg-b6ltY3Z37ja9u0lA .image-shape .label,#mermaid-svg-b6ltY3Z37ja9u0lA .icon-shape .label{text-align:center;}#mermaid-svg-b6ltY3Z37ja9u0lA .node.clickable{cursor:pointer;}#mermaid-svg-b6ltY3Z37ja9u0lA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-b6ltY3Z37ja9u0lA .arrowheadPath{fill:#333333;}#mermaid-svg-b6ltY3Z37ja9u0lA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-b6ltY3Z37ja9u0lA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-b6ltY3Z37ja9u0lA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-b6ltY3Z37ja9u0lA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-b6ltY3Z37ja9u0lA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-b6ltY3Z37ja9u0lA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-b6ltY3Z37ja9u0lA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-b6ltY3Z37ja9u0lA .cluster text{fill:#333;}#mermaid-svg-b6ltY3Z37ja9u0lA .cluster span{color:#333;}#mermaid-svg-b6ltY3Z37ja9u0lA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-b6ltY3Z37ja9u0lA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-b6ltY3Z37ja9u0lA rect.text{fill:none;stroke-width:0;}#mermaid-svg-b6ltY3Z37ja9u0lA .icon-shape,#mermaid-svg-b6ltY3Z37ja9u0lA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-b6ltY3Z37ja9u0lA .icon-shape p,#mermaid-svg-b6ltY3Z37ja9u0lA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-b6ltY3Z37ja9u0lA .icon-shape .label rect,#mermaid-svg-b6ltY3Z37ja9u0lA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-b6ltY3Z37ja9u0lA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-b6ltY3Z37ja9u0lA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-b6ltY3Z37ja9u0lA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 低基数

n < 10
中基数

10 ≤ n < 100
高基数

n ≥ 100
有序
无序
类别型特征
基数?
有序/无序?
Target Encoding

  • smoothing
    CatBoost Encoding

或 Frequency Encoding
Ordinal Encoding

映射为 0,1,2,...
One-Hot Encoding
需配合 Pipeline

防数据泄漏
留一法编码

天然防泄漏

2.2 五种编码方法详解

One-Hot Encoding(独热编码)

适用于低基数(通常 < 10 类)的无序类别特征。每个类别对应一个二进制列,只有一列为 1,其余为 0。优点是不引入虚假顺序关系;缺点是维度随类别数线性增长。

python 复制代码
from sklearn.preprocessing import OneHotEncoder

enc = OneHotEncoder(sparse_output=False, handle_unknown='ignore')
X_encoded = enc.fit_transform(df[['category']])

注意:handle_unknown='ignore' 在生产环境中必不可少。当训练时未出现的新类别进入推理阶段时,该选项会将其编码为全 0 向量,避免报错。

Ordinal Encoding(有序编码)

适用于类别之间存在自然顺序的场景,如 "低/中/高"、"青铜/白银/黄金"。直接将类别映射为整数 0, 1, 2... 保留顺序信息,同时不增加维度。

python 复制代码
from sklearn.preprocessing import OrdinalEncoder

# 手动指定顺序
enc = OrdinalEncoder(categories=[['低', '中', '高']])
df['level_encoded'] = enc.fit_transform(df[['level']])

Target Encoding(目标编码)

适用于中高基数类别特征(如用户 ID、商品 SKU、城市名)。用目标变量的均值替代类别值。例如,在房价预测中,用每个街区的平均房价替代街区名称。核心风险是数据泄漏------如果用全量数据计算均值再编码,编码后的特征已包含目标信息,会导致严重的过拟合。

解决方案是配合交叉验证进行编码:在每个 fold 内,只用训练 fold 的均值编码验证 fold。category_encoders 库提供了内置的 CV 支持。

python 复制代码
from category_encoders import TargetEncoder
from sklearn.model_selection import StratifiedKFold

enc = TargetEncoder(cols=['neighborhood'], smoothing=10.0, cv=5)
df['neighborhood_te'] = enc.fit_transform(df['neighborhood'], df['target'])

smoothing 参数是 Target Encoding 的灵魂。它控制先验均值(全局均值)与后验均值(类别均值)的混合比例。当某个类别的样本数很少时,其均值不可靠,smoothing 会自动将其拉向全局均值。数学表达式为:

复制代码
编码值 = (n × 类别均值 + m × 全局均值) / (n + m)

其中 n 是该类别的样本数,msmoothing 参数。

Frequency Encoding(频率编码)

用类别在数据集中的出现频率替代类别值。适用于高基数特征,且不需要目标变量。频率编码不会引入目标泄漏,但信息含量较低------两个不同类别如果出现频率相同,会被编码为同一个值。

python 复制代码
freq_map = df['user_id'].value_counts(normalize=True).to_dict()
df['user_id_freq'] = df['user_id'].map(freq_map)

CatBoost Encoding(留一法编码)

CatBoost 提出的编码方式是 Target Encoding 的改进版:对每个样本,用该类别在除当前样本外的所有样本上的目标均值进行编码。这天然消除了数据泄漏------当前样本的目标值不会参与自身的编码计算。

python 复制代码
from category_encoders import CatBoostEncoder

enc = CatBoostEncoder(cols=['category'], random_state=42)
df['category_cb'] = enc.fit_transform(df['category'], df['target'])
编码方法 适用基数 是否增加维度 数据泄漏风险 保留顺序
One-Hot 低(< 10) 是(n 列)
Ordinal 低(有序) 否(1 列)
Frequency 任意 否(1 列)
Target 中(10~100) 否(1 列) 高(需 CV) 是(隐含)
CatBoost 中高(> 10) 否(1 列) 是(隐含)

三、数值特征变换:让分布更适合模型

树模型对数值分布不敏感,但线性模型、神经网络和距离度量方法(如 KNN、SVM)对分布形态极为敏感。偏态分布、量纲差异会显著影响这些模型的收敛速度和预测精度。

3.1 数值变换方法选择流程

#mermaid-svg-TtBCnsE3kNxBaZky{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TtBCnsE3kNxBaZky .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TtBCnsE3kNxBaZky .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TtBCnsE3kNxBaZky .error-icon{fill:#552222;}#mermaid-svg-TtBCnsE3kNxBaZky .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TtBCnsE3kNxBaZky .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TtBCnsE3kNxBaZky .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TtBCnsE3kNxBaZky .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TtBCnsE3kNxBaZky .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TtBCnsE3kNxBaZky .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TtBCnsE3kNxBaZky .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TtBCnsE3kNxBaZky .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TtBCnsE3kNxBaZky .marker.cross{stroke:#333333;}#mermaid-svg-TtBCnsE3kNxBaZky svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TtBCnsE3kNxBaZky p{margin:0;}#mermaid-svg-TtBCnsE3kNxBaZky .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-TtBCnsE3kNxBaZky .cluster-label text{fill:#333;}#mermaid-svg-TtBCnsE3kNxBaZky .cluster-label span{color:#333;}#mermaid-svg-TtBCnsE3kNxBaZky .cluster-label span p{background-color:transparent;}#mermaid-svg-TtBCnsE3kNxBaZky .label text,#mermaid-svg-TtBCnsE3kNxBaZky span{fill:#333;color:#333;}#mermaid-svg-TtBCnsE3kNxBaZky .node rect,#mermaid-svg-TtBCnsE3kNxBaZky .node circle,#mermaid-svg-TtBCnsE3kNxBaZky .node ellipse,#mermaid-svg-TtBCnsE3kNxBaZky .node polygon,#mermaid-svg-TtBCnsE3kNxBaZky .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TtBCnsE3kNxBaZky .rough-node .label text,#mermaid-svg-TtBCnsE3kNxBaZky .node .label text,#mermaid-svg-TtBCnsE3kNxBaZky .image-shape .label,#mermaid-svg-TtBCnsE3kNxBaZky .icon-shape .label{text-anchor:middle;}#mermaid-svg-TtBCnsE3kNxBaZky .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TtBCnsE3kNxBaZky .rough-node .label,#mermaid-svg-TtBCnsE3kNxBaZky .node .label,#mermaid-svg-TtBCnsE3kNxBaZky .image-shape .label,#mermaid-svg-TtBCnsE3kNxBaZky .icon-shape .label{text-align:center;}#mermaid-svg-TtBCnsE3kNxBaZky .node.clickable{cursor:pointer;}#mermaid-svg-TtBCnsE3kNxBaZky .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TtBCnsE3kNxBaZky .arrowheadPath{fill:#333333;}#mermaid-svg-TtBCnsE3kNxBaZky .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TtBCnsE3kNxBaZky .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TtBCnsE3kNxBaZky .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TtBCnsE3kNxBaZky .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TtBCnsE3kNxBaZky .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TtBCnsE3kNxBaZky .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TtBCnsE3kNxBaZky .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TtBCnsE3kNxBaZky .cluster text{fill:#333;}#mermaid-svg-TtBCnsE3kNxBaZky .cluster span{color:#333;}#mermaid-svg-TtBCnsE3kNxBaZky div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-TtBCnsE3kNxBaZky .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TtBCnsE3kNxBaZky rect.text{fill:none;stroke-width:0;}#mermaid-svg-TtBCnsE3kNxBaZky .icon-shape,#mermaid-svg-TtBCnsE3kNxBaZky .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TtBCnsE3kNxBaZky .icon-shape p,#mermaid-svg-TtBCnsE3kNxBaZky .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TtBCnsE3kNxBaZky .icon-shape .label rect,#mermaid-svg-TtBCnsE3kNxBaZky .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TtBCnsE3kNxBaZky .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TtBCnsE3kNxBaZky .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TtBCnsE3kNxBaZky :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 右偏

长尾在右侧
左偏

长尾在左侧
近似正态
无零无负
有零或负
数值特征
分布形态?
是否有零/负值?
反射变换

max - x

再 Log
标准化

StandardScaler
Log 变换

np.log1p
Yeo-Johnson

自动选 λ
压缩大值

拉开小值
自动正态化

无需手动判断

3.2 常用变换方法

Log 变换

对右偏分布(长尾在右侧)的数值特征最为有效,如收入、房价、访问量。log1p(即 ln(1+x))比 log 更安全,能处理 x=0 的情况。

python 复制代码
import numpy as np

df['income_log'] = np.log1p(df['income'])

Box-Cox 变换

自动寻找最优 λ 参数,使数据最接近正态分布。只适用于严格正值数据。scipy.stats.boxcox 会自动估计 λ。

python 复制代码
from scipy import stats

df['value_boxcox'], fitted_lambda = stats.boxcox(df['value'])
# fitted_lambda 是估计的最优 λ,推理时需复用

Yeo-Johnson 变换

Box-Cox 的通用版本,支持包含零和负值的数据。在不知道数据符号的情况下,优先使用 Yeo-Johnson。

python 复制代码
from sklearn.preprocessing import PowerTransformer

pt = PowerTransformer(method='yeo-johnson')
df[['value']] = pt.fit_transform(df[['value']])
# pt.lambdas_ 保存估计的 λ,推理时复用

分箱(Binning)

将连续数值切分为若干区间,适用于异常值较多、噪声较大的场景。分箱方法有三种:

  • 等距分箱:每个区间宽度相同。优点是简单直观;缺点是数据分布不均时某些区间可能为空。
  • 等频分箱:每个区间包含相同数量的样本。优点是自适应分布;缺点是边界不稳定。
  • 决策树分箱:用决策树模型拟合目标变量,将叶节点的分裂阈值作为分箱边界。优点是考虑了目标变量的信息;缺点是可能过拟合。
python 复制代码
from sklearn.preprocessing import KBinsDiscretizer

# 等频分箱,5 个区间
kbd = KBinsDiscretizer(n_bins=5, encode='ordinal', strategy='quantile')
df['value_binned'] = kbd.fit_transform(df[['value']])

分箱的潜在风险:将连续信息离散化会损失部分信息。除非有明确的业务含义(如年龄段、收入等级),否则分箱通常作为备选方案而非首选。

四、交互特征与派生特征:让 1+1 > 2

单个特征的信息量有限,但多个特征的组合往往蕴含更强的预测信号。交互特征的构造需要业务洞察和数据探索的结合。

4.1 交互特征构造策略

算术组合

基于业务逻辑的加减乘除组合是最直接的交互方式。例如:

python 复制代码
# 房价预测:单价 = 总价 / 面积
df['price_per_sqm'] = df['sale_price'] / df['lot_area']

# 电商:人均消费 = 总消费 / 订单数
df['avg_order_value'] = df['total_spent'] / df['order_count']

# 金融:负债收入比
df['debt_to_income'] = df['monthly_debt'] / df['monthly_income']

多项式特征

PolynomialFeatures 自动生成特征的所有多项式组合,适用于线性模型捕捉非线性关系。

python 复制代码
from sklearn.preprocessing import PolynomialFeatures

poly = PolynomialFeatures(degree=2, interaction_only=True, include_bias=False)
X_poly = poly.fit_transform(df[['area', 'rooms']])
# 生成: area, rooms, area×rooms

注意:多项式特征的数量随维度指数增长。5 个特征的 degree=2 交互会产生 15 个新特征,degree=3 会产生 35 个。需配合特征选择使用。

分组聚合特征

将样本按某个类别分组,计算组内统计量作为新特征。这是电商和金融场景中最强力的特征构造手段之一。

python 复制代码
# 用户画像:每个用户的平均订单金额
user_avg = df.groupby('user_id')['order_amount'].transform('mean')
df['user_avg_order'] = user_avg

# 商品画像:每个商品类别的购买频次
category_freq = df.groupby('category_id')['user_id'].transform('count')
df['category_popularity'] = category_freq

4.2 时间特征提取

时间戳本身对模型无用,但从中提取的结构化信息往往极具价值。

python 复制代码
import pandas as pd

df['timestamp'] = pd.to_datetime(df['timestamp'])

# 基础拆分
df['year'] = df['timestamp'].dt.year
df['month'] = df['timestamp'].dt.month
df['day'] = df['timestamp'].dt.day
df['hour'] = df['timestamp'].dt.hour
df['weekday'] = df['timestamp'].dt.weekday
df['is_weekend'] = df['weekday'].isin([5, 6]).astype(int)
df['quarter'] = df['timestamp'].dt.quarter

# 周期性编码------用正弦/余弦保留周期关系
# 例如:23 点和 0 点在业务上很接近,但数值上相差 23
df['hour_sin'] = np.sin(2 * np.pi * df['hour'] / 24)
df['hour_cos'] = np.cos(2 * np.pi * df['hour'] / 24)

4.3 文本特征的简单处理

在结构化特征之外,文本数据可以通过简单向量化作为补充。

python 复制代码
from sklearn.feature_extraction.text import TfidfVectorizer

# 商品描述 → TF-IDF 向量
vectorizer = TfidfVectorizer(max_features=50, stop_words='english')
text_features = vectorizer.fit_transform(df['product_description'])
# 取前 50 个高频词作为特征列
text_df = pd.DataFrame(
    text_features.toarray(),
    columns=[f'tfidf_{w}' for w in vectorizer.get_feature_names_out()]
)

五、特征选择四大方法:从 200+ 特征到核心特征

特征过多会导致维度灾难、过拟合和训练速度下降。特征选择的目标是在保留信息量的前提下减少特征数量。

5.1 四种特征选择方法对比

#mermaid-svg-hbhzSvh2ddNEnm7u{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hbhzSvh2ddNEnm7u .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hbhzSvh2ddNEnm7u .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hbhzSvh2ddNEnm7u .error-icon{fill:#552222;}#mermaid-svg-hbhzSvh2ddNEnm7u .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hbhzSvh2ddNEnm7u .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hbhzSvh2ddNEnm7u .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hbhzSvh2ddNEnm7u .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hbhzSvh2ddNEnm7u .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hbhzSvh2ddNEnm7u .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hbhzSvh2ddNEnm7u .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hbhzSvh2ddNEnm7u .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hbhzSvh2ddNEnm7u .marker.cross{stroke:#333333;}#mermaid-svg-hbhzSvh2ddNEnm7u svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hbhzSvh2ddNEnm7u p{margin:0;}#mermaid-svg-hbhzSvh2ddNEnm7u .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-hbhzSvh2ddNEnm7u .cluster-label text{fill:#333;}#mermaid-svg-hbhzSvh2ddNEnm7u .cluster-label span{color:#333;}#mermaid-svg-hbhzSvh2ddNEnm7u .cluster-label span p{background-color:transparent;}#mermaid-svg-hbhzSvh2ddNEnm7u .label text,#mermaid-svg-hbhzSvh2ddNEnm7u span{fill:#333;color:#333;}#mermaid-svg-hbhzSvh2ddNEnm7u .node rect,#mermaid-svg-hbhzSvh2ddNEnm7u .node circle,#mermaid-svg-hbhzSvh2ddNEnm7u .node ellipse,#mermaid-svg-hbhzSvh2ddNEnm7u .node polygon,#mermaid-svg-hbhzSvh2ddNEnm7u .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hbhzSvh2ddNEnm7u .rough-node .label text,#mermaid-svg-hbhzSvh2ddNEnm7u .node .label text,#mermaid-svg-hbhzSvh2ddNEnm7u .image-shape .label,#mermaid-svg-hbhzSvh2ddNEnm7u .icon-shape .label{text-anchor:middle;}#mermaid-svg-hbhzSvh2ddNEnm7u .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hbhzSvh2ddNEnm7u .rough-node .label,#mermaid-svg-hbhzSvh2ddNEnm7u .node .label,#mermaid-svg-hbhzSvh2ddNEnm7u .image-shape .label,#mermaid-svg-hbhzSvh2ddNEnm7u .icon-shape .label{text-align:center;}#mermaid-svg-hbhzSvh2ddNEnm7u .node.clickable{cursor:pointer;}#mermaid-svg-hbhzSvh2ddNEnm7u .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hbhzSvh2ddNEnm7u .arrowheadPath{fill:#333333;}#mermaid-svg-hbhzSvh2ddNEnm7u .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hbhzSvh2ddNEnm7u .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hbhzSvh2ddNEnm7u .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hbhzSvh2ddNEnm7u .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hbhzSvh2ddNEnm7u .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hbhzSvh2ddNEnm7u .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hbhzSvh2ddNEnm7u .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hbhzSvh2ddNEnm7u .cluster text{fill:#333;}#mermaid-svg-hbhzSvh2ddNEnm7u .cluster span{color:#333;}#mermaid-svg-hbhzSvh2ddNEnm7u div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hbhzSvh2ddNEnm7u .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hbhzSvh2ddNEnm7u rect.text{fill:none;stroke-width:0;}#mermaid-svg-hbhzSvh2ddNEnm7u .icon-shape,#mermaid-svg-hbhzSvh2ddNEnm7u .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hbhzSvh2ddNEnm7u .icon-shape p,#mermaid-svg-hbhzSvh2ddNEnm7u .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hbhzSvh2ddNEnm7u .icon-shape .label rect,#mermaid-svg-hbhzSvh2ddNEnm7u .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hbhzSvh2ddNEnm7u .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hbhzSvh2ddNEnm7u .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hbhzSvh2ddNEnm7u :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 独立于模型

速度快
依赖模型

最准确

最慢
模型自带

零额外开销
最保守

几乎无假阳性
Boruta 阴影特征法
构造 Shadow Feature
统计检验

保留显著优于阴影的特征
Embedded 嵌入法
Lasso L1 正则化
树模型

feature_importances_
Wrapper 包装法
RFE 递归消除
前向选择
后向消除
Filter 过滤法
方差阈值

VarianceThreshold
互信息

mutual_info_classif
相关系数

f_classif
特征子集

Filter(过滤法)

独立于模型的统计方法,按特征与目标变量的相关性排序。速度快但忽略特征间的交互。

python 复制代码
from sklearn.feature_selection import mutual_info_classif, SelectKBest

# 互信息------捕捉非线性关系
selector = SelectKBest(mutual_info_classif, k=20)
X_selected = selector.fit_transform(X, y)
selected_features = X.columns[selector.get_support()]

Wrapper(包装法)

递归特征消除(RFE)通过反复训练模型并剔除最弱特征来寻找最优子集。准确性高但计算量大。

python 复制代码
from sklearn.feature_selection import RFECV
from sklearn.ensemble import RandomForestClassifier

estimator = RandomForestClassifier(n_estimators=100, random_state=42)
selector = RFECV(estimator, step=1, cv=5, scoring='roc_auc')
selector.fit(X, y)
print(f"最优特征数: {selector.n_features_}")
selected = X.columns[selector.support_]

Embedded(嵌入法)

模型在训练过程中自动完成特征选择。Lasso(L1 正则化)的系数会稀疏化到 0;树模型的 feature_importances_ 反映特征分裂带来的信息增益。

python 复制代码
from sklearn.linear_model import LassoCV

lasso = LassoCV(cv=5, random_state=42).fit(X, y)
selected = X.columns[lasso.coef_ != 0]
print(f"Lasso 保留 {len(selected)} 个特征")

Boruta(阴影特征法)

最保守的特征选择方法。为每个真实特征构造一个"阴影特征"(随机打乱该特征的值),然后比较真实特征与阴影特征的重要性。只有统计显著优于所有阴影特征的真实特征才会被保留。

python 复制代码
from boruta import BorutaPy
from sklearn.ensemble import RandomForestClassifier

rf = RandomForestClassifier(n_estimators=100, random_state=42, n_jobs=-1)
boruta = BorutaPy(rf, n_estimators='auto', random_state=42)
boruta.fit(X.values, y.values)
selected = X.columns[boruta.support_].tolist()
tentative = X.columns[boruta.support_weak_].tolist()
方法 速度 准确性 是否考虑特征交互 推荐场景
Filter 快速初筛,百万级特征
Wrapper 特征数 < 1000,追求最优子集
Embedded 中高 部分 与模型训练同步进行
Boruta 最高 追求保守选择,减少假阳性

六、特征工程 Pipeline 化:从脚本到可复现工作流

将特征工程步骤全部封装进 sklearn Pipeline,是保障训练-推理一致性的唯一可靠方式。手动分步处理容易在推理时遗漏某个变换步骤,导致线上线下不一致。

python 复制代码
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler, PowerTransformer
from category_encoders import TargetEncoder

# 定义列类型
num_features = ['area', 'rooms', 'age']
low_card_cat = ['heating_type', 'garage_type']  # 低基数 → One-Hot
high_card_cat = ['neighborhood', 'zipcode']     # 高基数 → Target Encoding

# 数值管线
num_pipeline = Pipeline([
    ('transform', PowerTransformer(method='yeo-johnson')),
    ('scale', StandardScaler())
])

# 类别编码管线
cat_pipeline = ColumnTransformer([
    ('onehot', OneHotEncoder(handle_unknown='ignore'), low_card_cat),
    ('target_enc', TargetEncoder(smoothing=10.0), high_card_cat)
])

# 组合特征工程管线
preprocessor = ColumnTransformer([
    ('num', num_pipeline, num_features),
    ('cat', cat_pipeline, low_card_cat + high_card_cat)
])

# 完整 Pipeline:特征工程 + 模型
full_pipeline = Pipeline([
    ('preprocess', preprocessor),
    ('model', XGBRegressor(random_state=42))
])

# 训练
full_pipeline.fit(X_train, y_train)

# 推理------Pipeline 自动按训练时的步骤处理新数据
predictions = full_pipeline.predict(X_test)

七、实战:房价预测的特征工程全流程

以 Ames Housing 数据集(79 个原始特征)为例,演示完整的特征工程流水线。

7.1 特征工程 Pipeline 架构

#mermaid-svg-lGrjcyhasg7FHXSU{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lGrjcyhasg7FHXSU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lGrjcyhasg7FHXSU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lGrjcyhasg7FHXSU .error-icon{fill:#552222;}#mermaid-svg-lGrjcyhasg7FHXSU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lGrjcyhasg7FHXSU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lGrjcyhasg7FHXSU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lGrjcyhasg7FHXSU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lGrjcyhasg7FHXSU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lGrjcyhasg7FHXSU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lGrjcyhasg7FHXSU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lGrjcyhasg7FHXSU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lGrjcyhasg7FHXSU .marker.cross{stroke:#333333;}#mermaid-svg-lGrjcyhasg7FHXSU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lGrjcyhasg7FHXSU p{margin:0;}#mermaid-svg-lGrjcyhasg7FHXSU .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-lGrjcyhasg7FHXSU .cluster-label text{fill:#333;}#mermaid-svg-lGrjcyhasg7FHXSU .cluster-label span{color:#333;}#mermaid-svg-lGrjcyhasg7FHXSU .cluster-label span p{background-color:transparent;}#mermaid-svg-lGrjcyhasg7FHXSU .label text,#mermaid-svg-lGrjcyhasg7FHXSU span{fill:#333;color:#333;}#mermaid-svg-lGrjcyhasg7FHXSU .node rect,#mermaid-svg-lGrjcyhasg7FHXSU .node circle,#mermaid-svg-lGrjcyhasg7FHXSU .node ellipse,#mermaid-svg-lGrjcyhasg7FHXSU .node polygon,#mermaid-svg-lGrjcyhasg7FHXSU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lGrjcyhasg7FHXSU .rough-node .label text,#mermaid-svg-lGrjcyhasg7FHXSU .node .label text,#mermaid-svg-lGrjcyhasg7FHXSU .image-shape .label,#mermaid-svg-lGrjcyhasg7FHXSU .icon-shape .label{text-anchor:middle;}#mermaid-svg-lGrjcyhasg7FHXSU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lGrjcyhasg7FHXSU .rough-node .label,#mermaid-svg-lGrjcyhasg7FHXSU .node .label,#mermaid-svg-lGrjcyhasg7FHXSU .image-shape .label,#mermaid-svg-lGrjcyhasg7FHXSU .icon-shape .label{text-align:center;}#mermaid-svg-lGrjcyhasg7FHXSU .node.clickable{cursor:pointer;}#mermaid-svg-lGrjcyhasg7FHXSU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lGrjcyhasg7FHXSU .arrowheadPath{fill:#333333;}#mermaid-svg-lGrjcyhasg7FHXSU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lGrjcyhasg7FHXSU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lGrjcyhasg7FHXSU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lGrjcyhasg7FHXSU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lGrjcyhasg7FHXSU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lGrjcyhasg7FHXSU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lGrjcyhasg7FHXSU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lGrjcyhasg7FHXSU .cluster text{fill:#333;}#mermaid-svg-lGrjcyhasg7FHXSU .cluster span{color:#333;}#mermaid-svg-lGrjcyhasg7FHXSU div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-lGrjcyhasg7FHXSU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lGrjcyhasg7FHXSU rect.text{fill:none;stroke-width:0;}#mermaid-svg-lGrjcyhasg7FHXSU .icon-shape,#mermaid-svg-lGrjcyhasg7FHXSU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lGrjcyhasg7FHXSU .icon-shape p,#mermaid-svg-lGrjcyhasg7FHXSU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lGrjcyhasg7FHXSU .icon-shape .label rect,#mermaid-svg-lGrjcyhasg7FHXSU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lGrjcyhasg7FHXSU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lGrjcyhasg7FHXSU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lGrjcyhasg7FHXSU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 79 原始特征
缺失值处理
类别编码

One-Hot + Target
数值变换

Log + Yeo-Johnson
交互特征

单价/房龄比/面积分组
文本向量化

TF-IDF 描述
200+ 扩展特征
方差过滤

去除 0 方差
互信息排序

Top 80
RFE 递归消除

保留 35
35 核心特征
XGBoost 训练

7.2 完整代码

python 复制代码
import pandas as pd
import numpy as np
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler, PowerTransformer
from sklearn.feature_selection import SelectKBest, mutual_info_regression, RFECV
from sklearn.ensemble import RandomForestRegressor
from category_encoders import TargetEncoder
import xgboost as xgb
from sklearn.model_selection import cross_val_score

# 加载数据
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')

# 分离目标变量
y = np.log1p(train['SalePrice'])
train = train.drop(['Id', 'SalePrice'], axis=1)
test_ids = test['Id']
test = test.drop('Id', axis=1)

# 合并训练/测试以统一预处理
all_data = pd.concat([train, test], axis=0, ignore_index=True)

# 1. 缺失值处理
all_data['PoolQC'] = all_data['PoolQC'].fillna('None')
all_data['Fence'] = all_data['Fence'].fillna('None')
all_data['FireplaceQu'] = all_data['FireplaceQu'].fillna('None')
all_data['LotFrontage'] = all_data.groupby('Neighborhood')['LotFrontage'].transform(
    lambda x: x.fillna(x.median())
)

# 2. 数值特征交互
all_data['TotalSF'] = all_data['TotalBsmtSF'] + all_data['1stFlrSF'] + all_data['2ndFlrSF']
all_data['TotalBathrooms'] = (
    all_data['FullBath'] + 0.5 * all_data['HalfBath'] +
    all_data['BsmtFullBath'] + 0.5 * all_data['BsmtHalfBath']
)
all_data['HouseAge'] = all_data['YrSold'] - all_data['YearBuilt']
all_data['RemodAge'] = all_data['YrSold'] - all_data['YearRemodAdd']
all_data['PricePerSF_proxy'] = all_data['GrLivArea'] / (all_data['LotArea'] + 1)

# 3. 时间特征
all_data['YearSold'] = all_data['YrSold']
all_data['MonthSold'] = all_data['MoSold']
all_data['IsNew'] = (all_data['YrSold'] == all_data['YearBuilt']).astype(int)

# 4. 分类列与数值列
num_cols = all_data.select_dtypes(include=[np.number]).columns.tolist()
cat_cols = all_data.select_dtypes(include=['object']).columns.tolist()

# 5. 构造 Pipeline
num_transformer = Pipeline([
    ('transform', PowerTransformer(method='yeo-johnson')),
    ('scale', StandardScaler())
])

cat_transformer = Pipeline([
    ('onehot', OneHotEncoder(handle_unknown='ignore', sparse_output=False))
])

preprocessor = ColumnTransformer([
    ('num', num_transformer, num_cols),
    ('cat', cat_transformer, cat_cols)
])

# 6. 拟合预处理并转换
X_all = preprocessor.fit_transform(all_data)
X_train = X_all[:len(train)]
X_test = X_all[len(train):]

# 7. Filter 阶段:互信息选择 Top 80
selector_filter = SelectKBest(mutual_info_regression, k=80)
X_train_filter = selector_filter.fit_transform(X_train, y)
X_test_filter = selector_filter.transform(X_test)

# 8. Wrapper 阶段:RFE 保留 35 个特征
estimator = RandomForestRegressor(n_estimators=100, random_state=42, n_jobs=-1)
selector_wrapper = RFECV(estimator, step=0.1, cv=5, scoring='neg_mean_squared_error',
                         min_features_to_select=35)
selector_wrapper.fit(X_train_filter, y)

X_train_final = selector_wrapper.transform(X_train_filter)
X_test_final = selector_wrapper.transform(X_test_filter)

print(f"最终特征数: {X_train_final.shape[1]}")

# 9. 训练最终模型
model = xgb.XGBRegressor(
    n_estimators=3000, learning_rate=0.01, max_depth=3,
    subsample=0.7, colsample_bytree=0.7, random_state=42
)
model.fit(X_train_final, y)

# 10. 评估
scores = cross_val_score(model, X_train_final, y,
                         cv=5, scoring='neg_root_mean_squared_error')
print(f"5-Fold RMSE: {-scores.mean():.4f}")

7.3 特征工程效果对比

方案 特征数 5-Fold RMSE(对数空间) 相对提升
基线(原始特征,无处理) 79 0.1456 ---
+ 缺失值填充 + 对数变换 79 0.1382 -5.1%
+ 交互特征(TotalSF/TotalBathrooms 等) 85 0.1315 -9.7%
+ 时间特征 + 类别编码 243 0.1268 -12.9%
+ 特征选择(Filter + Wrapper 至 35) 35 0.1234 -15.2%

从表中可见,交互特征带来了最大的单步提升(RMSE 从 0.1382 降到 0.1315),而特征选择则在压缩维度的同时进一步提升了性能(从 243 维降到 35 维,RMSE 再降 2.7%)。这验证了"好特征 + 适度精简"优于"堆砌特征"的原则。

八、小结

特征工程不是一门玄学,而是一套可系统化的方法论。面对一个类别特征,决策路径是:先看基数(低→One-Hot,中→Target Encoding + smoothing,高→CatBoost),再看是否有序(有序→Ordinal)。面对一个数值特征,决策路径是:先看分布形态(偏态→Log/Box-Cox/Yeo-Johnson,正态→StandardScaler),再看业务含义(是否有天然的比率关系→构造交互特征)。

特征选择不是可选项,而是必选项。Filter 做初筛、Wrapper 做精选、Embedded 做模型内建选择、Boruta 做保守验证------四种方法可以组合使用,而非互斥。

将所有步骤封装进 sklearn Pipeline,是保障训练-推理一致性的工程底线。手动特征工程脚本在 Notebook 中运行良好,但部署到生产环境时极易因遗漏某个变换步骤而导致预测偏差。

此前专栏关于 scikit-learn Pipeline 工程化、Pandas 数据处理和 NumPy 向量化计算的文章,为本文提供了从数据清洗到特征构造再到模型输入的完整上游支撑。如果本文对特征工程实践有所启发,欢迎点赞、收藏与关注。

相关推荐
用户276247978501 小时前
Agent demo 跑通了,然后呢?聊聊多用户生产化这道没人填的坑
人工智能
Holman1 小时前
给 Claude Code 装技能包:Skills 实战
人工智能·ai编程
财经资讯数据_灵砚智能1 小时前
基于全球经济类多源新闻的NLP情感分析与数据可视化(夜间-次晨)2026年6月8日
大数据·人工智能·python·ai·信息可视化·自然语言处理·灵砚智能
“码”力全开1 小时前
打破芯片与协议壁垒:基于 Docker+边缘计算 的企业级 AI 视频管理平台架构解析(附 GB28181/RTSP 统一接入与源码交付方案)
人工智能·docker·边缘计算
morning_judger1 小时前
Agent开发系列(十)-知识库建设(架构总览)
开发语言·人工智能
南知意-1 小时前
MonkeyCode:长亭开源的企业级AI开发平台,GitHub 3.2k Star!
人工智能·ai·开源·github·ai编程·开源项目
数字人小文1 小时前
生产环境 Agent 实战:4个真实踩坑场景
人工智能
ai产品老杨1 小时前
【架构深评】基于 Docker 与 边缘计算,如何打通 GB28181/RTSP 与 X86/ARM 异构算力的企业级 AI 视频流网关?(附源码交付)
人工智能·docker·架构
星幻元宇VR1 小时前
消防教育基地展厅设备【消防知识安全竞赛系统】
人工智能·科技·学习·安全