3、聚合特征工程:基于用户ID的多表GroupBy聚合,如何避免数据泄露
好,咱们今天聊一个实战中特别容易翻车的话题——多表GroupBy聚合。
说白了,就是你有好几张表,比如用户基本信息表、交易流水表、点击日志表。你想把每张表里跟用户相关的信息,按用户ID聚合成一条特征。比如“过去30天交易总金额”、“平均每次登录时长”之类的。
听起来很简单对吧?但这里有个坑,我当年刚入行时就踩过。而且这个坑,一旦踩了,模型上线后就会出大问题。
3.1 什么是多表聚合特征?
先看一个典型场景。你手上有三张表:
- 用户表:user_id, 注册时间, 年龄, 性别
- 交易表:user_id, 交易时间, 交易金额, 交易类型
- 行为表:user_id, 行为时间, 行为类型, 页面停留时长
你想给每个用户造一个特征向量,比如:
- 该用户过去7天交易总金额
- 该用户过去30天平均交易金额
- 该用户过去7天登录次数
- 该用户最近一次交易距今多少天
这些特征,都需要通过 GROUP BY user_id 从交易表和行为表中聚合出来。
嗯,这里要注意。聚合本身不难,难的是——你聚合的时候,用的是什么时间窗口?
3.2 数据泄露是怎么发生的?
我举个例子你就明白了。
假设今天是2024年6月1日。你要预测用户明天(6月2日)会不会逾期。你从交易表里聚合了一个特征:过去30天交易总金额。
如果你直接对整个交易表做 GROUP BY user_id,那这个特征会包含用户所有历史的交易数据,包括6月1日之后的交易。
这就出大事了。
因为你在训练模型时,用到了未来的信息去预测过去的事情。模型会学到一个虚假的规律:交易金额高 → 不会逾期。但实际上,这个高金额可能是因为用户6月10日发了一笔大额工资,而逾期发生在6月2日。
模型上线后,你拿不到未来的数据,这个特征就失效了。模型效果直接崩盘。
3.3 如何避免数据泄露?
核心就一句话:聚合时,只能用截止到当前样本时间点的历史数据。
具体怎么做?我总结了三种方法。
方法一:时间窗口截断法(最常用)
在聚合之前,先对交易表做时间过滤。只保留 交易时间 <= 样本时间 的数据。
代码示例:
import pandas as pd
# 假设样本表:每个用户有一个样本时间
samples = pd.DataFrame({
'user_id': [1, 2, 3],
'sample_time': ['2024-06-01', '2024-06-05', '2024-06-10']
})
# 交易表
transactions = pd.DataFrame({
'user_id': [1, 1, 2, 2, 3],
'trans_time': ['2024-05-20', '2024-06-02', '2024-05-15', '2024-06-08', '2024-05-25'],
'amount': [100, 200, 150, 300, 250]
})
# 关键步骤:只保留样本时间之前的交易
def safe_aggregate(sample_row):
user = sample_row['user_id']
cutoff = sample_row['sample_time']
user_trans = transactions[
(transactions['user_id'] == user) &
(transactions['trans_time'] <= cutoff)
]
return user_trans['amount'].sum()
samples['past_30d_amount'] = samples.apply(safe_aggregate, axis=1)
这个方法逻辑清晰,但性能一般。如果数据量大,建议用SQL或Spark来处理。
方法二:滑动窗口聚合(适合固定时间窗口)
如果你只需要“过去30天”这种固定窗口,可以提前在交易表上打标签。
# 给交易表打上时间窗口标签
transactions['window_start'] = transactions['trans_time'] - pd.Timedelta(days=30)
transactions['window_end'] = transactions['trans_time']
# 然后关联样本表时,只取窗口内的交易
# 这个逻辑在SQL里用 BETWEEN 实现更高效
我个人习惯用这种方法。因为一旦窗口定义好,后续的聚合逻辑就完全透明,不容易出错。
方法三:离线特征库 + 时间戳索引(工业级方案)
在真正的生产环境中,我们不会每次训练都重新聚合。而是维护一个离线特征库,每条特征都带一个 feature_time 时间戳。
训练时,根据样本时间,去特征库里取 feature_time <= sample_time 的最新特征值。
3.4 实战中的常见陷阱
我列几个我实际踩过的坑:
| 陷阱 | 表现 | 解决方案 |
|---|---|---|
| 全表聚合 | 特征包含未来数据 | 加时间过滤 |
| 时间戳格式不一致 | 过滤条件失效 | 统一转为datetime类型 |
| 跨时区问题 | 时间比较偏差 | 统一转为UTC |
| 样本时间缺失 | 无法做截断 | 用注册时间或默认时间兜底 |
| 多表关联顺序错误 | 先聚合再关联,导致数据膨胀 | 先过滤再聚合,最后关联 |
3.5 知识体系总结
下面这张图,是我自己总结的多表聚合特征工程的核心流程。你仔细看看,每一步都跟数据泄露有关。
你看这个流程,最关键的其实就是第二步——时间窗口截断。这一步做对了,后面基本不会出大问题。
你想想看,金融风控模型为什么容易过拟合?很多时候不是模型太复杂,而是特征里藏了未来的信息。模型学到的不是规律,而是“作弊答案”。
所以,每次做聚合特征,我都会问自己一个问题:这个特征,在预测时点真的能拿到吗?
如果答案是“不能”,那就得重新设计。
公众号:蓝海资料掘金营,微信deep3321