数据清洗:处理缺失值、异常值、时间序列对齐
数据清洗,说白了就是给数据「洗澡」。我做了这么多年农业数据,发现一个残酷的事实:80% 的时间都花在洗数据上,真正建模反而很快。你想想看,地里采集的温度、湿度、降雨量,哪个传感器不出点幺蛾子?
这一章,我们就来聊聊怎么给天气数据「搓澡」。核心就三件事:缺失值怎么补、异常值怎么揪、时间序列怎么对齐。
1. 缺失值处理:别让空值毁了你的模型
农业数据里,缺失值太常见了。传感器断电、通信故障、人为漏记……原因五花八门。我个人习惯,拿到数据第一件事就是画个缺失值热力图,看看哪些列、哪些时间段是重灾区。
核心原则: 缺失率 < 5% 可以插补,5%-20% 需要谨慎,> 20% 建议直接丢弃该特征。
1.1 常见插补方法
| 方法 | 适用场景 | 我的经验 |
|---|---|---|
| 前向填充 (ffill) | 短时间内连续缺失 | 温度数据常用,比如传感器断电2小时 |
| 后向填充 (bfill) | 序列开头缺失 | 很少单独用,一般配合ffill |
| 线性插值 | 变化平缓的变量 | 湿度、气压,效果不错 |
| 季节分解插值 | 有明显周期性的数据 | 温度、日照时长,我最推荐 |
| KNN 插值 | 多变量相关性强 | 计算量大,但精度高 |
举个例子,假设我们有某站点 2023年7月的气温数据,中间缺了3个小时:
import pandas as pd
import numpy as np
# 模拟数据
dates = pd.date_range('2023-07-01', periods=24, freq='H')
temp = [28.5, 29.1, 30.2, np.nan, np.nan, np.nan, 31.8, 32.1, ...]
df = pd.DataFrame({'datetime': dates, 'temp': temp})
df.set_index('datetime', inplace=True)
# 方法1:前向填充
df['temp_ffill'] = df['temp'].ffill()
# 方法2:线性插值
df['temp_interp'] = df['temp'].interpolate(method='linear')
# 方法3:时间插值(考虑时间间隔)
df['temp_time'] = df['temp'].interpolate(method='time')
我的小技巧: 对于农业气象数据,我一般先用线性插值,然后对比插值前后的统计分布。如果分布变化太大,说明缺失不是随机的,得换个方法。
2. 异常值检测:揪出那些「离谱」的数据
异常值,就是那些明显不符合常理的数据。比如7月份气温突然降到0度,或者降雨量一天下了1000mm。嗯,这里要注意:异常值不一定是错误,也可能是极端天气事件。但大多数时候,它就是传感器坏了。
2.1 常用检测方法
- 3σ 原则: 超出均值±3倍标准差的数据,标记为异常。适合正态分布的数据。
- IQR 方法: 低于 Q1-1.5*IQR 或高于 Q3+1.5*IQR 的数据。更稳健,不受极端值影响。
- 移动窗口法: 计算滑动窗口内的均值和标准差,检测局部异常。我最喜欢用这个。
- 领域知识法: 比如气温不可能超过60度,降雨量不可能为负数。这是最靠谱的。
我曾经在做一个水稻产量模型时,发现某年的数据特别「漂亮」,所有指标都完美。结果一查,是那个站点的维护人员偷懒,直接复制了去年的数据。用移动窗口法一跑,相关系数高达0.99,明显有问题。
def detect_outliers_iqr(data, column, factor=1.5):
Q1 = data[column].quantile(0.25)
Q3 = data[column].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - factor * IQR
upper = Q3 + factor * IQR
return data[(data[column] < lower) | (data[column] > upper)]
# 检测温度异常
outliers = detect_outliers_iqr(df, 'temp')
print(f"发现 {len(outliers)} 个异常值")
警告: 不要机械地删除异常值!我曾经删掉了一批「异常」的高温数据,结果模型在预测热害时完全失效。先标记,再人工核查,最后再决定是删除、修正还是保留。
3. 时间序列对齐:让所有数据「同步」
农业数据来自不同来源:气象站每小时记录一次,卫星数据每天一次,土壤传感器每10分钟一次。时间对齐,就是把它们统一到同一个时间频率上。
说白了,就是让所有数据「步调一致」。你想想看,如果温度是小时级,降雨是日级,你怎么建模?
3.1 对齐策略
| 策略 | 做法 | 适用场景 |
|---|---|---|
| 降采样 | 高频数据聚合到低频 | 10分钟数据 -> 小时数据 |
| 升采样 | 低频数据插值到高频 | 日数据 -> 小时数据 |
| 重采样 | 统一到固定时间频率 | 所有数据 -> 每3小时一次 |
# 降采样:10分钟数据聚合为小时均值
df_hourly = df_10min.resample('H').mean()
# 升采样:日数据插值为小时数据
df_hourly = df_daily.resample('H').interpolate(method='cubic')
# 对齐到固定时间点
common_index = pd.date_range('2023-01-01', '2023-12-31', freq='H')
df_aligned = df.reindex(common_index)
避坑指南: 我曾经在升采样时用了线性插值,结果把降雨量插出了负值。后来改用「前向填充+限制最大插值步长」才解决。记住:非连续变量(如降雨、风速)不要用平滑插值。
4. 知识体系总览
下面这张图,是我自己总结的数据清洗流程。每次做项目前,我都会对照着走一遍:
5. 实战经验总结
做了这么多年,我总结了几条铁律:
- 先看数据,再动手。 别上来就写代码。画个图,看看分布,心里有数再操作。
- 保留原始数据。 我习惯在清洗前备份一份 raw 数据。万一洗坏了,还能回滚。
- 记录每一步操作。 用日志或注释记录你做了什么、为什么这么做。模型效果不好时,这些记录能救命。
- 验证清洗效果。 清洗前后对比统计量(均值、方差、分布形状)。如果变化太大,说明洗过头了。
我的习惯: 每次清洗完数据,我都会跑一个简单的线性模型看看系数是否合理。比如气温对产量的影响应该是正的,如果系数为负,那数据可能还有问题。
数据清洗没有标准答案,每个项目都有自己的「脾气」。但只要你掌握了缺失值、异常值、时间对齐这三板斧,80% 的问题都能搞定。剩下的 20%,靠经验和耐心慢慢磨。