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 统计采样间隔分布,判断是否满足等间距假设。下图给出这一处理链与判定分支:
可运行的读取与对齐实现
下面用 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)再判重,避免浮点误差把同一点判成两点。