Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Your First X/mR Chart

This is the shortest path from a column of measurements to a process behavior chart: wrap the data, formulate, execute, read the limits. Ten minutes, one chart, one real signal.

An X/mR chart (individuals and moving range) is the workhorse of process behavior analysis: it charts each measurement individually (X) alongside the point-to-point variation (mR). It is the right first chart whenever you have one measurement per time period.

If you want the full tour of the library afterwards, the Quickstart covers the same workflow in more depth.

import numpy as np
import pandas as pd
from processbehavior import ProcessBehavior

# 30 days of oven temperature readings, one per day.
# Unknown to us (for now): the thermostat drifted on day 25.
rng = np.random.default_rng(7)
temps = rng.normal(72.0, 1.5, 30)
temps[24:] += 5.0

df = pd.DataFrame({'day': range(1, 31), 'temperature': temps.round(2)})
df.head()
Loading...

Formulate

formulate() is where you tell the library what your data is: which column is the response, and which orders it in time. It classifies the data’s structure before anything is computed — the observed design state (ODS) is what the raw data supports, and the analytical design state (ADS) is what the analysis will run at. One measurement per day with no factors is design state 2 on Bishop’s 1–6 reference scale: time-ordered, no replication.

pb = ProcessBehavior(df)
study = pb.formulate(response='temperature', time='day')

print(f"Observed:    ODS {study.observed_design_state.sds}")
print(f"Analytical:  ADS {study.analytical_design_state.sds}")
print(f"Why:         {study.ads_description}")
print(f"Recommended: {study.recommended_chart}")
Observed:    ODS 2
Analytical:  ADS 2
Why:         No replication (all cells n=1)
Recommended: X

Execute

execute() computes the recommended chart — here X. Pass companion=True to also get its partner chart, mR: the X chart watches location, the mR chart watches variation, and reading them together is what makes an “X/mR chart”.

result = study.execute(chart='X', companion=True)

print(f"Charts: {result.all_charts}")
print(f"X:  {result.get_statistics('X')}")
print(f"mR: {result.get_statistics('mR')}")
Charts: ['X', 'mR']
X:  {'N': 1, 'center': np.float64(72.379), 'lpl': np.float64(68.366), 'upl': np.float64(76.392)}
mR: {'N': 2, 'center': np.float64(1.509), 'lpl': np.float64(0.0), 'upl': np.float64(4.93)}

Read the limits

Every chart’s statistics come in the same four-key shape:

  • center — the center line (the process’s typical level)

  • lpl / upl — the lower and upper process limits, set at ±3 sigma from routine variation

  • N — the subgroup size the limits assume (1 for an X chart)

The limits are the voice of the process: they say how far routine variation reaches. A point beyond them — or a run hugging one side — is a signal that something changed.

Signals are formalized as the WECO rules, built on dividing the space between the limits into three sigma-wide zones on each side of the center line (zone C nearest the center, then B, then A). Rule 1 is a point beyond the limits; the run- and zone-based rules (2–8) catch drifts and shifts that never cross a limit. See the WECO rules reference for all eight.

signals = result.detect_signals(chart='X')

print(f"Signals found: {signals.count}")
print(f"Flagged days:  {sorted(signals.flagged_observations)}")
Signals found: 9
Flagged days:  [np.int64(20), np.int64(22), np.int64(24), np.int64(25), np.int64(28), np.int64(29)]

The thermostat drift on day 25 is caught — not just the shifted points themselves, but the run rules firing on the days leading into it. Now look at it:

result.plot()
Loading...

The X chart shows the level jump on day 25 with the shifted points flagged beyond the upper limit; the mR chart spikes exactly once — at the jump itself — then settles, telling you the variation didn’t change, only the level. That distinction (level shift vs. variation change) is exactly why the two charts are read as a pair.

What’s next