Skip to content

Commit 1a5b0d1

Browse files
feat(seaborn): implement lift-curve (#2388)
## Implementation: `lift-curve` - seaborn Implements the **seaborn** version of `lift-curve`. **File:** `plots/lift-curve/implementations/seaborn.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20543281168)* --------- 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 df9926a commit 1a5b0d1

2 files changed

Lines changed: 131 additions & 0 deletions

File tree

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
""" pyplots.ai
2+
lift-curve: Model Lift Chart
3+
Library: seaborn 0.13.2 | Python 3.13.11
4+
Quality: 92/100 | Created: 2025-12-27
5+
"""
6+
7+
import matplotlib.pyplot as plt
8+
import numpy as np
9+
import pandas as pd
10+
import seaborn as sns
11+
12+
13+
# Set seaborn style
14+
sns.set_theme(style="whitegrid")
15+
16+
# Data - simulate customer response prediction for marketing campaign
17+
np.random.seed(42)
18+
n_samples = 1000
19+
base_response_rate = 0.10 # 10% overall response rate
20+
21+
# Create a model with good predictive power
22+
# True propensity is a latent variable
23+
latent_propensity = np.random.normal(0, 1, n_samples)
24+
25+
# Model score approximates the latent propensity with some noise
26+
y_score = latent_propensity + np.random.normal(0, 0.3, n_samples)
27+
y_score = (y_score - y_score.min()) / (y_score.max() - y_score.min()) # Normalize to 0-1
28+
29+
# Actual responses based on latent propensity (strong correlation)
30+
response_threshold = np.percentile(latent_propensity, 100 * (1 - base_response_rate))
31+
y_true = (latent_propensity >= response_threshold).astype(int)
32+
33+
# Calculate lift curve data
34+
# Sort by predicted scores descending
35+
sorted_indices = np.argsort(y_score)[::-1]
36+
y_true_sorted = y_true[sorted_indices]
37+
38+
# Calculate cumulative gains
39+
n_positives = y_true.sum()
40+
cumulative_positives = np.cumsum(y_true_sorted)
41+
population_percentages = np.arange(1, n_samples + 1) / n_samples * 100
42+
43+
# Calculate lift: (cumulative positive rate) / (overall positive rate)
44+
cumulative_positive_rate = cumulative_positives / np.arange(1, n_samples + 1)
45+
baseline_rate = n_positives / n_samples
46+
lift = cumulative_positive_rate / baseline_rate
47+
48+
# Create dataframe for seaborn
49+
df = pd.DataFrame({"Population Targeted (%)": population_percentages, "Cumulative Lift": lift})
50+
51+
# Create plot (16:9 landscape format - 4800x2700 at 300 dpi)
52+
fig, ax = plt.subplots(figsize=(16, 9))
53+
54+
# Plot lift curve using seaborn lineplot
55+
sns.lineplot(
56+
data=df,
57+
x="Population Targeted (%)",
58+
y="Cumulative Lift",
59+
ax=ax,
60+
color="#306998", # Python Blue
61+
linewidth=3,
62+
label="Model Lift",
63+
)
64+
65+
# Add baseline reference line (random selection = lift of 1)
66+
ax.axhline(y=1, color="#FFD43B", linestyle="--", linewidth=2.5, label="Random (No Lift)", zorder=3)
67+
68+
# Add decile markers
69+
decile_indices = [int(n_samples * p / 100) - 1 for p in [10, 20, 30, 40, 50]]
70+
for idx in decile_indices:
71+
pct = population_percentages[idx]
72+
lift_val = lift[idx]
73+
ax.plot(pct, lift_val, "o", color="#306998", markersize=12, zorder=5)
74+
ax.annotate(
75+
f"{lift_val:.2f}x",
76+
(pct, lift_val),
77+
textcoords="offset points",
78+
xytext=(0, 15),
79+
ha="center",
80+
fontsize=14,
81+
fontweight="bold",
82+
color="#306998",
83+
)
84+
85+
# Styling
86+
ax.set_xlabel("Population Targeted (%)", fontsize=20)
87+
ax.set_ylabel("Cumulative Lift", fontsize=20)
88+
ax.set_title("lift-curve · seaborn · pyplots.ai", fontsize=24, fontweight="bold")
89+
ax.tick_params(axis="both", labelsize=16)
90+
91+
# Configure grid
92+
ax.grid(True, alpha=0.3, linestyle="--")
93+
94+
# Set axis limits
95+
ax.set_xlim(0, 100)
96+
ax.set_ylim(0, max(lift) * 1.15)
97+
98+
# Legend
99+
ax.legend(fontsize=16, loc="upper right")
100+
101+
# Tight layout
102+
plt.tight_layout()
103+
plt.savefig("plot.png", dpi=300, bbox_inches="tight")
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
library: seaborn
2+
specification_id: lift-curve
3+
created: '2025-12-27T19:20:51Z'
4+
updated: '2025-12-27T19:26:29Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20543281168
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: 0.13.2
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/lift-curve/seaborn/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/lift-curve/seaborn/plot_thumb.png
12+
preview_html: null
13+
quality_score: 92
14+
review:
15+
strengths:
16+
- Excellent visual clarity with appropriate font sizes and line widths for high-resolution
17+
output
18+
- Proper lift curve calculation methodology with clear documentation in comments
19+
- Good use of decile markers with lift value annotations to highlight key insights
20+
- Reference baseline at y=1 clearly distinguishes model performance from random
21+
selection
22+
- Realistic marketing campaign scenario with appropriate 10% base response rate
23+
- Clean, well-organized code following KISS principles
24+
weaknesses:
25+
- Seaborn is used minimally - only for sns.lineplot and theming; the core visualization
26+
could be done entirely in matplotlib
27+
- First decile annotation appears slightly cramped near the top of the curve
28+
- Y-axis label could clarify that lift is a ratio

0 commit comments

Comments
 (0)