3. 时间窗口类变量衍生:基于固定时间窗口的聚合统计,滑动窗口与滚动窗口的实现

在风控建模中,用户的信用行为并非静态快照,而是随时间演变的动态序列。时间窗口类变量衍生的核心思想是:在特定的历史时间段内,对用户的交易、借贷、还款等行为进行聚合统计,从而捕捉其近期的行为模式与风险趋势。本章将系统讲解固定窗口、滑动窗口与滚动窗口三种主流衍生方法,并给出可落地的 Python 实现。

3.1 时间窗口的基本概念与分类

时间窗口定义了变量衍生的“观察区间”。根据窗口的移动方式和重叠程度,主要分为以下三类:

窗口类型 定义 窗口是否重叠 典型应用场景
固定时间窗口 以某个固定时间点(如申请日)为终点,向前回溯固定长度(如30天) 否(每个样本独立计算) 申请评分卡:统计近30天申请次数、近90天逾期天数
滑动窗口 窗口按固定步长向前移动,每次移动后计算一次聚合值 是(相邻窗口高度重叠) 行为评分卡:计算近7天日均交易金额的移动平均线
滚动窗口 窗口长度固定,但每次计算时窗口的起点和终点都随时间推移 是(连续滚动,无跳跃) 贷中监控:滚动计算近30天最大逾期天数,每日更新
💡 关键区别: 固定窗口是“截面式”回溯,每个样本只计算一次;滑动窗口和滚动窗口是“时序式”计算,常用于时间序列特征工程。在风控建模中,固定窗口最常用于申请评分卡,而滑动/滚动窗口更多用于行为评分卡和贷中监控模型。

3.2 固定时间窗口聚合统计

固定时间窗口是最基础、最常用的变量衍生方式。其核心逻辑是:以每个样本的“观察时间点”(如申请时间、账单日)为基准,向前回溯固定的天数(如7天、30天、90天),然后在该时间区间内对用户的行为数据进行聚合统计。

3.2.1 常见聚合统计量

根据业务含义,聚合统计量可分为以下几类:

  • 计数类: 近N天交易次数、近N天申请次数、近N天逾期次数
  • 金额类: 近N天交易总额、近N天最大单笔金额、近N天平均金额
  • 比率类: 近N天逾期金额占比、近N天消费金额占收入比
  • 极值类: 近N天最大逾期天数、近N天最小还款金额
  • 趋势类: 近N天交易金额的斜率、近N天逾期天数的标准差

3.2.2 Python 实现示例

以下代码演示了如何基于 pandas 实现固定时间窗口的聚合统计。假设我们有一张用户交易流水表 transactions,包含字段 user_id、trans_date、trans_amount。

import pandas as pd
import numpy as np

# 示例数据:用户交易流水
data = {
    'user_id': ['A', 'A', 'A', 'B', 'B', 'B'],
    'trans_date': pd.to_datetime(['2024-01-05', '2024-01-20', '2024-02-10',
                                  '2024-01-08', '2024-01-25', '2024-02-15']),
    'trans_amount': [100, 200, 150, 300, 50, 400]
}
df = pd.DataFrame(data)

# 定义观察时间点(例如:申请日)
observation_dates = pd.DataFrame({
    'user_id': ['A', 'B'],
    'obs_date': pd.to_datetime(['2024-02-15', '2024-02-20'])
})

# 固定窗口聚合函数:统计近30天的交易特征
def fixed_window_agg(user_df, obs_date, window_days=30):
    start_date = obs_date - pd.Timedelta(days=window_days)
    window_df = user_df[(user_df['trans_date'] >= start_date) &
                        (user_df['trans_date'] < obs_date)]
    if window_df.empty:
        return pd.Series({'trans_cnt': 0, 'trans_sum': 0, 'trans_max': 0,
                          'trans_mean': 0, 'trans_std': 0})
    return pd.Series({
        'trans_cnt': len(window_df),
        'trans_sum': window_df['trans_amount'].sum(),
        'trans_max': window_df['trans_amount'].max(),
        'trans_mean': window_df['trans_amount'].mean(),
        'trans_std': window_df['trans_amount'].std() if len(window_df) > 1 else 0
    })

# 对每个用户计算固定窗口特征
result_list = []
for _, row in observation_dates.iterrows():
    user_id = row['user_id']
    obs_date = row['obs_date']
    user_df = df[df['user_id'] == user_id]
    features = fixed_window_agg(user_df, obs_date, window_days=30)
    features['user_id'] = user_id
    features['obs_date'] = obs_date
    result_list.append(features)

result_df = pd.DataFrame(result_list)
print(result_df)
⚠️ 注意事项:
  • 固定窗口的“终点”通常是申请时间或观察时间点,不要包含未来数据(避免数据泄露)。
  • 对于新用户或低频用户,窗口内可能无数据,需填充默认值(如0或-1)。
  • 窗口长度(7天、30天、90天)需结合业务经验与特征重要性评估来选择。

3.3 滑动窗口与滚动窗口的实现

滑动窗口和滚动窗口主要用于时间序列特征工程,它们能够捕捉行为指标的动态变化趋势。两者的核心区别在于:滑动窗口通常用于“移动平均”类计算,窗口按固定步长跳跃;滚动窗口则是逐日(或逐行)连续滚动,窗口始终包含最近N个数据点。

3.3.1 滑动窗口(Sliding Window)

滑动窗口的特点是:窗口长度固定,步长固定,相邻窗口有重叠。常用于计算移动平均线、移动标准差等趋势指标。

