Uplift Modeling for Personalized Promotions

We design and deploy artificial intelligence systems: from prototype to production-ready solutions. Our team combines expertise in machine learning, data engineering and MLOps to make AI work not in the lab, but in real business.
Showing 1 of 1All 1566 services
Uplift Modeling for Personalized Promotions
Medium
~1-2 weeks
Frequently Asked Questions

AI Development Areas

AI Solution Development Stages

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1317
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1226
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    925
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1156
  • image_logo-advance_0.webp
    B2B Advance company logo design
    620
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    894

Promo Personalization via Uplift Modeling

A large retailer with a million customers spends hundreds of millions of rubles 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 1.5 million rubles 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 2.3 million rubles 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 2.3 million rubles 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.