{"spec_id":"spc-xbar-r","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nspc-xbar-r: Statistical Process Control Chart (X-bar/R)\nLibrary: seaborn 0.13.2 | Python 3.13.14\nQuality: 87/100 | Updated: 2026-06-20\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\n\n\n# Theme tokens — Imprint palette + adaptive chrome\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\nBRAND = \"#009E73\"  # Imprint pos 1 — primary data series\nBLUE = \"#4467A3\"  # Imprint pos 3 — center line\nOOC_COLOR = \"#AE3030\"  # Imprint semantic red — out-of-control / error\nWARN_COLOR = \"#DDCC77\"  # Imprint semantic amber — warning / caution limits\n\n# Seaborn theme with Imprint chrome tokens\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.12,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Data — injection molding thickness measurements (target: 3.200 mm)\nnp.random.seed(7)\n\nn_samples = 30\nsubgroup_size = 5\ntarget = 3.200\nprocess_std = 0.008\n\n# SPC constants for subgroup size 5\nA2, D3, D4 = 0.577, 0.0, 2.114\n\nmeasurements = np.random.normal(target, process_std, (n_samples, subgroup_size))\n\n# OOC signals at samples 9/17/26 — down/up/down pattern (different from sibling impls)\nmeasurements[8] -= 0.030  # sample 9: below LCL\nmeasurements[16] += 0.027  # sample 17: above UCL\nmeasurements[25] -= 0.028  # sample 26: below LCL\n\nsample_means = measurements.mean(axis=1)\nsample_ranges = measurements.max(axis=1) - measurements.min(axis=1)\n\n# Control limits — X-bar chart\nxbar_bar = sample_means.mean()\nr_bar = sample_ranges.mean()\nxbar_ucl = xbar_bar + A2 * r_bar\nxbar_lcl = xbar_bar - A2 * r_bar\nxbar_upper_warn = xbar_bar + (2 / 3) * A2 * r_bar\nxbar_lower_warn = xbar_bar - (2 / 3) * A2 * r_bar\n\n# Control limits — R chart\nr_ucl = D4 * r_bar\nr_lcl = D3 * r_bar\nr_upper_warn = r_bar + (2 / 3) * (r_ucl - r_bar)\nr_lower_warn = max(0.0, r_bar - (2 / 3) * (r_bar - r_lcl))\n\nsample_ids = np.arange(1, n_samples + 1)\n\nxbar_ooc = (sample_means > xbar_ucl) | (sample_means < xbar_lcl)\nr_ooc = (sample_ranges > r_ucl) | (sample_ranges < r_lcl)\n\n# Build tidy DataFrame — seaborn-idiomatic long format for FacetGrid\ndf = pd.DataFrame(\n    {\n        \"Sample\": np.tile(sample_ids, 2),\n        \"Value\": np.concatenate([sample_means, sample_ranges]),\n        \"Chart\": np.repeat([\"X̄ Chart\", \"R Chart\"], n_samples),\n        \"OOC\": np.concatenate([xbar_ooc, r_ooc]),\n    }\n)\n\n# FacetGrid — seaborn's multi-panel layout API (dual-panel, shared x-axis)\ng = sns.FacetGrid(\n    df, row=\"Chart\", row_order=[\"X̄ Chart\", \"R Chart\"], height=2.25, aspect=8 / 2.25, sharex=True, sharey=False\n)\ng.figure.set_dpi(400)\ng.figure.patch.set_facecolor(PAGE_BG)\n\n# Map data lines via seaborn's map_dataframe\ng.map_dataframe(\n    sns.lineplot, x=\"Sample\", y=\"Value\", color=BRAND, linewidth=2.5, marker=\"o\", markersize=5, zorder=3, errorbar=None\n)\n\n\n# Map OOC markers via sns.scatterplot for out-of-control detection\ndef plot_ooc(data, **kwargs):\n    ax = plt.gca()\n    ooc = data[data[\"OOC\"]]\n    if not ooc.empty:\n        sns.scatterplot(\n            data=ooc,\n            x=\"Sample\",\n            y=\"Value\",\n            ax=ax,\n            color=OOC_COLOR,\n            s=120,\n            zorder=5,\n            marker=\"D\",\n            edgecolor=PAGE_BG,\n            linewidth=1.5,\n            legend=False,\n        )\n\n\ng.map_dataframe(plot_ooc)\n\n# Suppress default FacetGrid row labels (added manually as ax.text below)\ng.set_titles(template=\"\")\n\n# Per-panel control limits, labels, and annotations\npanels = [\n    (g.axes[0, 0], \"X̄ Chart\", xbar_bar, xbar_ucl, xbar_lcl, xbar_upper_warn, xbar_lower_warn, \"Sample Mean (mm)\"),\n    (g.axes[1, 0], \"R Chart\", r_bar, r_ucl, r_lcl, r_upper_warn, r_lower_warn, \"Sample Range (mm)\"),\n]\n\nfor ax, label, cl, ucl, lcl, uw, lw, ylabel in panels:\n    ax.set_facecolor(PAGE_BG)\n    ax.fill_between(sample_ids, lw, uw, alpha=0.11, color=BRAND, zorder=1)\n    ax.axhline(cl, color=BLUE, linewidth=2, label=f\"CL = {cl:.4f}\")\n    ax.axhline(ucl, color=OOC_COLOR, linewidth=1.5, linestyle=\"--\", label=f\"UCL = {ucl:.4f}\")\n    ax.axhline(lcl, color=OOC_COLOR, linewidth=1.5, linestyle=\"--\", label=f\"LCL = {lcl:.4f}\")\n    ax.axhline(uw, color=WARN_COLOR, linewidth=1, linestyle=\":\", label=f\"+2σ = {uw:.4f}\")\n    ax.axhline(lw, color=WARN_COLOR, linewidth=1, linestyle=\":\", label=f\"−2σ = {lw:.4f}\")\n    ax.set_ylabel(ylabel, fontsize=10, color=INK)\n    ax.legend(fontsize=8, loc=\"upper right\", framealpha=0.9)\n    ax.tick_params(axis=\"both\", labelsize=8)\n    ax.text(0.01, 0.95, label, transform=ax.transAxes, fontsize=10, fontweight=\"bold\", va=\"top\", color=INK)\n    ax.yaxis.grid(True, alpha=0.12, linewidth=0.8)\n\n# Main title on top panel; x-axis label on bottom panel only\ntitle = \"spc-xbar-r · python · seaborn · anyplot.ai\"\ng.axes[0, 0].set_title(title, fontsize=12, fontweight=\"medium\", pad=10, color=INK)\ng.axes[1, 0].set_xlabel(\"Sample Number\", fontsize=10, color=INK)\ng.axes[0, 0].set_xlabel(\"\")\n\n# Spine cleanup and canvas finalization — 3200×1800 px (figsize=(8,4.5), dpi=400)\nsns.despine(fig=g.figure, top=True, right=True)\ng.figure.set_size_inches(8, 4.5)\nplt.subplots_adjust(hspace=0.12, left=0.11, right=0.97, top=0.92, bottom=0.11)\n\n# Save\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\nplt.close()\n"}