Process-capability histogram with Cp/Cpk
Manufacturing — NIST MPC61 dataset
Example from the compendium of canonical charts
Python Code
"""Manufacturing — Process-capability histogram with Cp/Cpk (NIST MPC61 dataset)."""
import io
import numpy as np
from scipy import stats
import plotly.graph_objects as go
import requests
import warnings
warnings.filterwarnings("ignore")
URL = "https://www.itl.nist.gov/div898/handbook/datasets/MPC61.DAT"
LSL = 97.5
USL = 102.5
def generate():
print("fetching MPC61.DAT from NIST …")
r = requests.get(URL, verify=False, timeout=60)
r.raise_for_status()
# Parse: skip header lines (non-numeric), extract column 6 (0-indexed)
data_vals = []
for line in r.text.splitlines():
line = line.strip()
if not line:
continue
parts = line.split()
# Try to parse first token as float
try:
float(parts[0])
except (ValueError, IndexError):
continue
# We have a numeric line — take column index 6 if available
if len(parts) >= 7:
try:
data_vals.append(float(parts[6]))
except ValueError:
pass
elif len(parts) > 0:
# fallback: use first column
try:
data_vals.append(float(parts[0]))
except ValueError:
pass
print(f"parsed {len(data_vals)} data points, first few: {data_vals[:5]}")
if len(data_vals) < 5:
raise ValueError("Not enough data points parsed from NIST file")
data = np.array(data_vals)
mu = data.mean()
sigma = data.std(ddof=1)
Cp = (USL - LSL) / (6 * sigma)
Cpk = min((USL - mu) / (3 * sigma), (mu - LSL) / (3 * sigma))
# Fit normal curve
x_fit = np.linspace(data.min() - sigma, data.max() + sigma, 300)
y_fit = stats.norm.pdf(x_fit, mu, sigma)
# Scale normal pdf to match histogram counts
n_bins = 20
bin_width = (data.max() - data.min()) / n_bins
y_fit_scaled = y_fit * len(data) * bin_width
fig = go.Figure()
fig.add_trace(go.Histogram(
x=data,
nbinsx=n_bins,
name="Resistivity",
marker_color=VIOLET,
marker_opacity=0.7,
hovertemplate="Value: %{x:.2f}<br>Count: %{y}<extra></extra>",
))
fig.add_trace(go.Scatter(
x=x_fit,
y=y_fit_scaled,
mode="lines",
name="Fitted normal",
line=dict(color=TEAL, width=2.5),
hovertemplate="x=%{x:.2f}<extra>Normal fit</extra>",
))
# Spec limits
fig.add_vline(x=LSL, line=dict(color=PINK, width=2, dash="dash"),
annotation_text=f"LSL={LSL}", annotation_position="top right",
annotation_font=dict(size=11, color=PINK))
fig.add_vline(x=USL, line=dict(color=PINK, width=2, dash="dash"),
annotation_text=f"USL={USL}", annotation_position="top left",
annotation_font=dict(size=11, color=PINK))
fig.add_vline(x=mu, line=dict(color=GREEN, width=1.5, dash="dot"),
annotation_text=f"μ={mu:.2f}", annotation_position="top right",
annotation_font=dict(size=11, color=GREEN))
# Cp / Cpk annotation
fig.add_annotation(
x=0.98, y=0.95,
xref="paper", yref="paper",
text=f"<b>Cp = {Cp:.3f}</b><br><b>Cpk = {Cpk:.3f}</b>",
showarrow=False,
align="right",
font=dict(size=13),
bgcolor="rgba(255,255,255,0.8)",
bordercolor=MUTED,
borderwidth=1,
)
fig.update_layout(
xaxis=dict(title="Resistivity"),
yaxis=dict(title="Count"),
legend=dict(orientation="h", y=-0.14),
margin=dict(t=50, b=70, l=70, r=40),
)
return fig
if __name__ == "__main__":
generate()
Made with Plotly