4. 时间特征提取:交易时间戳拆解

交易时间戳是风控特征中最基础、信息密度最高的原始字段之一。一个完整的交易时间戳(如 2025-06-15 14:23:37)不仅记录了交易发生的绝对时刻,更隐含了周期性行为模式、节假日效应以及跨时区风险。本节将系统讲解如何将时间戳拆解为结构化特征,并构建高区分度的风控变量。

4.1 时间戳拆解层级

时间特征提取遵循“从粗到细、从绝对到周期”的原则。下表列出了标准拆解字段及其风控含义:

拆解字段 提取方式 典型风控应用
年 dt.year 年度政策变化、跨年欺诈团伙活跃度对比
月 dt.month 季节性消费异常(如双十一、春节前)
日 dt.day 月初/月末资金紧张型欺诈
时 dt.hour 凌晨异常交易(高风险时段)
分 dt.minute 高频试探攻击(如每5分钟连续失败)
秒 dt.second 毫秒级抢单、脚本自动化识别
星期 dt.weekday() 工作日/周末交易行为差异
节假日标记 节假日日历匹配 非营业时间的大额转账、虚假促销
实践要点: 在风控流水线中,时和星期的组合特征(如“周五23点”)往往比单一字段的区分度高出30%以上。建议将小时离散化为4~6个时段(凌晨、上午、下午、晚间、深夜)进行交叉统计。

4.2 时间特征提取流程

下图展示了从原始时间戳到风控特征向量的完整拆解流程:

时间戳拆解与特征生成流程 原始时间戳 拆解层:年 · 月 · 日 · 时 · 分 · 秒 · 星期 · 节假日 pd.to_datetime() → dt.year / dt.month / dt.hour / dt.weekday() / 节假日库匹配 衍生特征层:周期编码 · 时段划分 · 距节假日天数 sin/cos小时编码 | 凌晨/上午/下午/晚间/深夜 | 节前N天标记 距上次交易秒数 | 同设备小时集中度 | 跨天交易间隔 风控特征向量(20+维) 节假日标记需结合业务所在国家法定假日表,避免使用通用日历导致误判

4.3 核心特征工程方法

4.3.1 周期性时间编码

小时、星期等周期性字段不宜直接作为数值特征输入模型(如23点与0点实际距离为1,但数值差为23)。推荐使用正弦-余弦编码:

import pandas as pd
import numpy as np

def cyclic_encode(df, col, period):
    """对周期性字段进行sin/cos编码"""
    df[col + '_sin'] = np.sin(2 * np.pi * df[col] / period)
    df[col + '_cos'] = np.cos(2 * np.pi * df[col] / period)
    return df

# 示例:对小时(0-23)和星期(0-6)编码
df['hour_sin'] = np.sin(2 * np.pi * df['hour'] / 24)
df['hour_cos'] = np.cos(2 * np.pi * df['hour'] / 24)
df['weekday_sin'] = np.sin(2 * np.pi * df['weekday'] / 7)
df['weekday_cos'] = np.cos(2 * np.pi * df['weekday'] / 7)

4.3.2 节假日标记与距节假日天数

节假日对交易行为有显著影响。建议构建以下特征:

  • is_holiday:当日是否为法定节假日(0/1标记)
  • is_weekend:是否为周末(星期六/日)
  • days_to_holiday:距离下一个法定节假日的天数(可为负值表示已过)
  • holiday_type:节假日类型编码(如春节=1、国庆=2、普通周末=0)
import holidays

# 使用中国法定节假日库(需安装:pip install holidays)
cn_holidays = holidays.China()

def holiday_features(df, date_col):
    df['is_holiday'] = df[date_col].apply(lambda x: x in cn_holidays).astype(int)
    df['is_weekend'] = df[date_col].apply(lambda x: x.weekday() >= 5).astype(int)
    # 距最近节假日天数(简化示例)
    holiday_dates = sorted(cn_holidays.keys())
    def nearest_holiday_days(d):
        diffs = [(d - hd).days for hd in holiday_dates]
        return min(diffs, key=abs)
    df['days_to_nearest_holiday'] = df[date_col].apply(nearest_holiday_days)
    return df
注意: 节假日库需要每年更新。对于跨年交易数据,务必确保holidays库版本覆盖数据所在年份。建议在特征工程流水线中加入年份校验逻辑,避免因节假日列表缺失导致特征全部为0。

4.4 时间特征与风控场景的关联

下表总结了不同时间特征在高发欺诈场景中的典型表现:

风控场景 关键时间特征 异常模式
凌晨盗刷 小时∈[0,5] + 历史无此时段交易 短时间内连续多笔、金额递增
节假日钓鱼 is_holiday=1 + 短链接域名 仿冒银行/电商的虚假交易
脚本批量注册 秒级间隔固定(如每5秒) 同一设备在1分钟内注册≥5个账号
跨时区攻击 交易时间与IP时区偏差>3小时 注册地/常用IP与当前交易IP时区冲突
月末冲量刷单 日∈[28,31] + 金额接近整数 同一用户单日交易量突增10倍以上

