2. 数据探索与预处理:数据源分析、缺失值处理、异常值识别、数据标准化
好,咱们正式开始动手之前,先别急着写代码。我个人的习惯是,拿到任何一份交易数据,第一件事不是建模,而是——先跟数据“聊聊天”。你得知道它长什么样,有哪些坑,哪些字段是摆设,哪些字段藏着宝贝。
说白了,数据探索和预处理,占掉整个特征工程至少 60% 的时间。别嫌烦,这一步做扎实了,后面模型训练会顺滑很多。我曾经在一个项目里,因为没仔细看某个字段的缺失模式,结果上线后模型在凌晨时段疯狂误报……嗯,那教训够我记一辈子。
2.1 数据源分析:先搞清楚数据从哪来
交易数据通常来自多个源头。比如核心账务系统、风控日志、埋点数据。每个源头的数据质量参差不齐。你想想看,如果源头就错了,后面再怎么洗也是白搭。
我一般会先做这几步:
- 字段清单梳理: 哪些是数值型(交易金额、时间戳),哪些是类别型(交易渠道、商户类型),哪些是文本型(备注、设备指纹)。
- 时间范围确认: 数据覆盖了多久?有没有跨月、跨年的数据?节假日和日常的数据分布差异大吗?
- 数据量级评估: 每天多少笔?每秒峰值多少?这决定了你后续特征计算的效率策略。
举个例子,我在处理某支付平台的数据时,发现“交易时间”字段居然有 30% 是空的。后来一查,是上游日志系统在凌晨批次处理时丢了一部分。这种问题,你不做数据源分析,根本发现不了。
df.info() 和 df.describe() 快速扫描数据概貌。重点关注非空计数和数据类型。
2.2 缺失值处理:别让“空”骗了你
缺失值在交易数据里太常见了。但注意,缺失不一定等于“没有”。有时候,缺失本身就是一种信号。
比如“收货地址”字段为空,在正常交易里可能只是用户懒得填。但在异常交易里,可能是机器自动生成的订单,根本没填地址。所以,处理缺失值之前,先问问自己:这个字段为什么为空?
我常用的处理策略:
- 直接删除: 如果缺失比例超过 70%,且字段对业务无直接意义,果断删掉。别犹豫。
- 填充默认值: 数值型字段,我习惯用中位数填充(抗异常值干扰)。类别型字段,用“未知”或“-1”填充。
- 标记缺失本身: 创建一个新特征
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']]
)
2.5 本章知识体系总览
下面这张图,是我自己梳理的数据探索与预处理流程。你可以把它当作一个检查清单,每次做项目时对照着来。
嗯,到这里,数据探索与预处理的核心内容就讲完了。你可能会觉得这些步骤有点繁琐,但相信我,磨刀不误砍柴工。数据干净了,后面的特征工程和模型训练才能事半功倍。