时间序列特征:滑动窗口统计、滞后特征、差分特征
讲图谱变量衍生,避不开时间序列特征。为啥?因为很多业务场景里的实体关系是随时间演变的——用户在平台上的行为序列、传感器读数、交易流水,说白了都是时序数据。如果只拿当前时刻的截面值,等于扔掉了一大半信息。
我个人习惯把时序特征分成三大类:滑动窗口统计、滞后特征、差分特征。这三类各有各的用处,也各有各的坑。今天我们就一个一个掰开聊。
滑动窗口统计:让历史流动起来
什么是滑动窗口?就是给每个时间点开一个固定长度的“历史小窗”,然后在这个窗口里算统计量。你想想看,用户过去7天的平均登录次数、过去30天的最大消费金额——这些特征天然就有业务解释力。
SQL里实现滑动窗口,核心就是窗口函数的ROWS BETWEEN子句:
SELECT
user_id,
event_time,
AVG(amount) OVER (
PARTITION BY user_id
ORDER BY event_time
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) as avg_7day
FROM transactions;
这里我常用的是ROWS(基于行数),也可以换RANGE(基于时间范围),但RANGE在SQLite里支持不完整,我一般在生产中都用ROWS配合排序字段做等间隔采样。下边这张表列出最常见的滑动窗口统计量:
| 统计量 | SQL函数 | 典型窗口大小 | 业务含义 |
|---|---|---|---|
| 均值 | AVG() | 7天 / 30天 | 近期平均活跃度 |
| 标准差 | STDDEV() | 7天 | 行为波动性 |
| 最大值 | MAX() | 30天 | 历史峰值 |
| 最小值 | MIN() | 30天 | 历史低谷 |
| 计数 | COUNT() | 1天 / 7天 | 频次特征 |
| 滑动比率 | SUM(x)/SUM(y) | 7天 | 转化率、点击率 |
COUNT(*) 做分母校验,小于最小行数就置NULL。嗯,这个坑很隐蔽,但一遇长尾数据必炸。
滞后特征:拉一条时间线
滞后特征(Lag Feature)就是把某时刻的值往前挪几个时间点。说白了,就是拿昨天、前天、上一周的值当今天的特征。这在检测序列依赖性时特别有用——如果今天的操作和昨天的高度相关,模型必须能感知到。
SQL里的LAG()函数是标配:
SELECT
user_id,
event_time,
amount,
LAG(amount, 1) OVER (
PARTITION BY user_id ORDER BY event_time
) as amount_lag1,
LAG(amount, 7) OVER (
PARTITION BY user_id ORDER BY event_time
) as amount_lag7
FROM transactions;
我个人习惯只要业务允许,至少生成三阶滞后(lag1, lag3, lag7)。为什么?因为lag1捕捉最近依赖,lag3捕捉周中周期,lag7捕捉周同比。但注意,阶数一多,特征之间容易高度共线性。我记得有一次在风控模型里同时用了lag1~lag5,结果五阶滞后完美共线,模型直接跑了1小时没收敛。后来我只保留lag1, lag3, lag7。
差分特征:把趋势变成白噪声
差分特征就是今天的值减去昨天的值(一阶差分)。很多时候原始值非平稳,但差分后的序列是平稳的——模型学起来更稳定,尤其适合线性模型和树模型里的线性部分。
SQL里实现差分很简单,用LAG做减法:
SELECT
user_id,
event_time,
amount,
amount - LAG(amount, 1) OVER (
PARTITION BY user_id ORDER BY event_time
) as diff_1
FROM transactions;
差分不只一阶。如果数据有周季节性,可以做7步差分:amount - LAG(amount, 7)。我通常把一阶差分和季节差分都生成,然后让模型自己决定用哪个。
核心原则:差分只消除趋势,不消除信息。如果原始值本身已经平稳,加差分反而是噪音。建议先做ADF检验(显著性检验),平稳就不做差分。
三种特征如何协同?
实际项目中我通常这样搭配:滑动窗口统计捕捉短期聚集,滞后特征捕捉精确时间依赖,差分特征为模型提供更干净的变化信号。三者组合后特征量会翻好几倍,但同时也带来冗余。我建议先用树模型跑一轮特征重要性,把没用的砍掉。
下边这张SVG图概括了从原始时序到特征矩阵的完整衍生流程:
嗯,这里还要提醒一点:所有时序特征衍生都依赖数据按时间排序。SQL里的ORDER BY一定要加准确,不然特征值串了位,模型学到的全是噪声。我习惯在生成特征前先跑一次ROW_NUMBER()校验顺序,确保没有重复时间戳。
最后,我个人不推荐在一个SQL里把所有时序特征一股脑全生成。拆成多个CTE(公共表表达式),每个CTE负责一类特征,最后再用JOIN拼合。这样逻辑清晰,也方便调试。毕竟,时序衍生一旦出错,排查起来比普通特征麻烦得多。