Skip to content

Commit fd8d27f

Browse files
feat(matplotlib): implement lift-curve (#2384)
## Implementation: `lift-curve` - matplotlib Implements the **matplotlib** version of `lift-curve`. **File:** `plots/lift-curve/implementations/matplotlib.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20543281060)* --------- 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 745f27c commit fd8d27f

2 files changed

Lines changed: 122 additions & 0 deletions

File tree

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
""" pyplots.ai
2+
lift-curve: Model Lift Chart
3+
Library: matplotlib 3.10.8 | Python 3.13.11
4+
Quality: 93/100 | Created: 2025-12-27
5+
"""
6+
7+
import matplotlib.pyplot as plt
8+
import numpy as np
9+
10+
11+
# Data - Simulate realistic customer response model predictions
12+
np.random.seed(42)
13+
n_samples = 1000
14+
base_rate = 0.15 # 15% baseline response rate
15+
16+
# Generate true labels with base rate
17+
y_true = np.random.binomial(1, base_rate, n_samples)
18+
19+
# Generate model scores - correlated with true outcomes for realistic model
20+
# Good responders get higher scores, non-responders get lower scores
21+
y_score = np.where(
22+
y_true == 1,
23+
np.clip(np.random.beta(5, 2, n_samples), 0, 1), # Responders: higher scores
24+
np.clip(np.random.beta(2, 5, n_samples), 0, 1), # Non-responders: lower scores
25+
)
26+
27+
# Calculate lift curve
28+
# Sort by predicted score (descending)
29+
sorted_indices = np.argsort(y_score)[::-1]
30+
y_true_sorted = y_true[sorted_indices]
31+
32+
# Calculate cumulative response rate and lift
33+
n_total = len(y_true)
34+
n_positive = y_true.sum()
35+
baseline_rate = n_positive / n_total
36+
37+
# Calculate cumulative lift at each percentage
38+
percentages = np.arange(1, 101)
39+
lift_values = []
40+
41+
for pct in percentages:
42+
n_selected = int(np.ceil(n_total * pct / 100))
43+
n_responders = y_true_sorted[:n_selected].sum()
44+
response_rate = n_responders / n_selected
45+
lift = response_rate / baseline_rate
46+
lift_values.append(lift)
47+
48+
lift_values = np.array(lift_values)
49+
50+
# Create plot
51+
fig, ax = plt.subplots(figsize=(16, 9))
52+
53+
# Plot lift curve
54+
ax.plot(percentages, lift_values, color="#306998", linewidth=3, label="Model Lift", zorder=3)
55+
56+
# Reference line at y=1 (random selection)
57+
ax.axhline(y=1, color="#FFD43B", linestyle="--", linewidth=2.5, label="Random (Lift = 1)", zorder=2)
58+
59+
# Add markers at key deciles
60+
decile_pcts = [10, 20, 30, 40, 50]
61+
for pct in decile_pcts:
62+
idx = pct - 1
63+
ax.scatter(pct, lift_values[idx], color="#306998", s=150, zorder=4, edgecolors="white", linewidth=2)
64+
ax.annotate(
65+
f"{lift_values[idx]:.2f}x",
66+
(pct, lift_values[idx]),
67+
xytext=(0, 15),
68+
textcoords="offset points",
69+
ha="center",
70+
fontsize=14,
71+
fontweight="bold",
72+
color="#306998",
73+
)
74+
75+
# Fill area under curve for visual emphasis
76+
ax.fill_between(percentages, 1, lift_values, where=(lift_values > 1), alpha=0.15, color="#306998", zorder=1)
77+
78+
# Styling
79+
ax.set_xlabel("Population Targeted (%)", fontsize=20)
80+
ax.set_ylabel("Cumulative Lift", fontsize=20)
81+
ax.set_title("lift-curve · matplotlib · pyplots.ai", fontsize=24)
82+
ax.tick_params(axis="both", labelsize=16)
83+
84+
# Set axis limits
85+
ax.set_xlim(0, 100)
86+
ax.set_ylim(0, max(lift_values) * 1.15)
87+
88+
# Grid
89+
ax.grid(True, alpha=0.3, linestyle="--", zorder=0)
90+
91+
# Legend
92+
ax.legend(fontsize=16, loc="upper right")
93+
94+
plt.tight_layout()
95+
plt.savefig("plot.png", dpi=300, bbox_inches="tight")
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
library: matplotlib
2+
specification_id: lift-curve
3+
created: '2025-12-27T19:20:38Z'
4+
updated: '2025-12-27T19:23:10Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20543281060
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: 3.10.8
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/lift-curve/matplotlib/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/lift-curve/matplotlib/plot_thumb.png
12+
preview_html: null
13+
quality_score: 93
14+
review:
15+
strengths:
16+
- Excellent implementation of lift curve with proper calculation methodology (sort
17+
by score, cumulative lift at each percentage)
18+
- Clear visual distinction between model lift curve and random baseline with appropriate
19+
colors
20+
- Well-placed decile annotations that show practical lift values at key targeting
21+
thresholds
22+
- Shaded area between curves provides intuitive visualization of model value
23+
- Realistic customer response data with appropriate base rate and sample size
24+
- Clean, well-structured code following KISS principles
25+
weaknesses:
26+
- Y-axis label could include units or clarification (e.g., Cumulative Lift (ratio))
27+
- Does not leverage distinctive matplotlib features beyond basic plotting capabilities

0 commit comments

Comments
 (0)