{"spec_id":"gain-curve","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\ngain-curve: Cumulative Gains Chart\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 94/100 | Updated: 2026-05-11\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\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\"\nGRID = \"rgba(26,26,23,0.10)\" if THEME == \"light\" else \"rgba(240,239,232,0.10)\"\n\n# Okabe-Ito palette\nBRAND = \"#009E73\"  # First series — always\nBASELINE = \"#888888\"  # Neutral for reference\nPERFECT = \"#4467A3\"  # imprint blue — neutral reference for perfect model (red is reserved for semantic bad)\n\n# Data - Customer response model evaluation\nnp.random.seed(42)\nn_samples = 1000\n\n# Simulate a classification model with moderate discrimination\npositive_rate = 0.20\ny_true = np.random.binomial(1, positive_rate, n_samples)\n\n# Generate predicted scores that correlate with true labels\ny_score = np.where(\n    y_true == 1,\n    np.random.beta(5, 2, n_samples),  # Positives: skewed toward higher scores\n    np.random.beta(2, 5, n_samples),  # Negatives: skewed toward lower scores\n)\ny_score = np.clip(y_score + np.random.normal(0, 0.1, n_samples), 0, 1)\n\n# Calculate cumulative gains\nsorted_indices = np.argsort(y_score)[::-1]\ny_true_sorted = y_true[sorted_indices]\n\ncumulative_positives = np.cumsum(y_true_sorted)\ntotal_positives = y_true.sum()\n\npct_population = np.arange(1, n_samples + 1) / n_samples * 100\npct_positives_captured = cumulative_positives / total_positives * 100\n\n# Add origin point\npct_population = np.insert(pct_population, 0, 0)\npct_positives_captured = np.insert(pct_positives_captured, 0, 0)\n\n# Perfect model curve\npct_for_perfect = positive_rate * 100\nperfect_x = [0, pct_for_perfect, 100]\nperfect_y = [0, 100, 100]\n\n# Plot\nfig = go.Figure()\n\n# Random baseline (diagonal)\nfig.add_trace(\n    go.Scatter(\n        x=[0, 100],\n        y=[0, 100],\n        mode=\"lines\",\n        name=\"Random (Baseline)\",\n        line=dict(color=BASELINE, width=3, dash=\"dash\"),\n        hovertemplate=\"Baseline<br>Population: %{x:.1f}%<br>Positives: %{y:.1f}%<extra></extra>\",\n    )\n)\n\n# Perfect model\nfig.add_trace(\n    go.Scatter(\n        x=perfect_x,\n        y=perfect_y,\n        mode=\"lines\",\n        name=\"Perfect Model\",\n        line=dict(color=PERFECT, width=3, dash=\"dot\"),\n        hovertemplate=\"Perfect<br>Population: %{x:.1f}%<br>Positives: %{y:.1f}%<extra></extra>\",\n    )\n)\n\n# Model gains curve\nfig.add_trace(\n    go.Scatter(\n        x=pct_population,\n        y=pct_positives_captured,\n        mode=\"lines\",\n        name=\"Model\",\n        line=dict(color=BRAND, width=4),\n        fill=\"tozeroy\",\n        fillcolor=\"rgba(0, 158, 115, 0.15)\",\n        hovertemplate=\"Model<br>Population: %{x:.1f}%<br>Positives: %{y:.1f}%<extra></extra>\",\n    )\n)\n\n# Layout\nfig.update_layout(\n    title=dict(text=\"gain-curve · plotly · anyplot.ai\", font=dict(size=28, color=INK), x=0.5, xanchor=\"center\"),\n    xaxis=dict(\n        title=dict(text=\"Percentage of Population Targeted (%)\", font=dict(size=22, color=INK)),\n        tickfont=dict(size=18, color=INK_SOFT),\n        range=[0, 100],\n        dtick=20,\n        showgrid=True,\n        gridcolor=GRID,\n        linecolor=INK_SOFT,\n    ),\n    yaxis=dict(\n        title=dict(text=\"Percentage of Positives Captured (%)\", font=dict(size=22, color=INK)),\n        tickfont=dict(size=18, color=INK_SOFT),\n        range=[0, 100],\n        dtick=20,\n        showgrid=True,\n        gridcolor=GRID,\n        linecolor=INK_SOFT,\n    ),\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    legend=dict(\n        x=0.98,\n        y=0.02,\n        xanchor=\"right\",\n        yanchor=\"bottom\",\n        font=dict(size=18, color=INK_SOFT),\n        bgcolor=ELEVATED_BG,\n        bordercolor=INK_SOFT,\n        borderwidth=1,\n    ),\n    margin=dict(l=100, r=80, t=100, b=100),\n)\n\n# Save\nfig.write_image(f\"plot-{THEME}.png\", width=1600, height=900, scale=3)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}