Skip to content

Process capability for non-normal data in Python

Flatness, roughness, runout, particle counts, impurity levels, time to failure: a lot of what gets measured on a shop floor is bounded at zero and skewed to the right. Run a textbook Cpk on data like that and the normal model invents a left tail that does not exist and thins out the right tail that actually produces your scrap. The index looks fine. The process is not.

shewhart handles non-normal capability two ways, both in one call.

The problem, in numbers

Two hundred flatness measurements, bounded at zero, right-skewed, with an upper specification of 0.060 mm:

import shewhart as sw

r = sw.capability(x, usl=0.060)        # default: normal model
r.stats["ppk"]            # 1.55
r.stats["ppm_overall"]    # 1.6   expected parts-per-million over spec
r.meta["normality"]       # "Anderson-Darling = 3.249, rejected at 5%"

Ppk 1.55 reads as a comfortably capable process. The normality note says the number rests on a model the data already rejected. Fit a distribution that matches the shape and the picture changes:

r = sw.capability(x, usl=0.060, dist="auto")
r.meta["dist_selected"]   # "gamma"
r.stats["ppk"]            # 0.95
r.stats["ppm_overall"]    # 1862

Same data, same spec. The normal model overstates the index by about 60% and understates the expected nonconforming rate by three orders of magnitude. The gamma fit is where the real tail lives.

Route 1: the percentile method

dist="lognormal" | "weibull" | "gamma" | "auto" fits the distribution and reads the capability indices off its quantiles, the way ISO 22514 defines it. The spec spread is compared to the 0.135% and 99.865% percentiles of the fitted model instead of to multiples of a standard deviation:

r = sw.capability(x, usl=0.060, dist="weibull")
r.stats["ppk"]            # 1.20
r.stats["q_upper"]        # 0.0525, the 99.865% percentile of the fit

The distribution you assume drives the answer: on this data a Weibull fit gives Ppk 1.20, gamma gives 0.95, lognormal gives 0.50. That spread is the reason to let the data choose. dist="auto" fits lognormal, Weibull, gamma, and normal, ranks them by the Anderson-Darling statistic, and keeps the best one. The full comparison stays in the metadata so the choice is auditable:

r = sw.capability(x, usl=0.060, dist="auto")
r.meta["dist_fit_ad"]
# {'lognormal': 1.5516, 'weibull': 0.5633, 'gamma': 0.4819, 'normal': 3.2489}

The percentile route reports performance indices (Pp, Ppk) and the fitted quantiles. Within-sigma Cp/Cpk and the normal-theory confidence intervals are normal-model quantities, so they are left out rather than printed next to a distribution that does not support them.

Route 2: Box-Cox

transform="boxcox" estimates one Box-Cox lambda, applies it to the data and to the specification limits together, and runs the ordinary normal capability analysis on the transformed scale. You get the whole set back, indices and confidence intervals included, expressed on that scale:

r = sw.capability(x, usl=0.060, transform="boxcox")
r.stats["boxcox_lambda"]  # 0.40
r.stats["ppk"]            # 1.01
r.stats["ppk_lci"], r.stats["ppk_uci"]   # the interval comes along

Box-Cox needs strictly positive data and strictly positive specification limits, because the transform is only defined there.

Which route

The percentile method matches what most customer and ISO 22514 documents expect for non-normal characteristics, and dist="auto" removes the "which distribution" argument by letting the fit decide. Box-Cox is the better choice when you want confidence intervals on the indices, or when the same transform is already used elsewhere in the analysis. When the data passes a normality check, neither is needed; plain sw.capability(...) is the right call, and the Cpk guide covers it.

One rule holds across all three: capability assumes a stable process. shewhart runs the stability gate first and refuses to dress up indices computed on a process that is out of control. A capable-looking number on an unstable process is the most expensive kind of wrong.

Validation

The normal-model mean and sigma reproduce NIST StRD certified values in CI; the distribution fits use scipy's maximum-likelihood estimators and the Anderson-Darling statistic. See Validation.