4、缺失值处理:识别缺失值、向前填充、向后填充、插值法处理
做期货套利数据清洗,说白了就是跟「脏数据」打架。
而缺失值,是这场架里最常见的对手。
我刚开始做量化那会儿,总觉得数据源靠谱,结果回测跑出来收益曲线美得不像真的。后来一查,原来是某天某个合约的收盘价是 NaN,程序直接跳过去了,算出来的价差全是错的。嗯,从那以后,我再也不敢小看缺失值了。
4.1 缺失值长什么样?
在期货数据里,缺失值通常表现为 NaN(Not a Number),或者干脆是个空单元格。
你想想看,节假日、涨跌停、数据源断连、合约换月……任何一个环节出问题,都可能产生缺失值。
我个人习惯,拿到数据第一件事就是看缺失值分布。用 isnull() 扫一眼,心里就有底了。
import pandas as pd
import numpy as np
# 模拟一组套利价差数据
data = {
'datetime': ['2024-01-01', '2024-01-02', '2024-01-03', '2024-01-04', '2024-01-05'],
'spread': [12.5, np.nan, 13.0, np.nan, 14.2]
}
df = pd.DataFrame(data)
print(df.isnull())
输出结果会告诉你:哪一行、哪一列是缺失的。
核心原则:不要一上来就删数据。先搞清楚缺失的原因,再决定怎么处理。
4.2 向前填充(ffill)
向前填充,就是用上一个非缺失值来填补当前缺失值。
在期货套利场景里,这个方法特别实用。为什么?因为价差通常具有连续性,上一秒的价差和下一秒的价差不会差太多。
我曾经处理过一组高频数据,某天下午交易所网络波动,丢了 3 秒的数据。用向前填充,直接拿前一个 tick 的价差补上,回测结果几乎没受影响。
# 向前填充
df['spread_ffill'] = df['spread'].ffill()
print(df[['datetime', 'spread', 'spread_ffill']])
输出:
datetime spread spread_ffill
0 2024-01-01 12.5 12.5
1 2024-01-02 NaN 12.5
2 2024-01-03 13.0 13.0
3 2024-01-04 NaN 13.0
4 2024-01-05 14.2 14.2
你看,缺失值都被前一个值填上了。
小技巧:如果缺失值出现在数据开头,ffill 填不了。这时候可以配合 bfill() 一起用。
4.3 向后填充(bfill)
向后填充正好反过来——用下一个非缺失值来填补当前缺失值。
什么时候用?比如你知道某个缺失值之后的数据更可靠,或者你想保守一点,用「未来」的值来补「过去」的坑。
我个人习惯,在回测时尽量不用 bfill,因为它会引入未来信息。但在实时数据处理中,bfill 反而很常用——等下一个 tick 来了,再补上前面的空缺。
# 向后填充
df['spread_bfill'] = df['spread'].bfill()
print(df[['datetime', 'spread', 'spread_bfill']])
输出:
datetime spread spread_bfill
0 2024-01-01 12.5 12.5
1 2024-01-02 NaN 13.0
2 2024-01-03 13.0 13.0
3 2024-01-04 NaN 14.2
4 2024-01-05 14.2 14.2
注意看,第 1 行的缺失值被第 2 行的 13.0 填上了。
避坑指南:我曾经在回测里用了 bfill,结果策略表现异常好。后来发现是「用未来数据补过去」,相当于提前知道了价格走势。切记:回测时慎用 bfill!
4.4 插值法处理
向前向后填充说白了就是「复制粘贴」。但有些场景下,价差变化是有趋势的,直接复制会失真。
这时候,插值法就派上用场了。
插值法的核心思想:根据已知数据点,拟合出一条曲线,然后估算缺失点的值。
最常用的是线性插值。假设缺失值前后两个点分别是 (x0, y0) 和 (x1, y1),那么中间点的值就是一条直线上的对应值。
# 线性插值
df['spread_interp'] = df['spread'].interpolate(method='linear')
print(df[['datetime', 'spread', 'spread_interp']])
输出:
datetime spread spread_interp
0 2024-01-01 12.5 12.5
1 2024-01-02 NaN 12.75
2 2024-01-03 13.0 13.0
3 2024-01-04 NaN 13.6
4 2024-01-05 14.2 14.2
你看,第 1 行的缺失值被算成了 12.75,正好是 12.5 和 13.0 的中间值。第 3 行的 13.6 也是同理。
除了线性插值,还有时间插值、多项式插值等。我个人在期货套利里用得最多的是线性插值,简单、稳定、不容易过拟合。
经验之谈:如果缺失值连续出现很多个,线性插值可能不太准。这时候可以考虑用 method='time',它会根据时间间隔来加权计算,更符合期货数据的特性。
4.5 三种方法怎么选?
我整理了一张对比表,方便你快速决策:
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 向前填充 | 数据连续、缺失少、变化慢 | 简单、不引入未来信息 | 开头缺失无法处理 |
| 向后填充 | 实时处理、开头缺失 | 能补开头缺失 | 引入未来信息,回测慎用 |
| 线性插值 | 数据有趋势、缺失值在中间 | 更平滑、更符合真实走势 | 连续缺失多时可能不准 |
我个人习惯的优先级是:线性插值 > 向前填充 > 向后填充。
当然,具体用哪个,还得看你的数据特征和业务场景。没有银弹,只有最适合的。
4.6 本章知识体系
下面这张图,帮你把本章的核心逻辑串起来:
这张图展示了从识别缺失值到最终输出完整数据的完整路径。你可以在实际项目中照着这个流程走一遍,基本不会出错。
最后说一句:缺失值处理没有标准答案。我见过有人把所有缺失值都删掉,也见过有人用机器学习模型来预测缺失值。关键是理解你的数据,理解你的策略,然后做出合理的选择。
公众号:蓝海数据掘金营,微信deep3321