4、数据预处理:缺失值处理(插值法)、异常值检测、平稳性检验(ADF检验)
收益率曲线数据,说白了就是不同期限的利率点连成的一条线。但现实中的数据,从来不会像教科书那么干净。我做了这么多年量化,几乎每套收益率数据拿过来,第一件事就是「洗数据」。今天咱们就聊聊这个环节——缺失值、异常值、还有平稳性检验。
4.1 缺失值处理:插值法
先说说缺失值。你想想看,国债收益率数据经常会出现某个期限的利率没报价,尤其是长期限或者某些流动性差的券种。我遇到过最夸张的一次,30年期国债收益率连续三天没数据,整个曲线中间断了一截。
处理缺失值,我个人习惯首选插值法。为什么?因为收益率曲线本质上是连续的,相邻期限的利率之间有很强的相关性。直接删掉那行数据太浪费了,尤其是时间序列分析中,样本本来就珍贵。
4.1.1 线性插值
最简单的办法,线性插值。假设你缺了5年期收益率,前后有4年期和6年期的数据,那就按时间权重算一个中间值。
import pandas as pd
import numpy as np
# 假设df是收益率数据,index是期限
df = pd.DataFrame({
'yield': [2.1, 2.3, None, 2.7, 2.9]
}, index=[1, 2, 3, 4, 5])
# 线性插值
df['yield_interp'] = df['yield'].interpolate(method='linear')
print(df)
嗯,这里要注意:线性插值只适用于缺失值前后都有数据的情况。如果缺在开头或者结尾,那就得用别的方法了。
4.1.2 样条插值
我个人更推荐样条插值,尤其是处理收益率曲线这种光滑曲线。样条插值能保证曲线的一阶、二阶导数连续,说白了就是曲线更平滑,更符合市场实际。
from scipy.interpolate import CubicSpline
import numpy as np
# 已知期限和收益率
maturities = np.array([1, 2, 3, 5, 7, 10])
yields = np.array([2.1, 2.3, 2.5, 2.7, 2.9, 3.1])
# 创建三次样条插值函数
cs = CubicSpline(maturities, yields)
# 插值4年期收益率
yield_4y = cs(4)
print(f'4年期插值收益率: {yield_4y:.4f}')
4.2 异常值检测
异常值,说白了就是那些明显「不对劲」的数据点。比如某天1年期收益率突然跳了50个基点,而其他期限纹丝不动。这种情况我见过太多次了——要么是数据录入错误,要么是某个特殊交易导致的异常报价。
怎么检测?我常用的方法有两种:
4.2.1 基于统计的方法
最简单的,用Z-score。假设收益率服从正态分布,超过3倍标准差的数据点就标记为异常。
from scipy import stats
# 计算Z-score
z_scores = np.abs(stats.zscore(df['yield'].dropna()))
threshold = 3
outliers = np.where(z_scores > threshold)[0]
print(f'异常值索引: {outliers}')
但说实话,这个方法有个坑:如果异常值本身影响了均值和标准差的计算,那检测效果会大打折扣。我曾经踩过这个坑,后来改用MAD(中位数绝对偏差)方法,更稳健一些。
4.2.2 基于IQR的方法
另一种常用方法是四分位距法。把数据分成四份,超出Q1-1.5*IQR或Q3+1.5*IQR范围的,就算异常。
Q1 = df['yield'].quantile(0.25)
Q3 = df['yield'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = df[(df['yield'] < lower_bound) | (df['yield'] > upper_bound)]
print(f'异常值数量: {len(outliers)}')
4.3 平稳性检验:ADF检验
好了,缺失值和异常值处理完了,接下来是关键一步——平稳性检验。为什么重要?因为大多数时间序列模型(比如ARIMA、VAR)都要求数据是平稳的。如果数据不平稳,模型结果就是「伪回归」,说白了就是看起来相关,其实毫无意义。
ADF检验(Augmented Dickey-Fuller test)是最常用的单位根检验方法。它的原假设是「存在单位根,数据不平稳」。如果p值小于0.05,就拒绝原假设,认为数据平稳。
4.3.1 实战代码
from statsmodels.tsa.stattools import adfuller
# 假设yield_series是收益率时间序列
result = adfuller(yield_series.dropna())
print(f'ADF统计量: {result[0]:.4f}')
print(f'p值: {result[1]:.4f}')
print(f'临界值:')
for key, value in result[4].items():
print(f' {key}: {value:.4f}')
if result[1] < 0.05:
print('结论:拒绝原假设,序列平稳')
else:
print('结论:无法拒绝原假设,序列非平稳')
4.3.2 差分处理
如果ADF检验发现数据不平稳怎么办?别慌,这是常态。收益率曲线数据经常是非平稳的,尤其是长期利率。我一般会做一阶差分,然后再检验。
# 一阶差分
yield_diff = yield_series.diff().dropna()
# 再次检验
result_diff = adfuller(yield_diff)
print(f'差分后p值: {result_diff[1]:.4f}')
嗯,这里要注意:差分次数不是越多越好。差分一次通常就够了,过度差分会丢失信息。我个人习惯最多差分两次,如果还不平稳,那就要考虑数据本身的问题了。
4.4 本章知识体系
为了让你更直观地理解整个数据预处理的流程,我画了一张图:
这张图把整个流程串起来了。从原始数据开始,先处理缺失值,再检测异常值,最后做平稳性检验。每一步都有多种方法可选,具体用哪个,取决于你的数据特点和分析目标。
好了,数据预处理这块就聊到这儿。记住一句话:好的分析,从干净的数据开始。收益率曲线的主成分分析也不例外。下一节咱们就正式进入PCA的实战环节了。
公众号:蓝海数据掘金营,微信deep3321