{"spec_id":"curve-oc","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\ncurve-oc: Operating Characteristic (OC) Curve\nLibrary: letsplot 4.10.1 | Python 3.13.14\nQuality: 90/100 | Updated: 2026-06-20\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import *\nfrom scipy.stats import binom\n\n\nLetsPlot.setup_html()\n\n# Theme tokens\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 — canonical order, first 4 positions for the 4 OC curves\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\n# Data — OC curves for acceptance sampling plans\nfraction_defective = np.linspace(0, 0.20, 200)\nplans = [(50, 1, \"n=50, c=1\"), (100, 2, \"n=100, c=2\"), (80, 1, \"n=80, c=1\"), (150, 3, \"n=150, c=3\")]\naql = 0.02\nltpd = 0.10\n\nrows = []\nfor n, c, label in plans:\n    pa = binom.cdf(c, n, fraction_defective)\n    for p, prob in zip(fraction_defective, pa):\n        rows.append({\"fraction_defective\": p, \"probability_acceptance\": prob, \"plan\": label})\n\ndf = pd.DataFrame(rows)\n\n# Risk points for primary plan (n=50, c=1)\npa_aql = float(binom.cdf(1, 50, aql))\npa_ltpd = float(binom.cdf(1, 50, ltpd))\nalpha_risk = 1 - pa_aql\nbeta_risk = pa_ltpd\n\ndf_risk = pd.DataFrame(\n    {\"x\": [aql, ltpd], \"y\": [pa_aql, pa_ltpd], \"label\": [f\"α = {alpha_risk:.3f}\", f\"β = {beta_risk:.3f}\"]}\n)\n\n# Quality zone backgrounds — Imprint semantic colors (green/ochre/red) at low alpha\nzone_fills = [\"#009E73\", \"#BD8233\", \"#AE3030\"]\nzone_alpha = 0.08 if THEME == \"light\" else 0.14\n\ndf_zones = pd.DataFrame(\n    {\n        \"xmin\": [0.0, aql, ltpd],\n        \"xmax\": [aql, ltpd, 0.20],\n        \"ymin\": [0.0, 0.0, 0.0],\n        \"ymax\": [1.0, 1.0, 1.0],\n        \"fill\": zone_fills,\n        \"zone\": [\"Acceptable\\nQuality\", \"Indifference\\nZone\", \"Rejectable\\nQuality\"],\n        \"lx\": [0.01, 0.06, 0.15],\n    }\n)\n\n# Title length-aware fontsize scaling\ntitle = \"curve-oc · python · letsplot · anyplot.ai\"\nn_chars = len(title)\nratio = 67 / n_chars if n_chars > 67 else 1.0\ntitle_size = max(11, round(16 * ratio))\n\nplot = (\n    ggplot()\n    # Quality zone shading\n    + geom_rect(\n        data=df_zones,\n        mapping=aes(xmin=\"xmin\", xmax=\"xmax\", ymin=\"ymin\", ymax=\"ymax\", fill=\"fill\"),\n        alpha=zone_alpha,\n        color=\"rgba(0,0,0,0)\",\n    )\n    + scale_fill_identity()\n    # Zone labels\n    + geom_text(data=df_zones, mapping=aes(x=\"lx\", label=\"zone\"), y=0.5, size=8, color=INK_MUTED, fontface=\"italic\")\n    # Vertical reference lines (geom_vline — idiomatic)\n    + geom_vline(xintercept=aql, linetype=\"dotted\", color=INK_SOFT, size=0.7)\n    + geom_vline(xintercept=ltpd, linetype=\"dotted\", color=INK_SOFT, size=0.7)\n    # Horizontal risk reference lines (geom_hline — idiomatic)\n    + geom_hline(yintercept=pa_aql, linetype=\"dashed\", color=INK_SOFT, size=0.5)\n    + geom_hline(yintercept=pa_ltpd, linetype=\"dashed\", color=INK_SOFT, size=0.5)\n    # OC curves with formatted tooltips\n    + geom_line(\n        data=df,\n        mapping=aes(x=\"fraction_defective\", y=\"probability_acceptance\", color=\"plan\"),\n        size=2.2,\n        tooltips=layer_tooltips()\n        .format(\"fraction_defective\", \".3f\")\n        .format(\"probability_acceptance\", \".3f\")\n        .line(\"@plan\")\n        .line(\"p = @fraction_defective\")\n        .line(\"P(accept) = @probability_acceptance\"),\n    )\n    # Risk point markers — Imprint semantic red for risk/error role\n    + geom_point(data=df_risk, mapping=aes(x=\"x\", y=\"y\"), size=7, shape=21, fill=\"#AE3030\", color=PAGE_BG, stroke=2.5)\n    # Alpha risk label\n    + geom_text(\n        data=df_risk.iloc[:1],\n        mapping=aes(x=\"x\", y=\"y\", label=\"label\"),\n        size=9,\n        color=\"#AE3030\",\n        fontface=\"bold\",\n        nudge_x=0.015,\n        nudge_y=0.05,\n    )\n    # Beta risk label\n    + geom_text(\n        data=df_risk.iloc[1:2],\n        mapping=aes(x=\"x\", y=\"y\", label=\"label\"),\n        size=9,\n        color=\"#AE3030\",\n        fontface=\"bold\",\n        nudge_x=0.025,\n        nudge_y=0.03,\n    )\n    # AQL label\n    + geom_text(\n        data=pd.DataFrame({\"x\": [aql], \"label\": [\"AQL\"]}),\n        mapping=aes(x=\"x\", label=\"label\"),\n        y=0.08,\n        size=10,\n        color=INK,\n        fontface=\"bold\",\n    )\n    # LTPD label\n    + geom_text(\n        data=pd.DataFrame({\"x\": [ltpd], \"label\": [\"LTPD\"]}),\n        mapping=aes(x=\"x\", label=\"label\"),\n        y=0.12,\n        size=10,\n        color=INK,\n        fontface=\"bold\",\n    )\n    + scale_color_manual(values=IMPRINT_PALETTE[:4])\n    + scale_x_continuous(breaks=[0.0, 0.02, 0.04, 0.06, 0.08, 0.10, 0.12, 0.14, 0.16, 0.18, 0.20], limits=[0.0, 0.20])\n    + scale_y_continuous(limits=[0.0, 1.05], breaks=[0.0, 0.2, 0.4, 0.6, 0.8, 1.0])\n    + labs(\n        x=\"Fraction Defective (p)\",\n        y=\"Probability of Acceptance P(a)\",\n        title=title,\n        subtitle=\"Comparing acceptance sampling plans — producer's & consumer's risk at AQL=0.02, LTPD=0.10\",\n        color=\"Sampling Plan\",\n    )\n    + theme(\n        axis_title=element_text(size=20, color=INK),\n        axis_text=element_text(size=16, color=INK_SOFT),\n        plot_title=element_text(size=title_size, color=INK, face=\"bold\"),\n        plot_subtitle=element_text(size=16, color=INK_SOFT, face=\"italic\"),\n        legend_text=element_text(size=16, color=INK_SOFT),\n        legend_title=element_text(size=18, face=\"bold\", color=INK),\n        panel_grid_major_x=element_blank(),\n        panel_grid_minor_x=element_blank(),\n        panel_grid_major_y=element_line(color=INK_SOFT, size=0.3),\n        panel_grid_minor_y=element_blank(),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        axis_line_x=element_line(color=INK_SOFT, size=0.8),\n        axis_line_y=element_line(color=INK_SOFT, size=0.8),\n        axis_ticks=element_line(color=INK_SOFT, size=0.4),\n        legend_position=[0.82, 0.72],\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT, size=0.5),\n        plot_margin=[30, 30, 30, 20],\n    )\n    + ggsize(800, 450)\n)\n\n# Save\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}