{"spec_id":"curve-oc","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\ncurve-oc: Operating Characteristic (OC) Curve\nLibrary: seaborn 0.13.2 | Python 3.13.14\nQuality: 90/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\nfrom scipy.stats import binom\n\n\n# Theme tokens — Imprint palette 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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint palette — first series always #009E73\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\nANYPLOT_AMBER = \"#DDCC77\"  # warning / caution anchor for risk markers\n\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.15,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\nsns.set_context(\"notebook\", font_scale=1.0)\n\n# Data — Monte Carlo lot-acceptance simulation; seaborn estimates empirical mean + 95% CI\nnp.random.seed(42)\nN_SIMS = 5000  # runs per (plan, defect-level): large N → smooth curves + tight CI bands\n\nfraction_defective = np.linspace(0, 0.20, 50)\n\nplans = [\n    {\"n\": 50, \"c\": 1, \"label\": \"n=50, c=1\"},\n    {\"n\": 80, \"c\": 2, \"label\": \"n=80, c=2\"},\n    {\"n\": 100, \"c\": 2, \"label\": \"n=100, c=2\"},\n]\n\n# Long-format DataFrame: each row is one simulated lot-inspection outcome (0 = reject, 1 = accept)\nsim_dfs = []\nfor plan in plans:\n    outcomes = np.random.binomial(plan[\"n\"], fraction_defective[:, None], (len(fraction_defective), N_SIMS))\n    accepted = (outcomes <= plan[\"c\"]).astype(float)\n    sim_dfs.append(\n        pd.DataFrame(\n            {\n                \"Fraction Defective (p)\": np.repeat(fraction_defective, N_SIMS),\n                \"P(Accept)\": accepted.ravel(),\n                \"Sampling Plan\": plan[\"label\"],\n            }\n        )\n    )\n\ndf = pd.concat(sim_dfs, ignore_index=True)\n\naql = 0.02\nltpd = 0.10\n\n# Theoretical risk values (binomial CDF) for annotation anchors on the n=80/c=2 curve\nprob_at_aql = binom.cdf(plans[1][\"c\"], plans[1][\"n\"], aql)\nbeta_risk = binom.cdf(plans[1][\"c\"], plans[1][\"n\"], ltpd)\nalpha_risk = 1 - prob_at_aql\n\n# Plot\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# seaborn aggregates empirical runs and adds 95% CI shading — distinctive statistical feature\nsns.lineplot(\n    data=df,\n    x=\"Fraction Defective (p)\",\n    y=\"P(Accept)\",\n    hue=\"Sampling Plan\",\n    palette=IMPRINT_PALETTE[:3],\n    linewidth=2.5,\n    errorbar=(\"se\", 1.96),\n    ax=ax,\n)\n\n# AQL and LTPD reference lines\nax.axvline(x=aql, color=INK_SOFT, linestyle=\"--\", linewidth=1.0, alpha=0.6)\nax.axvline(x=ltpd, color=INK_SOFT, linestyle=\"--\", linewidth=1.0, alpha=0.6)\nax.text(aql + 0.002, 1.03, f\"AQL = {aql}\", fontsize=8, color=INK_MUTED, va=\"bottom\")\nax.text(ltpd + 0.002, 1.03, f\"LTPD = {ltpd}\", fontsize=8, color=INK_MUTED, va=\"bottom\")\n\n# Risk markers using amber (warning/caution anchor) at theoretical values\nax.plot(aql, prob_at_aql, \"o\", color=ANYPLOT_AMBER, markersize=6, zorder=5)\nax.plot(ltpd, beta_risk, \"o\", color=ANYPLOT_AMBER, markersize=6, zorder=5)\n\n# Annotation with filled background so text stands out against reference line dashes\nbbox_style = {\"boxstyle\": \"round,pad=0.25\", \"facecolor\": ELEVATED_BG, \"edgecolor\": INK_MUTED, \"alpha\": 0.9}\nax.annotate(\n    f\"Producer's risk\\nα = {alpha_risk:.3f}\",\n    xy=(aql, prob_at_aql),\n    xytext=(aql + 0.020, prob_at_aql - 0.10),\n    fontsize=8,\n    color=INK_SOFT,\n    arrowprops={\"arrowstyle\": \"->\", \"color\": INK_SOFT, \"lw\": 1.0},\n    bbox=bbox_style,\n)\n\nax.annotate(\n    f\"Consumer's risk\\nβ = {beta_risk:.3f}\",\n    xy=(ltpd, beta_risk),\n    xytext=(ltpd + 0.020, beta_risk + 0.13),\n    fontsize=8,\n    color=INK_SOFT,\n    arrowprops={\"arrowstyle\": \"->\", \"color\": INK_SOFT, \"lw\": 1.0},\n    bbox=bbox_style,\n)\n\n# Style\nax.set_title(\"curve-oc · python · seaborn · anyplot.ai\", fontsize=12, fontweight=\"medium\", color=INK)\nax.set_xlabel(\"Fraction Defective (p)\", fontsize=10, color=INK)\nax.set_ylabel(\"Probability of Acceptance P(a)\", fontsize=10, color=INK)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT, length=0)\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nax.spines[\"left\"].set_color(INK_SOFT)\nax.spines[\"bottom\"].set_color(INK_SOFT)\nax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)\nax.set_xlim(0, 0.20)\nax.set_ylim(0, 1.10)\n\n# Legend\nlegend = ax.get_legend()\nlegend.set_title(\"Sampling Plan\")\nplt.setp(legend.get_title(), fontsize=8, fontweight=\"medium\", color=INK)\nplt.setp(legend.get_texts(), fontsize=8, color=INK_SOFT)\nlegend.get_frame().set_facecolor(ELEVATED_BG)\nlegend.get_frame().set_edgecolor(INK_SOFT)\nlegend.get_frame().set_linewidth(0.5)\n\nplt.tight_layout()\n\n# Save — no bbox_inches='tight' to preserve exact 3200×1800 canvas\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}