4. 数据预处理:数据清洗、缺失值处理、异常值检测与平滑
在构建行情坏账率哨兵告警系统时,原始数据通常来自交易所行情推送、风控日志、清算对账等多个异构数据源。这些数据存在格式不一致、字段缺失、极端跳变等问题。如果直接用于阈值计算,会导致告警误报率升高或漏报。本章将系统讲解数据预处理的四个核心环节:数据清洗、缺失值处理、异常值检测与平滑。
4.1 数据清洗:去重、格式统一与业务规则过滤
数据清洗是预处理的第一步,主要解决数据质量的基础问题。针对行情坏账率数据,清洗工作包含以下三个子任务:
| 清洗任务 | 常见问题 | 处理方法 |
|---|---|---|
| 去重 | 同一时间戳、同一标的出现多条记录 | 按时间戳+标的ID去重,保留最后一条或取均值 |
| 格式统一 | 时间戳格式不一致(Unix秒/毫秒/字符串) | 统一转换为UTC毫秒时间戳,并校验时间连续性 |
| 业务规则过滤 | 坏账率超过100%或为负值 | 剔除明显违反业务逻辑的记录,并记录异常日志 |
实践建议:在清洗阶段保留原始数据副本,避免不可逆操作。建议使用
dataframe.duplicated()和dataframe.describe()快速定位异常范围。
4.2 缺失值处理:策略选择与填充方法
行情数据在传输过程中可能因网络抖动、交易所断连等原因产生缺失。缺失值处理策略直接影响后续异常值检测的准确性。根据缺失比例和业务场景,推荐以下处理方案:
- 直接删除:当缺失比例小于1%且缺失随机分布时,直接删除缺失记录。
- 前向填充(Forward Fill):适用于高频行情数据,用上一时刻值填充当前缺失,保持序列连续性。
- 线性插值:适用于坏账率等连续变化指标,利用缺失点前后有效值进行线性拟合。
- 业务默认值填充:当坏账率缺失时,可填充为0或最近N个周期的均值,需在告警日志中标记。
注意:缺失值填充后必须记录填充标记字段,避免在阈值计算时将填充值与真实值混淆。对于连续缺失超过5个时间窗口的情况,建议直接标记为“数据不可用”状态。
4.3 异常值检测:统计方法与业务规则结合
行情坏账率数据中,异常值通常表现为瞬时跳变(如从0.5%突然跳至15%)或持续偏离正常范围。单一检测方法容易误判,建议采用“统计+规则”双层检测策略。
| 检测方法 | 适用场景 | 参数说明 |
|---|---|---|
| 3σ原则 | 数据近似正态分布 | 均值±3倍标准差之外视为异常 |
| IQR四分位法 | 数据分布偏态明显 | Q1-1.5×IQR 或 Q3+1.5×IQR 之外为异常 |
| Z-Score | 标准化后的离群点检测 | |Z| > 3 视为异常 |
| 业务阈值规则 | 已知风控上限 | 坏账率 > 10% 直接标记为异常 |
4.4 数据平滑:降低噪声保留趋势
行情坏账率数据存在高频噪声,直接使用原始值进行阈值计算会导致告警频繁抖动。平滑处理的目标是滤除短期波动,保留中长期趋势。常用的平滑方法包括:
- 移动平均(Moving Average):取最近N个时间窗口的均值,N通常取5~20。
- 指数加权移动平均(EWMA):对近期数据赋予更高权重,衰减因子α通常取0.1~0.3。
- 中值滤波:取窗口内中位数,对脉冲型噪声抑制效果较好。
平滑参数调优建议:在哨兵告警系统中,建议对坏账率数据使用EWMA(α=0.15)进行平滑。该参数在抑制噪声与保持趋势灵敏度之间取得平衡。平滑后的数据用于阈值计算,原始数据保留用于异常复核。
4.5 预处理完整流程代码示例
以下代码展示了从原始数据到预处理完成的核心步骤,包含清洗、缺失填充、异常检测与平滑:
import pandas as pd
import numpy as np
# 1. 数据清洗:去重 + 格式统一
df = df.drop_duplicates(subset=['timestamp', 'symbol'])
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
df = df.sort_values('timestamp')
# 2. 缺失值处理:线性插值
df['bad_rate'] = df['bad_rate'].interpolate(method='linear', limit=3)
# 3. 异常值检测:IQR + 业务规则
Q1 = df['bad_rate'].quantile(0.25)
Q3 = df['bad_rate'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
df['is_anomaly'] = ((df['bad_rate'] < lower_bound) |
(df['bad_rate'] > upper_bound) |
(df['bad_rate'] > 10.0)).astype(int)
# 4. 数据平滑:EWMA
df['bad_rate_smooth'] = df['bad_rate'].ewm(alpha=0.15, adjust=False).mean()
print(f"预处理完成,总记录数:{len(df)},异常标记数:{df['is_anomaly'].sum()}")
关键点:预处理后的数据应包含原始值、平滑值、异常标记三个字段。在后续阈值调优中,平滑值用于计算基线,异常标记用于评估告警准确率。