{"spec_id":"precision-recall","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nprecision-recall: Precision-Recall Curve\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-05-10\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    annotate,\n    element_line,\n    element_rect,\n    element_text,\n    geom_hline,\n    geom_step,\n    ggplot,\n    labs,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n    theme_minimal,\n)\n\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\"\nBRAND = \"#009E73\"  # Okabe-Ito position 1\nACCENT = \"#C475FD\"  # Okabe-Ito position 2 for baseline\n\n# Data - Simulated binary classification results\nnp.random.seed(42)\nn_samples = 500\n\n# Create realistic classification scenario (imbalanced - ~20% positive class)\ny_true = np.random.choice([0, 1], size=n_samples, p=[0.8, 0.2])\n\n# Generate scores: positive class gets higher scores on average\ny_scores = np.where(\n    y_true == 1,\n    np.clip(np.random.beta(5, 2, size=n_samples), 0, 1),\n    np.clip(np.random.beta(2, 5, size=n_samples), 0, 1),\n)\n\n# Calculate precision-recall curve\nsorted_indices = np.argsort(y_scores)[::-1]\ny_true_sorted = y_true[sorted_indices]\ntp_cumsum = np.cumsum(y_true_sorted)\ntotal_positives = y_true.sum()\nn_predictions = np.arange(1, len(y_true_sorted) + 1)\n\nprecision = tp_cumsum / n_predictions\nrecall = tp_cumsum / total_positives\n\n# Add start point (recall=0, precision=1)\nprecision = np.concatenate([[1], precision])\nrecall = np.concatenate([[0], recall])\n\n# Calculate average precision (area under PR curve)\nrecall_diff = np.diff(recall)\nap_score = np.sum(recall_diff * precision[1:])\n\n# Baseline (positive class ratio)\nbaseline = y_true.mean()\n\n# Create DataFrame for plotting\ndf = pd.DataFrame({\"Recall\": recall, \"Precision\": precision})\n\n# Plot\nplot = (\n    ggplot(df, aes(x=\"Recall\", y=\"Precision\"))\n    + geom_step(color=BRAND, size=2, direction=\"vh\")\n    + geom_hline(yintercept=baseline, linetype=\"dashed\", color=ACCENT, size=1.5)\n    + annotate(\n        \"text\",\n        x=0.95,\n        y=baseline + 0.05,\n        label=f\"Random Classifier (baseline = {baseline:.2f})\",\n        ha=\"right\",\n        size=14,\n        color=ACCENT,\n    )\n    + annotate(\n        \"rect\", xmin=0.55, xmax=0.95, ymin=0.75, ymax=0.95, fill=ELEVATED_BG, alpha=0.95, color=INK_SOFT, size=0.3\n    )\n    + annotate(\n        \"text\", x=0.75, y=0.85, label=f\"Average Precision (AP) = {ap_score:.3f}\", size=16, color=INK, fontweight=\"bold\"\n    )\n    + labs(\n        x=\"Recall (Sensitivity)\",\n        y=\"Precision (Positive Predictive Value)\",\n        title=\"precision-recall · plotnine · anyplot.ai\",\n    )\n    + scale_x_continuous(limits=(0, 1), breaks=[0, 0.2, 0.4, 0.6, 0.8, 1.0])\n    + scale_y_continuous(limits=(0, 1), breaks=[0, 0.2, 0.4, 0.6, 0.8, 1.0])\n    + theme_minimal()\n    + theme(\n        figure_size=(16, 9),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        panel_grid_major=element_line(color=INK, size=0.3, alpha=0.10),\n        panel_grid_minor=element_line(color=INK, size=0.2, alpha=0.05),\n        panel_border=element_rect(color=INK_SOFT, fill=None, size=0.3),\n        axis_title=element_text(size=20, color=INK),\n        axis_text=element_text(size=16, color=INK_SOFT),\n        axis_line=element_line(color=INK_SOFT, size=0.3),\n        text=element_text(size=14),\n        plot_title=element_text(size=24, color=INK, ha=\"center\"),\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=300, verbose=False)\n"}