Data Analysis for Engineers/Module 4

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

  1. Remove the drift (drift = 0.0 unconditionally) and re-run - confirm no subgroups exceed the control limits.
  2. Increase the subgroup size to 5 and update A2, D3, D4 to the n=5 constants (A2=0.577, D3=0, D4=2.114).
  3. Add a rule check for "7 consecutive points on the same side of the centre line".
# Try the practice exercises here

Knowledge Check

  1. What does the X-bar chart track, and what does the R chart track?
  2. What does a point outside the UCL/LCL suggest about the process?
  3. Why use subgroups rather than plotting every individual reading?
Answer key
  1. X-bar tracks the subgroup average (process center); R tracks the subgroup range (variability)
  2. The process may have shifted due to a special, assignable cause
  3. Subgroup averages reduce noise and make a true shift in the mean easier to detect

39 / 63 sections · Course home · Join the coaching cohort