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 本章知识体系

下面这张图,帮你把本章的核心逻辑串起来:

缺失值处理核心流程 识别缺失值 isnull() / info() 判断缺失位置(开头/中间/结尾) 向前填充 ffill() 插值法 interpolate() 向后填充 bfill() 清洗后的完整数据

这张图展示了从识别缺失值到最终输出完整数据的完整路径。你可以在实际项目中照着这个流程走一遍,基本不会出错。

最后说一句:缺失值处理没有标准答案。我见过有人把所有缺失值都删掉,也见过有人用机器学习模型来预测缺失值。关键是理解你的数据,理解你的策略,然后做出合理的选择。


公众号:蓝海数据掘金营,微信deep3321