Module 4 · Section 6 of 10
Lesson 4.5 - Control Charts (SPC)
Target: ~10 min read - 20 min hands-on
Overview
Statistical Process Control (SPC) charts monitor a process over time against statistically-derived control limits. An X-bar chart tracks the subgroup average; an R chart tracks the subgroup range (within-batch variability). Points outside the Upper/Lower Control Limits (UCL/LCL) - or unusual patterns even within limits - signal the process may be going out of control.
Why This Matters (Engineering Context)
Monitoring a machined dimension, a fill weight, a coating thickness, or a chemical assay across production batches is a textbook SPC application - catching drift before it produces an out-of-spec lot saves rework and material cost.
Code-Along
np.random.seed(23)
# Simulate 25 subgroups of 4 measurements each; a small upward drift starts at #19
n_subgroups, subgroup_size, target = 25, 4, 25.00
subgroups = []
for i in range(n_subgroups):
drift = 0.04 if i >= 18 else 0.0 # 0 for the first 18, then +0.04 mm
subgroups.append(np.random.normal(target + drift, 0.02, subgroup_size))
subgroups = np.array(subgroups) # shape (25, 4)
xbar = subgroups.mean(axis=1) # axis=1 -> mean of each ROW (subgroup)
r = subgroups.max(axis=1) - subgroups.min(axis=1) # within-subgroup range
A2, D3, D4 = 0.729, 0, 2.282 # standard SPC constants for n = 4
xbar_c, r_c = xbar.mean(), r.mean() # centre lines
xbar_UCL, xbar_LCL = xbar_c + A2 * r_c, xbar_c - A2 * r_c # X-bar control limits
r_UCL, r_LCL = D4 * r_c, D3 * r_c # R-chart control limits
fig, axes = plt.subplots(2, 1, figsize=(10, 7), sharex=True)
axes[0].plot(range(1, n_subgroups + 1), xbar, marker="o", color="steelblue")
axes[0].axhline(xbar_c, color="green"); axes[0].axhline(xbar_UCL, color="red", linestyle="--")
axes[0].axhline(xbar_LCL, color="red", linestyle="--")
axes[0].set_ylabel("X-bar (mm)"); axes[0].set_title("X-bar Chart - Component Diameter")
axes[1].plot(range(1, n_subgroups + 1), r, marker="o", color="darkorange")
axes[1].axhline(r_c, color="green"); axes[1].axhline(r_UCL, color="red", linestyle="--")
axes[1].set_ylabel("Range (mm)"); axes[1].set_xlabel("Subgroup #")
axes[1].set_title("R Chart - Component Diameter")
plt.tight_layout(); plt.show()
# np.where(condition) returns the indices where it is True; +1 to label subgroups from 1
ooc = np.where((xbar > xbar_UCL) | (xbar < xbar_LCL))[0] + 1
print("Subgroups exceeding X-bar control limits:", ooc.tolist())
Run it: the drift starts at subgroup 19; the X-bar chart should show a point breaching the UCL a subgroup or two later. The lag between an actual process shift and statistical confirmation is normal - it's how a control chart flags a shift even when no single measurement is wildly out of spec.
Practice Exercises
- Remove the drift (
drift = 0.0unconditionally) and re-run - confirm no subgroups exceed the control limits. - Increase the subgroup size to 5 and update
A2, D3, D4to the n=5 constants (A2=0.577, D3=0, D4=2.114). - Add a rule check for "7 consecutive points on the same side of the centre line".
# Try the practice exercises here
Knowledge Check
- What does the X-bar chart track, and what does the R chart track?
- What does a point outside the UCL/LCL suggest about the process?
- Why use subgroups rather than plotting every individual reading?
Answer key
- X-bar tracks the subgroup average (process center); R tracks the subgroup range (variability)
- The process may have shifted due to a special, assignable cause
- Subgroup averages reduce noise and make a true shift in the mean easier to detect
39 / 63 sections · Course home · Join the coaching cohort