4、数据预处理技术:标准化、归一化、差分处理、滚动窗口统计
各位同学,咱们今天聊点实在的。宏观择时信号的质量,很大程度上取决于你喂给模型的数据长什么样。我见过太多人,模型选得花里胡哨,结果数据预处理一塌糊涂,最后跑出来的信号跟随机数没啥区别。
数据预处理,说白了就是给原始数据「洗澡」。把脏的、乱的、不听话的数据,收拾得服服帖帖。咱们这一章就讲四个最常用的手段:标准化、归一化、差分处理、滚动窗口统计。嗯,一个一个来。
核心观点:预处理不是随便选个方法就往上套。你得理解数据本身的特性,再决定用哪种手法。我个人的习惯是:先看分布,再定策略。
4.1 标准化(Z-score)
标准化,也叫Z-score标准化。公式很简单:(x - mean) / std。处理完之后,数据均值变成0,标准差变成1。
什么时候用?我个人习惯是:当数据分布近似正态,或者你要用线性模型、SVM、PCA这类对尺度敏感的方法时,标准化是首选。
我的经验:我在做宏观因子库的时候,CPI和M2的量级差了好几个数量级。如果不做标准化,模型基本只看M2,CPI直接被忽略。标准化之后,两个因子才能公平竞争。
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
# 假设 df 包含宏观数据
scaler = StandardScaler()
df_standardized = pd.DataFrame(
scaler.fit_transform(df),
columns=df.columns,
index=df.index
)
注意:标准化对异常值非常敏感。我曾经处理过一组PMI数据,里面有个异常值,结果标准化之后大部分数据都被压缩到很小的范围。遇到这种情况,我建议先做异常值处理,或者用RobustScaler。
4.2 归一化(Min-Max)
归一化是把数据缩放到[0,1]区间。公式:(x - min) / (max - min)。
你想想看,什么时候用归一化更合适?当数据有明确上下界,比如百分比、比率这类指标,归一化就很自然。另外,神经网络通常喜欢输入在0-1之间,这时候归一化也是好选择。
from sklearn.preprocessing import MinMaxScaler
mm_scaler = MinMaxScaler()
df_normalized = pd.DataFrame(
mm_scaler.fit_transform(df),
columns=df.columns,
index=df.index
)
关键区别:标准化不改变数据分布形状,归一化会改变。标准化保留异常值信息,归一化会把异常值拉到边界。选哪个?看你的业务需求。
4.3 差分处理
差分处理,说白了就是算「变化量」。一阶差分:diff = x_t - x_{t-1}。二阶差分:在一阶差分基础上再来一次。
为什么要做差分?因为很多宏观时间序列是非平稳的。GDP、CPI这些数据,长期看都有趋势。直接用原始数据建模,容易出现伪回归。差分之后,趋势被消除,序列变得平稳。
避坑指南:我曾经在构建利率择时信号时,直接用原始利率数据做回归,结果R²高得离谱,但回测一塌糊涂。后来才发现是伪回归。做了差分之后,模型才真正有效。记住:平稳性是时间序列建模的前提。
# 一阶差分
df_diff = df.diff().dropna()
# 二阶差分
df_diff2 = df.diff().diff().dropna()
# 季节性差分(比如月度数据,周期12)
df_seasonal_diff = df.diff(12).dropna()
怎么判断要不要差分?我一般用ADF检验。p值大于0.05,说明序列非平稳,需要差分。
from statsmodels.tsa.stattools import adfuller
result = adfuller(df['GDP_growth'])
print(f'ADF统计量: {result[0]:.4f}')
print(f'p值: {result[1]:.4f}')
# p值 < 0.05 表示平稳
4.4 滚动窗口统计
滚动窗口统计,就是在一个固定大小的窗口内,计算各种统计量。比如滚动均值、滚动标准差、滚动分位数。
这招在宏观择时里特别实用。为什么?因为宏观数据往往有噪音,单期数据波动大。滚动均值可以平滑噪音,让你看到真正的趋势。滚动标准差可以衡量波动率,这在风险控制里是核心指标。
# 滚动窗口统计
window = 12 # 12个月滚动
df['rolling_mean'] = df['CPI'].rolling(window=window).mean()
df['rolling_std'] = df['CPI'].rolling(window=window).std()
df['rolling_max'] = df['CPI'].rolling(window=window).max()
df['rolling_min'] = df['CPI'].rolling(window=window).min()
# 滚动分位数(比如20%分位数)
df['rolling_q20'] = df['CPI'].rolling(window=window).quantile(0.2)
实战技巧:我在做宏观择时信号时,经常用滚动Z-score。公式是 (当前值 - 滚动均值) / 滚动标准差。这个指标能告诉你当前数据相对于近期历史是偏高还是偏低。比如滚动Z-score大于2,说明当前值异常高,可能是卖出信号。
# 滚动Z-score
df['rolling_zscore'] = (
df['CPI'] - df['CPI'].rolling(window=window).mean()
) / df['CPI'].rolling(window=window).std()
注意:滚动窗口大小的选择很关键。窗口太小,噪音大;窗口太大,反应迟钝。我一般会试3个月、6个月、12个月,看哪个窗口在回测中表现最好。没有标准答案,得根据数据频率和策略周期来定。
4.5 四种方法的对比与选择
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 标准化 | 线性模型、PCA、数据近似正态 | 保留分布形状,不受量纲影响 | 对异常值敏感 |
| 归一化 | 神经网络、有界数据 | 简单直观,有明确区间 | 改变分布,异常值被压缩 |
| 差分处理 | 非平稳时间序列 | 消除趋势,获得平稳性 | 丢失长期信息 |
| 滚动窗口统计 | 平滑噪音、衡量波动 | 捕捉局部特征,实用性强 | 窗口大小需调参 |
嗯,四种方法讲完了。其实在实际项目中,我经常组合使用。比如先差分消除趋势,再标准化消除量纲,最后用滚动窗口统计生成特征。你想想看,这样一套组合拳下来,数据质量能提升不少。
最后说一句:数据预处理没有银弹。多试、多对比、多思考,才是正道。