Skip to content

Choosing a control chart

This page is task-oriented: match your data to the right chart, and override the default when you know better. mfgQC infers a variables chart from your subgroup size, but it never guesses for attribute (count) data — and it never silently switches a chart you asked for. The rules below are exactly what the code does.

If you are new to the load → spec → analysis flow, start with the Quickstart; the reference page Control charts documents the limit formulas and constants.

How mfgQC picks a chart when you don't

Call control_chart() with no kind= and mfgQC chooses a variables chart from the subgroup sizes in your QCData. The rule is purely a function of subgroup size:

Subgroup size Inferred chart kind recorded
every subgroup is size 1 Individuals + Moving Range i_mr
\(2 \le n \le 10\) \(\bar{X}\)–R xbar_r
\(n > 10\) \(\bar{X}\)–S xbar_s

The exact thresholds, from the code

The inference is i_mr if all subgroups are size 1; otherwise xbar_r when the (constant) subgroup size \(n \le 10\) and xbar_s when \(n > 10\). The boundary is inclusive at 10: \(n=10\) infers \(\bar{X}\)–R, \(n=11\) infers \(\bar{X}\)–S. The rationale is statistical — the range is an efficient spread estimator for small subgroups, but loses efficiency as \(n\) grows, so the sample standard deviation takes over once subgroups get large.

The inferred choice is recorded in the result and in provenance (the title reads (inferred)), so an audit can always see that mfgQC picked the chart rather than you. Inference requires a constant subgroup size; if sizes vary, \(\bar{X}\) charts raise rather than average over ragged groups (the individuals path is the exception).

print(qc.control_chart())     # no kind= → inferred from subgroup size
Control Chart: xbar_r (inferred); rules=nelson
==============================================
Xbar: CL=1.4992  UCL=1.6475  LCL=1.3509
R: CL=0.25705  UCL=0.5434  LCL=0

Out-of-control signals: none (process in control)

Assumption checks:
  [PASS] independence (lag-1 autocorrelation): r=0.193, p=0.387; n=20 [low power]

Decision table: data situation → chart → call

Your data Chart kind= Call
Continuous, one reading at a time Individuals + Moving Range i_mr (or i) qc.control_chart(kind="i_mr")
Continuous, rational subgroups, \(2 \le n \le 10\) \(\bar{X}\)–R xbar_r qc.control_chart() (inferred)
Continuous, rational subgroups, \(n > 10\) \(\bar{X}\)–S xbar_s qc.control_chart() (inferred)
Pass/fail counts, constant sample size np-chart (number defective) np qc.control_chart(kind="np", n=100)
Pass/fail counts, varying sample size p-chart (proportion defective) p qc.control_chart(kind="p", n="inspected")
Defect counts, constant area of opportunity c-chart (defects per unit) c qc.control_chart(kind="c")
Defect counts, varying area of opportunity u-chart (defects per unit area) u qc.control_chart(kind="u", n="area")
Small sustained shift you need to catch fast EWMA qc.ewma_chart(lam=0.1, L=2.7)
Small sustained shift, accumulate evidence CUSUM qc.cusum_chart(k=0.5, h=5)
Many part numbers on one chart Short-run (standardized) qc.short_run_chart(by="part", target=...)

EWMA, CUSUM, and short-run are their own methods

EWMA, CUSUM, and the standardized short-run chart are not kind= values of control_chart() — they are separate methods (ewma_chart, cusum_chart, short_run_chart) because they take their own parameters (smoothing \(\lambda\) and limit width \(L\); the reference value \(k\) and decision interval \(h\); the part-number column). Reach for EWMA or CUSUM when a Shewhart chart is too slow to detect a small, sustained drift — a Shewhart chart reacts only to the current point, while these accumulate. See the small-shift note in Control charts.

Overriding the inference

Pass kind= to force a specific chart. The complete set of valid strings is:

kind= Chart
"i_mr" (alias "i") Individuals + Moving Range
"xbar_r" \(\bar{X}\)–R
"xbar_s" \(\bar{X}\)–S
"p" proportion defective
"np" number defective
"c" count of defects
"u" defects per unit

Any other value raises ValueError. The override is honored exactly — mfgQC will not swap an explicitly requested kind for a "better" one. When you specify a kind the result title reads (specified) instead of (inferred).

