4. 特征工程基础:时间特征提取与统计特征构建

各位同学,咱们今天聊聊特征工程里最基础、也最容易被忽视的一块——时间特征。说实话,我刚入行那会儿,总觉得把历史负荷数据扔进模型就能出结果。结果呢?模型表现平平,调参调到怀疑人生。后来我才明白,特征工程才是决定模型上限的关键,而时间特征就是其中的地基。

核心观点:负荷预测的本质是挖掘时间序列中的周期性规律。没有好的时间特征,再牛的模型也白搭。

4.1 时间特征提取:小时、星期、节假日

时间特征说白了,就是把时间戳拆解成模型能理解的数值信息。我个人习惯把时间特征分成三类:周期性特征、日历特征、特殊事件特征

4.1.1 小时特征

小时特征是最直观的。但这里有个坑——小时是循环变量。你想想看,23点和0点其实只差1小时,但如果直接用数值表示,23和0的欧氏距离是23,这显然不合理。

我在项目中遇到过这个问题,后来用了正弦-余弦编码来解决:

import numpy as np
import pandas as pd

def encode_hour(hour):
    """将小时编码为正弦-余弦特征"""
    hour_rad = 2 * np.pi * hour / 24
    return np.sin(hour_rad), np.cos(hour_rad)

# 示例
df['hour_sin'], df['hour_cos'] = zip(*df['hour'].apply(encode_hour))

这样处理后,23点和0点在二维空间中的距离就很近了。嗯,这里要注意,正弦-余弦编码适用于所有循环特征,比如月份、星期等。

4.1.2 星期特征

星期特征相对简单。我一般用两种方式:

  • 数值编码:周一=1,周二=2,...,周日=7
  • 独热编码:将星期展开为7个二进制特征

我个人更推荐独热编码,尤其是当数据量不大的时候。为什么?因为星期几和负荷之间往往是非线性关系,比如周一和周五的负荷模式可能完全不同,但数值编码会让模型误以为它们之间有线性关系。

小技巧:如果你用树模型(如LightGBM、XGBoost),数值编码就够了。树模型对特征顺序不敏感,独热编码反而会增加特征维度。

4.1.3 节假日特征

节假日是负荷预测的难点。我曾经踩过一个坑——直接把节假日当成普通工作日处理,结果预测误差飙升。后来我总结了一套处理方案:

  1. 构建节假日标签:0表示非节假日,1表示节假日
  2. 区分节假日类型:春节、国庆等长假和普通周末的负荷模式完全不同
  3. 添加节前/节后特征:比如节前1天、节后1天的负荷往往有特殊模式
def create_holiday_features(df, holiday_dates):
    """构建节假日特征"""
    df['is_holiday'] = df['date'].isin(holiday_dates).astype(int)
    df['days_to_holiday'] = (df['date'] - df['date'].shift(-1)).dt.days
    df['days_after_holiday'] = (df['date'] - df['date'].shift(1)).dt.days
    return df

4.2 滞后特征:让模型记住历史

滞后特征,说白了就是用过去的数据预测未来。这是时间序列预测最核心的特征之一。

我记得刚开始做负荷预测时,只用了前1小时的负荷作为滞后特征。结果模型在负荷突变时完全跟不上。后来我意识到,不同时间尺度的滞后特征都很重要

滞后类型 说明 典型值
短期滞后 前1-6小时的负荷 lag_1, lag_2, ..., lag_6
日滞后 前1-7天同一时刻的负荷 lag_24, lag_48, ..., lag_168
周滞后 前1-4周同一时刻的负荷 lag_168, lag_336, ..., lag_672
def create_lag_features(df, target_col, lags):
    """创建滞后特征"""
    for lag in lags:
        df[f'lag_{lag}'] = df[target_col].shift(lag)
    return df

# 示例:创建前24小时和前168小时的滞后特征
lags = [1, 2, 3, 6, 12, 24, 48, 168]
df = create_lag_features(df, 'load', lags)

注意:创建滞后特征时会产生NaN值。记得用dropna()fillna()处理,否则模型会报错。

4.3 滑动窗口统计特征:捕捉趋势与波动

滑动窗口特征,就是在某个时间窗口内计算统计量。它能帮模型捕捉负荷的短期趋势和波动情况。

我常用的滑动窗口特征包括:

  • 均值:反映窗口内的平均水平
  • 标准差:反映窗口内的波动程度
  • 最大值/最小值:反映窗口内的极端值
  • 中位数:对异常值更鲁棒
  • 偏度/峰度:反映分布形态
def create_rolling_features(df, target_col, windows):
    """创建滑动窗口统计特征"""
    for window in windows:
        df[f'rolling_mean_{window}'] = df[target_col].rolling(window=window).mean()
        df[f'rolling_std_{window}'] = df[target_col].rolling(window=window).std()
        df[f'rolling_max_{window}'] = df[target_col].rolling(window=window).max()
        df[f'rolling_min_{window}'] = df[target_col].rolling(window=window).min()
    return df

# 示例:创建3小时、6小时、12小时的滑动窗口特征
windows = [3, 6, 12]
df = create_rolling_features(df, 'load', windows)

这里有个经验之谈——窗口大小的选择很关键。窗口太小,特征噪声大;窗口太大,特征反应迟钝。我一般会结合业务场景来选择:

  • 短期预测(1-6小时):用3-12小时的窗口
  • 中期预测(1-7天):用24-168小时的窗口
  • 长期预测(1-4周):用168-672小时的窗口

4.4 知识体系总览

说了这么多,咱们用一张图来总结一下本章的核心内容:

特征工程基础:时间特征提取与统计特征 时间特征提取 • 小时特征 正弦-余弦编码 解决循环变量问题 • 星期特征 数值编码 vs 独热编码 树模型 vs 线性模型 • 节假日特征 节假日标签 节假日类型区分 节前/节后特征 滞后特征 • 短期滞后 前1-6小时负荷 捕捉即时变化 • 日滞后 前1-7天同一时刻 捕捉日周期规律 • 周滞后 前1-4周同一时刻 捕捉周周期规律 • 注意事项 处理NaN值 避免数据泄露 滑动窗口统计特征 • 常用统计量 均值、标准差 最大值、最小值 中位数、偏度、峰度 • 窗口大小选择 短期:3-12小时 中期:24-168小时 长期:168-672小时 • 核心作用 捕捉趋势与波动 平滑噪声数据 三者结合,构建完整的时序特征体系

总结一下:时间特征提取是负荷预测的基石。小时、星期、节假日特征帮模型理解时间规律;滞后特征让模型记住历史;滑动窗口特征捕捉趋势和波动。三者缺一不可。

好了,这一章的内容就到这里。特征工程是个细活,需要反复尝试和调整。我建议你在实际项目中,先构建基础特征,再逐步添加复杂特征,观察模型效果的变化。这样既能避免过拟合,也能找到真正有用的特征。

专注资料整理