2. 铁路沿线邻区关系数据采集与清洗

数据来源与字段约定

铁路沿线邻区关系数据通常从网管系统按“小区—邻区”粒度导出,一条记录代表一个服务小区与其一个邻区的配置关系。为保证后续清洗可复现,先固定导出字段与主键约定:

  • 主键:serving_cell_id + neighbor_cell_id,即“服务小区—邻区”对,同一对只应出现一次。
  • 关键字段:服务小区标识、邻区标识、邻区所属线路/区段、邻区经纬度、切换门限、迟滞、小区偏置、邻区优先级、配置状态。
  • 方向约定:记录 A→B 表示“A 的邻区表中包含 B”,与 B→A 是两条独立记录,单向邻区即只有其中一条。

导出后先做一次字段级体检:统计总记录数、空值分布、主键重复数,作为清洗前的基线。

异常类型与判定规则

本例需要识别的四类异常,判定条件必须写成可执行的规则,而不是靠人工目测:

异常类型判定条件处理动作
缺失值邻区标识、经纬度、门限/迟滞等关键字段为空关键字段缺失则整条剔除;非关键字段缺失标记待补
重复邻区同一 serving_cell_id + neighbor_cell_id 出现多次保留配置状态有效、更新时间最新的一条
单向邻区存在 A→B 但不存在 B→A标记为单向,输出待补录清单,不直接删除
越界邻区邻区经纬度落在本线路区段缓冲区之外,或距离超过设定阈值标记越界,人工确认后剔除或转交核查
越界判定建议用“线路区段缓冲区 + 距离阈值”双重条件:先按区段外扩缓冲判断是否属于本线路,再用服务小区与邻区的球面距离做二次过滤,避免把跨线邻区误判为越界。

清洗实现(Python 示例)

下面给出可直接运行的清洗脚本,输入为网管导出的 CSV,输出规范化邻区关系表与异常清单。示例数据为教学构造,用于演示规则如何落地。

import pandas as pd
import numpy as np

# 1. 读取导出数据
df = pd.read_csv("neighbor_raw.csv", dtype=str)
df["threshold"] = pd.to_numeric(df["threshold"], errors="coerce")
df["hysteresis"] = pd.to_numeric(df["hysteresis"], errors="coerce")
df["lon"] = pd.to_numeric(df["lon"], errors="coerce")
df["lat"] = pd.to_numeric(df["lat"], errors="coerce")

key = ["serving_cell_id", "neighbor_cell_id"]
critical = ["neighbor_cell_id", "lon", "lat", "threshold", "hysteresis"]

# 2. 缺失值:关键字段缺失整条剔除
mask_missing = df[critical].isna().any(axis=1)
missing_rows = df[mask_missing].copy()
df = df[~mask_missing].copy()

# 3. 重复邻区:按更新时间保留最新一条
df["update_time"] = pd.to_datetime(df["update_time"], errors="coerce")
df = df.sort_values("update_time").drop_duplicates(subset=key, keep="last")

# 4. 单向邻区:构造反向键做存在性匹配
pairs = set(map(tuple, df[key].values))
def is_one_way(row):
    return (row["neighbor_cell_id"], row["serving_cell_id"]) not in pairs
df["one_way"] = df.apply(is_one_way, axis=1)
one_way_rows = df[df["one_way"]].copy()

# 5. 越界邻区:区段缓冲 + 距离阈值(示例阈值 15 km)
def haversine(lon1, lat1, lon2, lat2):
    r = 6371.0
    p1, p2 = np.radians(lat1), np.radians(lat2)
    dphi = np.radians(lat2 - lat1)
    dlmb = np.radians(lon2 - lon1)
    a = np.sin(dphi/2)**2 + np.cos(p1)*np.cos(p2)*np.sin(dlmb/2)**2
    return 2 * r * np.arcsin(np.sqrt(a))

df["dist_km"] = haversine(df["serving_lon"], df["serving_lat"], df["lon"], df["lat"])
df["out_of_range"] = (df["dist_km"] > 15) | (df["section_ok"] == "0")
out_rows = df[df["out_of_range"]].copy()

# 6. 输出规范化表与异常清单
clean = df[~df["out_of_range"]].drop(columns=["one_way", "out_of_range"])
clean.to_csv("neighbor_clean.csv", index=False)
missing_rows.to_csv("abnormal_missing.csv", index=False)
one_way_rows.to_csv("abnormal_one_way.csv", index=False)
out_rows.to_csv("abnormal_out_of_range.csv", index=False)

print("原始记录:", len(missing_rows) + len(df))
print("清洗后:", len(clean), "缺失:", len(missing_rows),
      "单向:", len(one_way_rows), "越界:", len(out_rows))

脚本中 section_ok 表示邻区是否落在本线路区段缓冲区内(由 GIS 预计算后随导出数据带入),serving_lon/serving_lat 为服务小区坐标。距离阈值 15 km 是示例值,实际应按线路站间距与覆盖半径调整。

清洗流程与结果判读

邻区关系数据采集与清洗链路 网管系统导出 neighbor_raw.csv 服务小区—邻区对 字段级体检 总记录数 / 空值分布 主键重复数基线 异常识别(规则判定) 缺失值 关键字段空 重复邻区 主键重复 单向邻区 缺反向记录 越界邻区 超缓冲区/距离 缺失值处理 关键字段缺失剔除 非关键字段标记待补 重复邻区处理 按更新时间排序 保留最新有效一条 单向邻区处理 标记单向 输出待补录清单 越界邻区处理 缓冲区+距离双判 人工确认后剔除 规范化邻区关系表 neighbor_clean.csv 主键唯一 · 关键字段完整 · 无越界 供后续覆盖交叠与门限优化使用 异常清单(分类留档) abnormal_missing.csv abnormal_one_way.csv abnormal_out_of_range.csv

清洗完成后必须做结果判读,而不是只看脚本跑通:

  1. 数量守恒:原始记录数 = 清洗后记录数 + 缺失剔除数 + 越界剔除数,重复记录在去重时已合并,需单独核对去重前后差值。
  2. 单向比例:单向邻区占比过高(例如超过 10%)通常说明导出范围不完整或存在跨系统邻区,应先补全数据再进入优化。
  3. 越界分布:越界记录若集中在某几个区段,优先核查该区段的线路缓冲区是否设置过窄,而非直接删除。
  4. 字段完整性:清洗后门限、迟滞、偏置等参数应无空值,否则后续参数计算会失真。

规范化输出与交付物

最终交付一张规范化邻区关系表,字段与约束如下,可直接作为后续优化环节的输入:

字段含义约束
serving_cell_id服务小区标识非空
neighbor_cell_id邻区标识非空,与 serving 组合唯一
section_id所属线路区段非空
lon / lat邻区经纬度非空,落在线路缓冲区内
threshold / hysteresis切换门限 / 迟滞非空,数值型
cell_offset小区偏置数值型,允许为空待补
priority邻区优先级整数
one_way_flag单向标记0/1,1 表示待补录
不要把单向邻区在清洗阶段直接删除:单向关系可能反映真实的覆盖不对称,也可能是导出遗漏。正确做法是保留并打标,交由后续覆盖交叠分析判断是否需要补录或调整。