{"spec_id":"spc-xbar-r","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nspc-xbar-r: Statistical Process Control Chart (X-bar/R)\nLibrary: plotnine 0.15.7 | Python 3.13.14\nQuality: 89/100 | Updated: 2026-06-20\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent this file from shadowing the installed plotnine package\n_script_dir = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p or \".\") != _script_dir]\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    facet_wrap,\n    geom_hline,\n    geom_label,\n    geom_line,\n    geom_point,\n    ggplot,\n    labs,\n    scale_color_identity,\n    scale_fill_identity,\n    scale_shape_identity,\n    scale_size_identity,\n    scale_x_continuous,\n    theme,\n    theme_minimal,\n)\n\n\n# Theme tokens (Imprint palette — theme-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\n# Imprint palette assignments\nTEAL = \"#009E73\"  # Imprint[0] — primary data line / in-control points\nBLUE = \"#4467A3\"  # Imprint[2] — center line\nRED = \"#AE3030\"  # Imprint[4] — UCL/LCL limits and out-of-control points\nAMBER = \"#DDCC77\"  # ANYPLOT_AMBER — warning limits (±2σ)\n\n# Data — CNC shaft diameter measurements, subgroups of n=5\nnp.random.seed(42)\nn_samples = 30\nsubgroup_size = 5\n\n# Control chart constants for n=5\nA2 = 0.577\nD3 = 0.0\nD4 = 2.114\n\ntarget = 25.0\nprocess_std = 0.02\nmeasurements = np.random.normal(target, process_std, (n_samples, subgroup_size))\n\n# Inject out-of-control signals\nmeasurements[7] += 0.06  # Upward mean shift\nmeasurements[18] -= 0.07  # Downward mean shift\nmeasurements[24] += np.array([0.06, -0.05, 0.06, -0.05, 0.06])  # Elevated variability\n\nsample_means = measurements.mean(axis=1)\nsample_ranges = measurements.max(axis=1) - measurements.min(axis=1)\n\nxbar_bar = sample_means.mean()\nr_bar = sample_ranges.mean()\n\nxbar_ucl = xbar_bar + A2 * r_bar\nxbar_lcl = xbar_bar - A2 * r_bar\nxbar_uwl = xbar_bar + (2 / 3) * A2 * r_bar\nxbar_lwl = xbar_bar - (2 / 3) * A2 * r_bar\n\nr_ucl = D4 * r_bar\nr_lcl = D3 * r_bar\nr_uwl = r_bar + (2 / 3) * (r_ucl - r_bar)\nr_lwl = max(0, r_bar - (2 / 3) * (r_bar - r_lcl))\n\nsample_ids = np.arange(1, n_samples + 1)\nxbar_ooc = (sample_means > xbar_ucl) | (sample_means < xbar_lcl)\nr_ooc = (sample_ranges > r_ucl) | (sample_ranges < r_lcl)\n\nCHART_XBAR = \"X̄ Chart · Sample Mean (mm)\"\nCHART_R = \"R Chart · Sample Range (mm)\"\nchart_order = [CHART_XBAR, CHART_R]\n\nxbar_df = pd.DataFrame({\"sample\": sample_ids, \"value\": sample_means, \"chart\": CHART_XBAR, \"ooc\": xbar_ooc})\nr_df = pd.DataFrame({\"sample\": sample_ids, \"value\": sample_ranges, \"chart\": CHART_R, \"ooc\": r_ooc})\ndf = pd.concat([xbar_df, r_df], ignore_index=True)\ndf[\"color\"] = np.where(df[\"ooc\"], RED, TEAL)\ndf[\"point_size\"] = np.where(df[\"ooc\"], 4.0, 2.5)\ndf[\"point_shape\"] = np.where(df[\"ooc\"], \"D\", \"o\")\ndf[\"chart\"] = pd.Categorical(df[\"chart\"], categories=chart_order, ordered=True)\n\n# Control limit lines — limit_lines excludes R chart LCL=0 so labels don't appear at y=0\nlimit_rows = [\n    {\"chart\": CHART_XBAR, \"yintercept\": xbar_ucl, \"ltype\": \"UCL\", \"color\": RED},\n    {\"chart\": CHART_XBAR, \"yintercept\": xbar_lcl, \"ltype\": \"LCL\", \"color\": RED},\n    {\"chart\": CHART_XBAR, \"yintercept\": xbar_bar, \"ltype\": \"CL\", \"color\": BLUE},\n    {\"chart\": CHART_XBAR, \"yintercept\": xbar_uwl, \"ltype\": \"UWL\", \"color\": AMBER},\n    {\"chart\": CHART_XBAR, \"yintercept\": xbar_lwl, \"ltype\": \"LWL\", \"color\": AMBER},\n    {\"chart\": CHART_R, \"yintercept\": r_ucl, \"ltype\": \"UCL\", \"color\": RED},\n    {\"chart\": CHART_R, \"yintercept\": r_bar, \"ltype\": \"CL\", \"color\": BLUE},\n    {\"chart\": CHART_R, \"yintercept\": r_uwl, \"ltype\": \"UWL\", \"color\": AMBER},\n    {\"chart\": CHART_R, \"yintercept\": r_lwl, \"ltype\": \"LWL\", \"color\": AMBER},\n]\nlimit_lines = pd.DataFrame(limit_rows)\nlimit_lines[\"chart\"] = pd.Categorical(limit_lines[\"chart\"], categories=chart_order, ordered=True)\n\n# R chart LCL at y=0 — rendered as dashed line only, no label\nr_lcl_row = pd.DataFrame([{\"chart\": CHART_R, \"yintercept\": r_lcl, \"ltype\": \"LCL\", \"color\": RED}])\nr_lcl_row[\"chart\"] = pd.Categorical(r_lcl_row[\"chart\"], categories=chart_order, ordered=True)\n\nall_lines = pd.concat([limit_lines, r_lcl_row], ignore_index=True)\ncl_lines = all_lines[all_lines[\"ltype\"] == \"CL\"]\nucl_lcl_lines = all_lines[all_lines[\"ltype\"].isin([\"UCL\", \"LCL\"])]\nwarn_lines = all_lines[all_lines[\"ltype\"].isin([\"UWL\", \"LWL\"])]\n\n# Right-edge labels with de-overlap logic to reduce crowding\nlabel_df = limit_lines.copy()\nlabel_df[\"sample\"] = n_samples + 0.5\n\nfor chart_name in chart_order:\n    mask = label_df[\"chart\"] == chart_name\n    chart_labels = label_df.loc[mask].sort_values(\"yintercept\")\n    vals = chart_labels[\"yintercept\"].values\n    if len(vals) < 2:\n        label_df.loc[chart_labels.index, \"y_label\"] = vals\n        continue\n    chart_range = vals[-1] - vals[0]\n    min_gap = chart_range * 0.30\n\n    adjusted = vals.copy().astype(float)\n    for i in range(1, len(adjusted)):\n        if adjusted[i] - adjusted[i - 1] < min_gap:\n            adjusted[i] = adjusted[i - 1] + min_gap\n    offset = np.mean(vals) - np.mean(adjusted)\n    adjusted += offset\n    label_df.loc[chart_labels.index, \"y_label\"] = adjusted\n\nlabel_df[\"fill\"] = ELEVATED_BG\n\n# Plot\nplot = (\n    ggplot(df, aes(x=\"sample\", y=\"value\"))\n    + geom_hline(aes(yintercept=\"yintercept\", color=\"color\"), data=cl_lines, size=1.2, linetype=\"solid\")\n    + geom_hline(aes(yintercept=\"yintercept\", color=\"color\"), data=ucl_lcl_lines, size=0.9, linetype=\"dashed\")\n    + geom_hline(aes(yintercept=\"yintercept\", color=\"color\"), data=warn_lines, size=0.6, linetype=\"dotted\", alpha=0.85)\n    + geom_line(color=TEAL, size=0.9, alpha=0.55)\n    + geom_point(aes(color=\"color\", size=\"point_size\", shape=\"point_shape\"), stroke=0.8)\n    + geom_label(\n        aes(x=\"sample\", y=\"y_label\", label=\"ltype\", color=\"color\", fill=\"fill\"),\n        data=label_df,\n        size=3,\n        ha=\"right\",\n        fontweight=\"bold\",\n        label_size=0,\n        alpha=0.9,\n    )\n    + scale_color_identity()\n    + scale_size_identity()\n    + scale_shape_identity()\n    + scale_fill_identity()\n    + scale_x_continuous(breaks=range(1, n_samples + 1, 2), limits=(-2.0, n_samples + 0.5))\n    + facet_wrap(\"chart\", ncol=1, scales=\"free_y\")\n    + labs(\n        x=\"Sample Number\",\n        y=\"Measurement (mm)\",\n        title=\"CNC Shaft Diameter Monitoring · spc-xbar-r · python · plotnine · anyplot.ai\",\n    )\n    + theme_minimal()\n    + theme(\n        figure_size=(8, 4.5),\n        text=element_text(size=7, color=INK),\n        axis_title=element_text(size=10, color=INK),\n        axis_text=element_text(size=8, color=INK_SOFT),\n        plot_title=element_text(size=12, weight=\"bold\", color=INK),\n        strip_text=element_text(size=9, weight=\"bold\", color=INK),\n        panel_grid_major_x=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_grid_major_y=element_line(color=INK, size=0.3, alpha=0.15),\n        panel_spacing_y=0.05,\n        axis_line=element_line(color=INK_SOFT, size=0.6),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        panel_border=element_rect(color=INK_SOFT, fill=None),\n        strip_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT, size=0.3),\n    )\n)\n\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\", verbose=False)\n"}