How to make targeted offers to customers?

This tutorial includes everything you need to set up IBM Decision Optimization CPLEX Modeling for Python (DOcplex), build a Mathematical Programming model, and get its solution by solving the model on the cloud with IBM ILOG CPLEX Optimizer.

When you finish this tutorial, you’ll have a foundational knowledge of Prescriptive Analytics.

This notebook is part of the Prescriptive Analytics for Python.

It requires a valid subscription to Decision Optimization on Cloud. Try it for free here.

Table of contents:

Describe the business problem

  • The Self-Learning Response Model (SLRM) node enables you to build a model that you can continually update. Such updates are useful in building a model that assists with predicting which offers are most appropriate for customers and the probability of the offers being accepted. These sorts of models are most beneficial in customer relationship management, such as marketing applications or call centers.
  • This example is based on a fictional banking company.
  • The marketing department wants to achieve more profitable results in future campaigns by matching the right offer of financial services to each customer.
  • Specifically, the example uses a Self-Learning Response Model to identify the characteristics of customers who are most likely to respond favorably based on previous offers and responses and to promote the best current offer based on the results.

A set of business constraints have to be respected:

  • We have a limited budget to run a marketing campaign based on “gifts”, “newsletter”, “seminar”...
  • We want to determine which is the best way to contact the customers.
  • We need to identify which customers to contact.

How decision optimization can help

  • Prescriptive analytics technology recommends actions based on desired outcomes, taking into account specific scenarios, resources, and knowledge of past and current events. This insight can help your organization make better decisions and have greater control of business outcomes.

  • Prescriptive analytics is the next step on the path to insight-based actions. It creates value through synergy with predictive analytics, which analyzes data to predict future outcomes.

  • Prescriptive analytics takes that insight to the next level by suggesting the optimal way to handle that future situation. Organizations that can act fast in dynamic conditions and make superior decisions in uncertain environments gain a strong competitive advantage.

  • For example:

    • Automate complex decisions and trade-offs to better manage limited resources.
    • Take advantage of a future opportunity or mitigate a future risk.
    • Proactively update recommendations based on changing events.
    • Meet operational goals, increase customer loyalty, prevent threats and fraud, and optimize business processes.

Prepare the data

Step 1 : Model the data

The predictions show which offers a customer is most likely to accept, and the confidence that they will accept, depending on each customer’s details.

For example: (139987, “Pension”, 0.13221, “Mortgage”, 0.10675) indicates that customer Id=139987 will certainly not buy a Pension as the level is only 13.2%, whereas (140030, “Savings”, 0.95678, “Pension”, 0.84446) is more than likely to buy Savings and a Pension as the rates are 95.7% and 84.4%.

This data is taken from a SPSS example, except that the names of the customers were modified.

A Python data analysis library, pandas, is used to store the data. Let’s set up and declare the data.

import pandas as pd

names = {
    139987 : "HokaOneOne", 140030 : "Salomon", 140089 : "Altra", 140097 : "Nike", 139068 : "Mizuno", 139154 : "Asics", 139158 : "Quechua", 139169 : "Adidas", 139220 : "Brooks", 139261 : "WAA",
    139416 : "NorthFace", 139422 : "Polar", 139532 : "Garmin", 139549 : "High5", 139560 : "Helly Hansen", 139577 : "Moutain Hardware", 139580 : "Saucony", 139636 : "Gore Running Wear", 139647 : "Under Armour",
    139649 : "Saucony", 139665 : "Deuter", 139667 : "Inov-8", 139696 : "Petzl", 139752 : "SKINS", 139832 : "Suunto", 139859 : "Zoot", 139881 : "Peak Performance"}


data = [(139987, "Pension", 0.13221, "Mortgage", 0.10675), (140030, "Savings", 0.95678, "Pension", 0.84446), (140089, "Savings", 0.95678, "Pension", 0.80233),
                        (140097, "Pension", 0.13221, "Mortgage", 0.10675), (139068, "Pension", 0.80506, "Savings", 0.28391), (139154, "Pension", 0.13221, "Mortgage", 0.10675),
                        (139158, "Pension", 0.13221, "Mortgage", 0.10675),(139169, "Pension", 0.13221, "Mortgage", 0.10675), (139220, "Pension", 0.13221, "Mortgage", 0.10675),
                        (139261, "Pension", 0.13221, "Mortgage", 0.10675), (139416, "Pension", 0.13221, "Mortgage", 0.10675), (139422, "Pension", 0.13221, "Mortgage", 0.10675),
                        (139532, "Savings", 0.95676, "Mortgage", 0.82269), (139549, "Savings", 0.16428, "Pension", 0.13221), (139560, "Savings", 0.95678, "Pension", 0.86779),
                        (139577, "Pension", 0.13225, "Mortgage", 0.10675), (139580, "Pension", 0.13221, "Mortgage", 0.10675), (139636, "Pension", 0.13221, "Mortgage", 0.10675),
                        (139647, "Savings", 0.28934, "Pension", 0.13221), (139649, "Pension", 0.13221, "Mortgage", 0.10675), (139665, "Savings", 0.95675, "Pension", 0.27248),
                        (139667, "Pension", 0.13221, "Mortgage", 0.10675), (139696, "Savings", 0.16188, "Pension", 0.13221), (139752, "Pension", 0.13221, "Mortgage", 0.10675),
                        (139832, "Savings", 0.95678, "Pension", 0.83426), (139859, "Savings", 0.95678, "Pension", 0.75925), (139881, "Pension", 0.13221, "Mortgage", 0.10675)]

