4、时间序列处理:时间戳标准化、重采样与时间特征提取

各位同学好,我是老张。在风场干了这么多年数据,我最大的感触就是——时间戳处理是SCADA数据清洗的「第一道鬼门关」。你想想看,风机每时每刻都在产生数据,但各个机组、各个采集系统的时间格式五花八门。有的用Unix时间戳,有的用字符串,有的甚至带时区偏移。嗯,今天我们就来把这团乱麻理清楚。

4.1 时间戳标准化——先把「钟」调准

我在项目中遇到过最离谱的一次:同一个风场,A机组的时间是UTC+8,B机组的时间是UTC+0,两台风机明明同时发电,时间戳却差了8个小时。你说这数据怎么分析?

所以第一步,必须把所有时间戳统一成无时区的标准时间。我个人习惯统一转成datetime64[ns]格式,时区全部对齐到北京时间(UTC+8)。

核心原则:入库前先标准化,入库后不折腾。

来看一段我常用的清洗代码:

import pandas as pd
import numpy as np

# 假设原始数据有各种奇葩时间格式
df = pd.DataFrame({
    'timestamp_raw': [
        '2024-01-15 10:30:00+08:00',  # 带时区
        '1705300200',                   # Unix秒
        '2024-01-15T02:30:00Z',        # UTC时间
        '20240115_103000'              # 自定义格式
    ]
})

# 第一步:统一转成datetime
def parse_timestamp(ts):
    if isinstance(ts, str):
        # 尝试多种格式解析
        try:
            return pd.to_datetime(ts, utc=True)
        except:
            # 处理自定义格式
            return pd.to_datetime(ts, format='%Y%m%d_%H%M%S', utc=True)
    elif isinstance(ts, (int, float)):
        # Unix时间戳
        return pd.to_datetime(ts, unit='s', utc=True)
    return pd.NaT

df['timestamp_std'] = df['timestamp_raw'].apply(parse_timestamp)

# 第二步:转成北京时间(UTC+8)
df['timestamp_beijing'] = df['timestamp_std'].dt.tz_convert('Asia/Shanghai')

# 第三步:去掉时区信息,方便后续计算
df['timestamp'] = df['timestamp_beijing'].dt.tz_localize(None)

避坑指南:我曾经因为没去掉时区信息,直接用带时区的列做groupby,结果pandas报了一堆时区不一致的错误。所以标准化后,记得用tz_localize(None)把时区信息剥离掉。

4.2 重采样——把数据「梳」整齐

SCADA数据采集频率其实很不稳定。有的风机10秒一条,有的1分钟一条,遇到通讯拥堵时可能5分钟才来一条。但做分析时,我们需要统一的时间间隔。说白了,就是把不规则的数据「梳」成规整的梳子齿。

4.2.1 10分钟平均——行业标准

风功率曲线测试、发电量评估,这些场景都要求10分钟平均数据。这是行业硬标准,IEC 61400-12也这么规定。

# 先确保时间戳是索引,且排序
df = df.set_index('timestamp').sort_index()

# 10分钟重采样,取平均值
df_10min = df.resample('10T').mean()

# 注意:如果某10分钟内数据点太少,平均值不可靠
# 我一般要求至少50%的数据点存在
min_points = 30  # 假设原始是10秒一条,10分钟应有60个点
df_10min_valid = df_10min[df_10min['count'] >= min_points]

我的经验:10分钟平均时,别光用mean()。我习惯同时算std()min()max(),这样能看出这10分钟内的波动情况。比如风速标准差大,说明湍流强,这对功率预测很重要。

4.2.2 1分钟平均——精细化分析

做故障诊断或控制策略优化时,10分钟太粗了。这时候需要1分钟平均数据。但要注意,1分钟平均的数据噪声更大,需要配合滤波使用。

# 1分钟重采样
df_1min = df.resample('1T').agg({
    'wind_speed': ['mean', 'std', 'max'],
    'power': ['mean', 'std', 'max'],
    'rotor_speed': 'mean'
})

