Process capability answers a different question from process behavior charts. Charts ask “Is this process stable?” Capability asks “Can this process consistently meet specifications?”
Wheeler & Bishop (Chapter 16) define two families of capability indices:
| Index | Measures | Based on |
|---|---|---|
| Pp / Ppk | Current capability — how the process IS performing | Overall sigma (all variation) |
| Cp / Cpk | Potential capability — what’s achievable by removing assignable causes | R2 residual sigma (within-subgroup variation only) |
The gap between Ppk and Cpk tells you how much improvement is available by addressing the assignable causes found in your VAS residual charts.
What You’ll Learn¶
Assess capability against specification limits
Interpret Pp/Ppk (current) vs Cp/Cpk (potential)
Use one-sided specs
Compare multiple specs against the same study
Understand when Cp/Cpk are unavailable
Visualize capability with
.plot()
Setup¶
We use Bishop’s PBTESTDATABASE_T100 — a 4000-row validation dataset with 4 factor levels x 2 factor levels x 100 time periods, and response columns designed to exercise each Design State (DS).
from processbehavior import ProcessBehavior
# Load the PBTESTDATABASE_T100
pb = ProcessBehavior.read_csv('../../validation/PBTESTDATABASE_T100.csv')
print(f"Dataset: {pb.data.shape[0]} rows x {pb.data.shape[1]} columns")
print(f"Columns: {pb.data.columns.tolist()}")Found 11655 garbage/NA values across 5 column(s):
• PM SDS 2: 3200 values
• PM SDS 3: 958 values
• PM SDS 4: 2134 values
• PM SDS 5: 3359 values
• PM SDS 6: 2004 values
These values were converted to NA and will be excluded from analysis.
Dataset: 4000 rows x 11 columns
Columns: ['PRODUCTION TIME', 'FACTOR 1', 'FACTOR 2', 'FACTOR 1xFACTOR 2', 'PM SDS 1', 'PM SDS 2', 'PM SDS 3', 'PM SDS 4', 'PM SDS 5', 'PM SDS 6', 'PM INERT']
# Formulate a DS 1 study (full replication — has VAS residuals)
study = pb.formulate(
response='PM SDS 1',
factors=['FACTOR 1', 'FACTOR 2'],
time='PRODUCTION TIME',
precision=3
)
print(study)Study(response='PM SDS 1', factors=[FACTOR 1, FACTOR 2], time='PRODUCTION TIME', ads=1)
Valid: Histogram, Xbar, S, X, mR | Recommended: Xbar
Residuals: R1, R2, R3, R4, R5, R6
→ study.execute() or study.support for details
Basic Capability Assessment¶
Suppose engineering specifications require the response to be between 233 and 243, with a target of 238. Call study.capability() with your spec limits:
cap = study.capability(usl=243, lsl=233, target=238)
capCapabilityResult:
Specs: LSL=233, USL=243, Target=238
N=4000, Ybar=237.817, S=1.74, sigma_hat=1.74
Current Capability (overall sigma):
Pp=0.958 Ppk=0.923 (lower=0.923, upper=0.993)
Potential Capability (R2 sigma):
Cp=2.077 Cpk=2.001 (lower=2.001, upper=2.153)
Z-scores:
Z_lower=2.768, Z_upper=2.978
Empirical (current):
Below LSL: 46 (1.15%) Above USL: 1 (0.025%) Total outside: 47 (1.175%)
Empirical (potential):
Below LSL: 0 (0.0%) Above USL: 0 (0.0%)
Note: Stability not assessed; run study.execute() and review signals before interpreting indices.Reading the Output¶
Current Capability (Pp/Ppk) uses the overall standard deviation — all sources of variation included:
Pp = (USL - LSL) / 6 — the spread of the spec window relative to 6 sigma
Ppk = min(Ppk_lower, Ppk_upper) — accounts for how centered the process is
When Ppk < Pp, the process mean is off-center
Potential Capability (Cp/Cpk) uses only the R2 residual sigma — the within-subgroup variation after removing factor and time effects:
If Cpk >> Ppk, significant improvement is possible by addressing assignable causes
If Cpk Ppk, the process variation is dominated by noise — improvements require fundamentally changing the process
Empirical counts show how many observations actually fell outside specs — no distributional assumptions, just counting.
Visualizing Capability¶
.plot() produces a histogram with spec limits (crimson), natural process limits (green dashed), out-of-spec shading, and a capability index box:
# Pass the response values — CapabilityResult is a lean dataclass that doesn't store raw data
cap.plot(values=study.dataset['PM SDS 1'])Accessing Individual Fields¶
CapabilityResult is a frozen dataclass. All fields are available as attributes with full raw precision:
print(f"Process mean: {cap.y_bar:.4f}")
print(f"Overall sigma: {cap.sigma_hat:.4f}")
print(f"R2 sigma: {cap.sigma_hat_r2:.4f}")
print()
print(f"Pp = {cap.pp:.3f} Ppk = {cap.ppk:.3f}")
print(f"Cp = {cap.cp:.3f} Cpk = {cap.cpk:.3f}")
print()
print(f"Outside specs: {cap.n_outside} of {cap.n} ({cap.pct_outside:.2f}%)")Process mean: 237.8169
Overall sigma: 1.7404
R2 sigma: 0.8025
Pp = 0.958 Ppk = 0.923
Cp = 2.077 Cpk = 2.001
Outside specs: 47 of 4000 (1.17%)
The as_dict() Method¶
For programmatic use or export, as_dict() returns a rounded dictionary. You can override the rounding precision:
cap.as_dict(round_to=4){'usl': 243,
'lsl': 233,
'target': 238,
'n': 4000,
'y_bar': 237.8169,
's': 1.7403,
'sigma_hat': np.float64(1.7404),
'pp': np.float64(0.9576),
'ppk_lower': np.float64(0.9225),
'ppk_upper': np.float64(0.9927),
'ppk': np.float64(0.9225),
'sigma_hat_r2': np.float64(0.8025),
'cp': np.float64(2.0768),
'cpk_lower': np.float64(2.0007),
'cpk_upper': np.float64(2.1529),
'cpk': np.float64(2.0007),
'potential_unavailable_reason': None,
'z_lower': np.float64(2.7676),
'z_upper': np.float64(2.9781),
'n_below_lsl': 46,
'n_above_usl': 1,
'n_outside': 47,
'pct_below_lsl': 1.15,
'pct_above_usl': 0.025,
'pct_outside': 1.175,
'potential_n_below_lsl': 0,
'potential_n_above_usl': 0,
'potential_pct_below_lsl': 0.0,
'potential_pct_above_usl': 0.0}Using SpecLimits Directly¶
For reusable spec definitions, create a SpecLimits object and pass it to capability():
from processbehavior import SpecLimits
customer_specs = SpecLimits(usl=243, lsl=233, target=238)
# Pass to capability()
cap = study.capability(customer_specs)
print(f"Ppk = {cap.ppk:.3f}")Ppk = 0.923
Comparing Multiple Specifications¶
capability() is cheap — it reuses the already-computed Study without re-formulation. This makes it easy to evaluate different spec scenarios:
scenarios = [
("Tight specs", SpecLimits(usl=241, lsl=235)),
("Normal specs", SpecLimits(usl=243, lsl=233)),
("Wide specs", SpecLimits(usl=245, lsl=231)),
]
print(f"{'Scenario':<16} {'Pp':>6} {'Ppk':>6} {'Cp':>6} {'Cpk':>6} {'% Outside':>10}")
print("-" * 58)
for name, specs in scenarios:
c = study.capability(specs)
print(f"{name:<16} {c.pp:6.3f} {c.ppk:6.3f} {c.cp:6.3f} {c.cpk:6.3f} {c.pct_outside:9.2f}%")Scenario Pp Ppk Cp Cpk % Outside
----------------------------------------------------------
Tight specs 0.575 0.539 1.246 1.170 7.80%
Normal specs 0.958 0.923 2.077 2.001 1.17%
Wide specs 1.341 1.306 2.908 2.831 0.15%
Notice how Cp/Cpk (potential) remains high across all scenarios — the within-subgroup variation is small. The gap between Ppk and Cpk shows the improvement available by addressing assignable causes.
One-Sided Specifications¶
Some processes only have a limit on one side — e.g. a maximum impurity level or minimum strength. Provide only usl or lsl:
# USL only — e.g. maximum allowable measurement
cap_upper = study.capability(usl=242)
print("USL-only:")
print(f" Pp = {cap_upper.pp} (None — not defined for one-sided)")
print(f" Ppk = {cap_upper.ppk:.3f} (= Ppk_upper)")
print(f" Ppk_lower = {cap_upper.ppk_lower}")
print(f" Ppk_upper = {cap_upper.ppk_upper:.3f}")
print()
# LSL only — e.g. minimum required measurement
cap_lower = study.capability(lsl=234)
print("LSL-only:")
print(f" Ppk = {cap_lower.ppk:.3f} (= Ppk_lower)")USL-only:
Pp = None (None — not defined for one-sided)
Ppk = 0.801 (= Ppk_upper)
Ppk_lower = None
Ppk_upper = 0.801
LSL-only:
Ppk = 0.731 (= Ppk_lower)
For one-sided specs:
PpandCpareNone(they require both limits to define the spec window)Ppkequals the relevant side —ppk_upperfor USL-only,ppk_lowerfor LSL-onlyThe irrelevant side’s fields are
None, socap.ppkworks without branching
When Cp/Cpk Are Unavailable¶
Potential capability (Cp/Cpk) requires R2 residuals from the Variance Analysis System. R2 is only computed when the study has both factors and time (DS 1-6). If either is missing, the result explains why:
# Formulate without time — no VAS residuals
study_no_time = pb.formulate(
response='PM SDS 1',
factors=['FACTOR 1', 'FACTOR 2'],
)
cap_no_r2 = study_no_time.capability(usl=243, lsl=233)
print(f"Cp: {cap_no_r2.cp}")
print(f"Cpk: {cap_no_r2.cpk}")
print(f"Reason: {cap_no_r2.potential_unavailable_reason}")
print()
print("Current capability (Pp/Ppk) is still available:")
print(f"Pp = {cap_no_r2.pp:.3f}, Ppk = {cap_no_r2.ppk:.3f}")Cp: None
Cpk: None
Reason: R2 residuals not available (ADS 1); Cp/Cpk require VAS residuals from a factorial+time design
Current capability (Pp/Ppk) is still available:
Pp = 0.958, Ppk = 0.923
Z-Scores¶
Z-scores express the distance from the process mean to each spec limit in sigma units. They are algebraically equivalent to 3 x Ppk:
cap = study.capability(usl=243, lsl=233)
print(f"Z_lower = {cap.z_lower:.3f} (= 3 x Ppk_lower = 3 x {cap.ppk_lower:.3f} = {3 * cap.ppk_lower:.3f})")
print(f"Z_upper = {cap.z_upper:.3f} (= 3 x Ppk_upper = 3 x {cap.ppk_upper:.3f} = {3 * cap.ppk_upper:.3f})")Z_lower = 2.768 (= 3 x Ppk_lower = 3 x 0.923 = 2.768)
Z_upper = 2.978 (= 3 x Ppk_upper = 3 x 0.993 = 2.978)
Stability Warning¶
Capability indices assume the process is stable. If the process has assignable causes (signals on the behavior chart), the indices may be misleading. The result includes a reminder:
print(f"Stability evaluated: {cap.stability_evaluated}")
print()
print("Always run study.execute() first and review the process behavior")
print("chart for signals before interpreting capability indices.")Stability evaluated: False
Always run study.execute() first and review the process behavior
chart for signals before interpreting capability indices.
The Equations¶
For reference, here are the Bishop Chapter 16 equations implemented by capability():
Current Capability (overall sigma):
Potential Capability (R2 residual sigma):
Note: in the Cpk formula is the overall response mean, not the mean of R2 (which is approximately zero).
Summary¶
| Concept | Key Point |
|---|---|
study.capability(usl=, lsl=) | Assess capability — no re-formulation needed |
| Pp / Ppk | Current performance (overall sigma) |
| Cp / Cpk | Potential performance (R2 sigma) — requires factors + time |
| Ppk vs Cpk gap | Improvement available by removing assignable causes |
| One-sided | Provide only usl or lsl; Pp/Cp become None |
SpecLimits | Reusable spec object for multiple assessments |
| Z-scores | Distance to spec in sigma units (= 3 x Ppk) |
cap.plot(values) | Histogram with spec limits, NPL overlay, and index box |
| Stability | Always check behavior charts before interpreting indices |
Next Steps¶
Xbar-S Analysis — understand the charts behind Cp/Cpk
DS Validation — see how DS affects available analyses
Complete DS 1 Analysis — full deep dive with VAS residuals