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']]
)
我的经验: 对于无人机遥测数据,我倾向于用标准化。因为飞行数据里经常有突发性的异常值(比如阵风导致的加速度尖峰),Min-Max 会被这些尖峰「压扁」正常数据。标准化会好很多。

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 秒
避坑指南: 我曾经犯过一个错——用滑动窗口计算特征时,忘了处理窗口边界。前几个点的统计量是 NaN,直接喂给模型就报错了。记得用 dropna() 或者 fillna() 处理一下。

3.5 本章知识体系

下面这张图,是我梳理的本章核心逻辑。你可以把它当作一个检查清单,做预处理时对照着来。

数据预处理实战流程 原始遥测数据 缺失值处理:插值 vs 删除 时间戳对齐与重采样 标准化 / 归一化 特征工程:滑动窗口统计量 关键决策点 • 缺失 < 5%:插值 • 缺失 > 80%:删除 • 统一到 Unix 时间戳 • 重采样到同一频率 • 有异常值:标准化 • 无异常值:归一化 • 窗口大小选 0.1~2 秒 • 记得处理边界 NaN

好了,这一章的内容就是这些。数据预处理看着琐碎,但做扎实了,后面建模会非常顺畅。我见过太多人急着建模,结果被脏数据坑得死去活来。记住:数据预处理花的时间,后面都会加倍还给你。

一句话总结: 缺失值先插后删,时间戳统一对齐,量纲不同就标准化,特征不够就滑动窗口。这套流程,我用了五年,稳得很。

可可精品资料库