Attribute charts are always explicit — never inferred

p, np, c, and u are only ever produced when you ask for them by name. mfgQC infers variables charts from subgroup size, but it cannot tell from the numbers alone whether a column of integers is a measurement (variables) or a count of defectives/defects (attribute) — 7 could be a thickness reading or seven rejects. The choice of attribute family also encodes a model assumption (binomial for p/np, Poisson for c/u) that only you know. So mfgQC requires you to declare it. For p, np (constant-\(n\) only), and u you supply the sample size via n= — a column name for per-point sizes (giving stepped limits when they vary), a constant int, or None to fall back to a size role.

Override worked example: force I-MR

Even with subgrouped continuous data you may want individuals (e.g. you don't trust the subgrouping). Pass kind="i_mr" on a plain measure column and each row is treated as its own size-1 subgroup:

import numpy as np, pandas as pd, mfgqc

rng = np.random.default_rng(3)
ind = pd.DataFrame({"thickness": np.round(rng.normal(12.0, 0.4, size=30), 3)})
qc  = mfgqc.load(ind, measure="thickness")

print(qc.control_chart(kind="i_mr"))
Control Chart: i_mr (specified); rules=nelson
=============================================
Individual: CL=12.023  UCL=13.35  LCL=10.697
MR: CL=0.49872  UCL=1.6293  LCL=0

Out-of-control signals: 2
  point 2 (dispersion): nelson_1 - one point beyond control limits
  point 10 (dispersion): nelson_1 - one point beyond control limits

Assumption checks:
  [PASS] independence (lag-1 autocorrelation): r=-0.144, p=0.43; n=30

The moving-range (dispersion) panel is checked independently, so a point whose moving range blows up is flagged even when the individual value stays inside its own limits.

Attribute chart example

A p-chart for fraction defective: name the defective-count column as the measure and pass the inspected sample size with n=.

defects = pd.DataFrame({
    "defectives": [3, 5, 2, 4, 6, 1, 3, 5, 8, 2, 4, 3, 7, 2, 5],
    "inspected":  [100] * 15,
})
qc = mfgqc.load(defects, measure="defectives")

print(qc.control_chart(kind="p", n="inspected"))
Control Chart: p (specified); rules=nelson
==========================================
Proportion (p): CL=0.04  UCL=0.098788  LCL=0

Out-of-control signals: none (process in control)

Assumption checks:
  [PASS] dispersion (chi-square dispersion): dispersion ratio 1.04, p=0.407; n=15 [low power]

A c-chart for total defects per inspection unit needs no n= (constant area of opportunity):

cdf = pd.DataFrame({"flaws": [7, 3, 5, 9, 4, 6, 2, 8, 5, 14, 3, 6, 4, 5, 7]})
qc  = mfgqc.load(cdf, measure="flaws")

print(qc.control_chart(kind="c"))
Control Chart: c (specified); rules=nelson
==========================================
Count (c): CL=5.8667  UCL=13.133  LCL=0

Out-of-control signals: 1
  point 10 (location): nelson_1 - one point beyond 3 sigma

Assumption checks:
  [PASS] dispersion (chi-square dispersion): dispersion ratio 1.51, p=0.0993; n=15 [low power]

What the chart checks for you

Every chart does two things beyond drawing limits:

  • It reports run-rule violations. The default ruleset is rules="nelson" (pass rules="western_electric" for the WE zone tests). Each signal lists the point, the panel (location or dispersion), and the rule that fired — see Run rules for the full rule set and what each one means. Signals are reported, never auto-removed.
  • It checks independence. Variables charts run a lag-1 autocorrelation test and attach the result as an assumption check (the [PASS]/[FAIL] independence line above). Control-chart limits assume successive points are independent; if they are autocorrelated, the limits are too tight and you will see false alarms. mfgQC tells you — it does not silently widen the limits.

Before you judge capability

Stability first, capability second

A capability index (\(C_p\), \(C_{pk}\), \(P_{pk}\)) only means something for a process that is in statistical control. Establish stability with the control chart — confirm there are no run-rule signals and that independence holds — before you compute capability on the same data. Computing capability on an out-of-control process produces a number that does not predict future performance. Run the control chart first; only once it is clean does the capability study describe a stable process.