问题描述
解决方案
在JoinQuant聚宽平台中,因子数据的预处理(去极值、中性化、标准化)主要通过 jqfactor 库中的函数来实现。
以下是关于如何进行去极值和中性化处理的详细说明及代码示例。
1. 去极值 (Winsorization)
去极值的目的是剔除或平滑数据中的异常值,防止极端值对后续的统计分析(如回归)产生过大影响。聚宽提供了两种主要方法:
winsorize_med(推荐):基于中位数和绝对中位差(MAD)去极值。这种方法对异常值更稳健。winsorize:基于标准差(如3倍标准差)或分位数(如1%和99%分位)去极值。
代码示例
from jqfactor import winsorize_med, winsorize
# 假设 factor_data 是一个 Series,索引为股票代码,值为因子值
# 方法一:中位数去极值 (推荐)
# scale=3 表示将大于 中位数 + 3 * MAD 的值替换为边界值
factor_win_med = winsorize_med(factor_data, scale=3, inclusive=True, inf2nan=True)
# 方法二:分位数去极值
# 将数据限制在 2.5% 到 97.5% 的分位数之间
factor_win_quant = winsorize(factor_data, qrange=[0.025, 0.975], inclusive=True, inf2nan=True)
# 方法三:标准差去极值
# 将数据限制在 3倍标准差范围内
factor_win_std = winsorize(factor_data, scale=3, inclusive=True, inf2nan=True)
2. 中性化 (Neutralization)
中性化的目的是剔除因子中包含的行业风险和市值风险,使因子纯粹反映其本身的选股能力。
neutralize:通过线性回归取残差的方式,剔除指定的风险暴露。how参数:指定要剔除的风险因子。常用['jq_l1', 'market_cap'](剔除聚宽一级行业和市值影响)。
代码示例
from jqfactor import neutralize
# date 参数非常重要,用于获取当天的行业分类和市值数据
target_date = '2023-01-05'
# 对去极值后的数据进行中性化
# how=['jq_l1', 'market_cap'] 表示同时进行行业中性化和市值中性化
factor_neu = neutralize(factor_win_med, how=['jq_l1', 'market_cap'], date=target_date)
3. 完整综合示例
通常的数据处理流程是:获取数据 -> 去极值 -> 中性化 -> 标准化。以下是一个完整的可运行代码示例:
# -*- coding: utf-8 -*-
import pandas as pd
import numpy as np
from jqdata import get_index_stocks, get_factor_values
from jqfactor import winsorize_med, neutralize, standardlize
def process_factor_example():
# 1. 设定日期和股票池 (以沪深300为例)
date = '2023-05-10'
securities = get_index_stocks('000300.XSHG', date)
# 2. 获取因子数据 (以市盈率 pe_ratio 为例)
# get_factor_values 返回的是字典,我们需要提取出 Series
factor_dict = get_factor_values(securities, ['pe_ratio'], end_date=date, count=1)
# 获取当天的因子值 Series,索引为股票代码
raw_factor = factor_dict['pe_ratio'].iloc[0]
# 打印原始数据统计
print("原始数据均值: %.4f, 标准差: %.4f" % (raw_factor.mean(), raw_factor.std()))
# 3. 去极值 (使用中位数法,3倍MAD)
# inf2nan=True 会将无穷大值视为NaN处理
factor_win = winsorize_med(raw_factor, scale=3, inclusive=True, inf2nan=True)
print("去极值后均值: %.4f, 标准差: %.4f" % (factor_win.mean(), factor_win.std()))
# 4. 中性化 (剔除行业和市值影响)
# 注意:必须传入 date 参数,以便函数内部获取当天的行业和市值数据
# how 参数支持 'jq_l1'(聚宽一级行业), 'sw_l1'(申万一级), 'market_cap'(市值) 等
factor_neu = neutralize(factor_win, how=['jq_l1', 'market_cap'], date=date)
print("中性化后均值: %.4f, 标准差: %.4f" % (factor_neu.mean(), factor_neu.std()))
# 5. 标准化 (Z-Score标准化)
# 将数据转化为均值为0,标准差为1的分布,便于不同因子间比较
factor_std = standardlize(factor_neu)
print("标准化后均值: %.4f, 标准差: %.4f" % (factor_std.mean(), factor_std.std()))
return factor_std
# 运行示例
final_factor = process_factor_example()
print("\n处理后的前5个数据:")
print(final_factor.head())
关键参数说明
winsorize_med:scale: 默认为1。通常设置为 3 或 5。inclusive: 是否将越界值替换为边界值(True)还是设置为 NaN(False)。通常设为True。
neutralize:how: 列表。- 行业:
'jq_l1'(聚宽一级),'sw_l1'(申万一级)。 - 市值:
'market_cap'(线性市值),'ln_market_cap'(对数市值)。
- 行业:
date: 必填。用于对齐行业和市值数据的时间。请避免使用datetime.date.today(),应使用回测或研究中的具体历史日期。
Q&A
Q: 为什么中性化需要传入 date 参数?
A: 中性化通常需要剔除行业和市值的影响。股票的行业分类可能会变更,市值每天都在变化。函数需要 date 参数来查询该日期当天对应的行业分类和市值数据,以构建回归模型计算残差。
Q: 去极值应该在中性化之前还是之后?
A: 通常建议在中性化之前进行去极值。因为极值(异常值)会严重影响中性化回归模型的参数估计(斜率),导致得到的残差(即中性化后的因子)失真。
Q: winsorize 和 winsorize_med 哪个更好?
A: 对于金融数据,通常推荐使用 winsorize_med。因为均值和标准差本身容易受极值影响,而中位数(Median)和绝对中位差(MAD)对异常值更不敏感(Robust),能更有效地识别真正的离群点。