Case source
case centre https://www.thecasecentre.org/educators/products/view?&id=132712
import pandas as pd
import numpy as np
from scipy.optimize import minimize
UNIT_NUMBER = 'unit_number'
UNIT_PRICE = 'unit_price'
TOTAL_PRICE = 'total_price'
WIN = 'win'
DISCOUNT_RATE = 'discount_rate'
UNIT_MARGIN = 'unit_margin'
UNIT_SOLD_NUMBER = 'unit_sold_number'
TOTAL_MARGIN = 'total_margin'
POLICE = 'police'
P = 'P'
Data reading
# Set the PYTHONPATH as root folder, like PYTHONPATH=/Users/willbe/PycharmProjects/FleetSalesPricingAtFjordMotor
DATA_DIR = 'data/fleet_sales_pricing_at_fjord_motor.csv'
def get_all_data(MSRP):
"""
Get data of all bids.
:return data: (DataFrame) 8 columns * 4000 rows
columns unit_number (int) The number of vehicles.
unit_price (int) The price of a vehicle.
total_price (int) unit_number * unit_price
win (int) If Fjord win a fleet bid, win is 1. Else, win is 0.
discount_rate (float) ( MSRP(the_manufacturer's_suggested_retail_price) - unit_price ) / MSRP
unit_margin (int) MSRP - cost
unit_sold_number (int) unit_number * win
total_margin (int) unit_margin * unit_sold_number
p (float) unit_price / MSRP.
"""
data = pd.read_csv(DATA_DIR)
data[P] = data[UNIT_PRICE] / MSRP
return data
def get_police_data(MSRP):
"""
The bids 1 through 2000 were to police departments.
:return data: (DataFrame) 8 columns * 2000 rows
columns unit_number (int) The number of vehicles.
unit_price (int) The price of a vehicle.
total_price (int) unit_number * unit_price
win (int) If Fjord win a fleet bid, win is 1. Else, win is 0.
discount_rate (float) ( MSRP(the_manufacturer's_suggested_retail_price) - unit_price ) / MSRP
unit_margin (int) MSRP - cost
unit_sold_number (int) unit_number * win
total_margin (int) unit_margin * unit_sold_number
p (float) unit_price / MSRP.
"""
data = get_all_data(MSRP)
data = data.iloc[:2000, :]
return data
def get_corporate_buyer_data(MSRP):
"""
The bids 2001 through 4000 were to corporate buyers.
:return data: (DataFrame) 8 columns * 2000 rows
columns unit_number (int) The number of vehicles.
unit_price (int) The price of a vehicle.
total_price (int) unit_number * unit_price
win (int) If Fjord win a fleet bid, win is 1. Else, win is 0.
discount_rate (float) ( MSRP(the_manufacturer's_suggested_retail_price) - unit_price ) / MSRP
unit_margin (int) MSRP - cost
unit_sold_number (int) unit_number * win
total_margin (int) unit_margin * unit_sold_number
p (float) unit_price / MSRP.
"""
data = get_all_data(MSRP)
data = data.iloc[2000:, :]
return data
Assignment 1
is the probability of winning the bid.
and are the parameters to be estimated.
is the price that Fjord bid on a deal expressed as a fraction of the MSRP of $25,000 per units; that is, if Fjord id $20,000 per unit, p would e equal to 20,000/25,000 or 0.8.
A model is .
What are the values of a and b that maximize the sum of log likelihoods?
1) Calculate winning probabilities using the model.
def calculate_winning_prob(a, b, p):
"""
:param a: (float) An intercept of the model.
:param b: (float) A beta of the model.
:param p: (pd.Series[float]) unit_price / MSRP.
:return winning_prob: (pd.Series[float]) The expected winning probabilities of the model.
"""
winning_prob = 1 / (1 + np.exp(a + b * p))
return winning_prob
2) Make a function for calculating a sum of log likelihoods.
log_likelihood =
def calculate_sum_of_log_likelihood(a, b, args):
"""
:param a: (float) An intercept of the model.
:param b: (float) A beta of the model.
:param args: (tuple)
p (pd.Series[float]) unit_price / MSRP.
observed_y (pd.Series[int]) If Fjord win a fleet bid, win is 1. Else, win is 0.
:return log_likelihood_sum: (float) The sum of log likelihood.
"""
p, observed_y = args
winning_prob = calculate_winning_prob(a, b, p)
log_likelihood_sum = np.sum(np.log(np.power(winning_prob, observed_y) * np.power(1-winning_prob, 1-observed_y)))
return log_likelihood_sum
3) Becasue numpy has only a minize optimizer, make a negative function of the function for calculating a sum of log likelihoods.
def calculate_negative_sum_of_log_likelihood(param, *args):
a, b = param
negative_log_likelihood_sum = -calculate_sum_of_log_likelihood(a, b, args=args)
return negative_log_likelihood_sum
4) Maximize the sum of log likelihood by minimizing the negative sum of log likelihoods.
def maximize_log_likelihood(args):
"""
: return optimal_a: (float) The optimal intercept of the model.
: return optimal_b: (float) The optimal beta of the model.
"""
# Make a list of initial parameter guesses (intercept, beta_1)
initial_guess = np.array([0, 0])
# Minimize a negative log likelihood for maximizing a log likelihood.
results = minimize(calculate_negative_sum_of_log_likelihood, x0=initial_guess, args=args)
optimal_a, optimal_b = results.x
return optimal_a, optimal_b
5) Calculate the values of a and b.
MSRP = 25000
all_data = get_all_data(MSRP)
args = (all_data[P], all_data[WIN])
optimal_a, optimal_b = maximize_log_likelihood(args)
print("a: {:.2f}, b: {:.2f}".format(optimal_a, optimal_b))
a: -7.76, b: 9.16
What is the optimum price Fjord should offer, assuming it is going to offer a single price for each bid?
Based on a and b we calcuated, we need to find a single price maximizing sum of margin. For that,
1) Calculate the expected margin.
cost = 15000
expected_margin = (single_price - cost) *
COST = 15000
def calculate_expected_margin(single_price, args):
"""
:param single_price: (float)
:return expected_margin: (float) The expected profits.
"""
a, b = args
p = single_price / MSRP
winning_prob = calculate_winning_prob(a, b, p)
expected_margin = (single_price - COST) * winning_prob
return expected_margin
2) Make a negative function of the function for calculating the expected margin.
def calculate_negative_expected_margin(param, *args):
single_price = param
negative_expected_margin = -calculate_expected_margin(single_price, args)
return negative_expected_margin
3) Maximize the expected margin by minimizing the negative expected margin.
def maxize_expected_margin(args):
# Make a list of initial parameter guesses (single_price)
initial_guess = np.array([0])
# Minimize a negative log likelihood for maximizing a log likelihood.
results = minimize(calculate_negative_expected_margin, x0=initial_guess, args=args)
optimal_single_price = results.x[0]
return optimal_single_price
4) Calculate the optimal single price maximizing margin.
args = (optimal_a, optimal_b)
optimal_single_price = maxize_expected_margin(args)
print("optimal single price: ${:.2f}".format(optimal_single_price))
optimal single price: $20818.70
What would the expected total contribution have been for the 4,000 bids?
ExpectedTotalContribution = $\sum_{i=1}^{4000}{[UnitNumber_i * (SinglePrice - Cost)]}$
def calculate_expected_total_contribution(unit_numbers, optimal_price, cost, a, b):
"""
:param unit_numbers: (pd.Series[int]) The numbers of unit of bids.
:param optimal_price: (float) The optimal price for bids.
:param cost: (float)
:param a: (float)
:param b: (float)
:return expected_total_contribution: The sum of expected contributions(unit_number * expected_margin).
"""
p = optimal_price / MSRP
expected_total_contribution = np.sum(unit_numbers * (optimal_price - cost) * calculate_winning_prob(a, b, p))
return expected_total_contribution
expected_total_contribution = calculate_expected_total_contribution(all_data[UNIT_NUMBER], optimal_single_price, COST, optimal_a, optimal_b)
print("The expected total contribution: ${:.2f}".format(expected_total_contribution))
The expected total contribution: $241083842.21
How does this compare to the contribution that Fjord actually received?
ActualTotalContribution = $\sum_{i=1}^{4000}{[Win_i * UnitNumber_i * (UnitPrice_i - Cost)]}$
def calculate_actual_total_contribution(wins, unit_numbers, unit_prices):
actual_total_contribution = np.sum(wins * unit_numbers * (unit_prices - COST))
return actual_total_contribution
actual_total_contribution = calculate_actual_total_contribution(all_data[WIN], all_data[UNIT_NUMBER], all_data[UNIT_PRICE])
print("The actual total contribution: ${}".format(actual_total_contribution))
print("Improvement: ${:.2f}, {:.2f}%".format(
expected_total_contribution - actual_total_contribution,
(expected_total_contribution / actual_total_contribution - 1) * 100
))
The actual total contribution: $171829002
Improvement: $69254840.21, 40.30%
Assignment 2
Fjord discovers that bids 1 through 2,000 were to police departments, and the bids 2,001 through 4,000 were to corporate buyers.
What are the corresponding values of a and b for each?
1) Calculate the values of a and b for police.
police_data = get_police_data(MSRP)
args = (police_data[P], police_data[WIN])
police_optimal_a, police_optimal_b = maximize_log_likelihood(args)
print("Police a: {:.2f}, b: {:.2f}".format(police_optimal_a, police_optimal_b))
Police a: -14.22, b: 20.01
2) Calculate the values of a and b for corporate buyer.
corporate_buyer_data = get_corporate_buyer_data(MSRP)
args = (corporate_buyer_data[P], corporate_buyer_data[WIN])
corporate_buyer_optimal_a, corporate_buyer_optimal_b = maximize_log_likelihood(args)
print("Corporate buyer a: {:.2f}, b: {:.2f}".format(corporate_buyer_optimal_a, corporate_buyer_optimal_b))
Corporate buyer a: -27.88, b: 28.81
What are the optimum price Fjord should offer to the police?
args = (police_optimal_a, police_optimal_b)
police_optimal_single_price = maxize_expected_margin(args)
print("Police optimal price: ${:.2f}".format(police_optimal_single_price))
Police optimal price: $17638.54
To corporate buyers?
args = (corporate_buyer_optimal_a, corporate_buyer_optimal_b)
corporate_buyer_optimal_single_price = maxize_expected_margin(args)
print("Corporate buyer optimal price: ${:.2f}".format(corporate_buyer_optimal_single_price))
Corporate buyer optimal price: $22431.46
What would the expected contribution have been if Fjord had used the price in the 4,000 bids in the database?
police_expected_total_contribution = calculate_expected_total_contribution(
police_data[UNIT_NUMBER],
police_optimal_single_price,
COST,
police_optimal_a,
police_optimal_b
)
expected_total_contribution = calculate_expected_total_contribution(
corporate_buyer_data[UNIT_NUMBER],
corporate_buyer_optimal_single_price,
COST,
corporate_buyer_optimal_a,
corporate_buyer_optimal_b
)
new_expected_total_contribution = police_expected_total_contribution + expected_total_contribution
print("The expected total contribution: ${:.2f}".format(new_expected_total_contribution))
The expected total contribution: $308695819.88
What is the difference between the contribution actually received and the best that Fjord could do when it could not differentiate between the police and corporate buyers?
print("Improvement from the contribution actually received: ${:.2f}, {:.2f}%".format(
new_expected_total_contribution - actual_total_contribution,
(new_expected_total_contribution / actual_total_contribution - 1) * 100
))
print("Improvement from the best that Fjord could do when it could not differentiate between the police and corporate buyers: ${:.2f}, {:.2f}%".format(
new_expected_total_contribution - expected_total_contribution,
(new_expected_total_contribution / expected_total_contribution - 1) * 100
))
Improvement from the contribution actually received: $136866817.88, 79.65%
Improvement from the best that Fjord could do when it could not differentiate between the police and corporate buyers: $54579338.40, 21.48%
Assignment 3
is the size of the order.
The new model:
What is the resulting improvement in total log likelihood?
1) Calculate winning probabilities using the model.
def calculate_2_factor_winning_prob(a, b, c, p, unit_number):
"""
:param a: (float) An intercept of the model.
:param b: (float) A coefficient for p of the bid.
:param c: (float) A coefficient for the size of the bid.
:param p: (pd.Series[float]) unit_price / MSRP.
:return winning_prob: (pd.Series[float]) The expected winning probabilities of the model.
"""
winning_prob = 1 / (1 + np.exp(a + b * p + c * unit_number))
return winning_prob
2) Make a function for calculating a sum of log likelihoods.
log_likelihood =
def calculate_2_factor_sum_of_log_likelihood(a, b, c, args):
"""
:param a: (float) An intercept of the model.
:param b: (float) A coefficient for p of the bid.
:param c: (float) A coefficient for the size of the bid.
:param args: (tuple)
p (pd.Series[float]) unit_price / MSRP.
unit_number (pd.Series[int]) The size of the bid.
observed_y (pd.Series[int]) If Fjord win a fleet bid, win is 1. Else, win is 0.
:return log_likelihood_sum: (float) The sum of log likelihood.
"""
p, unit_number, observed_y = args
winning_prob = calculate_2_factor_winning_prob(a, b, c, p, unit_number)
log_likelihood_sum = np.sum(np.log(np.power(winning_prob, observed_y) * np.power(1-winning_prob, 1-observed_y)))
return log_likelihood_sum
3) Becasue numpy has only a minize optimizer, make a negative function of the function for calculating a sum of log likelihoods.
def calculate_2_factor_negative_sum_of_log_likelihood(param, *args):
a, b, c = param
negative_log_likelihood_sum = -calculate_2_factor_sum_of_log_likelihood(a, b, c, args=args)
return negative_log_likelihood_sum
4) Maximize the sum of log likelihood by minimizing the negative sum of log likelihoods.
def maximize_2_factor_log_likelihood(args):
"""
:return optimal_a: (float) The optimal intercept of the model.
:return optimal_b: (float) The optimal coefficient for p.
:return optimal_c: (float) The optimal coefficient for the size of the bid.
"""
# Make a list of initial parameter guesses (intercept, beta_1)
initial_guess = np.array([0, 0, 0])
# Minimize a negative log likelihood for maximizing a log likelihood.
results = minimize(calculate_2_factor_negative_sum_of_log_likelihood, x0=initial_guess, args=args)
optimal_a, optimal_b, optimal_c = results.x
return optimal_a, optimal_b, optimal_c
5) Calculate the values of a and b.
# Previous model
args = (all_data[P], all_data[WIN])
previous_log_likelihood_sum = calculate_sum_of_log_likelihood(optimal_a, optimal_b, args)
# New model
# Police
args = (police_data[P], police_data[UNIT_NUMBER], police_data[WIN])
police_2_factor_optimal_a, police_2_factor_optimal_b, police_2_factor_optimal_c = maximize_2_factor_log_likelihood(args)
new_police_log_likelihood_sum = calculate_2_factor_sum_of_log_likelihood(police_2_factor_optimal_a, police_2_factor_optimal_b, police_2_factor_optimal_c, args)
# Corporate buyer
args = (corporate_buyer_data[P], corporate_buyer_data[UNIT_NUMBER], corporate_buyer_data[WIN])
corporate_buyer_2_factor_optimal_a, corporate_buyer_2_factor_optimal_b, corporate_buyer_2_factor_optimal_c = maximize_2_factor_log_likelihood(args)
new_corporate_buyer_log_likelihood_sum = calculate_2_factor_sum_of_log_likelihood(corporate_buyer_2_factor_optimal_a, corporate_buyer_2_factor_optimal_b, corporate_buyer_2_factor_optimal_c, args)
# Total
new_total_log_likelihood_sum = new_police_log_likelihood_sum + new_corporate_buyer_log_likelihood_sum
# Comparison
print("Improvement: ${:.2f}, {:.2f}%".format(
new_total_log_likelihood_sum - previous_log_likelihood_sum,
np.abs((new_total_log_likelihood_sum / previous_log_likelihood_sum - 1) * 100)
))
Improvement: $1241.95, 54.91%
How does this compare with the improvement from differentiating police and corporate sales?
# Previous model
# Police
args = (police_data[P], police_data[WIN])
previous_police_log_likelihood_sum = calculate_sum_of_log_likelihood(police_optimal_a, police_optimal_b, args)
# Corporate buyer
args = (corporate_buyer_data[P], corporate_buyer_data[WIN])
previous_corporate_buyer_log_likelihood_sum = calculate_sum_of_log_likelihood(corporate_buyer_optimal_a, corporate_buyer_optimal_b, args)
# Total
previous_total_log_likelihood_sum = previous_police_log_likelihood_sum + previous_corporate_buyer_log_likelihood_sum
# Comparison
print("Improvement: ${:.2f}, {:.2f}%".format(
new_total_log_likelihood_sum - previous_total_log_likelihood_sum,
np.abs((new_total_log_likelihood_sum / previous_total_log_likelihood_sum - 1) * 100)
))
Improvement: $10.27, 1.00%
What are the optimal prices Fjord should charge for orders of 20 cars and for orders of 40 cars to police departments and to corporate purchasers, respectively?
Based on a and b we calcuated, we need to find a single price maximizing sum of margin. For that,
1) Calculate the expected margin.
cost = 15000
expected_margin = (single_price - cost) *
def calculate_2_factor_expected_margin(single_price, args):
"""
:param single_price: (float)
:return expected_margin: (float) The expected profits.
"""
a, b, c, cost, unit_number = args
p = single_price / MSRP
winning_prob = calculate_2_factor_winning_prob(a, b, c, p, unit_number)
expected_margin = (single_price - cost) * winning_prob
return expected_margin
2) Make a negative function of the function for calculating the expected margin.
def calculate_2_factor_negative_expected_margin(param, *args):
single_price = param
negative_expected_margin = -calculate_2_factor_expected_margin(single_price, args)
return negative_expected_margin
3) Maximize the expected margin by minimizing the negative expected margin.
def maximize_2_factor_expected_margin(args):
# Make a list of initial parameter guesses (single_price)
initial_guess = np.array([0])
# Minimize a negative log likelihood for maximizing a log likelihood.
results = minimize(calculate_2_factor_negative_expected_margin, x0=initial_guess, args=args)
optimal_single_price = results.x[0]
return optimal_single_price
police_20_cars_args = (police_2_factor_optimal_a, police_2_factor_optimal_b, police_2_factor_optimal_c, COST, 20)
police_20_cars_optimal_price = maximize_2_factor_expected_margin(police_20_cars_args)
print("The optimal price of 20 cars to police departments: ${:.2f}".format(police_20_cars_optimal_price))
police_40_cars_args = (police_2_factor_optimal_a, police_2_factor_optimal_b, police_2_factor_optimal_c, COST, 40)
police_40_cars_optimal_price = maximize_2_factor_expected_margin(police_40_cars_args)
print("The optimal price of 40 cars to police departments: ${:.2f}".format(police_40_cars_optimal_price))
corporate_buyer_20_cars_args = (corporate_buyer_2_factor_optimal_a, corporate_buyer_2_factor_optimal_b, corporate_buyer_2_factor_optimal_c, COST, 20)
corporate_buyer_20_cars_optimal_price = maximize_2_factor_expected_margin(corporate_buyer_20_cars_args)
print("The optimal price of 20 cars to corporate purchasers: ${:.2f}".format(corporate_buyer_20_cars_optimal_price))
corporate_buyer_40_cars_args = (corporate_buyer_2_factor_optimal_a, corporate_buyer_2_factor_optimal_b, corporate_buyer_2_factor_optimal_c, COST, 40)
corporate_buyer_40_cars_optimal_price = maximize_2_factor_expected_margin(corporate_buyer_40_cars_args)
print("The optimal price of 40 cars to corporate purchasers: ${:.2f}".format(corporate_buyer_40_cars_optimal_price))
The optimal price of 20 cars to police departments: $17616.11
The optimal price of 40 cars to police departments: $17214.42
The optimal price of 20 cars to corporate purchasers: $22441.15
The optimal price of 40 cars to corporate purchasers: $22525.99
Calculate optimal price for all order sizes from 10 through 60 vehicles for both police and corporate sales, and use these price to determine the total contribution margin Fjord would have received if it had used these prices in the 4,000 historic bids.
police_optimal_prices = []
corporate_buyer_optimal_prices = []
for i in range(10, 61):
# Police
police_args = (police_2_factor_optimal_a, police_2_factor_optimal_b, police_2_factor_optimal_c, COST, i)
police_optimal_price = maximize_2_factor_expected_margin(police_args)
police_optimal_prices.append(police_optimal_price)
# Corporate buyer
corporate_buyer_args = (corporate_buyer_2_factor_optimal_a, corporate_buyer_2_factor_optimal_b, corporate_buyer_2_factor_optimal_c, COST, i)
corporate_buyer_optimal_price = maximize_2_factor_expected_margin(corporate_buyer_args)
corporate_buyer_optimal_prices.append(corporate_buyer_optimal_price)
pd.DataFrame(data={'police': police_optimal_prices,
'corporate_buyer': corporate_buyer_optimal_prices},
index=range(10, 61))
corporate_buyer | police | |
---|---|---|
10 | 22398.832514 | 17841.998337 |
11 | 22403.065253 | 17818.654108 |
12 | 22407.282696 | 17795.529174 |
13 | 22411.557702 | 17772.606965 |
14 | 22415.723659 | 17749.799144 |
15 | 22420.026251 | 17727.132447 |
16 | 22424.271564 | 17704.640133 |
17 | 22428.440539 | 17682.229293 |
18 | 22432.690807 | 17660.083406 |
19 | 22436.936602 | 17638.008948 |
20 | 22441.151170 | 17616.109653 |
21 | 22445.404314 | 17594.442419 |
22 | 22449.643958 | 17573.086999 |
23 | 22453.813047 | 17551.673509 |
24 | 22458.074484 | 17530.451576 |
25 | 22462.308636 | 17509.227803 |
26 | 22466.543872 | 17488.344587 |
27 | 22470.840222 | 17467.668814 |
28 | 22475.101310 | 17447.070235 |
29 | 22479.403692 | 17426.694747 |
30 | 22483.609712 | 17406.599145 |
31 | 22487.685601 | 17386.574284 |
32 | 22492.068984 | 17366.668712 |
33 | 22496.329942 | 17347.109827 |
34 | 22500.563714 | 17327.532611 |
35 | 22504.702582 | 17308.250370 |
36 | 22509.019517 | 17289.117823 |
37 | 22513.264192 | 17270.029364 |
38 | 22517.513312 | 17251.404789 |
39 | 22521.768850 | 17232.877346 |
40 | 22525.991300 | 17214.421578 |
41 | 22530.246494 | 17196.339595 |
42 | 22534.506684 | 17178.313565 |
43 | 22538.671058 | 17160.555318 |
44 | 22543.002327 | 17143.082284 |
45 | 22547.430786 | 17125.527459 |
46 | 22551.519997 | 17108.305135 |
47 | 22555.756078 | 17091.336017 |
48 | 22560.008469 | 17074.435613 |
49 | 22564.220936 | 17057.839270 |
50 | 22568.482607 | 17041.357755 |
51 | 22572.742502 | 17025.194591 |
52 | 22576.952570 | 17009.318247 |
53 | 22581.221811 | 16993.411402 |
54 | 22585.494939 | 16977.662457 |
55 | 22589.728859 | 16962.222977 |
56 | 22593.887900 | 16947.013351 |
57 | 22598.179947 | 16931.969925 |
58 | 22602.505608 | 16917.239199 |
59 | 22606.719729 | 16902.577904 |
60 | 22610.975364 | 16888.111587 |
Assignment 4
The police version of the Cornet Elizabeth costs $16,000 to manufacture versus $15,000 for the corporate version.
How would this change the optimal price charged to police departments for 20 vehicles? For 40?
NEW_COST = 16000
police_20_cars_args = (police_2_factor_optimal_a, police_2_factor_optimal_b, police_2_factor_optimal_c, NEW_COST, 20)
police_20_cars_optimal_price = maximize_2_factor_expected_margin(police_20_cars_args)
print("The optimal price of 20 cars to police departments: ${:.2f}".format(police_20_cars_optimal_price))
police_40_cars_args = (police_2_factor_optimal_a, police_2_factor_optimal_b, police_2_factor_optimal_c, NEW_COST, 40)
police_40_cars_optimal_price = maximize_2_factor_expected_margin(police_40_cars_args)
print("The optimal price of 40 cars to police departments: ${:.2f}".format(police_40_cars_optimal_price))
The optimal price of 20 cars to police departments: $18138.28
The optimal price of 40 cars to police departments: $17828.91