4. 切片数据准备:数据清洗与预处理、切片标签生成、时间序列对齐、异常值处理
切片数据的质量直接决定人群复盘结论的可靠性。原始数据通常来自交易系统、估值系统与外部行情源,存在缺失、重复、时间戳不一致、极端值等问题。本章聚焦于将原始数据转化为可用于切片归因的“干净面板数据”,涵盖四个核心步骤:清洗与预处理、标签生成、时间序列对齐、异常值处理。
4.1 数据清洗与预处理
清洗阶段的目标是消除数据中的系统性错误与噪声,确保后续切片操作不会因底层数据缺陷而产生偏差。主要操作包括:
- 缺失值处理:对于持仓市值、收益率等关键数值字段,若缺失比例低于5%,采用前向填充(ffill)或线性插值;若缺失比例过高,则标记为“无效切片”并剔除。
- 重复记录去重:按“组合代码+证券代码+交易日”联合主键去重,保留最后更新时间戳的记录。
- 数据类型统一:将日期字段统一为YYYY-MM-DD格式,收益率字段统一为小数形式(如0.0325),避免因格式混用导致计算错误。
- 字段标准化:行业分类、风格因子等标签字段需映射至统一编码体系(如申万一级行业、Barra风格因子)。
实践要点:清洗逻辑应封装为可复用的函数,并在每次切片复盘前自动执行。建议保留清洗前后的数据量统计,用于评估数据质量对切片结果的影响。
4.2 切片标签生成
切片标签是人群划分的依据,需根据复盘目标从原始数据中衍生或映射生成。标签分为静态标签与动态标签两类:
| 标签类型 | 示例 | 生成方式 | 更新频率 |
|---|---|---|---|
| 静态标签 | 组合类型、投资经理、托管机构 | 从组合主数据表直接映射 | 低频(月度/季度) |
| 动态标签 | 风格暴露(价值/成长)、行业偏离度、持仓集中度 | 基于持仓与因子模型计算 | 高频(日度/周度) |
| 复合标签 | 高动量+低波动、大盘价值、小盘成长 | 多标签交叉组合 | 按复盘周期重算 |
标签生成需注意以下规则:
- 可回溯性:标签生成逻辑必须使用切片时点的已知信息,避免未来函数。
- 分层粒度:标签取值不宜过多(建议单维度不超过10个分类),否则切片后样本量过小,统计意义下降。
- 缺失标签处理:对于无法生成标签的样本,归入“未分类”切片,并在复盘报告中单独说明。
4.3 时间序列对齐
不同数据源的时间戳粒度可能不一致:交易数据按交易日、估值数据按自然日、因子数据按周度更新。时间序列对齐的目的是将所有切片数据统一到相同的时频上,避免因时间错位导致归因偏差。
对齐策略选择:
- 降频对齐:将高频数据聚合至低频(如日度→周度),适用于切片周期较长(月度/季度)的复盘。
- 升频对齐:将低频数据插值至高频(如周度→日度),适用于需要捕捉短期切片切换的场景。
- 事件对齐:以关键事件(如调仓日、分红日)为锚点,对齐前后窗口的数据。
推荐做法:在切片复盘中优先采用“降频对齐+事件窗口”的组合策略。即:将日度数据聚合为周度,同时保留调仓日前后3个交易日的切片快照,用于分析调仓行为对收益的影响。
4.4 异常值处理
异常值会扭曲切片统计量(如均值、标准差),导致人群对比结论失真。异常值检测需结合业务逻辑与统计方法:
| 检测方法 | 适用场景 | 阈值设定 | 处理方式 |
|---|---|---|---|
| Z-Score法 | 收益率、波动率等近似正态分布指标 | |Z| > 3 | 截尾处理(Winsorize) |
| IQR四分位法 | 持仓市值、换手率等偏态分布指标 | Q1 - 1.5×IQR 或 Q3 + 1.5×IQR | 替换为边界值 |
| 业务规则法 | 日收益率超过±20%、持仓占比超过100% | 根据市场规则设定硬边界 | 标记为“数据异常”并剔除 |
注意:异常值处理不应一刀切。对于因市场极端行情(如2020年3月、2024年2月)产生的真实极端收益,应保留并单独标注,而非直接剔除。建议在切片数据集中增加“异常标记”字段,供后续复盘时选择性过滤。
4.5 数据准备清单与代码示例
以下为切片数据准备的完整操作清单,建议在每次复盘前按序执行:
- 数据加载:读取组合持仓表、交易流水表、因子暴露表、基准指数表。
- 字段校验:检查必填字段(组合代码、证券代码、交易日、市值、收益率)是否完整。
- 缺失值处理:对市值缺失的持仓,使用前一日市值填充;对收益率缺失的交易日,标记为NaN并后续剔除。
- 标签生成:根据复盘方案生成静态标签与动态标签,写入切片标签字段。
- 时间对齐:将所有数据统一至周度频率,保留调仓日前后3日快照。
- 异常检测:对收益率、换手率、行业偏离度执行IQR检测,标记异常值。
- 数据输出:生成宽表格式的切片面板数据,每行代表一个组合在某个切片标签下的时间序列。
import pandas as pd
import numpy as np
def prepare_slice_data(portfolio_df, factor_df, label_config):
"""
切片数据准备主函数
:param portfolio_df: 持仓数据,包含字段 [date, portfolio_id, security_id, market_value, return]
:param factor_df: 因子数据,包含字段 [date, security_id, factor_name, factor_value]
:param label_config: 标签配置字典,如 {'style': ['value', 'growth'], 'sector': 'sw_primary'}
:return: 切片面板数据 DataFrame
"""
# 1. 去重与缺失值处理
portfolio_df = portfolio_df.drop_duplicates(subset=['date', 'portfolio_id', 'security_id'])
portfolio_df['market_value'] = portfolio_df.groupby('portfolio_id')['market_value'].transform(
lambda x: x.fillna(method='ffill')
)
# 2. 标签生成(示例:风格标签)
factor_pivot = factor_df.pivot_table(index=['date', 'security_id'],
columns='factor_name', values='factor_value').reset_index()
portfolio_df = portfolio_df.merge(factor_pivot, on=['date', 'security_id'], how='left')
# 3. 时间序列对齐:降频至周度
portfolio_df['week'] = portfolio_df['date'].dt.isocalendar().week.astype(int)
weekly_df = portfolio_df.groupby(['portfolio_id', 'week']).agg({
'market_value': 'sum',
'return': 'mean',
'value_factor': 'mean',
'growth_factor': 'mean'
}).reset_index()
# 4. 异常值处理(IQR法)
for col in ['return', 'value_factor']:
Q1 = weekly_df[col].quantile(0.25)
Q3 = weekly_df[col].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
weekly_df[col] = weekly_df[col].clip(lower, upper)
return weekly_df
代码说明:上述示例展示了从原始持仓数据到切片面板数据的核心转换逻辑。实际生产环境中,需增加组合维度聚合、多标签交叉、异常标记字段等扩展。建议将清洗、标签、对齐、异常处理拆分为独立模块,便于单元测试与复用。