|
| 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") |
0 commit comments