数据清洗与预处理:缺失值处理、异常值识别、复权因子调整、行业分类标准化
做量化分析的朋友都知道,机构持仓数据看着光鲜,拿到手却常常是一团乱麻。我刚开始接触这类数据时,差点被各种坑整崩溃。缺失值、异常值、复权问题、行业分类混乱……每一个都能让回测结果面目全非。
今天我就把这四个核心环节拆开揉碎,讲讲我这些年踩过的坑和总结出的经验。
一、缺失值处理:别急着删,先问为什么
缺失值是最常见的问题。但处理方式不能一刀切。我个人习惯先分三类:
- 随机缺失:比如某只股票某季度数据没采集到
- 结构性缺失:比如新上市股票没有历史持仓
- 完全缺失:整列数据都是空的
对于随机缺失,我一般用前向填充(ffill)或插值法。举个例子:
# 前向填充:用上一期数据填充
df['持仓比例'] = df['持仓比例'].ffill()
# 线性插值:适合趋势明显的序列
df['持仓市值'] = df['持仓市值'].interpolate(method='linear')
但要注意,结构性缺失不能随便填充。比如某只股票刚上市,前几期数据就是空的,你硬填一个值进去,反而会引入偏差。我建议直接保留NaN,在后续分析中单独处理。
二、异常值识别:别被极端值带偏
异常值识别,说白了就是找出那些「不合理」的数据点。比如某只股票机构持仓比例突然从5%跳到95%,这明显有问题。
我常用的方法有三种:
- 3σ原则:超出均值±3倍标准差的值视为异常
- 箱线图法:超出1.5倍IQR(四分位距)的值
- 业务规则:比如持仓比例不能超过100%,市值不能为负
来看一个实际案例:
# 箱线图法识别异常值
Q1 = df['持仓比例'].quantile(0.25)
Q3 = df['持仓比例'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = df[(df['持仓比例'] < lower_bound) | (df['持仓比例'] > upper_bound)]
我的建议是:先标记异常值,再结合业务背景判断。是数据错误?还是市场异动?前者删除,后者保留。
三、复权因子调整:别让分红送股搞乱你的数据
复权调整是机构持仓分析中最容易被忽视的环节。你想想看,如果一只股票10送10,股价直接腰斩,但机构持仓市值没变,那持仓比例就会翻倍。这显然不合理。
复权因子调整的核心逻辑是:让价格和持仓数据在同一个「时间坐标系」下可比。
我一般用后复权方式:
# 假设复权因子列名为 'adj_factor'
df['调整后价格'] = df['收盘价'] * df['adj_factor']
df['调整后持仓市值'] = df['持仓市值'] * df['adj_factor']
这里有个细节:复权因子通常是累积的。比如某股票历史上发生过3次送股,复权因子就是3次调整的乘积。我建议直接从数据供应商获取复权因子序列,不要自己算,容易出错。
四、行业分类标准化:统一口径才能横向对比
行业分类是机构持仓分析的基础。但不同数据源用的分类标准不一样:GICS、申万、中信、证监会……光我知道的就有七八种。
我的做法是:统一映射到一个标准分类体系。我个人偏好用GICS(全球行业分类标准),因为它覆盖面广,且国际通用。
来看一个映射示例:
| 原始行业(申万) | 映射到GICS行业 | GICS代码 |
|---|---|---|
| 银行 | 金融 | 40 |
| 证券 | 金融 | 40 |
| 保险 | 金融 | 40 |
| 白酒 | 日常消费品 | 30 |
| 医药商业 | 医疗保健 | 35 |
映射时要注意:有些公司业务多元,可能跨多个行业。我建议按主营业务收入占比最高的那个行业来归类。如果实在分不清,可以归为「综合」类。
知识体系总览
下面这张图是我自己梳理的数据清洗与预处理流程,你可以对照着看:
嗯,这张图基本概括了今天讲的内容。从原始数据出发,经过四个核心步骤的清洗,最终得到可用的标准数据集。每一步都有坑,但只要你按照这个流程走,至少能避开80%的常见问题。
数据清洗这件事,说白了就是「磨刀不误砍柴工」。我见过太多人急着跑策略,结果数据没处理好,回测结果漂亮得不行,实盘一跑就崩。所以,别嫌麻烦,把基础打牢,后面的分析才能站得住脚。
公众号:蓝海资料掘金营,微信deep3321