4. 单变量衍生方法(下):行业中性化、Rank变换、极值处理
在因子研究流程中,原始变量往往混杂着行业结构、量纲差异和极端值噪声。本章聚焦三种核心衍生技术:行业中性化消除行业偏见,Rank变换统一变量分布,极值处理(MAD与分位数截尾)抑制异常值干扰。三者共同构成因子质量提升的基础工序。
4.1 行业中性化:剥离行业结构影响
行业中性化的目标是移除因子中由行业属性带来的系统性偏差,使因子在行业内可比。常见做法是回归残差法:将原始因子对行业虚拟变量做线性回归,取残差作为中性化后的因子值。
核心公式:
中性化后因子 = ε(残差)
Factor_raw = α + Σ(β_i × Industry_dummy_i) + ε中性化后因子 = ε(残差)
| 方法 | 原理 | 适用场景 | 注意事项 |
|---|---|---|---|
| 回归残差法 | 对行业虚拟变量回归,取残差 | 连续型因子,行业间均值差异大 | 需确保行业分类完整,避免多重共线性 |
| 行业内去均值 | 每个行业内减去该行业均值 | 简单快速,行业数量少 | 未考虑行业方差差异 |
| 行业内标准化 | 每个行业内减去均值后除以标准差 | 需要行业内尺度统一 | 小行业样本量不足时不稳定 |
# Python示例:回归残差法行业中性化
import pandas as pd
import statsmodels.api as sm
def industry_neutralize(factor, industry_dummies):
"""
factor: 原始因子值(Series)
industry_dummies: 行业虚拟变量矩阵(DataFrame)
返回中性化后的因子(Series)
"""
X = sm.add_constant(industry_dummies)
model = sm.OLS(factor, X).fit()
residual = model.resid
return residual
实践要点:行业中性化后应检查残差是否与行业虚拟变量正交(相关系数接近0)。若行业分类过细(如申万三级),可考虑合并为一级行业,避免自由度损失。
4.2 Rank变换:统一分布形态
Rank变换将原始变量转换为排序后的百分位秩,使因子值均匀分布在[0,1]区间。这能有效处理偏态分布和量纲差异,且对异常值具有天然鲁棒性。
变换步骤:
1. 对因子值排序(升序或降序)
2. 计算每个值的秩次:Rank_i = 排序位置
3. 归一化:RankNorm_i = (Rank_i - 1) / (N - 1) → 范围[0,1]
4. 可选:映射到标准正态分位数(正态得分变换)
1. 对因子值排序(升序或降序)
2. 计算每个值的秩次:Rank_i = 排序位置
3. 归一化:RankNorm_i = (Rank_i - 1) / (N - 1) → 范围[0,1]
4. 可选:映射到标准正态分位数(正态得分变换)
| 变换类型 | 输出分布 | 对异常值敏感度 | 信息损失 |
|---|---|---|---|
| 百分位秩 | 均匀分布 [0,1] | 低 | 丢失间距信息 |
| 正态得分 | 近似标准正态 | 低 | 中等 |
| 分位数分组 | 离散类别(如10组) | 极低 | 高 |
# Python示例:Rank变换实现
import numpy as np
from scipy.stats import rankdata, norm
def rank_transform(factor, method='percentile'):
"""
factor: 原始因子值(array-like)
method: 'percentile' 或 'norm_score'
返回变换后的因子
"""
N = len(factor)
ranks = rankdata(factor, method='average') # 处理并列秩
if method == 'percentile':
return (ranks - 1) / (N - 1)
elif method == 'norm_score':
percentiles = (ranks - 0.5) / N
return norm.ppf(percentiles)
else:
raise ValueError("method must be 'percentile' or 'norm_score'")
常见陷阱:Rank变换会丢失原始变量的间距信息(如10与20的差距被压缩为秩差1)。若因子本身具有经济含义的线性关系(如市盈率),建议先做行业中性化,再考虑是否使用Rank变换。
4.3 极值处理:MAD法与分位数截尾
极端值会扭曲因子与未来收益的相关性估计。两种主流方法:MAD(中位数绝对偏差)基于稳健统计量识别异常值;分位数截尾直接设定上下限阈值。
4.3.1 MAD法
MAD定义为数据点与中位数绝对偏差的中位数。相比标准差,MAD对异常值不敏感,是稳健的离散度度量。
MAD截断规则:
MAD = median(|X_i - median(X)|)
上界 = median(X) + k × MAD
下界 = median(X) - k × MAD
超出边界的值被截断至边界(Winsorize)或设为缺失。
常用k值:3(较严格)、5(较宽松)
MAD = median(|X_i - median(X)|)
上界 = median(X) + k × MAD
下界 = median(X) - k × MAD
超出边界的值被截断至边界(Winsorize)或设为缺失。
常用k值:3(较严格)、5(较宽松)
# Python示例:MAD截断
def mad_winsorize(factor, k=5):
median = np.median(factor)
mad = np.median(np.abs(factor - median))
upper = median + k * mad
lower = median - k * mad
return np.clip(factor, lower, upper)
4.3.2 分位数截尾
直接基于分位数设定阈值,适用于已知尾部形状或需要固定比例截断的场景。
| 方法 | 阈值设定 | 优点 | 缺点 |
|---|---|---|---|
| MAD截断 | 基于中位数和MAD | 对分布形状不敏感,稳健 | 对称假设,偏态分布效果差 |
| 分位数截尾 | 如1%/99%分位数 | 简单直观,控制尾部比例 | 阈值依赖样本,可能误删正常值 |
| 分位数Winsorize | 截断至分位数边界 | 保留样本量 | 改变分布形状 |
# Python示例:分位数截尾
def quantile_winsorize(factor, lower=0.01, upper=0.99):
lo = np.quantile(factor, lower)
hi = np.quantile(factor, upper)
return np.clip(factor, lo, hi)
4.4 三种方法的组合使用顺序
在实际因子衍生流水线中,三种方法通常按固定顺序组合:
- 第一步:极值处理(MAD或分位数截尾)—— 先抑制极端值对后续中性化和Rank变换的干扰。
- 第二步:行业中性化 —— 在干净数据上剥离行业结构。
- 第三步:Rank变换 —— 最后统一分布形态,便于跨截面比较和IC计算。
顺序选择依据:若先做Rank变换再中性化,秩变换会破坏行业虚拟变量的线性关系,导致中性化效果变差。因此推荐“极值处理 → 中性化 → Rank变换”的流水线顺序。
4.5 本章小结
| 方法 | 核心作用 | 关键参数 | 适用条件 |
|---|---|---|---|
| 行业中性化 | 消除行业系统性偏差 | 行业分类体系 | 因子存在行业聚集效应 |
| Rank变换 | 统一分布,抑制偏态 | 变换类型(百分位/正态得分) | 因子分布偏斜或量纲不统一 |
| MAD截断 | 稳健识别极端值 | k值(通常3~5) | 数据存在重尾或离群点 |
| 分位数截尾 | 固定比例截断尾部 | 上下分位数阈值 | 需要控制尾部样本比例 |