{"spec_id":"curve-oc","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\ncurve-oc: Operating Characteristic (OC) Curve\nLibrary: plotnine 0.15.7 | Python 3.13.14\nQuality: 91/100 | Updated: 2026-06-20\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    annotate,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_line,\n    geom_point,\n    geom_ribbon,\n    geom_vline,\n    ggplot,\n    guide_legend,\n    guides,\n    labs,\n    scale_color_manual,\n    scale_linetype_manual,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n    theme_minimal,\n)\nfrom scipy.stats import binom\n\n\n# Theme tokens — Imprint palette\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\"]\nZONE_ALPHA = 0.09 if THEME == \"light\" else 0.15\n\n# Data\nfraction_defective = np.linspace(0, 0.15, 200)\n\nsampling_plans = [\n    {\"n\": 75, \"c\": 2, \"label\": \"n=75, c=2\"},\n    {\"n\": 120, \"c\": 1, \"label\": \"n=120, c=1\"},\n    {\"n\": 200, \"c\": 2, \"label\": \"n=200, c=2\"},\n]\n\nrows = []\nfor plan in sampling_plans:\n    prob_accept = binom.cdf(plan[\"c\"], plan[\"n\"], fraction_defective)\n    for i, p in enumerate(fraction_defective):\n        rows.append({\"fraction_defective\": p, \"probability_acceptance\": prob_accept[i], \"plan\": plan[\"label\"]})\n\ndf = pd.DataFrame(rows)\nplan_order = [p[\"label\"] for p in sampling_plans]\ndf[\"plan\"] = pd.Categorical(df[\"plan\"], categories=plan_order, ordered=True)\n\n# Discrimination envelope between most lenient (n=75,c=2) and most strict (n=200,c=2)\nenvelope_df = df.pivot(index=\"fraction_defective\", columns=\"plan\", values=\"probability_acceptance\").reset_index()\nenvelope = pd.DataFrame(\n    {\n        \"fraction_defective\": envelope_df[\"fraction_defective\"],\n        \"ymin\": envelope_df[\"n=200, c=2\"],\n        \"ymax\": envelope_df[\"n=75, c=2\"],\n    }\n)\n\n# AQL and LTPD reference points\naql = 0.01\nltpd = 0.08\n\n# Risk metrics for reference plan (n=75, c=2)\nplan_ref = sampling_plans[0]\nalpha_risk = 1 - binom.cdf(plan_ref[\"c\"], plan_ref[\"n\"], aql)\nbeta_risk = binom.cdf(plan_ref[\"c\"], plan_ref[\"n\"], ltpd)\n\nrisk_points = pd.DataFrame(\n    [\n        {\"fraction_defective\": aql, \"probability_acceptance\": 1 - alpha_risk},\n        {\"fraction_defective\": ltpd, \"probability_acceptance\": beta_risk},\n    ]\n)\n\ncolors = {p[\"label\"]: c for p, c in zip(sampling_plans, IMPRINT[:3], strict=False)}\nlinetypes = {\"n=75, c=2\": \"solid\", \"n=120, c=1\": \"dashed\", \"n=200, c=2\": \"dashdot\"}\n\n# Plot\nplot = (\n    ggplot(df, aes(x=\"fraction_defective\", y=\"probability_acceptance\", color=\"plan\", linetype=\"plan\"))\n    # Shaded quality zones\n    + annotate(\"rect\", xmin=0, xmax=aql, ymin=0, ymax=1.05, fill=IMPRINT[0], alpha=ZONE_ALPHA)\n    + annotate(\"rect\", xmin=ltpd, xmax=0.15, ymin=0, ymax=1.05, fill=IMPRINT[4], alpha=ZONE_ALPHA)\n    # Discrimination envelope ribbon — shows range of plan discrimination power\n    + geom_ribbon(\n        aes(x=\"fraction_defective\", ymin=\"ymin\", ymax=\"ymax\"),\n        data=envelope,\n        inherit_aes=False,\n        fill=INK_MUTED,\n        alpha=0.06,\n    )\n    # AQL / LTPD reference lines\n    + geom_vline(xintercept=aql, linetype=\"dashed\", color=INK_SOFT, size=0.5, alpha=0.8)\n    + geom_vline(xintercept=ltpd, linetype=\"dashed\", color=INK_SOFT, size=0.5, alpha=0.8)\n    # OC curves\n    + geom_line(size=1.0, alpha=0.9)\n    # Producer's and consumer's risk markers on reference plan\n    + geom_point(\n        aes(x=\"fraction_defective\", y=\"probability_acceptance\"),\n        data=risk_points,\n        inherit_aes=False,\n        size=3,\n        color=IMPRINT[0],\n        fill=PAGE_BG,\n        stroke=1,\n        shape=\"o\",\n    )\n    # AQL / LTPD axis labels\n    + annotate(\"text\", x=aql + 0.002, y=0.05, label=\"AQL\", size=3.5, color=INK_MUTED, fontstyle=\"italic\")\n    + annotate(\"text\", x=ltpd + 0.002, y=0.11, label=\"LTPD\", size=3.5, color=INK_MUTED, fontstyle=\"italic\")\n    # Risk annotations\n    + annotate(\n        \"text\",\n        x=aql + 0.012,\n        y=1 - alpha_risk - 0.07,\n        label=f\"α = {alpha_risk:.2f}\",\n        size=3.2,\n        color=IMPRINT[0],\n        fontweight=\"bold\",\n    )\n    + annotate(\n        \"text\",\n        x=ltpd + 0.007,\n        y=beta_risk + 0.06,\n        label=f\"β = {beta_risk:.2f}\",\n        size=3.2,\n        color=IMPRINT[0],\n        fontweight=\"bold\",\n    )\n    + scale_color_manual(values=colors)\n    + scale_linetype_manual(values=linetypes)\n    + scale_x_continuous(\n        breaks=np.arange(0, 0.16, 0.02), labels=lambda lst: [f\"{v:.0%}\" for v in lst], limits=(0, 0.15)\n    )\n    + scale_y_continuous(breaks=np.arange(0, 1.1, 0.2), limits=(0, 1.05))\n    + labs(\n        x=\"Fraction Defective (p)\",\n        y=\"Probability of Acceptance P(a)\",\n        title=\"curve-oc · python · plotnine · anyplot.ai\",\n        color=\"Sampling Plan\",\n        linetype=\"Sampling Plan\",\n    )\n    + guides(color=guide_legend(override_aes={\"size\": 4, \"alpha\": 1}))\n    + theme_minimal()\n    + theme(\n        figure_size=(8, 4.5),\n        text=element_text(size=7, color=INK_SOFT),\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, color=INK),\n        legend_title=element_text(size=8, color=INK),\n        legend_text=element_text(size=8, color=INK_SOFT),\n        legend_position=(0.78, 0.78),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\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        axis_line=element_line(color=INK_SOFT, size=0.5),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\", verbose=False)\n"}