{"spec_id":"precision-recall","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nprecision-recall: Precision-Recall Curve\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-10\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pygal\nfrom pygal.style import Style\nfrom sklearn.datasets import make_classification\nfrom sklearn.ensemble import GradientBoostingClassifier\nfrom sklearn.metrics import average_precision_score, precision_recall_curve\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.svm import SVC\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\nIMPRINT = (\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\")\n\n# Data - Generate balanced binary classification dataset\nnp.random.seed(42)\nX, y = make_classification(\n    n_samples=2000,\n    n_features=20,\n    n_informative=10,\n    n_redundant=5,\n    n_classes=2,\n    weights=[0.5, 0.5],  # Balanced: 50% class 0, 50% class 1\n    flip_y=0.05,\n    random_state=42,\n)\n\n# Split into train and test sets\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)\n\n# Train two classifiers for comparison\n# Support Vector Machine\nsvm_model = SVC(kernel=\"rbf\", probability=True, random_state=42, gamma=\"scale\")\nsvm_model.fit(X_train, y_train)\nsvm_scores = svm_model.predict_proba(X_test)[:, 1]\nsvm_precision, svm_recall, _ = precision_recall_curve(y_test, svm_scores)\nsvm_ap = average_precision_score(y_test, svm_scores)\n\n# Gradient Boosting\ngb_model = GradientBoostingClassifier(n_estimators=50, max_depth=5, random_state=42)\ngb_model.fit(X_train, y_train)\ngb_scores = gb_model.predict_proba(X_test)[:, 1]\ngb_precision, gb_recall, _ = precision_recall_curve(y_test, gb_scores)\ngb_ap = average_precision_score(y_test, gb_scores)\n\n# Baseline (random classifier) - horizontal line at positive class ratio\nbaseline = np.mean(y_test)\n\n# Custom style for large canvas (4800x2700)\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=IMPRINT,\n    title_font_size=28,\n    label_font_size=22,\n    major_label_font_size=18,\n    legend_font_size=16,\n    value_font_size=14,\n    stroke_width=3,\n)\n\n# Create XY chart for precision-recall curve\nchart = pygal.XY(\n    width=4800,\n    height=2700,\n    title=\"precision-recall · pygal · anyplot.ai\",\n    x_title=\"Recall\",\n    y_title=\"Precision\",\n    style=custom_style,\n    show_dots=False,\n    stroke_style={\"width\": 4},\n    show_x_guides=True,\n    show_y_guides=True,\n    legend_at_bottom=True,\n    truncate_legend=-1,\n    range=(0, 1),\n    xrange=(0, 1),\n    show_minor_x_labels=False,\n    show_minor_y_labels=False,\n)\n\n# Downsample curves for cleaner visualization\nn_points = 100\nsvm_indices = np.linspace(0, len(svm_recall) - 1, n_points, dtype=int)\nsvm_recall_ds = svm_recall[svm_indices]\nsvm_precision_ds = svm_precision[svm_indices]\n\ngb_indices = np.linspace(0, len(gb_recall) - 1, n_points, dtype=int)\ngb_recall_ds = gb_recall[gb_indices]\ngb_precision_ds = gb_precision[gb_indices]\n\n# Create stepped data points for threshold-based visualization\nsvm_stepped_points = []\nfor i in range(len(svm_recall_ds)):\n    if i > 0:\n        svm_stepped_points.append((svm_recall_ds[i], svm_precision_ds[i - 1]))\n    svm_stepped_points.append((svm_recall_ds[i], svm_precision_ds[i]))\n\ngb_stepped_points = []\nfor i in range(len(gb_recall_ds)):\n    if i > 0:\n        gb_stepped_points.append((gb_recall_ds[i], gb_precision_ds[i - 1]))\n    gb_stepped_points.append((gb_recall_ds[i], gb_precision_ds[i]))\n\n# Add curves\nchart.add(f\"SVM (AP={svm_ap:.3f})\", svm_stepped_points)\nchart.add(f\"Gradient Boosting (AP={gb_ap:.3f})\", gb_stepped_points)\n\n# Add baseline\nbaseline_points = [(0, baseline), (1, baseline)]\nchart.add(f\"Random Baseline ({baseline:.2f})\", baseline_points, stroke_dasharray=\"10,5\")\n\n# Save as PNG and HTML\nchart.render_to_png(f\"plot-{THEME}.png\")\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}