4、数据预处理技术:数据清洗、格式化与时间戳对齐

数据预处理,说白了就是给传感器数据「洗澡」。

我做了这么多年物联网,见过太多「脏数据」把整个系统搞崩的例子。你想想看,传感器采集上来的数据,什么情况都有——重复的、异常的、格式乱的、时间对不上的。如果不处理,后面的分析全是垃圾。

这一章,我就把数据预处理的三个核心环节拆开来讲。

4.1 数据清洗:去重与异常值处理

数据清洗是第一步,也是最容易出坑的一步。

4.1.1 去重:别让重复数据骗了你

传感器数据重复,原因很多。网络重传、设备缓存、网关重复上报……我见过最夸张的一次,一个温度传感器因为网关配置错误,同一秒上报了 37 次同样的数据。

去重策略,我一般分三种情况:

  • 精确去重:时间戳 + 设备 ID + 数值完全一致,直接丢弃。
  • 时间窗口去重:比如 100ms 内同一设备上报的相同数值,只保留第一条。
  • 语义去重:比如连续 5 条数据变化小于 0.1%,只保留第一条和最后一条。

核心原则:去重不是简单删数据,而是保留信息量最大的那条。

代码实现上,我习惯用哈希表做精确去重:

// 伪代码:时间窗口去重
function deduplicate(stream, windowMs = 100) {
    let cache = new Map();
    return stream.filter(data => {
        let key = `${data.deviceId}_${data.value}`;
        let lastTime = cache.get(key);
        if (lastTime && (data.timestamp - lastTime) < windowMs) {
            return false; // 重复,丢弃
        }
        cache.set(key, data.timestamp);
        return true;
    });
}

我的经验:去重一定要考虑时间窗口。纯精确去重会漏掉「同一秒内不同毫秒的重复上报」。

4.1.2 异常值处理:别让一个坏点毁了整条曲线

异常值,我把它分成两类:

  • 硬异常:超出传感器量程的值。比如温度传感器量程是 -40°C ~ 85°C,上报了 200°C,直接扔掉。
  • 软异常:在量程内,但不符合物理规律。比如室温传感器 1 秒内从 25°C 跳到 60°C。

处理软异常,我常用三种方法:

方法 适用场景 缺点
3σ 原则 数据服从正态分布 对突变不敏感
IQR 四分位距 数据分布未知 计算开销大
滑动窗口均值 实时流处理 延迟较高

避坑指南:我曾经在一个项目中直接用 3σ 剔除异常值,结果把设备启动时的正常升温过程全删了。后来改成「先判断趋势,再判断数值」才解决问题。

我个人最推荐滑动窗口法,适合实时场景:

// 滑动窗口异常检测
function detectAnomaly(value, window) {
    let mean = window.reduce((a, b) => a + b) / window.length;
    let std = Math.sqrt(window.map(v => Math.pow(v - mean, 2)).reduce((a, b) => a + b) / window.length);
    return Math.abs(value - mean) > 3 * std;
}

4.2 数据格式化与标准化

数据格式不统一,是物联网项目里最常见的坑。

我记得有一次对接 5 家不同厂商的传感器,温度单位有 °C、°F、K,时间格式有 Unix 时间戳、ISO 8601、yyyy-MM-dd HH:mm:ss……光格式转换就写了 200 行代码。

4.2.1 格式化:统一数据模型

我建议所有传感器数据都转成统一的内部格式:

  • 数值类型:统一用 float64,避免精度丢失
  • 时间格式:统一用 Unix 毫秒时间戳(int64)
  • 设备 ID:统一用字符串,长度固定
  • 单位:统一用 SI 单位制

我的习惯:在数据入口处做一个「格式转换层」,所有原始数据先过这一层,后面就不用再操心格式问题了。

4.2.2 标准化:让数据可比较

标准化,说白了就是把不同量纲的数据拉到同一个尺度上。

常用的标准化方法:

  • Min-Max 标准化:映射到 [0, 1] 区间,适合有明确上下界的场景
  • Z-score 标准化:均值为 0,标准差为 1,适合异常检测
  • Robust 标准化:用中位数和 IQR,对异常值不敏感
// Min-Max 标准化
function minMaxNormalize(value, min, max) {
    return (value - min) / (max - min);
}

// Z-score 标准化
function zScoreNormalize(value, mean, std) {
    return (value - mean) / std;
}

经验之谈:标准化参数(min、max、mean、std)要定期更新。我见过一个项目用了半年前的参数做标准化,结果新数据全被压缩到 [0.8, 1.0] 区间,完全失去了区分度。

4.3 时间戳对齐技术

时间戳对齐,是数据预处理里最头疼的问题。

你想想看,一个系统里有 1000 个传感器,每个传感器上报时间都不一样。有的每 1 秒上报一次,有的每 5 秒,还有的因为网络延迟,上报时间飘忽不定。怎么把这些数据对齐到同一个时间轴上?

4.3.1 对齐策略:三种主流方法

方法 原理 适用场景
最近邻对齐 取时间上最接近的数据点 高频数据,精度要求不高
线性插值 在两个数据点之间做线性拟合 低频数据,需要平滑曲线
重采样 按固定时间窗口聚合(均值/中位数) 多源数据融合

我个人最常用的是重采样法。为什么呢?因为它天然能处理数据缺失和抖动。

// 重采样:按 1 秒窗口聚合
function resample(data, intervalMs = 1000) {
    let result = [];
    let windowStart = Math.floor(data[0].timestamp / intervalMs) * intervalMs;
    let windowEnd = windowStart + intervalMs;
    let bucket = [];
    
    for (let d of data) {
        if (d.timestamp >= windowEnd) {
            // 窗口结束,计算均值
            let avg = bucket.reduce((a, b) => a + b.value, 0) / bucket.length;
            result.push({ timestamp: windowStart, value: avg });
            windowStart = windowEnd;
            windowEnd += intervalMs;
            bucket = [];
        }
        bucket.push(d);
    }
    return result;
}

4.3.2 时间戳漂移处理

传感器设备的时间戳经常不准。我遇到过设备时钟一天能漂移好几秒的情况。

处理方法:

  • NTP 同步:设备端定期同步时间,这是最根本的解决方案
  • 服务端校正:用网关收到数据的时间戳替换设备时间戳
  • 时间戳平滑:对连续的时间戳做线性回归,修正漂移

避坑指南:我曾经直接用设备时间戳做对齐,结果因为设备时钟漂移,数据对齐后出现了「时间倒流」的现象——后上报的数据时间戳反而更早。后来我强制要求所有设备必须用 NTP 同步,并在服务端做二次校验。

4.3.3 多源数据融合对齐

当多个传感器数据需要融合时,对齐就变得复杂了。

我常用的做法是「主时钟对齐法」:

  1. 选定一个主时间轴(比如 1 秒一个点)
  2. 每个传感器数据都对齐到这个主时间轴上
  3. 缺失的数据用插值或前值填充
  4. 对齐后的数据组成一个多维时间序列

核心思想:对齐不是让所有数据的时间戳完全一致,而是让它们在同一个时间参考系下可比较。

嗯,数据预处理这块,说白了就是「把脏数据洗干净,把乱数据排整齐」。做好了,后面的分析事半功倍;做不好,再牛的算法也白搭。

我建议你在项目初期就把数据预处理流程固定下来,写成自动化脚本。别等到数据量大了再回头处理,那时候就晚了。