Python特征工程实战:从数据清洗到模型提效的完整流程
引言:为什么特征工程比调参更重要
先看一张本文的完整流程脑图,便于把握全局:
做机器学习项目,很多新手把80%的时间花在调参上,结果模型精度提升微乎其微。而真正决定模型上限的,往往是特征工程。
有句话在业内流传很广:"数据和特征决定了机器学习的上限,模型和算法只是逼近这个上限。"
今天这篇文章,我用一个完整的案例,带你走一遍特征工程的标准流程:数据清洗 → 特征构造 → 特征选择 → 模型验证。全程用 Python 实现,代码可以直接跑。
一、案例背景与数据准备
我们用一份模拟的电商用户购买数据,目标是预测用户是否会复购。数据集包含以下字段:
• user_id:用户ID
• gender:性别
• age:年龄
• register_days:注册天数
• total_orders:历史订单数
• total_amount:历史消费金额
• last_order_days:最近一次下单距今天数
• channel:注册渠道(app/web/小程序)
• is_return:是否复购(0/1)
先构造模拟数据:
import pandas as pd import numpy as np np.random.seed(42) n = 5000 df = pd.DataFrame({ 'user_id': range(1, n + 1), 'gender': np.random.choice(['M', 'F', np.nan], n, p=[0.45, 0.45, 0.1]), 'age': np.random.normal(30, 10, n).astype(int), 'register_days': np.random.randint(1, 1000, n), 'total_orders': np.random.poisson(3, n), 'total_amount': np.random.exponential(200, n).round(2), 'last_order_days': np.random.randint(0, 365, n), 'channel': np.random.choice(['app', 'web', 'mini', np.nan], n, p=[0.5, 0.3, 0.15, 0.05]), 'is_return': np.random.binomial(1, 0.3, n) }) # 构造一些异常值 df.loc[np.random.choice(df.index, 50, replace=False), 'age'] = 200 df.loc[np.random.choice(df.index, 30, replace=False), 'total_amount'] = -100 print(df.head()) print(df.info())二、数据清洗:别让脏数据毁了模型
数据清洗是特征工程的第一步。常见的问题包括:缺失值、异常值、重复值、类型错误。
下面是一段清洗代码:
# 缺失值处理 df['gender'] = df['gender'].fillna('unknown') df['channel'] = df['channel'].fillna('other') # 年龄异常值处理:超过100岁视为缺失,用中位数填充 df.loc[df['age'] > 100, 'age'] = np.nan df['age'] = df['age'].fillna(df['age'].median()).astype(int) # 消费金额异常值处理:负数置为0 df['total_amount'] = df['total_amount'].clip(lower=0) # 去重 df = df.drop_duplicates(subset=['user_id']) print(df.isnull().sum())【数据来源】上述代码为模拟数据生成与清洗示例,用于演示特征工程流程。
三、特征构造:把原始数据变成模型能看懂的东西
特征构造是特征工程的核心。好的特征能让模型学到更有用的模式。
3.1 统计特征
从现有字段派生出新特征:
# 客单价 df['avg_order_amount'] = df['total_amount'] / (df['total_orders'] + 1) # 活跃度:注册天数 / 订单数 df['days_per_order'] = df['register_days'] / (df['total_orders'] + 1) # 是否近期活跃 df['is_recent_active'] = (df['last_order_days'] <= 30).astype(int) # 年龄分段 df['age_group'] = pd.cut(df['age'], bins=[0, 18, 25, 35, 45, 100], labels=['少年', '青年', '中青年', '中年', '中老年']) print(df[['user_id', 'avg_order_amount', 'days_per_order', 'is_recent_active', 'age_group']].head())3.2 类别特征编码
模型无法直接处理字符串,需要对类别特征编码:
# 独热编码 df_encoded = pd.get_dummies(df, columns=['gender', 'channel', 'age_group'], drop_first=True) print(df_encoded.filter(like='gender_').head())3.3 特征标准化
对于数值型特征,标准化是很多模型的前提:
from sklearn.preprocessing import StandardScaler num_cols = ['age', 'register_days', 'total_orders', 'total_amount', 'last_order_days', 'avg_order_amount', 'days_per_order'] scaler = StandardScaler() df_encoded[num_cols] = scaler.fit_transform(df_encoded[num_cols]) print(df_encoded[num_cols].describe())四、特征选择:去掉 noise,留下 signal
特征不是越多越好。冗余特征会增加过拟合风险,也会拖慢训练速度。
这里介绍两种常用方法:相关性筛选和树模型特征重要性。
from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split import matplotlib.pyplot as plt # 准备训练数据 feature_cols = [c for c in df_encoded.columns if c not in ['user_id', 'is_return']] X = df_encoded[feature_cols] y = df_encoded['is_return'] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # 训练随机森林查看特征重要性 rf = RandomForestClassifier(n_estimators=100, random_state=42) rf.fit(X_train, y_train) importance = pd.DataFrame({ 'feature': feature_cols, 'importance': rf.feature_importances_ }).sort_values('importance', ascending=False) print(importance.head(10))输出结果通常会显示:avg_order_amount、last_order_days、is_recent_active 等特征是 top 重要特征。这符合业务直觉:消费能力、活跃度、复购意愿密切相关。
五、模型验证:特征工程的效果到底如何
我们用 AUC 和 F1 来对比特征工程前后的效果:
from sklearn.linear_model import LogisticRegression from sklearn.metrics import roc_auc_score, f1_score, classification_report # 只用原始特征 base_cols = ['age', 'register_days', 'total_orders', 'total_amount', 'last_order_days'] X_base = df_encoded[base_cols] X_train_base, X_test_base, _, _ = train_test_split(X_base, y, test_size=0.2, random_state=42) lr_base = LogisticRegression(max_iter=1000, random_state=42) lr_base.fit(X_train_base, y_train) base_auc = roc_auc_score(y_test, lr_base.predict_proba(X_test_base)[:, 1]) base_f1 = f1_score(y_test, lr_base.predict(X_test_base)) # 用工程后的特征 lr_enhanced = LogisticRegression(max_iter=1000, random_state=42) lr_enhanced.fit(X_train, y_train) enhanced_auc = roc_auc_score(y_test, lr_enhanced.predict_proba(X_test)[:, 1]) enhanced_f1 = f1_score(y_test, lr_enhanced.predict(X_test)) print(f"原始特征 AUC: {base_auc:.4f}, F1: {base_f1:.4f}") print(f"工程特征 AUC: {enhanced_auc:.4f}, F1: {enhanced_f1:.4f}") print(f"AUC 提升: {(enhanced_auc - base_auc) / base_auc * 100:.2f}%")【数据来源】以上结果基于模拟数据实验,不同数据集效果会有差异。
六、特征工程常见误区
最后分享几个新手容易踩的坑:
1. 泄露未来信息
用目标变量构造特征时,要避免把测试集的信息带入训练集。比如用"未来30天是否复购"作为特征,那就是在作弊。
2. 过度编码
高基数类别变量(如 user_id)直接做独热编码会爆炸。可以用 target encoding 或 embedding。
3. 忽视业务含义
不要为了构造特征而构造特征。每一个特征都要有业务解释性,否则模型很难稳定。
4. 不做交叉验证
特征选择必须在训练集上做,不能在整个数据集上提前筛选,否则会造成信息泄露。
总结
特征工程的标准流程可以概括为:
1.数据清洗:处理缺失、异常、重复
2.特征构造:统计特征、交叉特征、分桶、编码
3.特征选择:相关性、重要性、正则化
4.模型验证:对比特征工程前后的效果
这套流程不是一成不变的,需要根据业务场景调整。但核心思路是:让模型看到更多"有意义的信号",而不是"更多的噪声"。
【数据来源】本文数据集为模拟生成,方法参考 sklearn 官方文档及主流机器学习工程实践。
你在特征工程中最常用的技巧是什么?评论区一起交流。
觉得有用,转给正在做数据分析和机器学习的朋友。