products = ["Car loan", "Savings", "Mortgage", "Pension"]
productValue = [100, 200, 300, 400]
budgetShare = [0.6, 0.1, 0.2, 0.1]

availableBudget = 500
channels =  pd.DataFrame(data=[("gift", 20.0, 0.20), ("newsletter", 15.0, 0.05), ("seminar", 23.0, 0.30)], columns=["name", "cost", "factor"])

Offers are stored in a pandas DataFrame.

offers = pd.DataFrame(data=data, index=xrange(0, len(data)), columns=["customerid", "Product1", "Confidence1", "Product2", "Confidence2"])
offers.insert(0,'name',pd.Series(names[i[0]] for i in data))

Let’s customize the display of this data and show the confidence forecast for each customer.

CSS = """
body {
    margin: 0;
    font-family: Helvetica;
}
table.dataframe {
    border-collapse: collapse;
    border: none;
}
table.dataframe tr {
    border: none;
}
table.dataframe td, table.dataframe th {
    margin: 0;
    border: 1px solid white;
    padding-left: 0.25em;
    padding-right: 0.25em;
}
table.dataframe th:not(:empty) {
    background-color: #fec;
    text-align: left;
    font-weight: normal;
}
table.dataframe tr:nth-child(2) th:empty {
    border-left: none;
    border-right: 1px dashed #888;
}
table.dataframe td {
    border: 2px solid #ccf;
    background-color: #f4f4ff;
}
    table.dataframe thead th:first-child {
        display: none;
    }
    table.dataframe tbody th {
        display: none;
    }
"""
from IPython.core.display import HTML
HTML('<style>{}</style>'.format(CSS))

from IPython.display import display
try:
    display(offers.drop('customerid',1).sort_values(by='name')) #Pandas >= 0.17
except:
    display(offers.drop('customerid',1).sort('name')) #Pandas < 0.17
name Product1 Confidence1 Product2 Confidence2
7 Adidas Pension 0.13221 Mortgage 0.10675
2 Altra Savings 0.95678 Pension 0.80233
5 Asics Pension 0.13221 Mortgage 0.10675
8 Brooks Pension 0.13221 Mortgage 0.10675
20 Deuter Savings 0.95675 Pension 0.27248
12 Garmin Savings 0.95676 Mortgage 0.82269
17 Gore Running Wear Pension 0.13221 Mortgage 0.10675
14 Helly Hansen Savings 0.95678 Pension 0.86779
13 High5 Savings 0.16428 Pension 0.13221
0 HokaOneOne Pension 0.13221 Mortgage 0.10675
21 Inov-8 Pension 0.13221 Mortgage 0.10675
4 Mizuno Pension 0.80506 Savings 0.28391
15 Moutain Hardware Pension 0.13225 Mortgage 0.10675
3 Nike Pension 0.13221 Mortgage 0.10675
10 NorthFace Pension 0.13221 Mortgage 0.10675
26 Peak Performance Pension 0.13221 Mortgage 0.10675
22 Petzl Savings 0.16188 Pension 0.13221
11 Polar Pension 0.13221 Mortgage 0.10675
6 Quechua Pension 0.13221 Mortgage 0.10675
23 SKINS Pension 0.13221 Mortgage 0.10675
1 Salomon Savings 0.95678 Pension 0.84446
16 Saucony Pension 0.13221 Mortgage 0.10675
19 Saucony Pension 0.13221 Mortgage 0.10675
24 Suunto Savings 0.95678 Pension 0.83426
18 Under Armour Savings 0.28934 Pension 0.13221
9 WAA Pension 0.13221 Mortgage 0.10675
25 Zoot Savings 0.95678 Pension 0.75925

Use IBM Decision Optimization CPLEX Modeling for Python

Let’s create the optimization model to select the best ways to contact customers and stay within the limited budget.

Step 1: Download the library

First install docplex and set the credentials to solve the model using IBM ILOG CPLEX Optimizer on Cloud.

import sys
try:
    import docplex.mp
except:
    if hasattr(sys, 'real_prefix'):
        #we are in a virtual env.
        !pip install docplex
    else:
        !pip install --user docplex