# 滑动窗口示例:计算近7天交易金额的移动平均
import pandas as pd

# 假设已有按天聚合的用户交易数据
daily_data = {
    'date': pd.date_range(start='2024-01-01', periods=30, freq='D'),
    'amount': [100, 200, 150, 300, 250, 180, 220, 190, 210, 170,
               160, 240, 280, 210, 190, 230, 260, 200, 180, 250,
               220, 210, 190, 230, 270, 200, 180, 240, 260, 210]
}
df_daily = pd.DataFrame(daily_data)
df_daily.set_index('date', inplace=True)

# 滑动窗口:窗口大小7天,步长1天(即每天滑动)
df_daily['ma_7'] = df_daily['amount'].rolling(window=7, min_periods=1).mean()
df_daily['std_7'] = df_daily['amount'].rolling(window=7, min_periods=1).std()

# 滑动窗口:窗口大小7天,步长3天(每3天计算一次)
df_daily['ma_7_step3'] = df_daily['amount'].rolling(window=7, min_periods=1).mean()
# 步长3天:只保留索引为0,3,6,...的行
df_daily_step3 = df_daily.iloc[::3, :]
print(df_daily_step3[['amount', 'ma_7_step3']].head())

3.3.2 滚动窗口(Rolling Window)

滚动窗口是滑动窗口的一种特例,通常指步长为1的滑动窗口,即每次只向前移动一个时间单位。在风控中,滚动窗口常用于贷中监控,每日更新用户的近N天风险指标。

# 滚动窗口示例:逐日滚动计算近30天最大逾期天数
# 假设有用户每日逾期状态表
overdue_data = {
    'date': pd.date_range(start='2024-01-01', periods=60, freq='D'),
    'overdue_days': np.random.randint(0, 10, size=60)  # 模拟每日逾期天数
}
df_overdue = pd.DataFrame(overdue_data)
df_overdue.set_index('date', inplace=True)

# 滚动窗口:窗口大小30天,步长1天
df_overdue['max_overdue_30d'] = df_overdue['overdue_days'].rolling(window=30, min_periods=1).max()
df_overdue['avg_overdue_30d'] = df_overdue['overdue_days'].rolling(window=30, min_periods=1).mean()

# 查看最近10天的滚动特征
print(df_overdue.tail(10))
🚨 性能与数据泄露风险:
  • 滑动/滚动窗口在计算时,必须确保窗口内只包含历史数据,不能混入未来信息。使用 rolling 时默认是“左闭右开”的向后窗口,符合要求。
  • 对于大规模数据(百万级用户、每日更新),建议使用 polars 或 spark 进行分布式计算,避免单机内存溢出。
  • 窗口长度过短(如1天)会导致特征波动大、噪声多;窗口过长(如365天)则可能包含过时信息,降低模型时效性。

3.4 三种窗口的对比与选择策略

在实际风控建模中,如何选择窗口类型取决于业务场景、数据粒度、模型更新频率。下表总结了三种窗口的适用场景与优缺点:

维度 固定时间窗口 滑动窗口 滚动窗口
数据粒度 截面数据(每个样本一个时间点) 时间序列数据(连续时间点) 时间序列数据(连续时间点)
计算频率 一次计算,后续不变 按步长定期计算 每日/每次更新
特征稳定性 高(窗口固定,结果稳定) 中(随窗口移动缓慢变化) 低(每日更新,波动较大)
对时效性的敏感度 低(只依赖历史固定区间) 中(最近窗口反映近期趋势) 高(最新数据点直接影响特征值)
典型风控场景 申请评分卡、贷前审批 行为评分卡、交易反欺诈 贷中监控、额度管理
📌 实战建议:
  • 在申请评分卡中,优先使用固定时间窗口(如近30天、近90天),因为每个申请样本只需计算一次,计算成本低且特征稳定。
  • 在行为评分卡或贷中模型中,推荐使用滚动窗口(步长=1天),能够捕捉用户行为的细微变化,提升模型的预警能力。
  • 如果数据量极大且需要实时计算,可考虑滑动窗口(步长>1),在计算精度与性能之间取得平衡。

3.5 时间窗口变量衍生的最佳实践

为了确保时间窗口变量在风控模型中的有效性,请遵循以下最佳实践:

  1. 多窗口组合: 不要只使用单一窗口长度。建议同时衍生7天、30天、90天、180天等多个窗口的特征,让模型自动选择最优的时间尺度。
  2. 窗口对齐: 确保所有样本的窗口终点一致(如统一使用申请时间),避免因时间错位导致特征不可比。
  3. 缺失值处理: 对于窗口内无数据的用户(如新用户),建议填充为0(计数类)或-1(金额类),并在建模时单独处理。
  4. 特征监控: 定期检查时间窗口特征的分布稳定性(PSI),如果窗口内数据分布发生漂移,需要重新调整窗口长度或特征工程策略。
  5. 避免数据泄露: 在计算窗口特征时,严格使用历史数据,禁止使用未来信息。建议在代码中显式设置 closed='left' 或 min_periods 参数来确保窗口的“左闭右开”性质。
✅ 总结: 时间窗口变量衍生是风控特征工程的核心技能。固定窗口适合截面数据,滑动窗口和滚动窗口适合时序数据。掌握这三种窗口的实现方法,能够帮助你构建出更具预测能力的时间序列特征,显著提升风控模型的区分度和稳定性。