# devtools::install_github("your_username/elasticoptim")
# install.packages(
# "/Users/k0s0dih/office/macropsace/macro_work/elasticoptim_0.1.0.tar.gz",
# repos = NULL,
# type = 'source'
# )elasticoptim: A Flexible Optimization Framework
elasticoptim package offers a flexible optimization framework tailored for informed decision-making using elasticity curves generated from machine learning pipelines. This tool has broad applications across diverse industries like retail, marketing, advertising, supply chain management, and finance, where elasticity curves can be generated. This package facilitates optimization processes under different constraints to reach decisions, including setting item prices, allocating category-specific storage space, and determining the quantity of items to produce within budget limits. This framework establishes an interpretable and customizable system, requiring only limited expertise in machine learning and optimization. The utility is packaged as R and python packages with similarly name classes and methods.
1 Installation
To installelasticoptim package, you can use the following command: #TODO add once repo is set up and packages are uploaded to
2 Motivation
Elasticity curves are a crucial concept in various field that help us understand how changes in one variable affect another. They provide valuable insights into the responsiveness of a quantity to a change in a different factor. Let’s delve into elasticity curves, their relevance, and how they’re used across various contexts.
Elasticity Curves: The Basics
An elasticity curve is a graph that shows the relationship between the percentage change in one variable and the percentage change in another variable. In economics, elasticity curves are used to measure the responsiveness of one economic variable to changes in another.
Understanding the significance of elasticity values is essential to draw meaningful conclusions. Below is an example of price elasticity curve
Price elasticity is a fundamental concept in economics that measures the sensitivity of demand and supply to changes in the price of products and services. It encompasses a range of possibilities. For example, in cases where a life-saving medication is available, people might be willing to pay any amount to acquire it, resulting in demand remaining constant regardless of significant price increases.
3 A working example: Space Optimization
In the context of a retail store, selling a diverse range of products with varying sales volumes and storage needs is crucial. To achieve efficient space utilization, a space optimization process is required. This process focuses on determining the ideal allocation of pallets for each product category using historical sales data. Let us assume that the retail store has three categories at broader level. The primary objective is to find the optimal number of pallets(space), denoted as \(x\), for every category, considering their respective sales volumes denoted as \(f(x)\). This optimization strategy aims to maximize sales revenue by making the best use of available storage space.
We intend to find optimal pallet count for each category to maximize overall sales with certain algebraic constaints as defined below:
\[ \begin{aligned} &\max_{x_1, x_2, x_3} x_1 + x_2 + x_3\\ \textrm{subject to} \\ &10 \leq x_1 \leq 25\\ &10 \leq x_2 \leq 25\\ &10 \leq x_3 \leq 25\\ & x_1 + x_2 + x_3 \leq 100 \end{aligned} \]
To solve this optimization problem, below are the steps to follow.
3.1 Access space elasticity data
Using the historical sales, we get the following space elasticity curves for all three categories. The first five rows of elasticity curve data are
x y group
0 23 7043.0 C1
2 24 7043.0 C1
4 25 7043.0 C1
8 26 7043.0 C1
10 27 7043.0 C1
The elasticity curves for all three categories is plotted below:
<ggplot: (341968853)>
We intend to find optimal pallet count for each category to maximize overall sales.
3.2 Create a class instance
To use elasticoptim class, you need to load the package
from elasticoptim import elasticoptimdevtools::load_all()ℹ Loading elasticoptim
Warning: package 'testthat' was built under R version 4.1.2
#library(elasticoptim)We create an instance of the class by providing an elasticity dataframe.
opt_obj = elasticoptim(space_elasticity_curve)opt_obj = elasticoptim$new(space_elasticity_curve)3.3 Define objective and constraints
The elasticoptim class allows you to define the objective function and constraints and choose your solver to get optimzation results. The algebraic constraints are defined as
constraints_df = pd.DataFrame({
'group': ['C1', 'C2', 'C3','C1', 'C2', 'C3', ['C1', 'C2','C3']],
'reference': ['x','x','x', 'x','x','x',['x','x','x']],
'coefficient': [1,1,1,1,1,1,[1,1,1]],
'sign':[">=",">=",">=","<=","<=","<=","<="],
'value':[10, 1, 13, 35, 39, 32, 100]})
print(constraints_df) group reference coefficient sign value
0 C1 x 1 >= 10
1 C2 x 1 >= 1
2 C3 x 1 >= 13
3 C1 x 1 <= 35
4 C2 x 1 <= 39
5 C3 x 1 <= 32
6 [C1, C2, C3] [x, x, x] [1, 1, 1] <= 100
constraint_df = tibble::tibble(
category = list('C1', 'C2', 'C3','C1', 'C2', 'C3', c('C1', 'C2','C3')),
reference = list('x','x','x', 'x','x','x', c('x','x','x')),
coefficient = list(1,1,1,1,1,1,c(1,1,1)),
direction = c(">=",">=",">=","<=","<=","<=","<="),
value = c(10, 1, 13, 35, 39, 32, 100)
)
as.data.frame(constraint_df) category reference coefficient direction value
1 C1 x 1 >= 10
2 C2 x 1 >= 1
3 C3 x 1 >= 13
4 C1 x 1 <= 35
5 C2 x 1 <= 39
6 C3 x 1 <= 32
7 C1, C2, C3 x, x, x 1, 1, 1 <= 100
The objective function is defined as
multiplier_dict = {'x':0 , 'y':1, 'interaction': 0}multiplier = c(y = 1)3.4 Find optimal solution
The optimal is found by running the optimization with all above inputs
solver = 'glpk'
opt_obj.optimize(solver = solver,
constraints_df = constraints_df,
multiplier_dict = multiplier_dict)Termination Condition: optimal
Solver Status: ok
Optimization succeeded
/Users/k0s0dih/miniconda3/envs/elastic/lib/python3.11/site-packages/elasticoptim.py:242: UserWarning: all interaction value and multiplier value w.r.t. interaction is zero
opt_obj.solution row_number group x y
0 11 C1 35 8736.134046
1 34 C2 36 8965.234076
2 50 C3 29 7579.013163
library("ROI.plugin.glpk")
solver = 'glpk'
opt_obj$optimize(solver = solver,
constraint_df = constraint_df,
multiplier = multiplier)Solution is optimal.
Optimization succeeded.
3.5 Access fitted model
#opt_obj.modelstr(opt_obj$model) NULL
3.6 Visualize the optimal solution
You can plot the optimal solution on elasticity curves
opt_obj.plot()<ggplot: (346485173)>
opt_obj$plot()4 A general framework
Suppose we have \(n\) number of categories. For each category \(C_i\), we are given possible values of \(x\)’s and
\[ \begin{align*} f_i(x) &: \text{y-value on elasticity curve for $C_i$ given $x$}\\ w_{x_i}(x) &: \text{weight for $x$ for $C_i$ given $x$ (optional)}\\ w_{y_i}(x) &: \text{weight for $y$ for $C_i$ given $x$ (optional)}\\ I_{xy_i}(x) &: \text{Interaction between $x$ and $y$ for $C_i$ given $x$ (optional)} \end{align*} \]
\[ \begin{equation} \begin{aligned} \max_{x_1, x_2, \ldots, x_n} & \quad m_y \sum_{i=1}^{n} w_{y_i}(x_i) * f_i(x_i) + m_x \sum_{i=1}^{n} w_{x_i}(x_i) * {x_i} + m_{xy} \sum_{i=1}^{n} I_{xy_i}(x_i)\\ \textrm{subject to} & \\ & \sum_{i=1}^{n} a_{ij} x_ i + b_{ij} y_ i <= r_{j}, \quad j = 1, 2, \ldots k_1 \\ & \sum_{i=1}^{n} c_{ij} x_ i + d_{ij} y_ i = q_{j}, \quad j = 1, 2, \ldots k_2 \\ \textrm{where} & \\ n &= \text{number of groups} \\ k_1 + k_2 &= \text{number of constraints} \\ a_{ij}, b_{ij}, c_{ij}, d_{ij}, r_{j}, q_{j} & \in \mathbb{R} \quad \forall i, j, \\ m_{x}, m_{y}, m_{xy} &\in \mathbb{R}, \end{aligned} \end{equation} \]
5 More use cases
5.1 Promotional Pricing Strategy
Given a department level promotional budget, what is the optimal discount strategy for each item to maximize revenue?
5.1.1 Formulation
\[ \begin{aligned} &\text{maximize} & \sum_{i=1}^n \sum_{j=1}^m p_{ij} (1 - d_{ij}) q_{ij} \\ &\text{subject to} & \sum_{j=1}^m q_{ij}(p_{ij}d_{ij}) \leq B_i, \quad i = 1, 2, \dots, n \\ && 0 \leq d_{ij} \leq 1, \quad i = 1, 2, \dots, n, \quad j = 1, 2, \dots, m \end{aligned} \]
\[ \begin{align*} &\text{where} \\ &p_{ij} = \text{price of item } i \text{ in department } j \\ &q_{ij} = \text{number of items we expect to sell of item } i \text{ in department } j \\ &B_i = \text{total promotional budget for department } i \\ &d_{ij} = \text{discount on item } i \text{ in department } j \end{align*} \]
5.1.2 Constraints
- The price must be non-negative.
- The promotion must be feasible, meaning that it must be possible to implement it given the available resources. Example: Maximum discount% at department level or item level.
- The quantity sold must be non-negative.
- The total promotional budget for department d is limited to bd.
5.1.3 Additional information
Price elasticity of demand: This is monotonic relationship between number of units sold and discount for a particular item
5.1.4 Loading price elasticity curves and constraints.
5.1.5 Accessing price elasticity curves
pricing_elasticity_curve.head() x y group
0 0.935670 0 item_1
1 3.362509 0 item_1
2 9.237389 0 item_1
3 9.725993 0 item_1
4 9.956909 0 item_1
5.1.6 Plotting price elasticity curves
y - Number of items sold
x - discount percentage
from plotnine import *
(ggplot(pricing_elasticity_curve, aes(x = 'x', y = 'y'))
+ geom_line()
+ facet_grid('group ~ .', scales ='free')
)<ggplot: (344836385)>
5.1.7 Entering constraints
import pandas as pd
constraints_df = (pd.DataFrame({
'group':['item_2','item_4','item_5', 'item_1','item_2','item_3',
['item_1','item_2'],'item_4' ,'item_5', ['item_1','item_2', 'item_3'
,'item_4', 'item_5']],
'reference':['y', 'y', 'y', 'x', 'x', 'x', ['x', 'x'], 'x', 'x',
['x', 'x','x', 'x','x']],
'coefficient':[1, 1,1,9, 25, 11, [9, 10], 20, 19, [9, 10,11,20,19]],
'sign':[">=",">=",">=","<=","<=" , "<=" , "<=", "<=", "<=", "<="],
'value':[3, 2, 2, 900,800,700,1100,500,600, 1500]
})
)
constraints_df group reference ... sign value
0 item_2 y ... >= 3
1 item_4 y ... >= 2
2 item_5 y ... >= 2
3 item_1 x ... <= 900
4 item_2 x ... <= 800
5 item_3 x ... <= 700
6 [item_1, item_2] [x, x] ... <= 1100
7 item_4 x ... <= 500
8 item_5 x ... <= 600
9 [item_1, item_2, item_3, item_4, item_5] [x, x, x, x, x] ... <= 1500
[10 rows x 5 columns]
5.1.8 Getting Optimal solution after considering all the constraints
from elasticoptim import elasticoptim
solver = 'glpk'
opt_obj = elasticoptim(pricing_elasticity_curve)
multiplier_dict = {'x':0 , 'y':1, 'interaction':0}
opt_obj.optimize(constraints_df = constraints_df,
solver = solver,
multiplier_dict = multiplier_dict)Termination Condition: optimal
Solver Status: ok
Optimization succeeded
/Users/k0s0dih/miniconda3/envs/elastic/lib/python3.11/site-packages/elasticoptim.py:242: UserWarning: all interaction value and multiplier value w.r.t. interaction is zero
opt_obj.solution row_number group x y
0 47 item_1 71.607453 368
1 80 item_2 27.354203 111
2 120 item_3 1.142746 0
3 191 item_4 24.002027 3
4 240 item_5 4.600731 21
5.1.9 Plotting the solution on price elasticity curves provided by user
print("objective value "+ str(opt_obj.objective))objective value 503.0
opt_obj.plot()<ggplot: (346924905)>
5.2 Manufacturing Cost Optimization for Demand Forecasting
A manufacturing company produces a range of consumer goods, such as clothing, accessories, and household products. To ensure efficient production planning and resource allocation, the company wants to optimize its manufacturing costs based on demand forecasts. The company needs to minimize manufacturing costs while meeting the fluctuating demand for its products. Accurate demand forecasting is essential to avoid overproduction or underproduction, which can lead to increased costs or missed revenue opportunities. The company can utilize manufacturing cost optimization techniques in conjunction with demand forecasting to address this challenge. Here’s how the process might unfold:
5.2.1 Demand Data Collection and Analysis
Gather historical demand data for various products and analyze demand patterns, seasonality, and trends. Use statistical methods and machine learning algorithms to generate confidence interval for demand forecasts for each category or group of categories. Optimism can define level of significance to calculate the forecast demand intervals.
5.2.2 Formulation
The company has \(n\) number of categories of consumer goods. For each category \(i\), company incurs a total cost for manufacturing per unit. If company does manufacturing in bulk, this cost will reduce. So the manufacturing cost per unit depends on number of units manufactured.
5.2.3 Objective
The main objective is to minimize the overall manufacturing cost. This involves finding the optimal quantity of units to manufacture for each category. Mathematically, this can be expressed as: \[\begin{equation} \begin{aligned} \min_{x_1, x_2, \ldots, x_n} & \quad \sum_{i=1}^{n} f_i(x_i) \end{aligned} \end{equation}\]
5.2.4 Constraints
To make this optimization practical, the company has a few constraints to consider:
- The company’s demand forecasting model provides an interval of expected demand for a group of categories. They need to ensure that the total quantity of units produced within this interval. Mathematically, this can be stated as:
where \(l\) and \(u\) are the lower and upper bound of the forecasted demand interval and \(G\) represents the group of categories.
- The company has a budget limit for producing goods within a certain group of categories. This can be expressed as:
\[ \begin{equation} \begin{aligned} & \sum_{x \in G} f(x) \leq b \end{aligned} \end{equation} \] - Finally, the company has an overall budget constraint that applies to all categories combined:
\[ \begin{equation} \begin{aligned} & \sum_{i=1}^{n} f(x) = B \end{aligned} \end{equation} \]
5.2.5 Python Code
x = number of items
y = production cost
5.2.5.1 Constraints over here can be across all the items, or a combination of items
5.2.6 Plotting price elasticity curves
y - Cost of producing all the items
x - Number of items produced
(ggplot(budget_demand_curve, aes(x = 'x', y = 'y'))
+ geom_line()
+ facet_grid('group ~ .', scales = 'free')
)<ggplot: (347344821)>
5.2.7 Entering constraints
constraints_df = (pd.DataFrame({ 'group': ['item_1','item_2','item_3','item_4' ,
'item_5', ['item_1','item_2','item_5']],
'reference': ['x', 'x', 'x', 'x', 'x',
['y', 'y', 'y']],
'coefficient': [1, 1, 1 , 1, 1, [1, 1,1]],
'sign' : [">=",">=" , ">=" , ">=", ">=", "<="],
'value' : [10,5,6,10,5, 4000000]
})
)
constraints_df group reference coefficient sign value
0 item_1 x 1 >= 10
1 item_2 x 1 >= 5
2 item_3 x 1 >= 6
3 item_4 x 1 >= 10
4 item_5 x 1 >= 5
5 [item_1, item_2, item_5] [y, y, y] [1, 1, 1] <= 4000000
5.2.8 Getting Optimal solution after considering all the constraints
opt_obj = elasticoptim(budget_demand_curve)
multiplier_dict = {'x':0 , 'y':-1, 'interaction':0}
opt_obj.optimize(constraints_df = constraints_df,
solver = 'glpk',
multiplier_dict = multiplier_dict)Termination Condition: optimal
Solver Status: ok
Optimization succeeded
/Users/k0s0dih/miniconda3/envs/elastic/lib/python3.11/site-packages/elasticoptim.py:242: UserWarning: all interaction value and multiplier value w.r.t. interaction is zero
print("objective value "+ str(opt_obj.objective))objective value -349630.11327797076
opt_obj.plot()<ggplot: (347243633)>