2. 数据预处理:时间序列数据清洗、缺失值处理与异常值初步识别

大家好,我是老蓝。今天咱们聊聊数据预处理。

说实话,很多做时间序列的同学,一上来就急着调模型、跑预测。我见过太多这样的案例了——模型跑出来效果奇差,最后发现是原始数据里一堆坑。嗯,数据预处理这一步,看似基础,其实最考验功底。

核心观点:时间序列分析中,80%的问题出在数据质量上,而不是模型选择上。

2.1 时间序列数据清洗——先把脏东西筛掉

数据清洗,说白了就是把你手里的原始数据整理成能用的样子。我个人习惯分三步走:

  1. 检查时间索引——时间戳是不是连续的?有没有重复?
  2. 检查数据类型——时间列是不是datetime格式?数值列是不是float/int?
  3. 检查数据范围——有没有明显不合逻辑的值?比如负的销售额?

我在项目中遇到过最坑的一次:客户给的数据,时间戳居然有2023年2月30日。你想想看,2月哪来的30号?这种低级错误,不洗掉根本没法往下做。

小技巧:pandas.to_datetime() 转换时间列时,加上 errors='coerce' 参数,非法日期会自动变成NaT,方便后续处理。

2.2 缺失值处理——别让空值毁了你的模型

时间序列的缺失值,跟普通表格数据不一样。为什么?因为时间序列有顺序依赖,前一个值会影响后一个值。所以不能随便删,也不能随便填。

2.2.1 前向填充(Forward Fill)

这个方法最简单,也最常用。说白了就是用上一个有效值填充当前缺失值。

import pandas as pd

# 示例数据
df = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=10, freq='D'),
    'value': [100, 105, None, 110, None, None, 120, 125, None, 130]
})

# 前向填充
df['value_ffill'] = df['value'].ffill()
print(df)

我个人习惯在以下场景用前向填充:

  • 传感器数据短暂断连(比如几秒内)
  • 股票市场非交易日的缺失
  • 数据采集频率不稳定的情况

注意:如果缺失值连续出现太多(比如超过5个),前向填充会引入较大偏差。我曾经在电力负荷预测中吃过这个亏——连续填充了10个小时的缺失值,结果模型完全学偏了。

2.2.2 插值法(Interpolation)

插值法比前向填充更聪明一些。它利用缺失值前后的数据点,通过数学方法估算中间值。

# 线性插值
df['value_interp'] = df['value'].interpolate(method='linear')
print(df)

# 时间插值(考虑时间间隔)
df['value_time'] = df['value'].interpolate(method='time')

常用的插值方法有:

方法 适用场景 特点
linear 数据变化较平稳 简单快速,默认方法
quadratic 数据有弯曲趋势 更平滑,但可能过拟合
cubic 数据波动较大 三次样条,效果较好
time 时间间隔不均匀 考虑实际时间差

嗯,这里要注意:插值法假设数据在局部是平滑的。如果你的数据有突变(比如股价暴跌),插值反而会掩盖真实情况。

2.3 异常值初步识别——揪出那些"捣乱分子"

异常值,就是那些明显偏离正常范围的数据点。我一般把异常值分成两类:

  • 全局异常——明显超出整体数据范围的值
  • 局部异常——在局部时间段内突然跳变的值

2.3.1 基于统计的方法

最简单粗暴的方法:用均值和标准差。

import numpy as np

# 计算均值和标准差
mean = df['value'].mean()
std = df['value'].std()

# 3σ原则:超过3个标准差视为异常
threshold = 3
df['is_outlier'] = np.abs(df['value'] - mean) > threshold * std

为什么用3σ?其实这是经验值。数据服从正态分布时,99.7%的数据都在3σ范围内。超过这个范围的,大概率有问题。

避坑指南:我曾经用3σ处理一组数据,结果发现所有异常值都是真实的极端天气事件。所以记住:异常值≠错误值。先标记,再判断,别一棍子打死。

2.3.2 基于移动窗口的方法

时间序列的异常往往有局部性。比如某个时刻突然飙升,但整体数据是正常的。这时候用移动窗口更靠谱。

# 滚动窗口的均值和标准差
window = 5
df['rolling_mean'] = df['value'].rolling(window=window).mean()
df['rolling_std'] = df['value'].rolling(window=window).std()

# 局部异常检测
df['local_outlier'] = np.abs(df['value'] - df['rolling_mean']) > 2 * df['rolling_std']

我个人习惯用窗口大小为数据周期的3-5倍。比如日数据,窗口设15天左右。太小了容易误报,太大了又不够灵敏。

2.4 本章知识体系总览

下面这张图,是我自己总结的数据预处理流程。你跟着走,基本不会出大问题。

时间序列数据预处理流程 原始时间序列数据 数据清洗:时间索引、类型、范围检查 缺失值处理:前向填充 vs 插值法 异常值识别:统计方法 + 移动窗口 输出:干净、完整、无异常的时间序列数据

总结一下:数据预处理不是走过场。你花80%的时间把数据洗干净,后面建模只需要20%的时间。反过来,数据不干净,模型再花哨也是白搭。

好了,这一章就到这里。记住:好的数据预处理,是时间序列分析成功的一半。


公众号:蓝海资料掘金营,微信deep3321