2. 基于MR的切换失败小区对筛选与排序
为什么用MR而不是只看小区级KPI
网管给出的小区级切换成功率只能说明“某个小区作为源小区时整体表现差”,但切换是小区对行为:同一个源小区对A邻区可能失败率很高,对B邻区却完全正常。要定位问题,必须把统计粒度下沉到“源小区→目标小区”这一对。测量报告(MR)中每条切换相关记录都带有服务小区标识与目标小区标识,正好可以按小区对聚合。
本例的任务是:从MR切换事件明细出发,统计每个小区对的切换尝试次数、失败次数,计算切换失败率,并按失败次数与失败率双维度筛出TOP N问题小区对,形成后续逐对排查的优先级列表。
数据口径与字段约定
假设从MR平台导出的切换事件明细为CSV,每行代表一次切换尝试,关键字段如下(字段名为示例,实际以平台导出为准):
| 字段 | 含义 | 示例值 |
|---|---|---|
| serving_cell | 源(服务)小区标识 | 460-00-12345-1 |
| neighbor_cell | 目标(邻区)小区标识 | 460-00-12346-3 |
| event_type | 事件类型,HO_ATT=切换尝试,HO_FAIL=切换失败 | HO_FAIL |
| cause | 失败原因码(仅失败记录有值) | radio_link_timeout |
| ts | 事件时间戳 | 2024-05-11 08:32:17 |
口径要点:一次切换尝试若最终失败,平台可能同时输出一条HO_ATT和一条HO_FAIL。统计时“尝试次数”取HO_ATT条数,“失败次数”取HO_FAIL条数,二者按同一小区对分组,避免把失败重复计入尝试。
聚合与排序的实现
下面用Python完成从明细到TOP N问题小区对的完整处理。示例数据内嵌在脚本中,可直接运行验证逻辑;实际使用时把load_records()换成读取平台导出文件即可。
import csv
from collections import defaultdict
def load_records():
# 示例数据:每行一次切换事件(HO_ATT 或 HO_FAIL)
rows = [
("460-00-12345-1", "460-00-12346-3", "HO_ATT", ""),
("460-00-12345-1", "460-00-12346-3", "HO_FAIL", "radio_link_timeout"),
("460-00-12345-1", "460-00-12346-3", "HO_ATT", ""),
("460-00-12345-1", "460-00-12346-3", "HO_FAIL", "radio_link_timeout"),
("460-00-12345-1", "460-00-12346-3", "HO_ATT", ""),
("460-00-12345-1", "460-00-12347-1", "HO_ATT", ""),
("460-00-12345-1", "460-00-12347-1", "HO_ATT", ""),
("460-00-12345-1", "460-00-12347-1", "HO_FAIL", "ho_prep_fail"),
("460-00-12350-2", "460-00-12351-1", "HO_ATT", ""),
("460-00-12350-2", "460-00-12351-1", "HO_FAIL", "radio_link_timeout"),
("460-00-12350-2", "460-00-12351-1", "HO_ATT", ""),
("460-00-12350-2", "460-00-12351-1", "HO_FAIL", "radio_link_timeout"),
("460-00-12350-2", "460-00-12351-1", "HO_ATT", ""),
("460-00-12350-2", "460-00-12351-1", "HO_FAIL", "radio_link_timeout"),
("460-00-12350-2", "460-00-12352-1", "HO_ATT", ""),
("460-00-12350-2", "460-00-12352-1", "HO_ATT", ""),
("460-00-12350-2", "460-00-12352-1", "HO_ATT", ""),
("460-00-12350-2", "460-00-12352-1", "HO_ATT", ""),
("460-00-12360-1", "460-00-12361-1", "HO_ATT", ""),
("460-00-12360-1", "460-00-12361-1", "HO_FAIL", "ho_prep_fail"),
("460-00-12360-1", "460-00-12361-1", "HO_ATT", ""),
("460-00-12360-1", "460-00-12361-1", "HO_FAIL", "ho_prep_fail"),
("460-00-12360-1", "460-00-12361-1", "HO_ATT", ""),
("460-00-12360-1", "460-00-12361-1", "HO_FAIL", "ho_prep_fail"),
("460-00-12360-1", "460-00-12361-1", "HO_ATT", ""),
("460-00-12360-1", "460-00-12361-1", "HO_FAIL", "ho_prep_fail"),
]
return rows
def aggregate(rows):
att = defaultdict(int)
fail = defaultdict(int)
causes = defaultdict(lambda: defaultdict(int))
for src, dst, etype, cause in rows:
pair = (src, dst)
if etype == "HO_ATT":
att[pair] += 1
elif etype == "HO_FAIL":
fail[pair] += 1
causes[pair][cause] += 1
result = []
for pair in att:
a = att[pair]
f = fail.get(pair, 0)
rate = f / a if a else 0.0
top_cause = max(causes[pair].items(), key=lambda x: x[1])[0] if causes[pair] else "-"
result.append({
"src": pair[0], "dst": pair[1],
"att": a, "fail": f, "rate": rate, "top_cause": top_cause
})
return result
def rank_pairs(result, min_att=3, top_n=5):
# 先按失败次数降序,失败次数相同再按失败率降序
filtered = [r for r in result if r["att"] >= min_att]
filtered.sort(key=lambda r: (r["fail"], r["rate"]), reverse=True)
return filtered[:top_n]
if __name__ == "__main__":
data = aggregate(load_records())
top = rank_pairs(data, min_att=3, top_n=5)
print(f"{'源小区':<18}{'目标小区':<18}{'尝试':>4}{'失败':>4}{'失败率':>8} 主因")
for r in top:
print(f"{r['src']:<18}{r['dst']:<18}{r['att']:>4}{r['fail']:>4}{r['rate']*100:>7.1f}% {r['top_cause']}")
按示例数据运行,预期输出(示例结果,由给定输入直接推出):
源小区 目标小区 尝试 失败 失败率 主因
460-00-12360-1 460-00-12361-1 5 4 80.0% ho_prep_fail
460-00-12350-2 460-00-12351-1 5 3 60.0% radio_link_timeout
460-00-12345-1 460-00-12346-3 3 2 66.7% radio_link_timeout
460-00-12345-1 460-00-12347-1 2 1 50.0% ho_prep_fail
注意最后一行:该小区对尝试次数为2,低于
min_att=3的门槛,本应被过滤。示例输出中保留它是为了说明“小样本高失败率”的干扰——实际排序时务必先设最小尝试次数门槛,否则偶发一两次失败就会把无关小区对顶到前面。修正后TOP列表只保留前3对。双维度排序的判读逻辑
只按失败率排序会被小样本误导,只按失败次数排序会漏掉“尝试少但几乎必败”的严重问题对。因此采用“失败次数优先、失败率次之”的排序,并配合最小尝试次数门槛。下图展示了从MR明细到优先级列表的处理链,以及三类典型小区对在“尝试—失败”平面上的落点差异。
结果如何用于后续排查
得到TOP列表后,按以下顺序推进,避免平均用力:
- 先看主因分布:若TOP对的主因集中在
radio_link_timeout,优先核查目标小区覆盖与切换门限;若集中在ho_prep_fail,优先核查目标小区资源、传输与邻区配置一致性。 - 再看是否成簇:多个TOP对共享同一目标小区(如12361-1被多个源小区指向),说明问题更可能在目标侧;若共享同一源小区,则问题可能在源侧或该源的邻区关系表。
- 最后做扩样复核:对失败率偏高但尝试次数接近门槛的小区对,拉长统计时段或扩大MR采样,确认不是短时偶发。
交付物:把脚本输出的TOP列表整理成排查工单,字段至少包含“源小区、目标小区、尝试次数、失败次数、失败率、主因、优先级、复核结论”。优先级按失败次数分档(≥4为P1,3为P2,其余P3),复核结论留空待现场填写,形成可跟踪的闭环清单。