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 需要足够样本量

最后提醒一句:特征工程不是一次性工作。我建议你每次加一个新特征,都跑一遍模型看看效果。如果特征加了之后模型没提升,果断删掉。记住——少即是多。

专注资料整理