第四章 数据工程(下):特征工程基础

好,咱们接着聊。上一章我们把数据清洗和拼接讲透了,这一章进入真正让模型「吃饱吃好」的关键环节——特征工程。

说实话,我入行头两年,一直觉得特征工程就是「标准化一下、归一化一下」就完事了。直到我在实盘策略里栽了个大跟头——一个看起来完美的因子,回测夏普3.5,实盘直接变成0.8。后来排查了三天,发现是截面数据的时间戳对齐出了问题。嗯,从那以后,我再也不敢小看特征工程里的任何一个细节。

4.1 标准化:让所有特征站在同一起跑线

标准化,说白了就是把不同量纲的数据拉到同一个尺度上。比如市盈率可能是几十倍,换手率是百分之几,成交额是几亿——你让模型直接学,它肯定优先关注数值大的那个。

我个人习惯用Z-score标准化:

import pandas as pd
import numpy as np

def zscore_standardize(df, columns):
    """
    Z-score标准化
    公式: (x - mean) / std
    """
    result = df.copy()
    for col in columns:
        mean = result[col].mean()
        std = result[col].std()
        result[col] = (result[col] - mean) / std
    return result

# 示例:对因子数据做标准化
factor_data = pd.DataFrame({
    'pe_ratio': [15.2, 28.6, 9.8, 45.3, 12.1],
    'turnover': [0.03, 0.08, 0.01, 0.12, 0.05]
})
standardized = zscore_standardize(factor_data, ['pe_ratio', 'turnover'])
print(standardized)
我的小技巧: 标准化时一定要按「截面」做,而不是按「时序」做。什么意思?就是每个时间点单独算均值和标准差。如果你把过去三年的数据混在一起标准化,那最新数据会被历史极端值拉偏。

4.2 中性化:剔除你不想要的「杂质」

中性化这个概念,很多初学者容易搞混。它不是为了消除量纲,而是为了剔除某个已知因素的影响。

举个例子:你发现「市值越小,收益越高」——但这不是你想要的因子,你真正想研究的是「盈利能力」这个因子。那你就需要把市值的影响从中性化掉。

怎么做?回归法是最常用的:

from sklearn.linear_model import LinearRegression

def neutralize_factor(factor, exposure):
    """
    对因子做中性化处理
    factor: 原始因子值
    exposure: 需要中性化的暴露变量(如市值)
    """
    # 第一步:用exposure回归factor
    model = LinearRegression()
    model.fit(exposure.values.reshape(-1, 1), factor)
    
    # 第二步:取残差作为中性化后的因子
    predicted = model.predict(exposure.values.reshape(-1, 1))
    neutralized = factor - predicted
    
    return neutralized

# 示例:对盈利因子做市值中性化
profit_factor = np.array([0.05, 0.12, -0.03, 0.08, 0.15])
market_cap = np.array([100, 500, 50, 200, 800])  # 单位:亿

neutralized_profit = neutralize_factor(profit_factor, market_cap)
print(f"原始因子: {profit_factor}")
print(f"中性化后: {neutralized_profit}")
我曾经踩过的坑: 中性化不是万能的。如果你把因子和暴露变量高度相关(比如相关系数超过0.9),那中性化后因子几乎变成零向量,信息全丢了。这时候你要反思:是不是选错了暴露变量?或者这个因子本身就不该用?

4.3 哑变量处理:把「类别」变成「数字」

机器学习模型只认数字,不认「行业」「板块」「地区」这些文字。哑变量(也叫独热编码)就是干这个的。

但这里有个坑——哑变量陷阱。如果你有K个类别,只需要K-1个哑变量,否则会引入完全多重共线性。

import pandas as pd

# 原始数据:行业分类
data = pd.DataFrame({
    'stock': ['A', 'B', 'C', 'D', 'E'],
    'industry': ['金融', '科技', '消费', '金融', '科技']
})

