Uplift Modeling for Personalized Promotions

Mass discounts often go to those who would have bought anyway, draining the promo budget without boosting revenue. We build AI systems based on uplift modeling that determine who should get which discount to nudge hesitant customers. Our team delivers turnkey projects, from data audit to deployment and ongoing support, ensuring a reliable solution that scales with your business.

AI Development Areas

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1344
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1306
  • B2B Advance company logo design
    B2B Advance company logo design
    753
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1049
  • AIDER company logo development
    AIDER company logo development
    992
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1097

Promo Personalization via Uplift Modeling

A large retailer with a million customers spends hundreds of about $9k–13k in savings annually on discounts. Half goes to those who would have bought even without promo. We built an uplift model that reduced the discount budget by 35% in a two-week pilot while increasing revenue by 12%.

Mass "15% off everyone" campaigns look appealing, but 30–40% of recipients would have bought anyway. Money is wasted. Our AI system determines who to offer what discount and when—only to those who need it, and with the minimum incentive for conversion. This is a classic example of personalized promotions powered by AI retail and ML personalization.

Our team has 5+ years of experience in ML for retail and has delivered over 30 personalization projects, each involving customer-specific discounts and a promo personalization system.

How the AI System Personalizes Promotions

Uplift modeling predicts not the probability of purchase itself, but the increment in that probability due to a discount. We use a two-model approach: separately train a GradientBoostingClassifier on users who received the promo and those who did not. The difference in predictions gives the individual uplift.

import pandas as pd
import numpy as np
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import cross_val_score

class PromoUpliftModel:
    """
    Uplift modeling: predicts not the purchase probability, but the INCREMENT in probability from a discount.
    """
    def __init__(self):
        # Two-model approach
        self.model_treatment = GradientBoostingClassifier(
            n_estimators=200,
            learning_rate=0.05,
            random_state=42
        )
        self.model_control = GradientBoostingClassifier(
            n_estimators=200,
            learning_rate=0.05,
            random_state=42
        )

    def train(self, df: pd.DataFrame, feature_cols: list):
        """
        df: user_id, received_promo (0/1), purchased (0/1), features...
        """
        X = df[feature_cols].fillna(0)
        y = df['purchased']

        # Train separately on those who received promo and those who did not
        treatment_mask = df['received_promo'] == 1
        control_mask = df['received_promo'] == 0
        X_t, y_t = X[treatment_mask], y[treatment_mask]
        X_c, y_c = X[control_mask], y[control_mask]

        self.model_treatment.fit(X_t, y_t)
        self.model_control.fit(X_c, y_c)

        print(f"Treatment model AUC: {cross_val_score(self.model_treatment, X_t, y_t, scoring='roc_auc', cv=3).mean():.3f}")
        print(f"Control model AUC: {cross_val_score(self.model_control, X_c, y_c, scoring='roc_auc', cv=3).mean():.3f}")

    def predict_uplift(self, X: pd.DataFrame) -> pd.Series:
        """Predict uplift for each user"""
        p_treatment = self.model_treatment.predict_proba(X)[:, 1]
        p_control = self.model_control.predict_proba(X)[:, 1]
        return pd.Series(p_treatment - p_control, index=X.index)

