数据清洗与预处理:处理缺失值、异常值检测、日期对齐、插值方法的选择与实现

做利率曲线建模,有一件事我吃了不少亏才明白——数据预处理比模型本身更决定成败。你想想看,再漂亮的样条函数,喂进去一堆脏数据,出来的曲线能看吗?

这一章,我就把我在实际项目中踩过的坑、总结的经验,掰开揉碎了讲给你听。

4.1 缺失值处理:别让空值毁了你的曲线

利率数据里,缺失值太常见了。节假日、数据源中断、交易不活跃……原因五花八门。我个人习惯,拿到数据第一件事就是做缺失值扫描。

核心原则:先理解缺失原因,再选择处理方法。不要上来就插值。

4.1.1 缺失值检测

我一般用 pandas 的 isnull() 配合 sum() 快速定位。但要注意,有些数据看起来是数字,实际上是占位符,比如 9999、-1 这种。我在项目中遇到过某家数据商把缺失值填成 -999,差点没发现。

import pandas as pd
import numpy as np

# 加载利率数据
df = pd.read_csv('yield_curve_data.csv', parse_dates=['date'])
print(df.isnull().sum())

# 检查异常占位符
placeholder_values = [-999, -99, 9999]
for col in df.columns:
    for val in placeholder_values:
        if (df[col] == val).any():
            print(f"警告:{col} 列存在占位符 {val}")
            df[col].replace(val, np.nan, inplace=True)

4.1.2 缺失值处理策略

处理缺失值,没有银弹。我根据场景总结了几种做法:

场景 推荐方法 我的经验
短期利率(1M-1Y) 线性插值 流动性好,插值误差小
长期利率(10Y-30Y) 样条插值 曲线更平滑,符合经济直觉
节假日缺失 前向填充 用上一个交易日数据,合理
连续缺失超过5天 模型估计或剔除 插值会引入太多人为偏差

小技巧:对于关键期限点(如2Y、5Y、10Y),我建议优先使用市场报价,实在没有才用插值。因为插值会掩盖市场真实信息。

4.2 异常值检测:揪出那些离谱的数据点

异常值比缺失值更危险。缺失值你至少知道它不在,异常值会悄悄扭曲你的曲线。我曾经因为一个异常值,导致整个Nelson-Siegel模型的参数估计偏了20%。

4.2.1 统计方法检测

我常用的方法有三种:

  • Z-score 方法:适合正态分布的数据。一般取 |Z| > 3 作为异常。
  • IQR 方法:适合有厚尾的利率数据。取 Q1 - 1.5*IQR 和 Q3 + 1.5*IQR 之外的点。
  • 滚动窗口法:我最推荐。用过去20个交易日的均值和标准差,判断当前点是否异常。
def detect_outliers_rolling(df, column, window=20, threshold=3):
    """
    滚动窗口异常检测
    """
    rolling_mean = df[column].rolling(window=window).mean()
    rolling_std = df[column].rolling(window=window).std()
    
    z_scores = (df[column] - rolling_mean) / rolling_std
    outliers = np.abs(z_scores) > threshold
    
    return outliers

# 使用示例
df['outlier_10Y'] = detect_outliers_rolling(df, '10Y_rate')
print(f"检测到 {df['outlier_10Y'].sum()} 个异常点")

4.2.2 业务规则检测

统计方法会漏掉一些明显的业务异常。比如:

  • 负利率(在正常市场环境下)
  • 相邻期限利差超过历史最大值
  • 日内波动超过50个基点

嗯,这里要注意:不要机械地剔除异常值。我建议先标记,然后人工复核。有时候异常值恰恰是市场变盘的信号。

避坑指南:我曾经在2019年某次国债期货交割日,把正常的基差波动误判为异常值剔除了。后来复盘才发现,那是市场结构变化导致的。所以,异常值检测一定要结合市场事件日历

4.3 日期对齐:让不同期限的数据站在同一起跑线

利率数据最大的麻烦是什么?不同期限的交易日不一样。国债期货有交割日,IRS有重置日,债券有付息日……日期不对齐,你没法做曲线。