# 展平多级列索引
df_1min.columns = ['_'.join(col).strip() for col in df_1min.columns.values]

注意:1分钟重采样时,如果原始数据是10秒一条,那1分钟只有6个点。遇到通讯中断,可能只有2-3个点。这种短间隔的平均值方差很大,我一般会标记为「低置信度」数据,后续分析时酌情剔除。

4.3 时间特征提取——让模型「看懂」时间

风机发电量跟时间关系大吗?太大了!白天风大还是晚上风大?夏天风多还是冬天风多?这些规律模型自己学不会,得我们帮它提取出来。

4.3.1 小时特征

一天24小时,不同时段的风速、电价、负荷都不一样。我习惯把小时拆成两个维度:

df['hour'] = df['timestamp'].dt.hour
# 用正弦余弦编码,避免0点和23点的「跳跃」
df['hour_sin'] = np.sin(2 * np.pi * df['hour'] / 24)
df['hour_cos'] = np.cos(2 * np.pi * df['hour'] / 24)

为什么要用正弦余弦?你想想看,23点和0点只差1小时,但如果直接用数值编码,23和0差了23。模型会以为这两个时间点差异很大,其实它们挨着呢。正弦余弦编码就能解决这个问题。

4.3.2 季节特征

季节对风资源的影响是决定性的。我见过一个风场,冬季平均风速8m/s,夏季只有4m/s,发电量差了一倍。

# 提取季节
df['month'] = df['timestamp'].dt.month
# 北半球:春3-5,夏6-8,秋9-11,冬12-2
def get_season(month):
    if month in [3,4,5]: return 0  # 春
    elif month in [6,7,8]: return 1  # 夏
    elif month in [9,10,11]: return 2  # 秋
    else: return 3  # 冬

df['season'] = df['month'].apply(get_season)
# 同样用正弦余弦编码
df['season_sin'] = np.sin(2 * np.pi * df['season'] / 4)
df['season_cos'] = np.cos(2 * np.pi * df['season'] / 4)

4.3.3 月份特征

有时候季节太粗了,比如10月和11月虽然都是秋天,但风速差异可能很大。这时候需要月份特征。

# 月份直接作为类别特征
df['month'] = df['timestamp'].dt.month.astype('category')

# 或者用数值编码
df['month_num'] = df['timestamp'].dt.month

我的建议:月份特征最好用one-hot编码或者正弦余弦编码。直接用1-12的数值,模型会认为12月比1月大,这不对。月份是循环的,12月之后是1月。

4.4 本章知识体系

下面这张图是我自己整理的,把时间序列处理的三个核心步骤串起来了:

时间序列处理核心流程 第一步:时间戳标准化 统一格式 → 时区对齐 → 去除时区信息 常见坑:Unix时间戳、UTC+8混用、自定义格式 第二步:重采样 10分钟平均(行业标准) IEC标准、功率曲线评估 1分钟平均(精细化分析) 故障诊断、控制优化 第三步:时间特征提取 小时特征 正弦余弦编码 季节特征 春/夏/秋/冬 月份特征 one-hot或数值 核心目标:让时间数据「规整、连续、可计算」

4.5 实战小贴士

最后分享几个我踩过的坑:

  • 时间戳排序:重采样前一定要先排序。我有一回忘了排序,结果重采样出来的数据乱成一锅粥。
  • 缺失值处理:重采样后会有NaN,别急着填充。先看看缺失比例,超过20%的时段直接标记为无效。
  • 夏令时问题:如果你处理的是国外风场,注意夏令时切换那天会多一小时或少一小时。我一般直接统一用UTC,省心。
  • 性能优化:几百万行的数据,别用apply逐行处理时间戳。用pd.to_datetime的向量化操作,速度能快100倍。

嗯,最后说一句:时间序列处理没有银弹。每个风场的数据特点都不一样,多花点时间做探索性分析,比直接套模板强得多。

专注资料整理