# 方法一:pandas的get_dummies(自动处理K-1)
dummies = pd.get_dummies(data['industry'], prefix='industry', drop_first=True)
print("哑变量处理结果:")
print(pd.concat([data, dummies], axis=1))

# 方法二:手动编码(适合有序类别)
# 比如:小盘=0,中盘=1,大盘=2
data['size_level'] = [0, 1, 2, 1, 0]
print("\n有序编码示例:")
print(data[['stock', 'size_level']])
核心要点: 无序类别用独热编码(drop_first=True),有序类别用标签编码。别搞反了,否则模型会学到错误的「大小关系」。

4.4 时序 vs 截面:处理方式的本质差异

这是本章最核心的部分。我见过太多人把时序数据和截面数据混在一起处理,结果模型一塌糊涂。

你想想看:

  • 时序数据:同一只股票,不同时间点的数据。比如贵州茅台过去500天的日收益率。
  • 截面数据:同一时间点,不同股票的数据。比如2024年1月1日,全市场5000只股票的市盈率。

处理方式完全不同:

操作 时序处理 截面处理
标准化 按时间窗口滚动计算均值和标准差 每个时间点单独计算
中性化 通常不做,或做时间序列去趋势 按截面回归取残差
哑变量 时间哑变量(月份、星期几) 截面哑变量(行业、板块)
缺失值处理 前向填充或插值 截面均值填充或删除

举个例子,你就明白了:

# 时序标准化:滚动窗口
def time_series_standardize(series, window=60):
    """滚动窗口标准化,避免未来信息"""
    rolling_mean = series.rolling(window=window).mean()
    rolling_std = series.rolling(window=window).std()
    return (series - rolling_mean) / rolling_std

# 截面标准化:每个时间点单独做
def cross_section_standardize(df, date_col, factor_col):
    """按日期分组,每组内做Z-score"""
    def zscore_group(group):
        mean = group[factor_col].mean()
        std = group[factor_col].std()
        group[factor_col + '_zscore'] = (group[factor_col] - mean) / std
        return group
    
    return df.groupby(date_col).apply(zscore_group)

# 示例数据
import pandas as pd
import numpy as np

# 模拟数据:3只股票,5个交易日
dates = pd.date_range('2024-01-01', periods=5, freq='D')
stocks = ['A', 'B', 'C']
data = []
for date in dates:
    for stock in stocks:
        data.append({
            'date': date,
            'stock': stock,
            'factor': np.random.randn() * 0.1 + 0.05
        })
df = pd.DataFrame(data)

# 截面标准化
df_standardized = cross_section_standardize(df, 'date', 'factor')
print(df_standardized)
我的经验: 做因子挖掘时,90%的情况用截面处理就够了。时序处理主要用在「时间序列预测」类模型,比如用过去N天的数据预测未来1天的收益。千万别搞混,否则你的因子会「偷看未来」。

4.5 本章知识体系总览

下面这张图,是我自己梳理的特征工程核心流程。你照着这个框架走,基本不会出错:

特征工程核心流程 原始因子数据 数据类型? 时序 截面 时序处理 • 滚动窗口标准化 • 前向填充缺失值 • 时间哑变量(月/周) 截面处理 • 截面Z-score标准化 • 回归法中性化 • 行业哑变量 最终特征矩阵(统一格式) 输入机器学习模型

这张图的核心逻辑就是:拿到原始因子后,先判断数据类型。时序数据走左边,截面数据走右边。两边处理完,最终汇合成一个统一格式的特征矩阵,再喂给模型。

一句话总结: 标准化让尺度统一,中性化剔除干扰,哑变量编码类别。时序和截面,处理方式天差地别——千万别混为一谈。

好了,这一章的内容就到这里。特征工程是因子挖掘的「地基」,地基打不牢,模型再花哨也是空中楼阁。下一章我们进入真正的「因子挖掘」环节,到时候会用到今天讲的所有技巧。


专注资料整理