XmR Chart Module — Wheeler/Chambers + xmrit.com style
Reusable Python module for producing XmR (X and Moving Range) charts with Wheeler-grade rule detection and locked-limit segmentation.
Recommended workflow: see mrr-bridge-and-annotation-layer for the full discipline — chart input metrics not output metrics, and capture an analyst annotation against every signal so the chart accumulates into a learning system rather than a museum exhibit. The chart math below is half the practice; the annotation layer is the other half.
Files
xmr.py— the module. Importable asfrom xmr import auto_segment, detect_all_signals, plot_xmr.example-run.py— example: renders Mammoth income (2-segment) + Food & Dining (1-segment, predictable) charts.mrr-bridge-and-annotation-layer.md— the framework doc: input-metric decomposition + annotation schema. Read this before deploying XmR against a SaaS metric.
What it does
- Detection rules: implements Wheeler’s canonical 3 rules:
- Rule 1: point outside UNPL/LNPL on X chart, or above URL on mR chart
- Rule 2: 8 successive points on the same side of the central line
- Rule 3: 3 of 4 successive points in the outer third (between 2σ and limit, same side)
- Locked limits: limits computed from a baseline window and HELD FIXED. Not recomputed as new data arrives. (The xmrit.com “lock the limits” principle.)
- Segmentation: when Rule 2 fires (sustained shift), opens a new segment with new locked limits. Caller can also pass explicit
process_changes=[idx, ...]for known events. - Visual encoding:
- ±1σ and ±2σ shaded zones (per segment)
- CL (green), UNPL/LNPL (red dashed) per segment
- Vertical dotted line at limit-reset points
- Points colored by which rule violated (red R1, purple R2, orange R3); blue otherwise
- Sparse annotation: R1 always labelled; R2/R3 labelled at first occurrence of a run only
- mR chart stacked underneath
Sigma estimate
Uses Wheeler’s successive-differences method:
σ = mean(mR) / 1.128 # NOT global stdev — this is the load-bearing distinction
UNPL = mean(X) + 2.66 × mean(mR)
LNPL = mean(X) - 2.66 × mean(mR) (clamped to 0 for $ data)
URL = 3.268 × mean(mR)
For median MR (robust to baseline outliers):
σ = median(mR) / 0.9539
X-factor = 3.14, mR-factor = 3.865
Usage
Standard (additive) XmR
For series whose noise is roughly additive — dollars, counts, durations, anything where a $200 swing means the same thing whether you’re at $1k or $20k.
from datetime import datetime
from xmr import auto_segment, detect_all_signals, plot_xmr
X = [16000, 16000, 16000, ...] # observations
dates = [datetime(2024, 1, 31), datetime(2024, 3, 1), ...]
# Auto-segmentation (will split on Rule 2 trigger)
segments = auto_segment(X, baseline_n=12)
# Or with explicit known process changes
segments = auto_segment(X, baseline_n=12, explicit_breaks=[19])
signals = detect_all_signals(X, segments)
plot_xmr(
X, dates, segments, signals,
title="My metric — XmR Chart",
subtitle="Locked limits + segmentation",
footer="Interpretation note here",
output_path="/tmp/my-chart.png",
y_label="Metric",
money=True,
)
Log-transform XmR (added 2026-04-22)
For multiplicative or heteroskedastic series — revenue, MRR, headcount, traffic, anything where the natural step is a percentage rather than an absolute amount and variance grows with level. Standard XmR misapplied to such a series produces inflated limits and misses regime changes.
from xmr import log_xmr, plot_xmr_log
# Log-transform pipeline: log_X is for plotting context; segments + signals
# are computed in log space (CL/UNPL/LNPL are log values)
log_X, segments, signals = log_xmr(X, baseline_n=12, explicit_breaks=None)
plot_xmr_log(
X, log_X, dates, segments, signals,
title="MRR — XmR (log-transform)",
subtitle="Multiplicative bands · ratio-based limits",
footer="exp(CL) = geometric mean. Step bounds = upper/lower per-step ratio.",
output_path="/tmp/my-mrr-chart.png",
y_label="MRR ($, log scale)",
)
What changes vs the additive path:
- Y axis is plotted log-scale (so multiplicative bands look like equal stripes)
- CL is the geometric mean of the segment, not the arithmetic mean
- UNPL/LNPL bound a ratio range per step (e.g. ÷1.16 to ×1.16 = ±16% per period)
- mR chart shows |Δ log X| = absolute log step-ratios; URL bounds the largest expected step in log units
- All chart annotations show both the log value and the back-transformed natural-units value for interpretability
When NOT to use log_xmr:
- Any X[i] ≤ 0 — log is undefined; raises ValueError
- Series with a clear long-term trend that’s not multiplicative noise (linear-regression detrending is the next upgrade — see Notion task on the residuals transform)
- Counts/proportions — use raw XmR; Wheeler USPC Ch.13 is explicit that XmR works on counts in most cases without log transform
Design notes
Why explicit breaks beat auto-detection in many cases: If the baseline contains an outlier, the locked CL is biased and Rule 2 will fire spuriously on the “normal” range that follows. The xmrit.com guidance explicitly recommends user-driven baseline selection for known process-change events. Auto-detection is best when you genuinely don’t know if/when a shift occurred.
What we deliberately did NOT add (per Wheeler’s USPC 3rd ed Ch. 5.2):
- Run of 6+ trending up/down — increases false alarm rate without proportional gain
- Run of 7 same side — superseded by Rule 2 (8 same side)
- Adding more rules degrades the chart’s signal-to-noise
Annotation policy: Rule 1 always annotated (severe, point-specific). Rule 2 and Rule 3 annotated only on the first point of each consecutive run, since they fire on every subsequent point in the run by construction.
Dependencies
- Python 3.10+
- matplotlib
- (no pandas/numpy required for the module itself; the example uses stdlib only)
Install:
~/.claude/scripts/finance-venv/bin/pip install matplotlib
Sources
- Wheeler & Chambers, Understanding Statistical Process Control (3rd ed), Ch. 5 — detection rules
- Wheeler & Chambers, USPC 3rd ed Ch. 9 — XmR specifics
- xmrit.com glossary — UNPL/LNPL/URL definitions, locked-limits principle
- xmrit.com articles — “Why Locking Limits is Essential”, “How to Plot an XmR Chart”
- ../../06-reference/concepts/operational-definitions — prerequisite layer for any metric chart