3. 竞价异常波动复盘:波动幅度与显著性初筛
任务对象与数据准备
本节处理某搜索广告账户连续 7 天(D1–D7)的分小时竞价数据,字段为:日期、小时、消耗(元)、转化数。目标是先做波动幅度初筛,再用 Z-score 判断显著性,输出待复盘时段清单。为避免与口径定义、数据清洗重复,这里假定数据已通过完整性校验,直接进入计算环节。
关键约定:
- 波动幅度用相对变化率:
Δ = (X_t − X_base) / X_base × 100%,正为突增、负为骤降。 - 基线取同小时的历史均值(D1–D6 同一小时均值),避免把日内正常节律误判为异常。
- Z-score 用稳健版本:
Z = (X_t − median) / (1.4826 × MAD),MAD 为中位数绝对偏差,对少量极端值不敏感。 - 显著性阈值:
|Δ| ≥ 30%且|Z| ≥ 3同时满足才进入待复盘清单,减少单指标误报。
计算实现
下面代码用纯 Python 完成 7 天 × 24 小时数据的构造、基线计算、幅度与 Z-score 计算、双条件筛选,可直接运行验证。
import statistics as st
# 示例数据:D1-D7 每天 24 小时消耗(元),此处仅展示 0-5 时与 20-23 时片段
# 实际使用时替换为报表导出的完整 7×24 数据
data = {
"D1": [12, 10, 9, 8, 11, 15, 30, 55, 80, 95, 100, 98, 90, 88, 92, 96, 99, 105, 110, 120, 130, 90, 60, 30],
"D2": [11, 9, 8, 9, 10, 14, 28, 52, 78, 92, 98, 96, 88, 86, 90, 94, 97, 102, 108, 118, 128, 88, 58, 28],
"D3": [13, 11, 10, 9, 12, 16, 32, 58, 82, 97, 102, 100, 92, 90, 94, 98, 101, 107, 112, 122, 132, 92, 62, 32],
"D4": [12, 10, 9, 8, 11, 15, 30, 55, 80, 95, 100, 98, 90, 88, 92, 96, 99, 105, 110, 120, 130, 90, 60, 30],
"D5": [11, 9, 8, 9, 10, 14, 28, 52, 78, 92, 98, 96, 88, 86, 90, 94, 97, 102, 108, 118, 128, 88, 58, 28],
"D6": [12, 10, 9, 8, 11, 15, 30, 55, 80, 95, 100, 98, 90, 88, 92, 96, 99, 105, 110, 120, 130, 90, 60, 30],
# D7 为待检测日:20 时突增、22 时骤降
"D7": [12, 10, 9, 8, 11, 15, 30, 55, 80, 95, 100, 98, 90, 88, 92, 96, 99, 105, 110, 120, 210, 90, 20, 30],
}
days = list(data.keys())
target = "D7"
base_days = days[:-1]
# 1) 同小时基线:D1-D6 同一小时的均值与稳健统计量
baseline, medians, mads = {}, {}, {}
for h in range(24):
vals = [data[d][h] for d in base_days]
baseline[h] = st.mean(vals)
medians[h] = st.median(vals)
mad = st.median([abs(v - medians[h]) for v in vals])
mads[h] = mad if mad > 0 else 1e-6 # 防止除零
# 2) 计算 D7 各小时的 Δ 与 Z
rows = []
for h in range(24):
x = data[target][h]
base = baseline[h]
delta = (x - base) / base * 100
z = (x - medians[h]) / (1.4826 * mads[h])
flag = abs(delta) >= 30 and abs(z) >= 3
rows.append((h, x, round(base, 1), round(delta, 1), round(z, 2), flag))
# 3) 输出待复盘清单
print("小时 消耗 基线 Δ% Z 入清单")
for h, x, base, delta, z, flag in rows:
if flag:
print(f"{h:>2} {x:>4} {base:>5} {delta:>6} {z:>6} {'是' if flag else '否'}")
示例数据下,D7 的 20 时消耗 210 元、基线约 130 元,Δ≈+61.5%,Z 显著为正;22 时消耗 20 元、基线约 60 元,Δ≈−66.7%,Z 显著为负。其余小时 Δ 与 Z 均在阈值内,不进入清单。这组数值属于教学示例,实际结果以真实报表计算为准。
筛选逻辑与判读
双条件筛选的意义在于:只看幅度,低基线小时(如凌晨 3 时消耗 8 元变 12 元,Δ=+50%)会被误报;只看 Z-score,高波动但幅度小的时段又可能被漏掉。两者同时满足,才说明该时段既偏离基线幅度大、又在统计上显著。
待复盘清单与后续动作
按上述逻辑,D7 的待复盘清单为两条:
| 日期 | 小时 | 消耗 | 同小时基线 | Δ | Z-score | 方向 | 建议下一步 |
|---|---|---|---|---|---|---|---|
| D7 | 20 | 210 | 130.0 | +61.5% | 显著正 | 突增 | 按小时拆分计划,查是否有加预算/提价/竞争加剧 |
| D7 | 22 | 20 | 60.0 | −66.7% | 显著负 | 骤降 | 查预算耗尽、审核拒登、出价被压或定向收窄 |
清单只回答“哪些时段值得复盘”,不回答“为什么”。下一步应把这两个小时作为切片入口,进入计划层级的下钻排查。若某小时 Δ 达标但 Z 未达标,说明基线本身波动大,可先复核该小时历史数据质量,再决定是否纳入。
稳健 Z-score 中 MAD 为 0 时(历史同小时消耗完全一致),代码用 1e-6 兜底,此时任何微小变化都会得到极大 Z 值。遇到这种情况应改用标准差或直接以幅度阈值判断,避免机械套用。