class PromoPersonalizationEngine:
    def __init__(self, uplift_model: PromoUpliftModel):
        self.uplift_model = uplift_model
        self.promo_tiers = [
            {'discount': 5, 'min_uplift': 0.05},
            {'discount': 10, 'min_uplift': 0.04},
            {'discount': 15, 'min_uplift': 0.03},
            {'discount': 20, 'min_uplift': 0.025},
            {'discount': 25, 'min_uplift': 0.02},
        ]

    def assign_promo(self, users_df: pd.DataFrame, feature_cols: list, budget_per_user: float = 50) -> pd.DataFrame:
        """Personalized assignment of promo discounts"""
        X = users_df[feature_cols].fillna(0)
        uplifts = self.uplift_model.predict_uplift(X)

        result = users_df[['user_id']].copy()
        result['predicted_uplift'] = uplifts.values
        result['segment'] = 'no_promo'
        result['discount_pct'] = 0
        result['expected_roi'] = 0

        for _, row in result.iterrows():
            idx = row.name
            uplift = result.at[idx, 'predicted_uplift']
            avg_order = users_df.at[idx, 'avg_order_value'] if 'avg_order_value' in users_df.columns else 100

            # Choose the minimum discount with positive ROI
            for tier in self.promo_tiers:
                if uplift >= tier['min_uplift']:
                    promo_cost = avg_order * tier['discount'] / 100
                    expected_revenue_lift = uplift * avg_order
                    roi = (expected_revenue_lift - promo_cost) / promo_cost
                    if roi > 0.5 and promo_cost <= budget_per_user:
                        result.at[idx, 'discount_pct'] = tier['discount']
                        result.at[idx, 'expected_roi'] = roi

                        # Segmentation
                        if uplift > 0.15:
                            result.at[idx, 'segment'] = 'persuadable_high'
                        elif uplift > 0.07:
                            result.at[idx, 'segment'] = 'persuadable_low'
                        else:
                            result.at[idx, 'segment'] = 'sure_thing'
                        break

        return result

    def calculate_promo_roi(self, results_df: pd.DataFrame) -> dict:
        """Calculate ROI of the promo campaign"""
        with_promo = results_df[results_df['discount_pct'] > 0]
        without_promo = results_df[results_df['discount_pct'] == 0]
        return {
            'total_users_targeted': len(with_promo),
            'avg_discount': with_promo['discount_pct'].mean(),
            'estimated_total_cost': (with_promo['discount_pct'] / 100 * 100).sum(),
            'segment_breakdown': results_df['segment'].value_counts().to_dict(),
            'expected_avg_roi': with_promo['expected_roi'].mean()
        }

Proper segmentation is key to budget savings. The uplift model identifies four groups:

  • Sure Things (~20%): will buy without a discount — do not waste budget.
  • Persuadables (~35%): need the right incentive — give minimal discount.
  • Lost Causes (~25%): will not buy even with a discount — do not waste.
  • Sleeping Dogs (~20%): discounts annoy them — leave alone.

Typical result: 40–50% reduction in promo budget while maintaining 85–90% of sales. For a chain with 500,000 clients, that is savings of up to $14k–20k per month. Campaign ROI is 3–5x compared to 0.8x for mass campaigns. Another example: a hypermarket chain cut its discount budget by 38% in one quarter after implementing an uplift model, while revenue increased by 9% thanks to precise targeting. Additional profit reached $21k–30k in that quarter.

Why Uplift Modeling Outperforms Mass Discounts

Parameter Mass 15% Discount Uplift Personalization
Reach 100% of customers ~35–40% with highest uplift
Promo costs High Reduced by 30–40%
ROI 0.5–1x 3–5x (5x better)
Customer irritation risk High Minimal

Real-world example: a hypermarket chain with 500,000 clients cut its discount budget by 38% in one quarter after implementing an uplift model, while revenue increased by 9% thanks to precise targeting. Additional profit reached $21k–30k in that quarter. This clearly demonstrates the advantage of causal ML over traditional approaches.

Comparison of Uplift Modeling Approaches

Method Complexity Precision Interpretability
Two-model (our choice) Medium High Good
Transformed outcome Low Medium Low
Meta-learners (S,T,X) High High Medium

The two-model approach with XGBoost delivers stable results on moderately sized data (100k+ records) and scales easily to production. Source: Uplift Modeling: A Review

Implementation Process: From Analytics to Deployment

We work according to a proven plan:

  1. Data audit — gather and verify transaction history, promo records, customer features.
  2. Model building — implement two-model uplift (GradientBoosting, XGBoost) with cross-validation.
  3. Integration — connect the model to your CRM or promo platform via an API.
  4. A/B test — run a pilot on 10% of your audience, compare with a control group.
  5. Scaling — roll out to the entire base with real-time monitoring.

Timeline: from audit to pilot — 2 weeks; full launch — 8 weeks. Cost is calculated individually after analyzing your data.

More on model metrics:

To assess uplift model quality, we use the Qini curve and uplift AUC. In the two-model approach, separate AUC on treatment and control samples is also important. Typical values: AUC > 0.7 for treatment and > 0.65 for control. During validation, we check uplift on a holdout set — the difference in predictions must be positive and statistically significant.

Scope of Work

  • Ready-to-use personalization module with documentation and MLflow tracking.
  • Access to model source code and configurations.
  • Training for your team on system operation.
  • Technical support during the pilot and quality guarantee for results.

Contact us to get an assessment of your project within 2–3 days. Request a consultation: we will analyze your data and propose the optimal solution. Order an audit today — it is the first step to effective promo personalization.