特征工程基础:单因子分析、多因子共线性处理、标准化与归一化
各位同学,咱们今天聊点实在的。
做量化因子挖掘,说白了就是跟数据打交道。你挖出来的因子好不好,能不能用,第一步就看特征工程做得怎么样。我见过太多人,因子逻辑听起来挺漂亮,一上实盘就崩——十有八九是特征工程这步没走稳。
今天这一章,咱们把三个核心问题讲透:单因子怎么分析、多因子共线性怎么处理、数据标准化归一化怎么做。嗯,都是基本功,但基本功往往最要命。
一、单因子分析:先看看你的因子有没有“料”
我个人习惯,拿到一个新因子,第一件事不是跑模型,而是做单因子分析。说白了,就是看看这个因子单独跟收益率有没有关系。
怎么做?我一般看三个维度:
- IC(信息系数):因子值与未来收益的秩相关性。IC均值绝对值大于0.02,就算有点意思了。
- IR(信息比率):IC均值除以IC标准差。IR大于0.5,说明因子比较稳定。
- 分组收益:把股票按因子值分成10组,看多空组合的收益曲线。
核心经验:IC高但IR低,说明因子时灵时不灵,容易过拟合。我宁愿要一个IC=0.03但IR=0.8的因子,也不要IC=0.08但IR=0.2的。
举个代码例子,咱们用Python算一下IC:
import pandas as pd
import numpy as np
from scipy.stats import spearmanr
# 假设 factor_df 是因子值,return_df 是未来收益率
def calc_ic(factor_df, return_df):
ic_series = pd.Series(index=factor_df.index)
for date in factor_df.index:
# 每个截面做秩相关
ic, _ = spearmanr(factor_df.loc[date], return_df.loc[date])
ic_series[date] = ic
return ic_series
# 使用示例
ic_values = calc_ic(factor_data, forward_returns)
print(f"IC均值: {ic_values.mean():.4f}")
print(f"IC标准差: {ic_values.std():.4f}")
print(f"IR: {ic_values.mean() / ic_values.std():.4f}")
我在项目中遇到过,有个因子IC均值0.05,看着不错,但IR只有0.1。后来一查,原来是某几个月IC突然变负,把整体拉垮了。所以,单因子分析一定要看时间序列,别只看均值。
小技巧:画个IC的累积曲线,比看数字直观得多。如果曲线像心电图一样乱跳,那这个因子基本靠不住。
二、多因子共线性处理:别让你的因子“打架”
你想想看,如果你同时用市盈率和市净率,这两个因子相关性通常很高。模型会困惑:到底该信谁?这就是共线性问题。
共线性的危害很明显:
- 模型系数不稳定,换一个样本区间结果就大变
- 解释力下降,你分不清哪个因子真正起作用
- 泛化能力差,实盘容易翻车
怎么检测?我一般看两个指标:
| 指标 | 阈值 | 说明 |
|---|---|---|
| 相关系数矩阵 | |r| > 0.7 | 两两相关性过高,需要处理 |
| VIF(方差膨胀因子) | VIF > 10 | 该因子与其他因子存在严重共线性 |
处理方式,我常用的有三种:
- 直接剔除:保留逻辑更清晰的那个因子。比如市盈率和市净率,我一般留市盈率。
- PCA降维:把相关因子合成几个主成分。但缺点是解释性变差。
- 正则化:用Lasso或Ridge回归,自动压缩不重要因子的系数。
注意:千万不要为了降低共线性而盲目剔除因子。我曾经犯过这个错,把一些有独立信息但相关性稍高的因子删了,结果模型收益直接腰斩。先做业务理解,再做技术处理。
代码实现VIF计算:
from statsmodels.stats.outliers_influence import variance_inflation_factor
from sklearn.preprocessing import StandardScaler
def calc_vif(df):
# 标准化后计算VIF
scaler = StandardScaler()
df_scaled = scaler.fit_transform(df)
vif_data = pd.DataFrame()
vif_data["feature"] = df.columns
vif_data["VIF"] = [variance_inflation_factor(df_scaled, i)
for i in range(df_scaled.shape[1])]
return vif_data
# 假设 factor_matrix 是多个因子的DataFrame
vif_result = calc_vif(factor_matrix)
print(vif_result[vif_result["VIF"] > 10])
三、标准化与归一化:让因子站在同一起跑线
这个问题其实很简单。你的因子有的在0-1之间,有的在几百万的量级,直接扔进模型,量级大的因子会主导训练过程。这不公平。
我常用的方法:
- Z-score标准化:减去均值,除以标准差。适合数据近似正态分布的情况。
- Min-Max归一化:缩放到[0,1]区间。适合有明确上下界的数据。
- Rank归一化:把因子值转为百分位排名。对异常值最鲁棒,我个人最常用。
为什么我偏爱Rank归一化?
因为金融数据里异常值太多了。某一天一个股票因为乌龙指暴涨,因子值直接飞上天。如果用Z-score,这个异常值会严重扭曲分布。但Rank归一化只关心排序,异常值最多排到第1名,影响可控。
我的经验:做截面标准化时,一定要按时间分组处理。也就是每个交易日单独做标准化,不要跨时间做。否则会引入未来信息,造成严重的过拟合。
代码实现:
def rank_normalize(series):
"""Rank归一化,将因子值转为0-1之间的百分位"""
return series.rank(pct=True)
def zscore_normalize(series):
"""Z-score标准化"""
return (series - series.mean()) / series.std()
# 截面处理示例
def cross_sectional_normalize(df, method='rank'):
"""对每个时间截面做标准化"""
df_norm = df.copy()
for date in df.index:
if method == 'rank':
df_norm.loc[date] = rank_normalize(df.loc[date])
elif method == 'zscore':
df_norm.loc[date] = zscore_normalize(df.loc[date])
return df_norm
本章知识体系
下面这张图,把咱们今天讲的内容串起来了:
嗯,以上就是特征工程基础的三个核心模块。单因子分析帮你筛选好因子,共线性处理让因子之间不打架,标准化归一化让数据更规整。这三步走扎实了,后面的模型训练才能事半功倍。
最后提醒一句:特征工程没有银弹。每个数据集都有自己的脾气,多试、多验证、多复盘,才是正道。
公众号:蓝海数据掘金营,微信deep3321