4.3.1 统一日期基准

我一般选择 T+0 工作日日历 作为基准。把所有数据都映射到同一个日期序列上。

# 生成统一的工作日序列
import pandas_market_calendars as mcal

# 使用中国银行间市场日历
cal = mcal.get_calendar('SSE')
schedule = cal.schedule(start_date='2023-01-01', end_date='2023-12-31')
trading_days = schedule.index

# 对齐所有数据到交易日
df_aligned = df.set_index('date').reindex(trading_days)

4.3.2 处理不同频率的数据

实际工作中,你会遇到:

  • 日频数据(大部分利率)
  • 周频数据(某些信用债指数)
  • 月频数据(宏观经济指标)

我的做法是:高频向低频对齐时用平均,低频向高频对齐时用插值。举个例子,要把月度CPI数据对齐到日频,我会用样条插值,因为CPI变化是平滑的。

4.4 插值方法的选择与实现

插值是数据预处理的核心环节。说白了,就是用已知点去猜未知点。但怎么猜,差别很大。

4.4.1 线性插值

最简单,也最稳定。适合短期利率,因为短期利率变化近似线性。

from scipy import interpolate

# 线性插值
x = [1, 3, 6, 12]  # 期限(月)
y = [2.1, 2.3, 2.5, 2.8]  # 利率(%)

f_linear = interpolate.interp1d(x, y, kind='linear')
x_new = [2, 4, 9]  # 需要插值的点
y_new = f_linear(x_new)
print(f"线性插值结果:{y_new}")

4.4.2 样条插值

样条插值生成的曲线更平滑。我偏好用 三次样条,因为它能保证一阶和二阶导数连续,曲线看起来更自然。

# 三次样条插值
f_spline = interpolate.CubicSpline(x, y, bc_type='natural')
y_spline = f_spline(x_new)
print(f"样条插值结果:{y_spline}")

重要提醒:样条插值在边界处容易产生振荡(Runge现象)。我建议在两端各加一个虚拟点来控制边界行为。比如,在1个月之前加一个0.5个月的虚拟点,利率设为1个月利率减去一个合理利差。

4.4.3 前向填充

前向填充(Forward Fill)适用于:

  • 节假日数据(用上一个交易日)
  • 非活跃交易日的报价
  • 某些场外衍生品的参考利率

它的逻辑很简单:用最近的有效值填充后面的缺失值。但要注意,如果连续缺失太多,前向填充会引入滞后偏差。

# 前向填充
df_filled = df.fillna(method='ffill')

# 限制最大填充天数
df_filled = df.fillna(method='ffill', limit=5)

4.4.4 插值方法对比

方法 优点 缺点 适用场景
线性插值 简单、稳定、无振荡 不平滑,导数不连续 短期利率、流动性好的期限
样条插值 平滑、自然、导数连续 边界可能振荡,计算稍复杂 长期利率、完整曲线构建
前向填充 保留最新信息、简单 引入滞后、不适合趋势数据 节假日、非活跃日

4.5 知识体系框架

下面这张图,是我自己梳理的数据预处理流程。每次做新项目,我都会先过一遍这个框架。

利率数据预处理流程框架 原始利率数据 步骤1:缺失值检测与处理 步骤2:异常值检测与标记 步骤3:日期对齐与频率统一 步骤4:插值方法选择与实现 关键决策点 • 缺失原因分析 • 异常值人工复核 • 日历选择 • 插值方法对比 • 边界条件控制 • 结果验证

你看,整个流程是串行的。每一步的输出,都是下一步的输入。我建议你在实际项目中,每一步都做一次可视化检查——把处理前后的数据画出来,一眼就能看出问题。

我的习惯:每次做完数据预处理,我都会生成一份数据质量报告,包含缺失率、异常值数量、插值误差等指标。这份报告既是工作记录,也是后续模型调试的重要参考。

好了,数据预处理这部分就讲到这里。记住一句话:干净的数据是利率曲线建模的基石。花在预处理上的时间,会在后续建模阶段加倍回报给你。


专注资料整理