2. 数据探索与预处理:数据源分析、缺失值处理、异常值识别、数据标准化

好,咱们正式开始动手之前,先别急着写代码。我个人的习惯是,拿到任何一份交易数据,第一件事不是建模,而是——先跟数据“聊聊天”。你得知道它长什么样,有哪些坑,哪些字段是摆设,哪些字段藏着宝贝。

说白了,数据探索和预处理,占掉整个特征工程至少 60% 的时间。别嫌烦,这一步做扎实了,后面模型训练会顺滑很多。我曾经在一个项目里,因为没仔细看某个字段的缺失模式,结果上线后模型在凌晨时段疯狂误报……嗯,那教训够我记一辈子。

核心目标: 把原始交易日志,清洗成干净、稳定、可建模的结构化数据。

2.1 数据源分析:先搞清楚数据从哪来

交易数据通常来自多个源头。比如核心账务系统、风控日志、埋点数据。每个源头的数据质量参差不齐。你想想看,如果源头就错了,后面再怎么洗也是白搭。

我一般会先做这几步:

  • 字段清单梳理: 哪些是数值型(交易金额、时间戳),哪些是类别型(交易渠道、商户类型),哪些是文本型(备注、设备指纹)。
  • 时间范围确认: 数据覆盖了多久?有没有跨月、跨年的数据?节假日和日常的数据分布差异大吗?
  • 数据量级评估: 每天多少笔?每秒峰值多少?这决定了你后续特征计算的效率策略。

举个例子,我在处理某支付平台的数据时,发现“交易时间”字段居然有 30% 是空的。后来一查,是上游日志系统在凌晨批次处理时丢了一部分。这种问题,你不做数据源分析,根本发现不了。

小技巧:df.info()df.describe() 快速扫描数据概貌。重点关注非空计数和数据类型。

2.2 缺失值处理:别让“空”骗了你

缺失值在交易数据里太常见了。但注意,缺失不一定等于“没有”。有时候,缺失本身就是一种信号。

比如“收货地址”字段为空,在正常交易里可能只是用户懒得填。但在异常交易里,可能是机器自动生成的订单,根本没填地址。所以,处理缺失值之前,先问问自己:这个字段为什么为空?

我常用的处理策略:

  1. 直接删除: 如果缺失比例超过 70%,且字段对业务无直接意义,果断删掉。别犹豫。
  2. 填充默认值: 数值型字段,我习惯用中位数填充(抗异常值干扰)。类别型字段,用“未知”或“-1”填充。
  3. 标记缺失本身: 创建一个新特征 is_null_xxx,值为 0 或 1。这在异常检测里有时比原始值还重要。
# 我个人习惯的缺失值处理代码
import pandas as pd
import numpy as np

# 假设 df 是交易数据
# 数值型字段:用中位数填充
df['amount'].fillna(df['amount'].median(), inplace=True)

# 类别型字段:用 'UNKNOWN' 填充
df['channel'].fillna('UNKNOWN', inplace=True)

# 标记缺失:新增特征
df['amount_is_null'] = df['amount'].isnull().astype(int)
注意: 千万不要用均值填充!交易数据里异常值太多,均值会被拉偏。中位数更稳健。

2.3 异常值识别:找到那些“不对劲”的交易

异常值识别,说白了就是找“坏孩子”。但注意,异常值不一定是错误,它可能就是我们要抓的欺诈交易。

我常用的方法有三种:

  • 统计法(3σ原则): 假设数据服从正态分布,超过均值±3倍标准差的值视为异常。适合交易金额这种近似正态的字段。
  • 箱线图法(IQR): 用四分位距来界定。不受极端值影响,更稳健。我一般用这个。
  • 业务规则法: 比如单笔交易超过 100 万,直接标红。这需要业务经验。

我记得有一次,用 IQR 方法识别“交易间隔”字段,发现一批凌晨 3 点的交易,间隔只有 0.1 秒。正常人手速根本不可能。后来一查,果然是机器脚本在跑批量转账。

# 箱线图法识别异常值
Q1 = df['amount'].quantile(0.25)
Q3 = df['amount'].quantile(0.75)
IQR = Q3 - Q1

lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

# 标记异常
df['is_outlier_amount'] = ((df['amount'] < lower_bound) | (df['amount'] > upper_bound)).astype(int)
避坑指南: 我曾经直接把异常值删掉,结果模型在测试集上表现很好,上线后却一塌糊涂。后来发现,那些“异常值”其实是少数欺诈样本,删掉后模型根本没见过欺诈长什么样。所以,异常值先标记,别急着删

2.4 数据标准化:让所有特征站在同一起跑线

交易数据里,字段的量纲差异巨大。交易金额可能是几百万,交易次数可能是个位数。如果不做标准化,模型会天然偏向数值大的特征。

我常用的标准化方法:

方法 公式 适用场景
Z-score 标准化 (x - μ) / σ 数据近似正态分布,无严格上下界
Min-Max 归一化 (x - min) / (max - min) 数据有明确上下界,如评分、比例
Robust 标准化 (x - median) / IQR 数据包含较多异常值,抗干扰强

我个人最常用的是 Robust 标准化。为什么?因为交易数据里异常值太多了,Z-score 会被异常值带偏,Min-Max 会被极端值压缩。Robust 用中位数和 IQR,稳得很。

from sklearn.preprocessing import RobustScaler

scaler = RobustScaler()
df[['amount', 'tx_count', 'interval']] = scaler.fit_transform(
    df[['amount', 'tx_count', 'interval']]
)
注意: 标准化参数(中位数、IQR)一定要用训练集拟合,然后应用到测试集。千万别在整个数据集上 fit,否则会造成数据泄露。

2.5 本章知识体系总览

下面这张图,是我自己梳理的数据探索与预处理流程。你可以把它当作一个检查清单,每次做项目时对照着来。

数据探索与预处理流程 数据源分析 字段清单 · 时间范围 · 量级 缺失值处理 删除 · 填充 · 标记缺失 异常值识别 3σ · IQR · 业务规则 数据标准化 Z-score · Min-Max · Robust 特征工程 衍生特征 · 聚合特征 · 时序特征 模型训练与评估 分类 · 聚类 · 异常检测 每一步都包含“标记”和“处理”两个动作,不要跳过标记直接处理 关键提醒:异常值先标记,缺失值先分析原因,标准化参数只从训练集学习

嗯,到这里,数据探索与预处理的核心内容就讲完了。你可能会觉得这些步骤有点繁琐,但相信我,磨刀不误砍柴工。数据干净了,后面的特征工程和模型训练才能事半功倍。

最后说一句: 每次做完预处理,记得把清洗逻辑保存成脚本。下次遇到类似数据,直接复用。我自己的工具箱里,光数据清洗函数就有几十个,都是一个个坑踩出来的。

专注资料整理