4.5 时间特征质量评估

提取完成后,建议通过以下指标评估每个时间特征对风控模型的贡献:

  • IV值(信息量):IV>0.1的特征可纳入模型,IV>0.3为强特征
  • 缺失率:时间戳缺失通常直接剔除该样本
  • 时间稳定性:按月计算特征分布,PSI<0.1为稳定
  • 与目标变量的相关性:Spearman相关系数绝对值>0.05即有区分度
# 快速评估时间特征的IV值示例
import scipy.stats as stats

def calc_iv(df, feature, target):
    """计算二分类目标的IV值(简化版)"""
    df['bin'] = pd.qcut(df[feature], q=10, duplicates='drop')
    grouped = df.groupby('bin')[target].agg(['count', 'sum'])
    grouped['good'] = grouped['count'] - grouped['sum']
    grouped['pct_bad'] = grouped['sum'] / grouped['sum'].sum()
    grouped['pct_good'] = grouped['good'] / grouped['good'].sum()
    grouped['woe'] = np.log(grouped['pct_bad'] / grouped['pct_good'])
    grouped['iv'] = (grouped['pct_bad'] - grouped['pct_good']) * grouped['woe']
    return grouped['iv'].sum()

# 计算小时特征的IV值
iv_hour = calc_iv(df, 'hour', 'is_fraud')
print(f"小时特征的IV值: {iv_hour:.4f}")
实战建议: 在风控特征池中,时间相关特征通常占全部特征的15%~25%。建议将时间特征分为“绝对时间特征”(年/月/日)和“周期时间特征”(小时/星期/节假日)两组,分别进行特征筛选,避免多重共线性。

4.6 完整特征提取代码模板

以下是一个可直接复用的时间特征提取函数,覆盖了本节所有核心拆解与衍生方法:

def extract_time_features(df, timestamp_col='trans_time'):
    """
    从交易时间戳中提取全套时间特征
    输入: df - 包含时间戳列的DataFrame
          timestamp_col - 时间戳列名(字符串或datetime类型)
    输出: 新增20+列时间特征的DataFrame
    """
    df = df.copy()
    # 确保时间戳为datetime类型
    df[timestamp_col] = pd.to_datetime(df[timestamp_col])

    # 1. 基础拆解
    df['year'] = df[timestamp_col].dt.year
    df['month'] = df[timestamp_col].dt.month
    df['day'] = df[timestamp_col].dt.day
    df['hour'] = df[timestamp_col].dt.hour
    df['minute'] = df[timestamp_col].dt.minute
    df['second'] = df[timestamp_col].dt.second
    df['weekday'] = df[timestamp_col].dt.weekday  # 0=周一, 6=周日

    # 2. 周期编码
    df['hour_sin'] = np.sin(2 * np.pi * df['hour'] / 24)
    df['hour_cos'] = np.cos(2 * np.pi * df['hour'] / 24)
    df['weekday_sin'] = np.sin(2 * np.pi * df['weekday'] / 7)
    df['weekday_cos'] = np.cos(2 * np.pi * df['weekday'] / 7)

    # 3. 时段划分
    def hour_segment(h):
        if 0 <= h < 6: return 0   # 凌晨
        elif 6 <= h < 12: return 1 # 上午
        elif 12 <= h < 14: return 2# 午间
        elif 14 <= h < 18: return 3# 下午
        elif 18 <= h < 22: return 4# 晚间
        else: return 5             # 深夜
    df['hour_segment'] = df['hour'].apply(hour_segment)

    # 4. 节假日标记(需安装holidays库)
    try:
        import holidays
        cn_holidays = holidays.China()
        df['is_holiday'] = df[timestamp_col].apply(
            lambda x: 1 if x.date() in cn_holidays else 0
        )
    except ImportError:
        df['is_holiday'] = 0
        print("警告: holidays库未安装,节假日标记默认为0")

    df['is_weekend'] = df['weekday'].apply(lambda x: 1 if x >= 5 else 0)

    # 5. 距节假日天数(简化:使用固定节假日列表)
    major_holidays = [
        '2025-01-01', '2025-01-28', '2025-01-29', '2025-01-30',
        '2025-05-01', '2025-10-01', '2025-10-02', '2025-10-03'
    ]
    major_holidays = pd.to_datetime(major_holidays)
    def days_to_nearest(d):
        diffs = abs((d - major_holidays).days)
        return diffs.min() if len(diffs) > 0 else 999
    df['days_to_holiday'] = df[timestamp_col].apply(days_to_nearest)

    return df

调用示例:

# 加载交易数据
df_trans = pd.read_csv('transactions.csv')
df_trans = extract_time_features(df_trans, 'trans_time')
print(f"特征提取完成,新增时间特征列数: {len([c for c in df_trans.columns if c not in ['trans_time']])}")
效率优化: 当数据量超过100万行时,建议使用pd.to_datetime(..., cache=True)加速解析,并将节假日匹配改为哈希表查找(set或dict),避免逐行调用holidays库。实测可提速5~10倍。