Step 2: Set up the prescriptive engine

  • Subscribe to the Decision Optimization on Cloud solve service here.
  • Get the service URL and your personal API key and enter your credentials here:
url = "ENTER YOUR URL HERE"
key = "ENTER YOUR KEY HERE"

Step 3: Set up the prescriptive model

Create the model

from docplex.mp.model import Model

mdl = Model(name="marketing_campaign")

Define the decision variables

  • The integer decision variables channelVars, represent whether or not a customer will be made an offer for a particular product via a particular channel.
  • The integer decision variable totaloffers represents the total number of offers made.
  • The continuous variable budgetSpent represents the total cost of the offers made.
offersR = xrange(0, len(offers))
productsR = xrange(0, len(products))
channelsR = xrange(0, len(channels))

channelVars = mdl.binary_var_cube(offersR, productsR, channelsR)
totaloffers = mdl.integer_var(lb=0)
budgetSpent = mdl.continuous_var()

Set up the constraints

  • Offer only one product per customer.
  • Compute the budget and set a maximum on it.
  • Compute the number of offers to be made.
# Only 1 product is offered to each customer
mdl.add_constraints( mdl.sum(channelVars[o,p,c] for p in productsR for c in channelsR) <=1
                   for o in offersR)

mdl.add_constraint( totaloffers == mdl.sum(channelVars[o,p,c]
                                           for o in offersR
                                           for p in productsR
                                           for c in channelsR) )

mdl.add_constraint( budgetSpent == mdl.sum(channelVars[o,p,c]*channels.get_value(index=c, col="cost")
                                           for o in offersR
                                           for p in productsR
                                           for c in channelsR) )

# Balance the offers among products
for p in productsR:
    mdl.add_constraint( mdl.sum(channelVars[o,p,c] for o in offersR for c in channelsR)
                       <= budgetShare[p] * totaloffers )

# Do not exceed the budget
mdl.add_constraint( mdl.sum(channelVars[o,p,c]*channels.get_value(index=c, col="cost")
                            for o in offersR
                            for p in productsR
                            for c in channelsR)  <= availableBudget )

mdl.print_information()
Model: marketing_campaign
 - number of variables: 326
   - binary=324, integer=1, continuous=1
 - number of constraints: 34
 -   LE=32, EQ=2, GE=0, RNG=0
 - parameters: defaults

Express the objective

We want to maximize the expected revenue.

mdl.maximize(
    mdl.sum( channelVars[idx,p,idx2] * c.factor * productValue[p]* o.Confidence1
            for p in productsR
            for idx,o in offers[offers['Product1'] == products[p]].iterrows()
            for idx2, c in channels.iterrows())
    +
    mdl.sum( channelVars[idx,p,idx2] * c.factor * productValue[p]* o.Confidence2
            for p in productsR
            for idx,o in offers[offers['Product2'] == products[p]].iterrows()
            for idx2, c in channels.iterrows())
    )

Solve with the Decision Optimization solve service

s = mdl.solve(url=url, key=key)
assert s, "No Solution !!!"

Step 4: Analyze the solution

First, let’s display the Optimal Marketing Channel per customer.

report = [(channels.get_value(index=c, col="name"), products[p], names[offers.get_value(o, "customerid")])
          for c in channelsR
          for p in productsR
          for o in offersR  if channelVars[o,p,c].solution_value==1]

assert len(report) == totaloffers.solution_value

print("Marketing plan has {0} offers costing {1}".format(totaloffers.solution_value, budgetSpent.solution_value))

report_bd = pd.DataFrame(report, columns=['channel', 'product', 'customer'])
display(report_bd)
Marketing plan has 20 offers costing 364.0
channel product customer
0 newsletter Car loan Quechua
1 newsletter Car loan Adidas
2 newsletter Car loan NorthFace
3 newsletter Car loan Polar
4 newsletter Car loan High5
5 newsletter Car loan Moutain Hardware
6 newsletter Car loan Saucony
7 newsletter Car loan Gore Running Wear
8 newsletter Car loan Under Armour
9 newsletter Car loan Saucony
10 newsletter Car loan Inov-8
11 newsletter Car loan Petzl
12 seminar Savings Altra
13 seminar Savings Zoot
14 seminar Mortgage Nike
15 seminar Mortgage Garmin
16 seminar Mortgage SKINS
17 seminar Mortgage Peak Performance
18 seminar Pension Salomon
19 seminar Pension Helly Hansen

Then let’s focus on seminar.

display(report_bd[report_bd['channel'] == "seminar"].drop('channel',1))
product customer
12 Savings Altra
13 Savings Zoot
14 Mortgage Nike
15 Mortgage Garmin
16 Mortgage SKINS
17 Mortgage Peak Performance
18 Pension Salomon
19 Pension Helly Hansen

Summary

You learned how to set up and use IBM Decision Optimization CPLEX Modeling for Python to formulate a Mathematical Programming model and solve it with IBM Decision Optimization on Cloud.

References