Chris Parmer — home

Shewhart individuals (I) chart

NIST MAVRO filter-transmittance data

Example from the compendium of canonical charts

Shewhart individuals (I) chart — NIST MAVRO filter-transmittance data

Python Code

"""Shewhart individuals (I) chart — NIST MAVRO filter-transmittance data."""


import numpy as np
import pandas as pd
import plotly.graph_objects as go
import requests, warnings

URL = "https://www.itl.nist.gov/div898/handbook/datasets/MAVRO.DAT"
D2 = 1.128   # unbiasing constant for n=2 moving range


def generate():
    print("fetching NIST MAVRO.DAT …")
    warnings.filterwarnings("ignore")
    requests.packages.urllib3.disable_warnings()
    r = requests.get(URL, verify=False, timeout=30)
    r.raise_for_status()
    lines = r.text.strip().split("\n")
    # Skip first 25 header lines; remaining lines are the data values
    data_lines = lines[25:]
    values = []
    for line in data_lines:
        for token in line.split():
            try:
                values.append(float(token))
            except ValueError:
                pass
    values = values[:50]   # spec says 50 values
    print(f"  parsed {len(values)} values")

    x = list(range(1, len(values) + 1))
    y = np.array(values)

    # Center line and control limits via moving-range method
    mr = np.abs(np.diff(y))
    mr_bar = np.mean(mr)
    y_bar  = np.mean(y)
    sigma  = mr_bar / D2
    ucl    = y_bar + 3 * sigma
    lcl    = y_bar - 3 * sigma

    # Flag out-of-control: beyond ±3σ
    ooc_mask = (y > ucl) | (y < lcl)
    # Western Electric run rule: 8 consecutive points on same side of center
    run8_idx = []
    for i in range(7, len(y)):
        window = y[i-7:i+1]
        if np.all(window > y_bar) or np.all(window < y_bar):
            run8_idx.append(i)

    fig = go.Figure()

    # UCL / LCL / Center
    for val, label, color, dash in [
        (ucl, f"UCL = {ucl:.4f}", PINK, "dash"),
        (y_bar, f"CL = {y_bar:.4f}", VIOLET, "solid"),
        (lcl, f"LCL = {lcl:.4f}", PINK, "dash"),
    ]:
        fig.add_hline(y=val, line=dict(color=color, width=1.5, dash=dash),
                      annotation_text=label, annotation_position="right",
                      annotation_font=dict(size=10))

    # All points
    fig.add_trace(go.Scatter(
        x=x, y=values,
        mode="lines+markers",
        name="Transmittance",
        line=dict(color=VIOLET, width=1.5),
        marker=dict(color=VIOLET, size=6),
        hovertemplate="Sample %{x}<br>Value=%{y:.4f}<extra></extra>",
    ))

    # Run-rule violations
    if run8_idx:
        fig.add_trace(go.Scatter(
            x=[x[i] for i in run8_idx],
            y=[values[i] for i in run8_idx],
            mode="markers", name="8-pt run rule",
            marker=dict(color=TEAL, size=16, symbol="diamond"),
            hovertemplate="Run rule: Sample %{x}<extra></extra>",
        ))

    # Out-of-control points
    if ooc_mask.any():
        ooc_x = [x[i] for i in range(len(x)) if ooc_mask[i]]
        ooc_y = [values[i] for i in range(len(values)) if ooc_mask[i]]
        fig.add_trace(go.Scatter(
            x=ooc_x, y=ooc_y,
            mode="markers", name="Out of control",
            marker=dict(color=PINK, size=12, symbol="circle-open", line=dict(width=2)),
            hovertemplate="OOC: Sample %{x}, Value=%{y:.4f}<extra></extra>",
        ))

    fig.update_layout(
        title=dict(text="Shewhart I-Chart — NIST MAVRO Filter Transmittance", x=0.5),
        xaxis=dict(title="Sample"),
        yaxis=dict(title="Transmittance"),
        legend=dict(orientation="h", y=1.08),
        margin=dict(t=60, b=50, l=80, r=130),
        height=460,
    )
    return fig


if __name__ == "__main__":
    generate()

Made with Plotly