一、特征工程的核心目标
特征工程的目标是提取、构造或转换特征,使其更好地表达与目标变量的关系,从而提升机器学习模型的预测性能。
简单来说:特征工程 = 用数学、统计和领域知识,将原始数据变成模型更易理解的信息表
达。
二、常见特征工程方法
当你通过特征筛选(如互信息、相关性分析)找到潜在有用的特征后,下一步就是开发新特征。常见的开发方式包括:
数学变换(Math Transforms)
计数特征(Counts)
分解和组合特征(Building-Up and Breaking-Down)
分组聚合变换(Group Transforms)
1️⃣ 数学变换(Mathematical Transforms)
作用:重塑数值特征分布,提取潜在关系。
(1) 比例、乘积、加减运算
常见于领域知识场景,例如:
房地产:
房价/面积 = 单价
,面积 × 质量等级 = 房屋价值因子
汽车:
stroke_ratio = stroke / bore
衡量效率
df["stroke_ratio"] = df.stroke / df.bore
原因:模型尤其是线性模型对比率特征较难学习,显式添加能降低学习难度。
(2) 幂次或对数变换
用于偏态分布特征归一化,提升模型拟合能力。
df["LogWindSpeed"] = np.log1p(df.WindSpeed) # log(1+x)避免log(0)
作用:
缩小极端值影响(重尾分布)。
满足线性模型和神经网络的近似正态假设。
相关数学:Box-Cox变换
(3) 标准化与归一化
标准化 (Z-score): 使特征均值为0,标准差为1
归一化 (Min-Max): 将特征缩放到[0,1]
适用模型:
线性模型(线性回归、逻辑回归)
神经网络(梯度下降对尺度敏感)
树模型(XGBoost、RF)不需要标准化,因为分裂点与数值范围无关。
2️⃣ 计数特征(Counts / Aggregation)
作用:将多个布尔或稀疏特征合并为一个信息浓缩的计数型变量。
(1) 二值/布尔累加
交通事故:统计事故周边道路设施数:
roadway_features = ["Amenity","Bump","Crossing"]
df["RoadFeatures"] = df[roadway_features].sum(axis=1)
医疗领域:统计患者风险因素个数(如高血压、糖尿病)。
(2) 非零元素计数
混凝土配方:统计一个配方包含的有效材料种类:
components = ["Cement","FlyAsh","Water"]
df["ComponentsCount"] = df[components].gt(0).sum(axis=1)
意义:
对树模型特别有效,能直接表示“某特征的数量多寡与目标的关系”。
3️⃣ 特征分解与组合(Decomposition & Combination)
作用:从复杂字符串/标识符中拆分信息,或组合特征以捕获交互。
(1) 字符串分解
电话号提取区号:
df["AreaCode"] = df["Phone"].str.extract(r'\((\d{3})\)')
地址拆分:街道、城市、州。
(2) 特征组合(交互特征)
拼接两列类别型特征:
df["Make_Style"] = df["make"] + "_" + df["body_style"]
构造交互特征(乘积、比率):
df["Area_Quality"] = df["GrLivArea"] * df["OverallQual"]
作用:
显式表达特征交互关系,尤其在数据量较小时帮助模型更快捕捉模式。
4️⃣ 分组聚合(Group Transforms)
作用:基于类别变量分组,对另一变量计算聚合统计量。
(1) 分组均值/中位数
按州计算平均收入:
df["AvgIncomeByState"] = df.groupby("State")["Income"].transform("mean")
2) 频率编码(Frequency Encoding)
类别特征转换为类别频率:
df["StateFreq"] = df.groupby("State")["State"].transform("count") / df.State.count()
(3) 验证集注意事项
必须仅用训练集统计,再 merge 到验证集,避免数据泄露:
df_valid = df_valid.merge(
df_train[["Coverage","AverageClaim"]].drop_duplicates(),
on="Coverage", how="left")
5️⃣ 日期与时间特征
时间特征是高信息量变量,可拆分为:
年、月、日、季度、是否周末。
事件间间隔:DaysSinceLastPurchase
。
df["Year"] = df["Date"].dt.year
df["IsWeekend"] = df["Date"].dt.weekday >= 5
6️⃣ 空间(地理)特征
经纬度转为距离特征:如距市中心、距最近医院。
使用Haversine公式计算地理距离。
三、模型与特征工程的适配性
线性模型(LR、Lasso、Ridge):
需要显式构造交互项与非线性变换。
对缩放敏感,需标准化。
树模型(RF、XGBoost、LightGBM):
自动捕捉非线性和交互关系。
不依赖特征缩放,但计数特征和类别频率编码有帮助。
神经网络(NN):
需要归一化(梯度稳定)。
高维组合可用嵌入(Embedding)。
四、例子详解
这个是kaggle里面的例子,首先是载入需要进行实验的文件。
# Setup feedback system
from learntools.core import binder
binder.bind(globals())
from learntools.feature_engineering_new.ex3 import *
import numpy as np
import pandas as pd
from sklearn.model_selection import cross_val_score
from xgboost import XGBRegressor
def score_dataset(X, y, model=XGBRegressor()):
# Label encoding for categoricals
for colname in X.select_dtypes(["category", "object"]):
X[colname], _ = X[colname].factorize()
# Metric for Housing competition is RMSLE (Root Mean Squared Log Error)
score = cross_val_score(
model, X, y, cv=5, scoring="neg_mean_squared_log_error",
)
score = -1 * score.mean()
score = np.sqrt(score)
return score
# Prepare data
df = pd.read_csv("../input/fe-course-data/ames.csv")
X = df.copy()
y = X.pop("SalePrice")
1) 创建数学变换
创建以下特征:
LivLotRatio
:GrLivArea
与 LotArea
的比率
Spaciousness
:FirstFlrSF
和 SecondFlrSF
之和除以 TotRmsAbvGrd
TotalOutsideSF:
WoodDeckSF、
OpenPorchSF、
EnclosedPorch、
Threeseasonporch
和 ScreenPorch
的总和
X_1 = pd.DataFrame() # dataframe to hold new features
X_1["LivLotRatio"] = df.GrLivArea / df.LotArea
X_1["Spaciousness"] = (df.FirstFlrSF + df.SecondFlrSF) / df.TotRmsAbvGrd
X_1["TotalOutsideSF"] = df.WoodDeckSF + df.OpenPorchSF + df.EnclosedPorch + df.Threeseasonporch + df.ScreenPorch
2) 与分类的交互
如果发现了数值特征和分类特征之间的交互效应,则可能需要使用单热编码对其进行显式建模,如下所示:
# One-hot encode Categorical feature, adding a column prefix "Cat"
X_new = pd.get_dummies(df.Categorical, prefix="Cat")
# Multiply row-by-row
X_new = X_new.mul(df.Continuous, axis=0)
# Join the new features to the feature set
X = X.join(X_new)
这段代码的作用是:
基于一个分类特征(Categorical
)和一个连续特征(Continuous
),生成交互特征(Interaction Features),并加入到原始特征集 X
中。
BldgType
和 GrLivArea
之间的交互
# One-hot encode BldgType. Use `prefix="Bldg"` in `get_dummies`
X_2 = pd.get_dummies(df.BldgType, prefix="Bldg")
# Multiply
X_2 = X_2.mul(df.GrLivArea, axis=0)
3) 计数特征
让我们尝试创建一个功能来描述住宅有多少种室外区域。创建一个功能 PorchTypes
,用于计算以下大于 0.0 的要素数量:WoodDeckSF OpenPorchSF EnclosedPorch Threeseasonporch ScreenPorch
X_3 = pd.DataFrame()
# YOUR CODE HERE
X_3["PorchTypes"] = df[["WoodDeckSF","OpenPorchSF","EnclosedPorch","Threeseasonporch","ScreenPorch"]].gt(0.0).sum(axis=1)
4) 分解分类特征
MSSubClass
描述住宅的类型:
df.MSSubClass.unique()
结果是:
array(['One_Story_1946_and_Newer_All_Styles', 'Two_Story_1946_and_Newer',
'One_Story_PUD_1946_and_Newer',
'One_and_Half_Story_Finished_All_Ages', 'Split_Foyer',
'Two_Story_PUD_1946_and_Newer', 'Split_or_Multilevel',
'One_Story_1945_and_Older', 'Duplex_All_Styles_and_Ages',
'Two_Family_conversion_All_Styles_and_Ages',
'One_and_Half_Story_Unfinished_All_Ages',
'Two_Story_1945_and_Older', 'Two_and_Half_Story_All_Ages',
'One_Story_with_Finished_Attic_All_Ages',
'PUD_Multilevel_Split_Level_Foyer',
'One_and_Half_Story_PUD_All_Ages'], dtype=object)
可以看到,每个类别的第一个词(大致)描述了一个更通用的分类。通过在第一个"_"拆分 MSSubClass
,创建仅包含这些第一个单词的功能。(提示:在拆分
方法中使用参数 n=1
)
例如:
"20_1Story" → ["20", "1Story"]
"60_2Story" → ["60", "2Story"]
X_4 = pd.DataFrame()
# YOUR CODE HERE
X_4["MSClass"] = df.MSSubClass.str.split("_", n=1, expand=True)[0]
'''
.str.split("_", n=1):按下划线 "_" 只分割一次(参数 n=1 限制最大分割次数)。
expand=True:将结果展开为多个列(DataFrame 形式)。
'''
5) 使用分组变换
房屋的价值通常取决于它与附近典型房屋的比较。创建一个要素 MedNhbdArea
,用于描述按Neighborhood分组的 GrLivArea
的中位数 。
X_5 = pd.DataFrame()
# YOUR CODE HERE
X_5["MedNhbdArea"] = df.groupby("Neighborhood").GrLivArea.transform("median")