Model Lift Chart — Seaborn

A lift curve visualizes how much better a predictive model performs compared to random selection, showing the cumulative lift ratio as you target increasing percentages of the population. It answers the question: "If I target the top X% of predictions, how many times more responders will I capture than random targeting?" This plot is essential for evaluating and comparing classification models in scenarios where targeting efficiency matters.

Model Lift Chart rendered with Seaborn

Python source (Seaborn)

""" anyplot.ai
lift-curve: Model Lift Chart
Library: seaborn 0.13.2 | Python 3.13.13
Quality: 90/100 | Updated: 2026-05-10
"""

import os

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns


# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
RULE = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)"

# Okabe-Ito colors
BRAND = "#009E73"  # bluish green - first series
SECONDARY = "#C475FD"  # vermillion

# Set seaborn theme with theme-adaptive colors
sns.set_theme(
    style="ticks",
    rc={
        "figure.facecolor": PAGE_BG,
        "axes.facecolor": PAGE_BG,
        "axes.edgecolor": INK_SOFT,
        "axes.labelcolor": INK,
        "text.color": INK,
        "xtick.color": INK_SOFT,
        "ytick.color": INK_SOFT,
        "grid.color": INK,
        "grid.alpha": 0.10,
        "legend.facecolor": ELEVATED_BG,
        "legend.edgecolor": INK_SOFT,
    },
)

# Data - simulate customer response prediction for marketing campaign
np.random.seed(42)
n_samples = 1000
base_response_rate = 0.10  # 10% overall response rate

# Create a model with good predictive power
latent_propensity = np.random.normal(0, 1, n_samples)

# Model score approximates the latent propensity with some noise
y_score = latent_propensity + np.random.normal(0, 0.3, n_samples)
y_score = (y_score - y_score.min()) / (y_score.max() - y_score.min())

# Actual responses based on latent propensity (strong correlation)
response_threshold = np.percentile(latent_propensity, 100 * (1 - base_response_rate))
y_true = (latent_propensity >= response_threshold).astype(int)

# Calculate lift curve data
sorted_indices = np.argsort(y_score)[::-1]
y_true_sorted = y_true[sorted_indices]

n_positives = y_true.sum()
cumulative_positives = np.cumsum(y_true_sorted)
population_percentages = np.arange(1, n_samples + 1) / n_samples * 100

cumulative_positive_rate = cumulative_positives / np.arange(1, n_samples + 1)
baseline_rate = n_positives / n_samples
lift = cumulative_positive_rate / baseline_rate

# Create dataframe for seaborn
df = pd.DataFrame({"Population Targeted (%)": population_percentages, "Cumulative Lift": lift})

# Create plot
fig, ax = plt.subplots(figsize=(16, 9), facecolor=PAGE_BG)

# Plot lift curve using seaborn lineplot
sns.lineplot(
    data=df, x="Population Targeted (%)", y="Cumulative Lift", ax=ax, color=BRAND, linewidth=3, label="Model Lift"
)

# Add baseline reference line (random selection = lift of 1)
ax.axhline(y=1, color=INK_SOFT, linestyle="--", linewidth=2.5, label="Random (No Lift)", zorder=3)

# Add decile markers with improved spacing
decile_percentages = [10, 20, 30, 40, 50]
for pct in decile_percentages:
    idx = int(n_samples * pct / 100) - 1
    pop_pct = population_percentages[idx]
    lift_val = lift[idx]
    ax.plot(pop_pct, lift_val, "o", color=BRAND, markersize=12, zorder=5)

    # Alternate annotation positions to avoid cramping
    offset_y = 25 if pct % 20 == 10 else 10
    ax.annotate(
        f"{lift_val:.2f}x",
        (pop_pct, lift_val),
        textcoords="offset points",
        xytext=(0, offset_y),
        ha="center",
        fontsize=14,
        fontweight="bold",
        color=INK,
    )

# Styling
ax.set_xlabel("Population Targeted (%)", fontsize=20, color=INK)
ax.set_ylabel("Cumulative Lift Ratio", fontsize=20, color=INK)
ax.set_title("lift-curve · seaborn · anyplot.ai", fontsize=24, fontweight="bold", color=INK)
ax.tick_params(axis="both", labelsize=16, colors=INK_SOFT)

# Remove top and right spines
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
for s in ("left", "bottom"):
    ax.spines[s].set_color(INK_SOFT)

# Subtle grid
ax.yaxis.grid(True, alpha=0.10, linewidth=0.8, color=INK)

# Set axis limits
ax.set_xlim(0, 100)
ax.set_ylim(0, max(lift) * 1.1)

# Legend styling
legend = ax.legend(fontsize=16, loc="upper right", framealpha=0.95)
legend.get_frame().set_facecolor(ELEVATED_BG)
legend.get_frame().set_edgecolor(INK_SOFT)

# Tight layout
plt.tight_layout()
plt.savefig(f"plot-{THEME}.png", dpi=300, bbox_inches="tight", facecolor=PAGE_BG)

Part of Model Lift Chart on anyplot.ai.

Other implementations