2. 传感器采样频率一致性校验

采样频率一致性校验的核心不是看“数据量够不够”,而是看每台设备相邻时间戳的差值是否稳定落在其标称采样周期附近。矿山现场常见 1 s、5 s、10 s、30 s 等采样周期,一旦某台设备出现短间隔(重复触发、时钟抖动)或长间隔(丢包、断链、休眠),后续的滑动平均、报警阈值判断都会失真。因此本例以“相邻时间戳差值”为唯一判据,逐设备统计间隔分布并输出不一致设备清单。

判定口径与阈值设计

设设备标称采样周期为 T(秒),相邻时间戳差值为 dt。工程上通常允许一定容差,因为采集端调度、网络传输都会带来毫秒级抖动。本例采用相对容差 tol=0.2,即:

  • 正常区间:T×(1-tol) ≤ dt ≤ T×(1+tol)
  • 过短异常:dt < T×(1-tol),典型原因是重复上报或时钟回拨
  • 过长异常:dt > T×(1+tol),典型原因是丢包、断链或设备休眠

单点异常不足以判定设备频率不一致,还需给出异常占比阈值 bad_ratio=0.05:当某设备异常间隔数占全部间隔数的比例超过 5% 时,才进入“频率不一致设备清单”。这样可避免偶发一次抖动造成误判。

相邻时间戳差值 dt 的判定区间(以标称周期 T 为基准) 过短异常区 dt < T×(1−tol) 正常采样区 T×(1−tol) ≤ dt ≤ T×(1+tol) 过长异常区 dt > T×(1+tol) T×(1−tol) T T×(1+tol) 逐设备异常占比与清单判定 设备 A · 风速 · T=5s 间隔总数 1200,异常 18 异常占比 1.5% ≤5% → 频率一致 设备 B · 瓦斯 · T=10s 间隔总数 600,异常 96 异常占比 16.0% >5% → 频率不一致 设备 C · 水位 · T=30s 间隔总数 200,异常 3 异常占比 1.5% ≤5% → 频率一致 输出清单仅保留异常占比超过 bad_ratio 的设备,并附过短/过长异常计数,便于定位丢包或重复上报。

实现:逐设备计算 dt 并汇总异常

下面代码可直接运行,使用 pandas 构造三台设备的示例数据(含人为插入的短间隔与长间隔),按设备分组计算 dt,再按标称周期与容差判定异常,最后输出频率不一致设备清单。示例数据与结果均为教学构造,用于演示判定逻辑。

import pandas as pd

# 1. 构造示例数据:每台设备标称周期不同
raw = []
specs = {"A_wind": 5, "B_gas": 10, "C_level": 30}
base = pd.Timestamp("2024-06-01 08:00:00")

# 设备A:正常5s,插入2处短间隔(1s)和1处长间隔(20s)
t = base
for i in range(40):
    raw.append(("A_wind", t))
    step = 5
    if i in (10, 11):
        step = 1          # 过短
    if i == 25:
        step = 20         # 过长
    t = t + pd.Timedelta(seconds=step)

# 设备B:正常10s,但每5个点出现一次1s短间隔(模拟重复上报)
t = base
for i in range(40):
    raw.append(("B_gas", t))
    step = 1 if i % 5 == 0 else 10
    t = t + pd.Timedelta(seconds=step)

# 设备C:正常30s,仅1处长间隔(90s)
t = base
for i in range(40):
    raw.append(("C_level", t))
    step = 90 if i == 20 else 30
    t = t + pd.Timedelta(seconds=step)

df = pd.DataFrame(raw, columns=["device_id", "ts"]).sort_values(["device_id", "ts"])

# 2. 逐设备计算相邻时间戳差值
df["dt"] = df.groupby("device_id")["ts"].diff().dt.total_seconds()

# 3. 按标称周期与容差判定
TOL = 0.2
BAD_RATIO = 0.05
rows = []
for dev, g in df.groupby("device_id"):
    T = specs[dev]
    dt = g["dt"].dropna()
    low, high = T * (1 - TOL), T * (1 + TOL)
    too_short = int((dt < low).sum())
    too_long = int((dt > high).sum())
    total = len(dt)
    bad = too_short + too_long
    ratio = bad / total if total else 0.0
    rows.append({
        "device_id": dev, "T": T, "intervals": total,
        "too_short": too_short, "too_long": too_long,
        "bad_ratio": round(ratio, 4),
        "inconsistent": ratio > BAD_RATIO,
    })

report = pd.DataFrame(rows).sort_values("bad_ratio", ascending=False)
print(report.to_string(index=False))

# 4. 输出频率不一致设备清单
bad_devices = report.loc[report["inconsistent"], ["device_id", "T", "too_short", "too_long", "bad_ratio"]]
print("\n频率不一致设备清单:")
print(bad_devices.to_string(index=False))

运行后 report 会给出每台设备的间隔总数、过短数、过长数、异常占比与是否不一致;bad_devices 即最终清单。按上述构造数据,设备 B 因每 5 个点出现一次 1 s 短间隔,异常占比约 20%,会被判为频率不一致;设备 A 与 C 的异常占比均低于 5%,判为一致。这一结果与构造逻辑一致,可作为验证判定口径是否正确的对照。

结果判读与常见陷阱

  • 首条记录 dt 为 NaN:diff() 后首条必为空,统计时必须 dropna(),否则分母偏大、占比被稀释。
  • 设备排序:计算 diff() 前必须按 device_id 与 ts 排序,否则跨设备相邻会算出巨大负值或错误间隔。
  • 负 dt:若出现负值,说明时间戳乱序或时钟回拨,应单独计数并视为异常,不能直接归入“过短”。
  • 标称周期来源:T 应来自设备台账或采集配置,不能简单用 dt.median() 代替,否则设备整体偏移时会被误判为一致。
  • 容差与占比阈值:tol 过小会把正常抖动判为异常,bad_ratio 过大会漏掉持续丢包设备,需结合现场采集频率与网络状况调整。
若设备数量多、标称周期不统一,可把 specs 换成从设备台账 CSV 读取的 device_id → T 映射表,再合并进 report,即可批量校验全场传感器。