1. 轨道不平顺实测数据的读取与里程对齐

任务对象与数据形态

轨道检测车或便携式轨检设备导出的原始不平顺文件,通常按“采样点序号 + 里程 + 各通道幅值”组织。以本例使用的示例文件 track_raw.csv 为例,其列结构为:

seq,mileage,gauge,level,align_l,align_r,profile_l,profile_r
1,1000.000,1435.2,0.31,-0.12,0.08,1.42,-0.87
2,1000.250,1435.4,0.28,-0.15,0.11,1.38,-0.91
...

其中 mileage 单位为米,gauge 为轨距(mm),level 为水平(mm),align_l/align_r 为左右轨向(mm),profile_l/profile_r 为左右高低(mm)。PSD 分析要求序列沿里程等间距、单调递增、无重复点,因此读取阶段必须完成三件事:按里程排序、剔除重复里程、校验采样间隔一致性。通道对齐的本质是保证同一行内各通道来自同一里程位置,排序与去重必须整行同步移动,不能单独对某一列操作。

里程对齐的处理逻辑

实测文件常见的里程问题有三类:检测中断后重跑导致里程回退、同一位置被多次记录产生重复里程、以及设备丢帧造成局部间隔跳变。处理顺序应为:先按 mileage 稳定排序,再对相同里程保留首条记录(或按业务规则保留幅值有效的一条),最后用相邻里程差 Δd 统计采样间隔分布,判断是否满足等间距假设。下图给出这一处理链与判定分支:

原始检测文件 seq / mileage gauge·level·align·profile 按里程稳定排序 整行同步移动 消除里程回退 重复里程剔除 相同 mileage 保留首条 记录剔除条数 采样间隔统计 Δd 中位数 d₀、最大/最小 Δd 偏离计数与位置 Δd 是否 恒等于 d₀? 输出规整数据表 里程单调递增、等间距 各通道逐行对齐 可直接进入 PSD 计算 是 定位间隔异常点 输出 Δd≠d₀ 的里程区间 区分丢帧(Δd=k·d₀)与跳变 重采样或分段后再分析 否 判定依据:d₀ 取 Δd 中位数;Δd 为 k·d₀(k 为整数)视为丢帧,非整数倍视为里程跳变。

可运行的读取与对齐实现

下面用 Python 完成整个流程,仅依赖标准库与 numpy,便于在任何环境复现。示例数据由脚本内生成,模拟“里程回退 + 重复里程 + 一处丢帧”的典型问题,运行后可直接观察对齐前后的差异。

import csv
import numpy as np

# ---------- 1. 构造示例原始数据(模拟实测问题) ----------
def make_raw(path):
    rows = []
    seq = 0
    # 正常段:1000.000 ~ 1000.750,步长 0.25
    for m in np.arange(1000.000, 1000.751, 0.25):
        seq += 1
        rows.append([seq, round(m, 3), 1435.0, 0.30, -0.10, 0.10, 1.40, -0.90])
    # 里程回退段:重跑导致回到 1000.500
    for m in np.arange(1000.500, 1000.751, 0.25):
        seq += 1
        rows.append([seq, round(m, 3), 1435.2, 0.28, -0.12, 0.12, 1.38, -0.88])
    # 重复里程:1000.750 再记一次
    seq += 1
    rows.append([seq, 1000.750, 1435.1, 0.29, -0.11, 0.11, 1.39, -0.89])
    # 丢帧:跳过 1001.000,直接到 1001.250
    for m in [1001.250, 1001.500, 1001.750]:
        seq += 1
        rows.append([seq, m, 1435.3, 0.27, -0.13, 0.13, 1.37, -0.87])
    with open(path, "w", newline="") as f:
        w = csv.writer(f)
        w.writerow(["seq","mileage","gauge","level","align_l","align_r","profile_l","profile_r"])
        w.writerows(rows)

make_raw("track_raw.csv")

# ---------- 2. 读取 ----------
with open("track_raw.csv") as f:
    reader = csv.DictReader(f)
    data = list(reader)

channels = ["gauge","level","align_l","align_r","profile_l","profile_r"]
for r in data:
    r["mileage"] = float(r["mileage"])
    for c in channels:
        r[c] = float(r[c])

# ---------- 3. 按里程稳定排序(整行同步) ----------
data.sort(key=lambda r: r["mileage"])

# ---------- 4. 重复里程剔除:相同里程保留首条 ----------
dedup, seen, dropped = [], set(), 0
for r in data:
    key = round(r["mileage"], 3)
    if key in seen:
        dropped += 1
        continue
    seen.add(key)
    dedup.append(r)

# ---------- 5. 采样间隔一致性校验 ----------
m = np.array([r["mileage"] for r in dedup])
d = np.diff(m)
d0 = float(np.median(d))
bad = np.where(np.abs(d - d0) > 1e-6)[0]

print(f"原始记录 {len(data)} 条,剔除重复 {dropped} 条,保留 {len(dedup)} 条")
print(f"里程范围 {m[0]:.3f} ~ {m[-1]:.3f} m,中位间隔 d0 = {d0:.3f} m")
print(f"间隔异常位置数:{len(bad)}")
for i in bad:
    k = d[i] / d0
    kind = "丢帧" if abs(k - round(k)) < 1e-6 else "里程跳变"
    print(f"  {m[i]:.3f} → {m[i+1]:.3f}  Δd={d[i]:.3f}  ({kind}, k={k:.2f})")

# ---------- 6. 输出规整表 ----------
with open("track_aligned.csv", "w", newline="") as f:
    w = csv.writer(f)
    w.writerow(["mileage"] + channels)
    for r in dedup:
        w.writerow([f"{r['mileage']:.3f}"] + [r[c] for c in channels])

结果判读与通过条件

对上述示例数据运行脚本,预期输出为:原始记录 15 条,剔除重复 1 条,保留 14 条;里程范围 1000.000 ~ 1001.750 m,中位间隔 d0 = 0.250 m;间隔异常位置 1 处,位于 1000.750 → 1001.250,Δd = 0.500,k = 2.00,判定为丢帧。这一结果说明排序与去重已生效(里程回退被消除、重复点被剔除),但数据仍不满足等间距要求,需在进入 PSD 前对该区间做插值补齐或分段处理。

通过条件可归纳为三条:其一,输出表 mileage 严格单调递增且无重复;其二,Δd 的众数或中位数与设备标称采样间隔一致(本例 0.25 m);其三,所有 Δd ≠ d0 的位置均被显式列出并给出丢帧/跳变分类,不允许静默通过。只有三条同时满足,才可将 track_aligned.csv 作为后续 PSD 分析的输入。

若设备标称采样间隔未知,可用 d0 = median(Δd) 作为参考基准,但需在报告中注明该基准由数据自身估计得到,避免把估计值当作设备参数。
去重时不要只对 mileage 列去重而保留其他列的原始行号,否则会造成通道错位;必须整行保留或整行剔除。里程比较建议先按设备精度四舍五入(本例 0.001 m)再判重,避免浮点误差把同一点判成两点。