2. 数据采集与预处理:日志埋点规范、数据清洗与缺失值处理、时间窗口与滑动窗口构建

做异常交易检测,说白了就是跟数据打交道。我入行那会儿,接过一个项目,数据量看着挺大,一跑模型才发现——全是坑。日志字段对不上,时间戳格式乱成一锅粥,缺失值直接让模型罢工。嗯,从那以后我就明白了:数据预处理做不好,后面再牛的算法也是白搭。

这一章,我就把我在实战中踩过的坑、总结的经验,掰开揉碎了讲给你听。

2.1 日志埋点规范:别让数据从源头就歪了

你想想看,如果埋点不规范,后面清洗数据得有多痛苦?我见过最夸张的,同一个“交易金额”字段,有的系统叫amount,有的叫trans_amt,还有的叫money。这还不算完,有的用分,有的用元,有的甚至用字符串存着。

我个人习惯,在项目启动阶段就定好埋点规范。核心就三条:

  • 字段命名统一:全小写+下划线,比如user_idtrans_timeamount_cents(单位明确)
  • 时间戳标准化:一律用Unix时间戳(毫秒级),或者ISO 8601格式,比如2025-01-15T14:30:00Z
  • 枚举值可追溯:交易类型、渠道来源这些,提前定义好字典,别用“1、2、3”这种让人猜的编码

避坑指南:我曾经接手过一个项目,日志里“交易状态”字段居然有“成功”、“失败”、“SUCCESS”、“FAILED”、“0”、“1”六种写法。清洗那部分数据,我整整花了两天。所以,埋点规范一定要写在开发文档里,并且上线前做校验。

2.2 数据清洗与缺失值处理:脏数据比坏数据更可怕

数据清洗,我一般分三步走:

  1. 格式统一:把金额、时间、ID这些字段,全部转成统一格式。比如金额统一转成“分”为单位的整数,避免浮点数精度问题。
  2. 异常值过滤:交易金额为负数?时间戳是未来的?用户ID为空?这些直接标记为异常,或者剔除。
  3. 缺失值处理:这是重头戏,我单独说。

缺失值处理,没有银弹。我常用的策略有这几种:

缺失字段类型 常用方法 我的经验
连续型(如金额) 均值/中位数填充 如果数据分布偏态严重,用中位数更稳
分类型(如渠道) 众数填充,或单独标记为“未知” 我建议单独标记,因为“未知”本身可能是个风险信号
时间戳 前向填充或后向填充 交易日志里,时间戳缺失很少见,一旦出现,我倾向于直接丢弃这条记录
用户画像类 模型预测填充 如果缺失率超过30%,我会考虑重新设计特征,而不是硬填

小技巧:我习惯在清洗完数据后,生成一份“数据质量报告”。里面记录每列的缺失率、异常值数量、填充方式。这样后面排查问题时,有据可查。

2.3 时间窗口与滑动窗口构建:抓住交易的“时间模式”

异常交易检测,时间维度特别重要。一个用户平时每天交易3笔,突然某天交易了30笔,这本身就是个强信号。怎么捕捉这种变化?靠时间窗口。

时间窗口,说白了就是“看过去多久的数据”。我常用的窗口有:

  • 固定窗口:比如过去1小时、过去24小时。适合做离线分析。
  • 滑动窗口:每来一笔新交易,窗口就往前滑一下。适合做实时检测。

举个例子,我要计算“过去1小时内,该用户的交易总金额”。用滑动窗口实现,代码大概长这样:

import pandas as pd

def build_sliding_window_features(df, user_col='user_id', time_col='trans_time', amount_col='amount', window='1h'):
    """
    构建滑动窗口特征
    df: 原始交易数据,按时间排序
    """
    df = df.sort_values(time_col).reset_index(drop=True)
    
    # 按用户分组,对每个用户单独计算滑动窗口
    features = []
    for user, group in df.groupby(user_col):
        group = group.set_index(time_col)
        # 计算过去1小时内的交易次数和总金额
        group['trans_count_1h'] = group[amount_col].rolling(window, min_periods=1).count()
        group['trans_amount_1h'] = group[amount_col].rolling(window, min_periods=1).sum()
        features.append(group)
    
    result = pd.concat(features)
    return result.reset_index()

这段代码里,rolling(window='1h')就是滑动窗口的核心。它会自动处理时间边界,比如窗口跨天、跨小时都没问题。

注意:滑动窗口有个坑——数据泄露。如果你用未来的数据来计算当前的特征,模型会“作弊”。比如计算“过去1小时”时,一定要确保窗口只包含当前时间之前的数据。我习惯在代码里显式加上closed='left'参数,避免不小心用了未来数据。

除了简单的计数和求和,我还会构建一些更复杂的窗口特征:

  • 窗口内金额的方差:如果金额波动特别大,可能是异常
  • 窗口内交易间隔的均值:如果间隔突然变短,可能是机器在刷单
  • 窗口内不同渠道的数量:如果短时间内换了多个渠道,风险也高

这些特征组合起来,能很好地刻画用户的“行为画像”。

2.4 本章知识体系总览

下面这张图,是我自己梳理的本章核心逻辑。你可以把它当成一个“地图”,后面遇到具体问题时,回来对照着看。

数据采集与预处理核心流程 日志埋点规范 字段命名 · 时间戳 · 枚举值 数据清洗与缺失值处理 格式统一 · 异常过滤 · 缺失填充 时间窗口构建 固定窗口 · 滑动窗口 · 特征工程 日志埋点规范 • 字段命名:全小写+下划线 • 时间戳:Unix毫秒或ISO 8601 • 枚举值:提前定义字典 • 单位统一:金额用“分” • 上线前做校验 数据清洗与缺失值处理 • 格式统一:金额、时间、ID • 异常过滤:负数、未来时间 • 连续型:中位数填充 • 分类型:单独标记“未知” • 生成数据质量报告 时间窗口构建 • 固定窗口:1h/24h • 滑动窗口:实时检测 • 窗口内计数/求和/方差 • 交易间隔特征 • 注意数据泄露

这张图把本章的三个核心模块串起来了。从日志埋点开始,到数据清洗,再到时间窗口构建,每一步都环环相扣。你想想看,如果埋点不规范,清洗就得花大力气;清洗不干净,窗口特征就是错的。所以,每一步都不能偷懒。

好了,数据预处理这部分,我就讲这么多。记住一句话:好的数据预处理,是异常检测成功的一半。后面我们讲特征工程时,你会发现,很多高级特征都是建立在今天这些基础工作之上的。