特征工程
概述
特征工程创建和转换特征,以通过领域知识和数学变换来改善模型性能、可解释性和泛化能力。
何时使用
- 当您需要使用原始特征之外的方法来提升模型性能时
- 当处理需要编码的分类变量以供ML算法使用时
- 当特征具有不同的尺度且需要规范化时
- 当基于业务知识创建特定领域的特征时
- 当处理偏斜分布或非线性关系时
- 当为具有特定要求的不同类型ML算法准备数据时
工程技术
- 编码:将分类变量转换为数值
- 缩放:规范化特征范围
- 多项式特征:更高阶项
- 交互:组合特征
- 特定领域:业务相关的转换
- 时间:基于时间的特征
核心原则
- 基于领域知识创建特征
- 移除冗余特征
- 适当缩放特征
- 处理分类变量
- 创建有意义的交互
使用Python实现
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import (
StandardScaler, MinMaxScaler, RobustScaler, PolynomialFeatures,
OneHotEncoder, OrdinalEncoder, LabelEncoder
)
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
import seaborn as sns
# 创建样本数据集
np.random.seed(42)
df = pd.DataFrame({
'age': np.random.uniform(18, 80, 1000),
'income': np.random.uniform(20000, 150000, 1000),
'experience_years': np.random.uniform(0, 50, 1000),
'category': np.random.choice(['A', 'B', 'C'], 1000),
'city': np.random.choice(['NYC', 'LA', 'Chicago'], 1000),
'purchased': np.random.choice([0, 1], 1000),
})
print("原始数据:")
print(df.head())
print(df.info())
# 1. 分类编码
# 独热编码
print("
1. 独热编码:")
df_ohe = pd.get_dummies(df, columns=['category', 'city'], drop_first=True)
print(df_ohe.head())
# 序数编码
print("
2. 序数编码:")
ordinal_encoder = OrdinalEncoder()
df['category_ordinal'] = ordinal_encoder.fit_transform(df[['category']])
print(df[['category', 'category_ordinal']].head())
# 标签编码
print("
3. 标签编码:")
le = LabelEncoder()
df['city_encoded'] = le.fit_transform(df['city'])
print(df[['city', 'city_encoded']].head())
# 2. 特征缩放
print("
4. 特征缩放:")
X = df[['age', 'income', 'experience_years']].copy()
# StandardScaler (均值为0,标准差为1)
scaler = StandardScaler()
X_standard = scaler.fit_transform(X)
# MinMaxScaler [0, 1]
minmax_scaler = MinMaxScaler()
X_minmax = minmax_scaler.fit_transform(X)
# RobustScaler (抗离群值)
robust_scaler = RobustScaler()
X_robust = robust_scaler.fit_transform(X)
# 可视化
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
axes[0, 0].hist(X['age'], bins=30, edgecolor='black')
axes[0, 0].set_title('原始年龄')
axes[0, 1].hist(X_standard[:, 0], bins=30, edgecolor='black')
axes[0, 1].set_title('StandardScaler 年龄')
axes[1, 0].hist(X_minmax[:, 0], bins=30, edgecolor='black')
axes[1, 0].set_title('MinMaxScaler 年龄')
axes[1, 1].hist(X_robust[:, 0], bins=30, edgecolor='black')
axes[1, 1].set_title('RobustScaler 年龄')
plt.tight_layout()
plt.show()
# 3. 多项式特征
print("
5. 多项式特征:")
X_simple = df[['age']].copy()
poly = PolynomialFeatures(degree=2, include_bias=False)
X_poly = poly.fit_transform(X_simple)
X_poly_df = pd.DataFrame(X_poly, columns=['age', 'age^2'])
print(X_poly_df.head())
# 可视化
plt.figure(figsize=(12, 5))
plt.scatter(df['age'], df['income'], alpha=0.5)
plt.xlabel('年龄')
plt.ylabel('收入')
plt.title('年龄与收入')
plt.grid(True, alpha=0.3)
plt.show()
# 4. 特征交互
print("
6. 特征交互:")
df['age_income_interaction'] = df['age'] * df['income'] / 10000
df['age_experience_ratio'] = df['age'] / (df['experience_years'] + 1)
print(df[['age', 'income', 'age_income_interaction', 'age_experience_ratio']].head())
# 5. 特定领域转换
print("
7. 特定领域特征:")
df['age_group'] = pd.cut(df['age'], bins=[0, 30, 45, 60, 100],
labels=['年轻', '中年', '老年', '退休'])
df['income_level'] = pd.qcut(df['income'], q=3, labels=['低', '中', '高'])
df['log_income'] = np.log1p(df['income'])
df['sqrt_experience'] = np.sqrt(df['experience_years'])
print(df[['age', 'age_group', 'income', 'income_level', 'log_income']].head())
# 6. 时间特征(如果有日期数据)
print("
8. 时间特征:")
dates = pd.date_range('2023-01-01', periods=len(df))
df['date'] = dates
df['year'] = df['date'].dt.year
df['month'] = df['date'].dt.month
df['day_of_week'] = df['date'].dt.dayofweek
df['quarter'] = df['date'].dt.quarter
df['is_weekend'] = df['date'].dt.dayofweek >= 5
print(df[['date', 'year', 'month', 'day_of_week', 'is_weekend']].head())
# 7. 特征标准化流水线
print("
9. 特征工程流水线:")
# 分离数值和分类特征
numerical_features = ['age', 'income', 'experience_years']
categorical_features = ['category', 'city']
# 创建预处理流水线
preprocessor = ColumnTransformer(
transformers=[
('num', StandardScaler(), numerical_features),
('cat', OneHotEncoder(drop='first'), categorical_features),
]
)
X_processed = preprocessor.fit_transform(df[numerical_features + categorical_features])
print(f"处理后的形状: {X_processed.shape}")
# 8. 特征统计
print("
10. 特征统计:")
X_for_stats = df[numerical_features].copy()
X_for_stats['category_A'] = (df['category'] == 'A').astype(int)
X_for_stats['city_NYC'] = (df['city'] == 'NYC').astype(int)
feature_stats = pd.DataFrame({
'Feature': X_for_stats.columns,
'Mean': X_for_stats.mean(),
'Std': X_for_stats.std(),
'Min': X_for_stats.min(),
'Max': X_for_stats.max(),
'Skewness': X_for_stats.skew(),
'Kurtosis': X_for_stats.kurtosis(),
})
print(feature_stats)
# 9. 特征相关性
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
X_numeric = df[numerical_features].copy()
X_numeric['purchased'] = df['purchased']
corr_matrix = X_numeric.corr()
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', center=0, ax=axes[0])
axes[0].set_title('特征相关性矩阵')
# 工程特征的分布
axes[1].hist(df['age_income_interaction'], bins=30, edgecolor='black', alpha=0.7)
axes[1].set_title('年龄-收入交互分布')
axes[1].set_xlabel('值')
axes[1].set_ylabel('频率')
plt.tight_layout()
plt.show()
# 10. 特征分箱/离散化
print("
11. 特征分箱:")
df['age_bin_equal'] = pd.cut(df['age'], bins=5)
df['age_bin_quantile'] = pd.qcut(df['age'], q=5)
df['income_bins'] = pd.cut(df['income'], bins=[0, 50000, 100000, 150000])
print("等宽分箱:")
print(df['age_bin_equal'].value_counts().sort_index())
print("
等频分箱:")
print(df['age_bin_quantile'].value_counts().sort_index())
# 11. 缺失值创建和处理
print("
12. 缺失值插补:")
df_with_missing = df.copy()
missing_indices = np.random.choice(len(df), 50, replace=False)
df_with_missing.loc[missing_indices, 'age'] = np.nan
# 均值插补
age_mean = df_with_missing['age'].mean()
df_with_missing['age_imputed_mean'] = df_with_missing['age'].fillna(age_mean)
# 中位数插补
age_median = df_with_missing['age'].median()
df_with_missing['age_imputed_median'] = df_with_missing['age'].fillna(age_median)
# 前向填充
df_with_missing['age_imputed_ffill'] = df_with_missing['age'].fillna(method='ffill')
print(df_with_missing[['age', 'age_imputed_mean', 'age_imputed_median']].head(10))
print("
特征工程完成!")
print(f"原始特征: {len(df.columns) - 5}")
print(f"最终可用特征: {len(df.columns)}")
最佳实践
- 在工程特征之前了解您的领域
- 创建可解释的特征
- 避免数据泄露(使用未来信息)
- 在工程后测试特征重要性
- 记录所有转换
- 对不同算法使用适当的缩放
常见转换
- 对数变换:用于偏斜分布
- 多项式特征:用于非线性关系
- 交互项:用于组合效应
- 分箱:用于分类近似
- 规范化:用于跨尺度比较
交付物
- 工程化特征数据集
- 特征转换文档
- 新特征的相关性分析
- 分布比较(前后)
- 特征重要性排名
- 预处理流水线代码
- 带有特征描述的数据字典