3、数据预处理实战:缺失值处理、时间戳对齐与重采样、标准化与归一化、特征工程基础
各位同学,欢迎来到数据预处理这一章。
说实话,遥测数据拿到手,十有八九是「脏」的。我在项目里见过太多这样的情况:飞控日志里时间戳跳变、传感器偶尔掉线导致数据断崖、不同传感器的采样率还不一样…… 如果不把这些坑填平,后面做异常检测就是白搭。
这一章,我就带你手把手把这些预处理工作做扎实。
3.1 缺失值处理:插值还是删除?
先问个问题:遇到缺失值,你第一反应是什么?
我刚开始做的时候,图省事,直接删。后来发现,有些缺失是传感器间歇性掉线造成的,删掉之后时间序列就不连续了,后续的频谱分析直接崩掉。所以,我的建议是:能不删,尽量不删。
3.1.1 什么时候该删?
- 缺失比例过高:比如某个传感器在整段飞行中缺失了 80% 以上的数据。这种数据插值也没意义,直接删掉这个特征列。
- 连续缺失过长:比如 GPS 信号丢失了 10 秒以上。这种长段缺失,插值会引入大量虚假信息,不如直接删除这段数据。
3.1.2 什么时候该插值?
- 零星缺失:比如某个时间点掉了一帧数据。这种用前后值插补一下就行。
- 传感器不同步:比如 IMU 是 200Hz,GPS 是 10Hz,GPS 的缺失是「天然」的。这时候需要插值来对齐时间戳。
3.1.3 插值方法实战
我个人习惯用 pandas 的 interpolate() 方法。它默认是线性插值,对于无人机遥测数据来说,大部分场景够用了。
import pandas as pd
import numpy as np
# 模拟一段遥测数据,故意制造缺失
time = np.arange(0, 10, 0.1)
altitude = np.sin(time) + np.random.normal(0, 0.1, len(time))
altitude[10:15] = np.nan # 制造连续缺失
altitude[30] = np.nan # 制造零星缺失
df = pd.DataFrame({'time': time, 'altitude': altitude})
# 线性插值
df['altitude_interp'] = df['altitude'].interpolate(method='linear')
# 对于无人机高度数据,有时用二次插值更平滑
df['altitude_quad'] = df['altitude'].interpolate(method='quadratic')
3.2 时间戳对齐与重采样
这是个大坑。无人机上不同传感器的时间戳,往往不是对齐的。
我记得有一次做多传感器融合,IMU 的时间戳是微秒级,GPS 是毫秒级,而且 GPS 还有 200 毫秒的传输延迟。如果不做对齐,融合出来的姿态数据直接飘了。
3.2.1 统一时间基准
第一步,把所有时间戳转成统一格式。我习惯用 Unix 时间戳(秒或毫秒),方便计算。
# 假设原始时间戳是字符串格式
df['timestamp'] = pd.to_datetime(df['timestamp'])
df['timestamp_unix'] = df['timestamp'].astype(np.int64) // 10**9 # 转成秒
3.2.2 重采样到统一频率
第二步,把所有数据重采样到同一个频率。比如,IMU 是 200Hz,GPS 是 10Hz,我们统一到 100Hz。
# 设置时间戳为索引
df_imu = df_imu.set_index('timestamp_unix')
df_gps = df_gps.set_index('timestamp_unix')
# 重采样到 100Hz(0.01秒一个点)
df_imu_resampled = df_imu.resample('0.01S').interpolate()
df_gps_resampled = df_gps.resample('0.01S').interpolate()
resample 会生成 NaN。所以重采样之后,通常紧接着做一次插值。我一般把这两个步骤写成一个 pipeline。
3.3 数据标准化与归一化
为什么要做这一步?你想想看,高度是几百米,加速度是几个 g,角速度是几十度每秒。这些量纲完全不一样。如果不做标准化,机器学习模型会天然「偏爱」数值大的特征。
3.3.1 归一化(Min-Max Scaling)
把数据缩放到 [0, 1] 区间。适合数据分布比较均匀的情况。
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
df[['altitude', 'velocity', 'accel_x']] = scaler.fit_transform(
df[['altitude', 'velocity', 'accel_x']]
)
3.3.2 标准化(Z-score Scaling)
把数据变成均值为 0,标准差为 1。适合数据有异常值的情况,因为它对异常值不那么敏感。
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
df[['altitude', 'velocity', 'accel_x']] = scaler.fit_transform(
df[['altitude', 'velocity', 'accel_x']]
)
3.4 特征工程基础:滑动窗口统计量
原始遥测数据是「裸」的。比如你拿到一个加速度序列,它本身看不出什么异常。但如果你计算过去 1 秒内的加速度方差,就能发现剧烈抖动。
这就是特征工程的意义——从原始数据中提取更有信息量的特征。
3.4.1 滑动窗口统计量
最常用的就是滑动窗口的均值、方差、最大值、最小值。
# 设置窗口大小为 10 个点(假设 100Hz,即 0.1 秒窗口)
window_size = 10
df['accel_x_mean'] = df['accel_x'].rolling(window=window_size).mean()
df['accel_x_std'] = df['accel_x'].rolling(window=window_size).std()
df['accel_x_max'] = df['accel_x'].rolling(window=window_size).max()
df['accel_x_min'] = df['accel_x'].rolling(window=window_size).min()
3.4.2 窗口大小的选择
窗口大小很关键。太小了,噪声大;太大了,反应迟钝。
- 检测快速抖动:窗口 0.1~0.2 秒(10~20 个点)
- 检测缓慢漂移:窗口 1~2 秒(100~200 个点)
- 检测整体趋势:窗口 5~10 秒
dropna() 或者 fillna() 处理一下。
3.5 本章知识体系
下面这张图,是我梳理的本章核心逻辑。你可以把它当作一个检查清单,做预处理时对照着来。
好了,这一章的内容就是这些。数据预处理看着琐碎,但做扎实了,后面建模会非常顺畅。我见过太多人急着建模,结果被脏数据坑得死去活来。记住:数据预处理花的时间,后面都会加倍还给你。