缺失值处理:删除法、填充法、插值法、向前/向后填充

做市业务的数据清洗,说白了就是跟「脏数据」打架。而缺失值,是这场架里最常见的对手。

我记得刚入行那会儿,接手了一套高频做市系统的回测数据。跑完策略一看收益曲线,漂亮得不像真的。后来一查,好家伙,某天的中间价字段全是空值,策略直接跳过了那天。你说这要是上线了,得亏多少钱?

所以今天咱们就聊聊缺失值怎么处理。我个人的习惯是:先看缺失比例,再决定方案。别一上来就删,也别无脑填充。

一、删除法:最粗暴,但有时最有效

删除法分两种:删除行和删除列。

删除行:如果某条记录的缺失字段太多,或者关键字段(比如买卖报价、成交量)缺失了,直接扔掉。我一般设个阈值——比如缺失超过30%的字段,这条记录就不要了。

删除列:如果某个字段的缺失率超过80%,我会考虑直接删掉这列。为什么?因为剩下的20%数据很难代表整体,强行填充反而引入偏差。

⚠️ 注意: 删除法只适合缺失比例小的情况。我曾经在回测中删掉了5%的缺失行,结果发现这些行恰好集中在波动最大的时段——策略表现直接失真了。
# 删除缺失比例超过30%的行
df_clean = df.dropna(thresh=len(df.columns) * 0.7)

# 删除缺失比例超过80%的列
df_clean = df.dropna(axis=1, thresh=len(df) * 0.2)

二、填充法:均值、中位数、众数

填充法是最常用的。但选哪个统计量,得看数据分布。

统计量 适用场景 做市业务举例
均值 数据近似正态分布,无极端值 订单处理延迟(通常集中在某个范围)
中位数 数据有偏或存在异常值 买卖价差(经常被大单拉偏)
众数 分类变量或离散值 订单类型(市价单/限价单)

我个人习惯:对于做市数据里的数值型字段,优先用中位数。为什么?因为做市数据里经常有极端值——比如某笔大单把价差瞬间拉大到正常值的10倍。均值会被这种值带偏,中位数稳得多。

💡 小技巧: 填充前先分组。比如按交易对分组,再填充组内的中位数。不同币种的价差差异很大,混在一起填充会出问题。
# 按交易对分组,填充中位数
df['spread'] = df.groupby('symbol')['spread'].transform(
    lambda x: x.fillna(x.median())
)

三、插值法:让数据更平滑

插值法适合时间序列数据。说白了,就是用前后数据「猜」中间的值。

做市业务里,我最常用的是线性插值。比如某笔订单的成交时间戳缺失了,用前后两条记录的时间取中间值,基本八九不离十。

但要注意:插值法假设数据是连续变化的。如果市场出现跳空(比如突发新闻导致价格瞬间跳变),插值出来的值可能完全不对。

🔑 核心原则: 插值法适合「渐变」场景,不适合「突变」场景。
# 线性插值
df['price'] = df['price'].interpolate(method='linear')

# 时间加权插值(更精确)
df['price'] = df['price'].interpolate(method='time')

四、向前/向后填充:做市数据的「惯性」

向前填充(ffill)和向后填充(bfill),在量化做市里特别实用。

向前填充:用上一个有效值填充当前缺失值。比如某笔订单的「订单簿快照」字段缺失了,用上一笔的快照填充——因为订单簿在毫秒级变化不大。

向后填充:用下一个有效值填充。比如某笔成交的「对手方ID」缺失了,用下一笔同价格的对手方ID填充——这招我在处理交易所数据时用过,效果不错。

我曾经遇到过一个坑:用向前填充处理「买卖方向」字段。结果某笔订单的方向缺失,向前填充用了上一笔的「买」,但实际应该是「卖」。从那以后,我对于分类变量的填充都会先做一次逻辑校验。

# 向前填充
df['orderbook'] = df['orderbook'].ffill()

# 向后填充
df['counterparty'] = df['counterparty'].bfill()

# 限制填充步数(防止填充太久远的数据)
df['price'] = df['price'].ffill(limit=3)

五、实战选择:到底用哪个?

我总结了一个简单的决策流程,画成图给大家参考:

缺失值出现 缺失比例 > 80%? 删除列 时间序列数据? 时间序列处理 插值法 / 向前向后填充 缺失比例 > 30%? 删除行 填充处理 均值/中位数/众数 数值型还是分类型? 数值型 中位数 分类型 众数

嗯,这个图基本概括了我的思路。但实际项目中,我还会考虑一个因素:缺失是否随机

如果缺失是随机的(比如网络抖动丢了几条数据),填充法没问题。但如果缺失是有偏的(比如大行情时数据总缺失),那就要小心了——这时候缺失本身可能包含了信息。

📌 总结一下:
  • 缺失少、关键字段 → 删除行
  • 缺失多、非关键字段 → 删除列
  • 时间序列 → 插值或向前向后填充
  • 数值型 → 中位数(稳健)
  • 分类型 → 众数

最后说一句:没有万能的方法。我每次处理缺失值,都会先做一次缺失值分析——看看缺失的分布、跟其他字段的关系。有时候,缺失值本身就是一个特征,值得单独建模。

好了,这一章就到这儿。下一章咱们聊聊异常值检测,那又是另一场硬仗。


蓝海数据掘金营,专注资料整理