3、数据预处理:缺失值处理(向前填充、插值)、异常值检测与处理、时间序列重采样
大家好,我是你们的老朋友。今天咱们聊聊数据预处理。
说实话,做时间序列分析,百分之六十的精力都花在数据清洗上。这不是夸张。我见过太多人,模型调得飞起,结果数据本身就有问题,最后全白干。所以,这一章咱们把地基打牢。
3.1 缺失值处理:别让空值毁了你的模型
时间序列数据里,缺失值太常见了。传感器坏了、网络断了、人为漏记……原因五花八门。但处理方式,就那么几种。
3.1.1 向前填充(Forward Fill)
我个人习惯,在金融数据里用得最多。比如股票价格,某一天停牌了,用前一天的收盘价填充,逻辑上说得通。
说白了,就是用上一个有效值,填补后面的空缺。
import pandas as pd
# 示例数据
data = {'date': pd.date_range('2024-01-01', periods=5, freq='D'),
'value': [100, None, None, 103, 105]}
df = pd.DataFrame(data)
df.set_index('date', inplace=True)
# 向前填充
df['value_ffill'] = df['value'].ffill()
print(df)
3.1.2 插值法(Interpolation)
插值就灵活多了。它假设数据点之间存在某种数学关系,然后估算出缺失值。
我在项目中遇到过,温度传感器每隔10分钟采集一次数据,中间偶尔丢几个点。用线性插值,效果就很好。
# 线性插值
df['value_interp'] = df['value'].interpolate(method='linear')
print(df)
除了线性插值,还有多项式插值、样条插值。你想想看,如果数据有明显的周期性,用时间插值(method='time')会更合理。
3.2 异常值检测与处理:揪出那些“捣蛋鬼”
异常值,就是那些明显偏离正常范围的数据点。比如,某天的销售额突然暴增100倍,结果发现是系统bug。
怎么揪出它们?我常用的方法有几种。
3.2.1 基于统计的方法
最简单粗暴的,就是3σ原则。数据服从正态分布的话,超过均值±3倍标准差的值,就是异常。
import numpy as np
# 生成数据
np.random.seed(42)
data = np.random.normal(100, 10, 100)
data[50] = 200 # 人为加入异常值
# 3σ检测
mean = np.mean(data)
std = np.std(data)
threshold = 3 * std
outliers = data[np.abs(data - mean) > threshold]
print(f"检测到 {len(outliers)} 个异常值")
但说实话,这个方法对非正态分布的数据不太友好。我建议用四分位距法(IQR),更稳健。
3.2.2 基于移动窗口的方法
时间序列有连续性。如果某个点的值,跟它前后几个点的平均值差太多,那大概率是异常。
# 移动窗口检测
window = 5
rolling_mean = df['value'].rolling(window=window).mean()
rolling_std = df['value'].rolling(window=window).std()
df['is_outlier'] = np.abs(df['value'] - rolling_mean) > 2 * rolling_std
3.2.3 异常值处理
找到异常值之后,怎么处理?
- 删除:如果异常值很少,直接删掉。但要注意,别把有效信息也删了。
- 替换:用前后值的均值、中位数替换。我一般用中位数,因为它对极端值不敏感。
- 插值:把异常值当成缺失值,用插值法处理。
我曾经在一个电商项目中,发现某天的订单量异常高。查了半天,原来是促销活动导致的。这种“异常”其实是有效信息,不能随便处理。嗯,这里要注意,业务理解比技术更重要。
3.3 时间序列重采样:改变数据的时间粒度
重采样,就是把数据从一个时间频率,转换到另一个频率。比如,把分钟级数据聚合成小时级数据。
3.3.1 降采样(Downsampling)
从高频到低频。比如,把每天的销售数据,汇总成每周的。
# 创建日频数据
date_rng = pd.date_range(start='2024-01-01', end='2024-01-31', freq='D')
df_daily = pd.DataFrame(date_rng, columns=['date'])
df_daily['sales'] = np.random.randint(100, 200, size=(len(date_rng)))
df_daily.set_index('date', inplace=True)
# 降采样为周频,取平均值
weekly = df_daily.resample('W').mean()
print(weekly.head())
降采样时,聚合函数很关键。求和、平均、最大值……选哪个?看你的业务需求。我习惯先画个图,看看数据分布,再决定。
3.3.2 升采样(Upsampling)
从低频到高频。比如,把季度数据转换成月度数据。这就会引入缺失值,需要配合填充方法。
# 升采样为日频,向前填充
daily_upsampled = weekly.resample('D').ffill()
print(daily_upsampled.head())
3.3.3 重采样的坑
重采样时,时间索引必须是datetime类型。另外,注意时区问题。我有个朋友,因为没处理时区,结果数据对不上,排查了一整天。
| 频率代码 | 含义 | 示例 |
|---|---|---|
| D | 日历日 | 每日数据 |
| W | 每周 | 每周一的数据 |
| M | 每月最后一天 | 月度汇总 |
| Q | 每季度最后一天 | 季度数据 |
| H | 每小时 | 小时级数据 |
好了,这一章的内容就到这里。数据预处理是脏活累活,但也是最有价值的一步。你想想看,数据干净了,后面的分析才能顺风顺水。
下一章,咱们聊聊时间序列的平稳性检验。到时候见。
公众号:蓝海资料掘金营,微信deep3321