Skip to content

Commit e502d82

Browse files
feat(plotly): implement gain-curve (#2492)
## Implementation: `gain-curve` - plotly Implements the **plotly** version of `gain-curve`. **File:** `plots/gain-curve/implementations/plotly.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20584841141)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent ff63f57 commit e502d82

2 files changed

Lines changed: 146 additions & 0 deletions

File tree

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
""" pyplots.ai
2+
gain-curve: Cumulative Gains Chart
3+
Library: plotly 6.5.0 | Python 3.13.11
4+
Quality: 92/100 | Created: 2025-12-29
5+
"""
6+
7+
import numpy as np
8+
import plotly.graph_objects as go
9+
10+
11+
# Data - Customer response model evaluation
12+
np.random.seed(42)
13+
n_samples = 1000
14+
15+
# Simulate a classification model with moderate discrimination
16+
# Positive class ~20% of population
17+
positive_rate = 0.20
18+
y_true = np.random.binomial(1, positive_rate, n_samples)
19+
20+
# Generate predicted scores that correlate with true labels
21+
# Good predictions: positives tend to have higher scores
22+
y_score = np.where(
23+
y_true == 1,
24+
np.random.beta(5, 2, n_samples), # Positives: skewed toward higher scores
25+
np.random.beta(2, 5, n_samples), # Negatives: skewed toward lower scores
26+
)
27+
# Add some noise to make it realistic
28+
y_score = np.clip(y_score + np.random.normal(0, 0.1, n_samples), 0, 1)
29+
30+
# Calculate cumulative gains
31+
sorted_indices = np.argsort(y_score)[::-1] # Sort by score descending
32+
y_true_sorted = y_true[sorted_indices]
33+
34+
# Cumulative sum of positives
35+
cumulative_positives = np.cumsum(y_true_sorted)
36+
total_positives = y_true.sum()
37+
38+
# Percentage of population (x-axis)
39+
pct_population = np.arange(1, n_samples + 1) / n_samples * 100
40+
41+
# Percentage of positives captured (y-axis)
42+
pct_positives_captured = cumulative_positives / total_positives * 100
43+
44+
# Add origin point for complete curve
45+
pct_population = np.insert(pct_population, 0, 0)
46+
pct_positives_captured = np.insert(pct_positives_captured, 0, 0)
47+
48+
# Perfect model curve
49+
pct_for_perfect = positive_rate * 100
50+
perfect_x = [0, pct_for_perfect, 100]
51+
perfect_y = [0, 100, 100]
52+
53+
# Plot
54+
fig = go.Figure()
55+
56+
# Random baseline (diagonal)
57+
fig.add_trace(
58+
go.Scatter(
59+
x=[0, 100], y=[0, 100], mode="lines", name="Random (Baseline)", line=dict(color="#888888", width=3, dash="dash")
60+
)
61+
)
62+
63+
# Perfect model
64+
fig.add_trace(
65+
go.Scatter(
66+
x=perfect_x, y=perfect_y, mode="lines", name="Perfect Model", line=dict(color="#FFD43B", width=3, dash="dot")
67+
)
68+
)
69+
70+
# Model gains curve
71+
fig.add_trace(
72+
go.Scatter(
73+
x=pct_population,
74+
y=pct_positives_captured,
75+
mode="lines",
76+
name="Model",
77+
line=dict(color="#306998", width=4),
78+
fill="tonexty",
79+
fillcolor="rgba(48, 105, 152, 0.2)",
80+
)
81+
)
82+
83+
# Layout
84+
fig.update_layout(
85+
title=dict(text="gain-curve · plotly · pyplots.ai", font=dict(size=28), x=0.5, xanchor="center"),
86+
xaxis=dict(
87+
title=dict(text="Percentage of Population Targeted (%)", font=dict(size=22)),
88+
tickfont=dict(size=18),
89+
range=[0, 100],
90+
dtick=20,
91+
showgrid=True,
92+
gridcolor="rgba(0, 0, 0, 0.1)",
93+
),
94+
yaxis=dict(
95+
title=dict(text="Percentage of Positives Captured (%)", font=dict(size=22)),
96+
tickfont=dict(size=18),
97+
range=[0, 100],
98+
dtick=20,
99+
showgrid=True,
100+
gridcolor="rgba(0, 0, 0, 0.1)",
101+
),
102+
template="plotly_white",
103+
legend=dict(
104+
x=0.98,
105+
y=0.02,
106+
xanchor="right",
107+
yanchor="bottom",
108+
font=dict(size=18),
109+
bgcolor="rgba(255, 255, 255, 0.8)",
110+
bordercolor="rgba(0, 0, 0, 0.2)",
111+
borderwidth=1,
112+
),
113+
margin=dict(l=80, r=40, t=80, b=80),
114+
)
115+
116+
# Save
117+
fig.write_image("plot.png", width=1600, height=900, scale=3)
118+
fig.write_html("plot.html", include_plotlyjs="cdn")
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
library: plotly
2+
specification_id: gain-curve
3+
created: '2025-12-29T23:20:47Z'
4+
updated: '2025-12-29T23:23:00Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20584841141
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: 6.5.0
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/gain-curve/plotly/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/gain-curve/plotly/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/gain-curve/plotly/plot.html
13+
quality_score: 92
14+
review:
15+
strengths:
16+
- Excellent implementation of cumulative gains chart with all three required curves
17+
(model, random baseline, perfect model)
18+
- Clear, readable text sizing following the library guidelines (28pt title, 22pt
19+
labels, 18pt ticks)
20+
- Good use of Plotly fill feature to highlight the gain over baseline
21+
- Well-structured code with clear data simulation using beta distributions for realistic
22+
model scores
23+
- Correct title format and professional appearance
24+
weaknesses:
25+
- Legend placement in lower-right corner has minor overlap with the baseline diagonal
26+
line
27+
- Could better leverage Plotly interactive features (hover tooltips with specific
28+
values, annotations for key thresholds)

0 commit comments

Comments
 (0)