3、数据清洗与预处理:缺失值处理、异常值检测、时间戳对齐、数据去重

数据回放,说白了就是让历史数据“重演”一遍。但现实中的数据,从来都不是干干净净的。我做了这么多年数据工程,见过太多“脏数据”把回放结果搞得一塌糊涂。你想想看,如果原始数据本身就有问题,回放出来的结果能靠谱吗?

所以,数据清洗与预处理,是数据回放前必须跨过的一道坎。我个人习惯把这一步叫做“数据安检”。今天咱们就聊聊这四个核心环节:缺失值、异常值、时间戳对齐、数据去重。

核心观点:数据清洗不是“锦上添花”,而是“雪中送炭”。跳过这一步,回放结果就是垃圾进、垃圾出。

3.1 缺失值处理:别让“空”毁了你的回放

缺失值,就是数据里那些“该有却没有”的东西。比如传感器突然断连,或者日志没写全。我在项目中遇到过,有一次回放股票交易数据,因为网络抖动丢了几个 tick,结果回放出来的曲线直接断崖式下跌——其实是假象。

处理缺失值,常用的方法就这几种:

  • 删除法:直接扔掉缺失的行。简单粗暴,但数据量少的时候慎用。
  • 填充法:用均值、中位数、众数填进去。适合连续型数据。
  • 插值法:用前后值做线性插值。时间序列数据我比较推荐这个。
  • 向前/向后填充:用上一个有效值填充。比如传感器数据,短时间缺失用这个很稳。
# 我个人常用的缺失值处理代码(Python)
import pandas as pd

# 读取数据
df = pd.read_csv('sensor_data.csv')

# 检查缺失值
print(df.isnull().sum())

# 向前填充(适合时间序列)
df['temperature'].fillna(method='ffill', inplace=True)

# 线性插值(适合平滑变化的数据)
df['pressure'].interpolate(method='linear', inplace=True)

# 如果缺失太多,直接删除
df.dropna(subset=['critical_field'], inplace=True)

我的小技巧:别一上来就填充。先看看缺失值的分布。如果缺失集中在某个时间段,可能是设备故障,这时候填充反而会掩盖问题。

3.2 异常值检测:揪出那些“离谱”的数据点

异常值,就是数据里那些“明显不对劲”的值。比如温度传感器突然蹦出个1000度,或者股价瞬间跌到负数。嗯,这里要注意:异常值不一定是错误,也可能是真实事件,比如熔断。

我常用的检测方法:

  • 3σ原则:数据服从正态分布时,超过均值±3倍标准差的就是异常。简单好用。
  • 箱线图法:用四分位数判断。不受极端值影响,我比较喜欢这个。
  • IQR(四分位距)法:Q1 - 1.5*IQR 到 Q3 + 1.5*IQR 之外的都是异常。
  • 领域知识法:比如股票价格不可能为负,直接设阈值。
# 箱线图法检测异常值
Q1 = df['value'].quantile(0.25)
Q3 = df['value'].quantile(0.75)
IQR = Q3 - Q1

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

# 标记异常
df['is_outlier'] = (df['value'] < lower_bound) | (df['value'] > upper_bound)

# 查看异常数据
print(df[df['is_outlier']])

避坑指南:我曾经犯过一个错——把所有异常值都删掉了。结果回放时发现,那些“异常”其实是市场剧烈波动的真实记录。所以,先分析,再处理。别一刀切。

3.3 时间戳对齐:让数据“踩在同一个节拍上”

数据回放最头疼的问题之一,就是时间戳对不齐。比如一个传感器每秒采样一次,另一个每500毫秒采样一次。回放时,它们的时间点根本对不上。你想想看,这怎么回放?

时间戳对齐的核心思路,就是“重采样”。把不同频率的数据,统一到一个时间基准上。

  • 降采样:高频数据转低频。比如把毫秒级数据聚合成秒级。用均值或中位数聚合。
  • 升采样:低频数据转高频。比如把秒级数据插值到毫秒级。用线性插值或向前填充。
  • 对齐方式:左对齐、右对齐、中心对齐。我一般用左对齐,简单直观。
# 时间戳对齐示例
# 假设有两个数据源:df1(1秒间隔),df2(500毫秒间隔)

# 统一转为秒级时间戳
df1['timestamp'] = pd.to_datetime(df1['timestamp']).dt.floor('S')
df2['timestamp'] = pd.to_datetime(df2['timestamp']).dt.floor('S')

# 按时间戳合并
aligned_df = pd.merge(df1, df2, on='timestamp', how='outer')

# 对缺失值进行插值填充
aligned_df.interpolate(method='time', inplace=True)

我的经验:时间戳对齐时,一定要先检查时区。我遇到过因为时区没统一,回放结果差了8小时的惨案。统一用UTC,准没错。

3.4 数据去重:别让重复数据“刷屏”

重复数据,就是那些一模一样的记录出现了多次。原因很多:网络重传、日志重复写入、数据采集bug。我记得有一次回放交易数据,因为重复数据,回放出来的成交量直接翻倍——这要是拿去分析,结论全错。

去重的方法:

  • 完全去重:所有字段都相同,直接删掉重复行。
  • 基于关键字段去重:比如时间戳+设备ID相同,就认为是重复。
  • 保留第一条/最后一条:根据业务需求决定。我一般保留第一条。
# 数据去重
# 完全去重
df.drop_duplicates(inplace=True)

# 基于时间戳和设备ID去重,保留第一条
df.drop_duplicates(subset=['timestamp', 'device_id'], keep='first', inplace=True)

# 查看去重后的数据量
print(f"去重后数据量: {len(df)}")

注意:去重之前,先确认一下“重复”的定义。有些数据看起来重复,但其实是不同时间点的正常记录。比如心跳包,每5秒发一次,内容可能完全一样——这不是重复,是正常数据。

知识体系总览

下面这张图,是我梳理的数据清洗与预处理核心逻辑。你可以把它当作一个检查清单,每次做数据回放前,照着走一遍。

数据清洗与预处理核心流程 原始数据 缺失值处理 删除/填充/插值 异常值检测 3σ/箱线图/IQR 时间戳对齐 重采样/插值 数据去重 完全/基于字段 清洗后的数据 四个步骤按顺序执行,但异常值检测和缺失值处理可以并行

说白了,数据清洗就是一场“排雷”行动。每个环节都马虎不得。我个人习惯把这四个步骤写成脚本,每次回放前自动跑一遍。省心,也放心。

最后说一句:数据清洗没有银弹。不同的业务场景,处理方式天差地别。多积累经验,多踩坑,慢慢你就知道该怎么做了。

可可精品资料库