4. 特征工程(上):时间特征与统计特征
各位同学,咱们今天聊点实在的。特征工程这东西,说白了就是给模型喂“好饭”。数据本身是原材料,特征工程就是厨艺。你想想看,同样的食材,米其林大厨和普通人做出来能一样吗?在利率预测这个领域,我踩过的坑比你们走过的路还多,今天就先把时间特征和统计特征这两块硬骨头啃下来。
核心观点:利率数据本质上是时间序列。时间序列的特征工程,核心就两件事——提取“过去的信息”和“分布的形态”。
4.1 时间特征:让模型学会“回头看”
我个人习惯把时间特征分成三类:滞后项、移动平均、差分。这三兄弟各有各的脾气,咱们一个一个说。
4.1.1 滞后项(Lag Features)
滞后项是什么?就是把过去某一天的数据,拿到今天来用。比如预测明天的利率,昨天的利率肯定是个重要信号。
# 生成滞后项示例
import pandas as pd
import numpy as np
# 假设这是我们的利率数据
dates = pd.date_range('2023-01-01', periods=10, freq='D')
rates = [3.5, 3.55, 3.52, 3.58, 3.60, 3.57, 3.62, 3.65, 3.63, 3.68]
df = pd.DataFrame({'date': dates, 'rate': rates})
df['lag_1'] = df['rate'].shift(1) # 滞后1天
df['lag_2'] = df['rate'].shift(2) # 滞后2天
df['lag_3'] = df['rate'].shift(3) # 滞后3天
print(df.head(10))
嗯,这里要注意。滞后项不是越多越好。我在项目中遇到过一位同事,一口气做了30个滞后项,结果模型直接过拟合,训练集上表现完美,测试集上一塌糊涂。为什么会这样?因为利率市场有“记忆衰减”的特性——太远的历史数据,其实没什么参考价值。
我的经验:对于日频利率数据,滞后3-5天通常就够了。如果是周频或月频,可以适当放宽到12期。具体选多少,可以用偏自相关函数(PACF)来辅助判断。
4.1.2 移动平均(Moving Average)
移动平均,说白了就是“平滑”。利率数据噪声很大,单日的波动可能是随机扰动,但一段时间的均值往往能反映趋势。
# 移动平均示例
df['ma_3'] = df['rate'].rolling(window=3).mean() # 3日移动平均
df['ma_5'] = df['rate'].rolling(window=5).mean() # 5日移动平均
df['ma_10'] = df['rate'].rolling(window=10).mean() # 10日移动平均
print(df[['date', 'rate', 'ma_3', 'ma_5', 'ma_10']].head(10))
你想想看,移动平均其实是在告诉模型:“别被单日的涨跌骗了,看看最近几天的整体走势。” 我个人习惯把移动平均和滞后项搭配使用——滞后项捕捉短期记忆,移动平均捕捉中期趋势。
避坑指南:我曾经在构建特征时,不小心把移动平均和原始数据混在一起,导致数据泄露。比如用当天的数据计算移动平均,那模型就提前知道了未来信息。记住:移动平均的窗口必须完全基于历史数据,不能包含当前时刻。
4.1.3 差分(Differencing)
差分是处理非平稳时间序列的利器。利率数据往往有趋势,比如长期上涨或下跌。如果不做差分,模型可能会学到虚假的相关性。
# 差分示例
df['diff_1'] = df['rate'].diff(1) # 一阶差分:今天 - 昨天
df['diff_2'] = df['rate'].diff(2) # 二阶差分:今天 - 前天
print(df[['date', 'rate', 'diff_1', 'diff_2']].head(10))
为什么差分这么重要?因为很多机器学习模型(比如线性回归)假设数据是平稳的。差分之后,我们得到的是“变化量”而不是“绝对值”。这在利率预测中特别有用——央行加息降息,市场反应的是“变化”而不是“水平”。
小技巧:如果你不确定要不要做差分,可以先用ADF检验(单位根检验)看看数据是否平稳。p值大于0.05,基本就要做差分。
4.2 统计特征:描述数据的“脾气”
时间特征讲的是“过去发生了什么”,统计特征讲的是“数据长什么样”。均值、方差、偏度、峰度,这四个指标能帮我们刻画利率数据的分布形态。
4.2.1 均值(Mean)和方差(Variance)
均值告诉你“中心在哪里”,方差告诉你“波动有多大”。在利率预测中,这两个特征特别适合做滚动窗口计算。
# 滚动统计特征
df['rolling_mean_5'] = df['rate'].rolling(window=5).mean()
df['rolling_std_5'] = df['rate'].rolling(window=5).std() # 标准差
df['rolling_var_5'] = df['rate'].rolling(window=5).var() # 方差
print(df[['date', 'rate', 'rolling_mean_5', 'rolling_std_5']].head(10))
我记得有一次做国债收益率预测,发现方差这个特征特别敏感。市场恐慌的时候,方差会急剧放大。说白了,方差就是市场的“情绪指标”。
4.2.2 偏度(Skewness)和峰度(Kurtosis)
这两个指标稍微抽象一点,但非常有用。
- 偏度:衡量数据分布是否对称。正偏度意味着右尾更长(极端高值多),负偏度意味着左尾更长(极端低值多)。
- 峰度:衡量数据分布的“尖峭”程度。高峰度意味着数据集中在均值附近,但尾部有极端值;低峰度意味着分布更平坦。
# 滚动偏度和峰度
from scipy.stats import skew, kurtosis
# 手动计算滚动偏度
def rolling_skew(series, window):
return series.rolling(window).apply(lambda x: skew(x, bias=False))
def rolling_kurt(series, window):
return series.rolling(window).apply(lambda x: kurtosis(x, bias=False))
df['skew_10'] = rolling_skew(df['rate'], 10)
df['kurt_10'] = rolling_kurt(df['rate'], 10)
print(df[['date', 'rate', 'skew_10', 'kurt_10']].head(10))
实战经验:在利率预测中,偏度和峰度能捕捉到“市场异动”。比如央行突然降息,利率数据会出现极端值,偏度会瞬间变大。我曾经用这两个特征做风险预警,效果比单纯看涨跌幅好得多。
4.3 知识体系总览
说了这么多,咱们用一张图把本章的核心逻辑串起来。我手绘了一张结构图,方便你理解这些特征之间的关系。
4.4 实战要点总结
好了,咱们把今天的内容捋一捋。我个人觉得,特征工程最忌讳的就是“贪多嚼不烂”。你想想看,如果你一股脑儿把几十个特征全扔给模型,它反而不知道该看什么。
| 特征类型 | 核心作用 | 常用参数 | 注意事项 |
|---|---|---|---|
| 滞后项 | 捕捉短期记忆 | lag_1 ~ lag_5 | 避免过多导致过拟合 |
| 移动平均 | 平滑噪声,捕捉趋势 | 窗口3~20天 | 注意数据泄露 |
| 差分 | 处理非平稳性 | 一阶或二阶 | 差分后数据解释性变差 |
| 均值/方差 | 描述中心与波动 | 滚动窗口5~30 | 窗口大小影响灵敏度 |
| 偏度/峰度 | 检测极端值与分布形态 | 滚动窗口10~60 | 需要足够样本量 |
最后提醒一句:特征工程不是一次性工作。我建议你每次加一个新特征,都跑一遍模型看看效果。如果特征加了之后模型没提升,果断删掉。记住——少即是多。