{"spec_id":"precision-recall","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nprecision-recall: Precision-Recall Curve\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 81/100 | Updated: 2026-05-10\n\"\"\"\n\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n\n# Generate synthetic classification data with imbalanced classes\nnp.random.seed(42)\nn_samples = 500\n\n# Simulate imbalanced dataset: 20% positive, 80% negative\npositive_ratio = 0.2\nn_positive = int(n_samples * positive_ratio)\nn_negative = n_samples - n_positive\n\n# True labels\ny_true = np.concatenate([np.ones(n_positive), np.zeros(n_negative)])\n\n# Simulate classifier scores - good classifier gives higher scores to positives\npositive_scores = np.random.beta(5, 2, n_positive)  # Skewed higher\nnegative_scores = np.random.beta(2, 5, n_negative)  # Skewed lower\ny_scores = np.concatenate([positive_scores, negative_scores])\n\n# Calculate precision-recall curve\n# Sort by scores descending\ndesc_score_indices = np.argsort(y_scores)[::-1]\ny_scores_sorted = y_scores[desc_score_indices]\ny_true_sorted = y_true[desc_score_indices]\n\n# Get unique thresholds\ndistinct_value_indices = np.where(np.diff(y_scores_sorted))[0]\nthreshold_idxs = np.concatenate([[0], distinct_value_indices + 1])\n\n# Calculate TP, FP cumulative sums\ntps = np.cumsum(y_true_sorted)\nfps = np.cumsum(1 - y_true_sorted)\n\n# Calculate precision and recall at each threshold\nprecision_vals = tps / (tps + fps)\nrecall_vals = tps / tps[-1]\n\n# Use thresholds at distinct values (add starting point: recall=0, precision=1)\nprecision = np.concatenate([[1], precision_vals[threshold_idxs]])\nrecall = np.concatenate([[0], recall_vals[threshold_idxs]])\n\n# Calculate Average Precision (area under curve)\nrecall_diff = np.diff(recall)\naverage_precision = np.sum(precision[1:] * recall_diff)\n\n# Create plot\nfig, ax = plt.subplots(figsize=(16, 9))\n\n# Plot precision-recall curve with stepped line style\nax.step(\n    recall, precision, where=\"post\", color=\"#306998\", linewidth=3, label=f\"Classifier (AP = {average_precision:.3f})\"\n)\n\n# Fill area under curve for visual emphasis\nax.fill_between(recall, precision, step=\"post\", alpha=0.2, color=\"#306998\")\n\n# Baseline: random classifier (horizontal line at positive class ratio)\nax.axhline(\n    y=positive_ratio,\n    color=\"#FFD43B\",\n    linestyle=\"--\",\n    linewidth=2.5,\n    label=f\"Random Baseline (P = {positive_ratio:.0%})\",\n)\n\n# Iso-F1 curves\nf1_scores = np.linspace(0.2, 0.8, num=4)\nfor f1_score in f1_scores:\n    x = np.linspace(0.01, 1, 100)\n    y = f1_score * x / (2 * x - f1_score)\n    mask = (y >= 0) & (y <= 1) & (x >= f1_score / 2)\n    ax.plot(x[mask], y[mask], color=\"gray\", alpha=0.3, linewidth=1.5, linestyle=\":\")\n    # Add F1 label at end of curve\n    if np.any(mask):\n        label_x = x[mask][-1]\n        label_y = y[mask][-1]\n        ax.annotate(\n            f\"F1={f1_score:.1f}\", xy=(label_x, label_y), fontsize=12, color=\"gray\", alpha=0.7, ha=\"left\", va=\"bottom\"\n        )\n\n# Styling\nax.set_xlabel(\"Recall\", fontsize=20)\nax.set_ylabel(\"Precision\", fontsize=20)\nax.set_title(\"precision-recall · matplotlib · pyplots.ai\", fontsize=24)\nax.tick_params(axis=\"both\", labelsize=16)\nax.set_xlim([0.0, 1.0])\nax.set_ylim([0.0, 1.05])\nax.legend(loc=\"upper right\", fontsize=16)\nax.grid(True, alpha=0.3, linestyle=\"--\")\n\nplt.tight_layout()\nplt.savefig(\"plot.png\", dpi=300, bbox_inches=\"tight\")\n"}