1. 时间序列数据特征工程基础
时间序列分析是数据科学领域的重要分支,广泛应用于金融、气象、工业监测等多个领域。与传统的监督学习不同,时间序列数据具有明显的时序依赖性,这使得我们需要采用特殊的方法来构建特征。
关键认知:时间序列预测本质上是通过历史数据预测未来值,这要求我们将时间序列重构为监督学习问题。
1.1 时间序列的特殊性
时间序列数据与普通表格数据的根本区别在于:
- 数据点之间存在时间上的先后顺序
- 相邻观测值通常具有相关性(自相关)
- 可能包含趋势、季节性和周期性等模式
- 没有天然的输入/输出特征划分
1.2 监督学习的重构方法
要将时间序列转化为监督学习问题,我们需要:
- 确定预测目标(如t+1时刻的值)
- 设计能够反映时序关系的特征
- 构建包含特征和目标的数据集
基本转换形式如下:
时间序列形式: 时间1, 值1 时间2, 值2 时间3, 值3 监督学习形式: 特征1, 目标值 特征2, 目标值 特征3, 目标值2. 日期时间特征构建
2.1 基础日期特征
日期时间信息是最直接可用的特征。对于每日温度数据,我们可以提取:
- 月份(1-12)
- 日期(1-31)
- 星期几(0-6)
- 是否为周末
- 季度(1-4)
import pandas as pd # 加载数据 series = pd.read_csv('daily-min-temperatures.csv', header=0, index_col=0, parse_dates=True) # 创建特征DataFrame features = pd.DataFrame() features['month'] = series.index.month features['day'] = series.index.day features['day_of_week'] = series.index.dayofweek features['is_weekend'] = features['day_of_week'].isin([5,6]).astype(int)2.2 高级时间特征
根据领域知识,可以构建更有意义的特征:
- 季节特征(春夏秋冬)
- 是否节假日
- 距特定日期的天数
- 日照时长(结合地理位置)
- 温度的季节性基准值
# 添加季节特征 def get_season(month): if month in [12,1,2]: return 0 # 冬季 elif month in [3,4,5]: return 1 # 春季 elif month in [6,7,8]: return 2 # 夏季 else: return 3 # 秋季 features['season'] = series.index.month.map(get_season)3. 滞后特征构建
3.1 基础滞后特征
滞后特征是最常用的时间序列特征,直接使用历史观测值作为预测依据:
# 创建滞后特征 lags = pd.concat([ series.shift(1).rename('lag_1'), series.shift(2).rename('lag_2'), series.shift(3).rename('lag_3'), series.shift(7).rename('lag_7'), # 一周前 series.shift(30).rename('lag_30') # 一月前 ], axis=1) # 合并所有特征 all_features = pd.concat([features, lags, series.rename('target')], axis=1)3.2 滞后特征选择策略
选择滞后特征时需要考虑:
- 近期滞后(t-1, t-2):捕捉短期依赖
- 季节性滞后(t-7, t-30):捕捉周期性模式
- 长期滞后(t-365):捕捉年度趋势
- 基于自相关分析选择最优滞后
实践经验:开始时可以包含多个滞后,然后使用特征重要性分析筛选最有价值的滞后项。
4. 窗口统计特征
4.1 滚动窗口统计
滚动窗口计算固定大小窗口内的统计量:
# 滚动窗口特征 window_size = 7 rolling_stats = series.rolling(window=window_size) window_features = pd.concat([ rolling_stats.mean().rename('rolling_mean'), rolling_stats.std().rename('rolling_std'), rolling_stats.min().rename('rolling_min'), rolling_stats.max().rename('rolling_max'), rolling_stats.median().rename('rolling_median') ], axis=1) # 需要调整窗口起始点 window_features = window_features.shift(1)4.2 扩展窗口统计
扩展窗口包含所有历史数据:
expanding_stats = series.expanding() expanding_features = pd.concat([ expanding_stats.mean().rename('expanding_mean'), expanding_stats.std().rename('expanding_std'), expanding_stats.min().rename('expanding_min'), expanding_stats.max().rename('expanding_max') ], axis=1) expanding_features = expanding_features.shift(1)5. 特征工程实战技巧
5.1 特征组合与变换
除了原始特征,还可以创建:
- 滞后特征的变化率
- 窗口统计的比值
- 温度的季节性差分
- 标准化/归一化值
# 温度变化率 all_features['temp_change_1'] = all_features['lag_1'] - all_features['lag_2'] all_features['temp_change_7'] = all_features['lag_1'] - all_features['lag_7'] # 季节性差分 all_features['seasonal_diff'] = all_features['lag_1'] - all_features['lag_365']5.2 特征选择与评估
特征工程后需要评估特征重要性:
- 计算特征与目标的相关性
- 使用模型的特征重要性评分
- 递归特征消除
- 基于性能的特征选择
from sklearn.ensemble import RandomForestRegressor # 删除缺失值 clean_data = all_features.dropna() # 分离特征和目标 X = clean_data.drop('target', axis=1) y = clean_data['target'] # 训练模型获取特征重要性 model = RandomForestRegressor() model.fit(X, y) # 特征重要性 importance = pd.Series(model.feature_importances_, index=X.columns) print(importance.sort_values(ascending=False))6. 常见问题与解决方案
6.1 数据缺失处理
时间序列常见缺失问题:
- 节假日数据缺失
- 传感器故障
- 记录错误
处理方法:
- 前向填充(ffill)
- 后向填充(bfill)
- 插值法(线性、样条)
- 基于模型的填充
# 多种填充方法比较 filled_data = { 'ffill': series.fillna(method='ffill'), 'bfill': series.fillna(method='bfill'), 'linear': series.interpolate(method='linear'), 'spline': series.interpolate(method='spline', order=3) }6.2 特征缩放策略
不同特征的缩放方法:
- 标准化(Z-score):适用于大多数数值特征
- 归一化(MinMax):适用于有界特征
- Robust Scaling:适用于有异常值的数据
- 分位数变换:适用于非正态分布
from sklearn.preprocessing import StandardScaler, MinMaxScaler # 对数值特征进行标准化 numeric_features = ['lag_1', 'lag_7', 'rolling_mean'] scaler = StandardScaler() all_features[numeric_features] = scaler.fit_transform(all_features[numeric_features]) # 对月份等循环特征使用正弦/余弦变换 all_features['month_sin'] = np.sin(2*np.pi*all_features['month']/12) all_features['month_cos'] = np.cos(2*np.pi*all_features['month']/12)7. 高级特征工程技术
7.1 傅里叶变换特征
对于周期性明显的时间序列,傅里叶变换可以提取频率特征:
from scipy.fft import fft # 计算傅里叶变换 n = len(series) yf = fft(series.values) xf = np.linspace(0.0, 1.0/(2.0*1.0), n//2) # 提取主要频率成分 dominant_freq = xf[np.argmax(np.abs(yf[:n//2]))] all_features['dominant_frequency'] = dominant_freq7.2 小波变换特征
小波变换可以同时捕捉时域和频域信息:
import pywt # 进行小波分解 coeffs = pywt.wavedec(series.values, 'db1', level=3) # 提取小波系数作为特征 for i, coeff in enumerate(coeffs): all_features[f'wavelet_coeff_{i}_mean'] = np.mean(coeff) all_features[f'wavelet_coeff_{i}_std'] = np.std(coeff)7.3 基于模型的特征
使用简单模型提取特征:
- 线性回归残差
- 移动平均残差
- 季节性分解成分
from statsmodels.tsa.seasonal import seasonal_decompose # 季节性分解 result = seasonal_decompose(series, model='additive', period=365) # 提取分解成分 all_features['trend'] = result.trend all_features['seasonal'] = result.seasonal all_features['residual'] = result.resid8. 特征工程实战建议
- 领域知识优先:温度预测中,考虑地理位置、季节、天气系统等专业因素
- 可视化分析:绘制特征与目标的关系图,发现非线性关系
- 迭代优化:从简单特征开始,逐步添加复杂特征
- 验证策略:使用时间序列交叉验证评估特征效果
- 避免泄漏:确保特征构建不会使用未来信息
# 时间序列交叉验证示例 from sklearn.model_selection import TimeSeriesSplit tscv = TimeSeriesSplit(n_splits=5) for train_index, test_index in tscv.split(X): X_train, X_test = X.iloc[train_index], X.iloc[test_index] y_train, y_test = y.iloc[train_index], y.iloc[test_index] # 训练和评估模型 model.fit(X_train, y_train) score = model.score(X_test, y_test) print(f'Fold score: {score:.3f}')在实际项目中,我发现温度预测的特征工程有几个关键点:
- 季节性特征比想象中更重要,特别是年度和季度周期
- 近期的温度变化率(导数)是非常有预测力的特征
- 天气系统的移动速度决定了最优的滞后窗口大小
- 节假日效应在温度预测中表现明显
最后一个小技巧:当处理长时间序列时,可以先将数据按季节分组,分别计算统计特征,这样能更好地捕捉季节性模式。