{"spec_id":"lift-curve","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nlift-curve: Model Lift Chart\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 89/100 | Updated: 2026-05-10\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    element_line,\n    element_rect,\n    element_text,\n    geom_hline,\n    geom_line,\n    geom_point,\n    ggplot,\n    labs,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n    theme_minimal,\n)\n\n\n# Theme colors\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\n\n# Okabe-Ito palette - first series is brand color\nBRAND = \"#009E73\"\nREFERENCE_LINE = INK_SOFT\n\n# Data - simulated customer response model scores\nnp.random.seed(42)\nn_samples = 1000\n\n# Generate realistic response probabilities\nbase_prob = 0.15  # 15% baseline response rate\nmodel_score = np.random.beta(2, 5, n_samples)  # Model predictions\n\n# True responses correlated with model score (good model)\nresponse_prob = 0.05 + 0.6 * model_score  # Higher score = higher response chance\ny_true = (np.random.random(n_samples) < response_prob).astype(int)\ny_score = model_score + np.random.normal(0, 0.05, n_samples)\ny_score = np.clip(y_score, 0, 1)\n\n# Calculate lift curve data\n# Sort by predicted score descending\nsorted_indices = np.argsort(y_score)[::-1]\ny_true_sorted = y_true[sorted_indices]\n\n# Calculate cumulative lift\nn_total = len(y_true)\nn_positive = y_true.sum()\nbaseline_rate = n_positive / n_total\n\n# Calculate cumulative values at each percentile\npercentiles = np.arange(1, 101)\nlift_values = []\npct_population = []\n\nfor pct in percentiles:\n    n_targeted = int(np.ceil(n_total * pct / 100))\n    n_positive_captured = y_true_sorted[:n_targeted].sum()\n\n    # Lift = (response rate in targeted group) / (baseline response rate)\n    targeted_rate = n_positive_captured / n_targeted\n    lift = targeted_rate / baseline_rate if baseline_rate > 0 else 0\n\n    lift_values.append(lift)\n    pct_population.append(pct)\n\n# Create DataFrame for plotting\ndf = pd.DataFrame({\"pct_population\": pct_population, \"lift\": lift_values})\n\n# Decile markers for emphasis\ndecile_points = df[df[\"pct_population\"].isin([10, 20, 30, 40, 50, 60, 70, 80, 90, 100])]\n\n# Theme configuration\nanyplot_theme = theme(\n    figure_size=(16, 9),\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG, color=None),\n    panel_grid_major=element_line(color=INK, size=0.3, alpha=0.10),\n    panel_grid_minor=element_line(color=INK, size=0.2, alpha=0.05),\n    panel_border=element_rect(color=INK_SOFT, fill=None, size=0.5),\n    axis_title=element_text(size=20, color=INK),\n    axis_text=element_text(size=16, color=INK_SOFT),\n    axis_line=element_line(color=INK_SOFT, size=0.5),\n    plot_title=element_text(size=24, color=INK),\n    legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n    legend_text=element_text(size=16, color=INK_SOFT),\n    legend_title=element_text(size=16, color=INK),\n)\n\n# Create plot\nplot = (\n    ggplot()\n    + geom_hline(yintercept=1.0, linetype=\"dashed\", color=REFERENCE_LINE, size=1.2, alpha=0.5)\n    + geom_line(data=df, mapping=aes(x=\"pct_population\", y=\"lift\"), color=BRAND, size=2.5)\n    + geom_point(data=decile_points, mapping=aes(x=\"pct_population\", y=\"lift\"), color=BRAND, size=5)\n    + scale_x_continuous(breaks=[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100], limits=(0, 100))\n    + scale_y_continuous(breaks=[0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5], limits=(0, None))\n    + labs(title=\"Model Lift Curve\", x=\"Population Targeted (%)\", y=\"Cumulative Lift\")\n    + theme_minimal()\n    + anyplot_theme\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=300, verbose=False)\n"}