Period-over-Period Metric-Change Attribution
A metric moved between two periods. This skill attributes why: it ranks the
dimension members that drove the change (解释度/ep), optionally splits a ratio
into multiplicative factors (structure × quality), drills the top contributor
level by level to the root, and classifies old/new turnover at the leaf — while
guarding against the dual-source denominator trap.
Metric-agnostic and config-driven: the same engine works for ratio metrics
(numerator/denominator) and additive totals (a single measure). You declare
the metric, dimensions, and sources in a small YAML config; you do not edit code.
When to use
- Two comparable periods of the same metric, sliceable by ≥1 categorical dimension.
- You want "which segment/factor/item is responsible", ranked and drilled.
- NOT for: forecasting, single-series anomaly-point detection, or causal inference
beyond decomposition of an observed change.
The 6-step method
- Declare a config — period (prev/curr), metric (ratio or additive), the
dimension hierarchy, sources + column maps. Copy
templates/analysis.config.yaml.
- Validate + dual-source check — long-format sanity; confirm the deduplicated
source is the denominator truth and any leaf "pool" source is direction-only.
- Score the top dimension — ratio: ep / 解释度 (sums to 100%); additive: Δ-share.
- (ratio, optional) factor split — log-decompose the rate into structure × quality.
- Rank, noise-filter, drill Top-1 — skip near-zero-magnitude noise; recurse
into the top contributor to the next level.
- Leaf turnover — at the leaf, classify each member expired / shrinking /
stable / ramping / new; a pool-backed leaf attributes by the driver's Δ-share.
Quickstart
# run the worked example (bundled synthetic reach-rate sample):
python scripts/run_analysis.py --config examples/reach-rate.config.yaml
# your own analysis:
cp templates/analysis.config.yaml my.yaml # fill it in
python scripts/run_analysis.py --config my.yaml --json out.json
# verify the engine (synthetic invariants + frozen golden regression):
python scripts/selftest.py
Or import the stateless engine directly: from scripts import attribution as A
(see signatures in scripts/attribution.py).
Choosing the metric mode
- ratio — the metric is
numerator / denominator (a rate). Rich path: ep/解释度
- optional structure×quality factor split. Use for reach rate, CTR, conversion,
retention, win rate, ARPU-as-rate.
- additive — the metric is a single total (revenue, events, DAU). Lighter path:
each member's share of the absolute Δ.
Reading the output
run_attribution returns {overall, tree, dual_source, drilled_path, warnings}.
overall — the headline (大盘) metric from total_selector, separate from the
decomposed partition.
- each tree node —
members ranked by |解释度|/|contribution| (with is_noise),
the picked top1, and a child (the drill). 解释度 sums to ±100% per level.
- leaf node —
turnover labels per member.
- a positive 解释度 means the member pushed the aggregate in the direction it
moved (a drag if the metric fell); negative means it pushed the other way
(reverse support).
metric.direction drives the drag/support wording.
Critical correctness rules
- Dedup source is the only valid denominator. A finer "pool" source double-counts
across overlapping segments (national/cross-segment drift) — never sum it as a
denominator. A pool-backed leaf is attributed by the driver's Δ-share, not pool-ratio ep.
- Log factor decomposition fails at extreme/degenerate WoW (rate≈unchanged or a
factor ≤ 0) → it returns
status='unstable'; fall back to a magnitude narrative.
- Drop near-zero-magnitude rows before trusting 解释度 — a tiny-volume row can show
a huge 解释度 and isn't a real driver. Tune via
noise.*.
References
- methodology-math.md — ep, log structure×quality, additive Δ derivations + failure modes.
- data-format-guide.md — long-format schema, the two-grain dual-source rule, validation.
- config-reference.md — every config field.
- worked-example-reach-rate.md — the 触达率 case mapped end-to-end on the bundled synthetic sample.
- pitfalls.md — drift double-count, log instability, noise rows, pp-vs-relative, leaf pool-ep.
Reference implementation (optional, out of scope here)
In a typical deployment this attribution feeds a separate rendering/orchestration
layer that turns the ep/解释度 output into a spreadsheet or report (period-string
replacement, formula tabs, conditional-format heat-maps, a narrative summary). That
rendering + orchestration layer is intentionally not part of this skill — this
skill is the portable, data-source-agnostic methodology + engine. See
worked-example-reach-rate.md for how the two relate.
1---2name: metric-change-attribution3description: Use when a metric moved between two periods (week-over-week, month-over-month, pre/post, A-vs-B) and you need to attribute WHY — which dimension members, segments, or factors drove it, ranked and drilled to root cause. Covers ratio/rate metrics (reach rate, CTR, conversion, retention, win rate, ARPU) and additive totals (revenue, events, DAU). Triggers: "why did X change", "what drove the drop/spike", "归因", "解释度", "拆解 WoW/MoM 变化", "which segment caused the change", "attribute the change".4---56# Period-over-Period Metric-Change Attribution78A metric moved between two periods. This skill attributes *why*: it ranks the9dimension members that drove the change (解释度/ep), optionally splits a ratio10into multiplicative factors (structure × quality), drills the top contributor11level by level to the root, and classifies old/new turnover at the leaf — while12guarding against the dual-source denominator trap.1314Metric-agnostic and config-driven: the same engine works for **ratio** metrics15(numerator/denominator) and **additive** totals (a single measure). You declare16the metric, dimensions, and sources in a small YAML config; you do **not** edit code.1718## When to use19- Two comparable periods of the same metric, sliceable by ≥1 categorical dimension.20- You want "which segment/factor/item is responsible", ranked and drilled.21- NOT for: forecasting, single-series anomaly-point detection, or causal inference22 beyond decomposition of an observed change.2324## The 6-step method251. **Declare a config** — period (prev/curr), metric (ratio or additive), the26 dimension hierarchy, sources + column maps. Copy `templates/analysis.config.yaml`.272. **Validate + dual-source check** — long-format sanity; confirm the deduplicated28 source is the denominator truth and any leaf "pool" source is direction-only.293. **Score the top dimension** — ratio: ep / 解释度 (sums to 100%); additive: Δ-share.304. **(ratio, optional) factor split** — log-decompose the rate into structure × quality.315. **Rank, noise-filter, drill Top-1** — skip near-zero-magnitude noise; recurse32 into the top contributor to the next level.336. **Leaf turnover** — at the leaf, classify each member expired / shrinking /34 stable / ramping / new; a pool-backed leaf attributes by the driver's Δ-share.3536## Quickstart37```bash38# run the worked example (bundled synthetic reach-rate sample):39python scripts/run_analysis.py --config examples/reach-rate.config.yaml40# your own analysis:41cp templates/analysis.config.yaml my.yaml # fill it in42python scripts/run_analysis.py --config my.yaml --json out.json43# verify the engine (synthetic invariants + frozen golden regression):44python scripts/selftest.py45```46Or import the stateless engine directly: `from scripts import attribution as A`47(see signatures in `scripts/attribution.py`).4849## Choosing the metric mode50- **ratio** — the metric is `numerator / denominator` (a rate). Rich path: ep/解释度51 + optional structure×quality factor split. Use for reach rate, CTR, conversion,52 retention, win rate, ARPU-as-rate.53- **additive** — the metric is a single total (revenue, events, DAU). Lighter path:54 each member's share of the absolute Δ.5556## Reading the output57`run_attribution` returns `{overall, tree, dual_source, drilled_path, warnings}`.58- `overall` — the headline (大盘) metric from `total_selector`, separate from the59 decomposed partition.60- each tree node — `members` ranked by |解释度|/|contribution| (with `is_noise`),61 the picked `top1`, and a `child` (the drill). 解释度 sums to ±100% per level.62- leaf node — `turnover` labels per member.63- a **positive** 解释度 means the member pushed the aggregate in the direction it64 moved (a drag if the metric fell); **negative** means it pushed the other way65 (reverse support). `metric.direction` drives the drag/support wording.6667## Critical correctness rules68- **Dedup source is the only valid denominator.** A finer "pool" source double-counts69 across overlapping segments (national/cross-segment drift) — never sum it as a70 denominator. A pool-backed leaf is attributed by the driver's Δ-share, not pool-ratio ep.71- **Log factor decomposition fails at extreme/degenerate WoW** (rate≈unchanged or a72 factor ≤ 0) → it returns `status='unstable'`; fall back to a magnitude narrative.73- **Drop near-zero-magnitude rows before trusting 解释度** — a tiny-volume row can show74 a huge 解释度 and isn't a real driver. Tune via `noise.*`.7576## References77- [methodology-math.md](references/methodology-math.md) — ep, log structure×quality, additive Δ derivations + failure modes.78- [data-format-guide.md](references/data-format-guide.md) — long-format schema, the two-grain dual-source rule, validation.79- [config-reference.md](references/config-reference.md) — every config field.80- [worked-example-reach-rate.md](references/worked-example-reach-rate.md) — the 触达率 case mapped end-to-end on the bundled synthetic sample.81- [pitfalls.md](references/pitfalls.md) — drift double-count, log instability, noise rows, pp-vs-relative, leaf pool-ep.8283## Reference implementation (optional, out of scope here)84In a typical deployment this attribution feeds a separate rendering/orchestration85layer that turns the ep/解释度 output into a spreadsheet or report (period-string86replacement, formula tabs, conditional-format heat-maps, a narrative summary). That87rendering + orchestration layer is intentionally **not** part of this skill — this88skill is the portable, data-source-agnostic methodology + engine. See89`worked-example-reach-rate.md` for how the two relate.