1

Problem: Find optimal discount for each product such that spend budget is fully utilized. In simpler terms, I need to maximize sales by changing discount with the following constraints:

  • min discount <= discount <= max discount
  • spend_value <= 100 #spend_budget

Formula used(relation between diff variables): (details shared, at the end in the section, where I have used excel to solve the problem.)

  • sales_value = discount discount_coef + constant [Excel formula = F2G2 + H2]

  • spend_value = (sales_value/(mrp-discount))*discount [Excel formula = (E2/(B2-G2))*G2]

Work done: with naive knowledge on optimization, and extreme googling/ checking various SOs post, I managed to find some relevant post related to my problem here, which suggested use of NlcOptim::solnl. and code as follows:

Input data

structure(list(product = c("A", "B", "C", "D", "E", "F", "G", 
"H", "I", "J", "K", "L", "M", "N"), mrp = c(159, 180, 180, 230, 
230, 500, 500, 310, 288, 310, 500, 425, 425, 465), discount_coef = c(0.301594884229324, 
0.614829352312733, 0.149146787052132, 0.248723558155458, 0.138769169527518, 
0.330703149210594, 0.335917219291645, 0.296582160231912, 0.357483743973616, 
0.24978922074796, 0.334178652809571, 0.292011550773066, 0.157611497322651, 
0.357562105368776), min_discount = c(14.31, 25.2, 25.2, 29.9, 
29.9, 100, 100, 71.3, 66.24, 71.3, 100, 51, 51, 51.15), max_discount = c(39.75, 
30.6, 30.6, 39.1, 39.1, 200, 200, 179.8, 155.52, 179.8, 200, 
174.25, 174.25, 190.65)), row.names = c(NA, 14L), class = "data.frame") -> optim_data

code

library("NlcOptim")

(coeff <- optim_data$discount_coef)
#>  [1] 0.3015949 0.6148294 0.1491468 0.2487236 0.1387692 0.3307031 0.3359172
#>  [8] 0.2965822 0.3574837 0.2497892 0.3341787 0.2920116 0.1576115 0.3575621
(min_discount <- optim_data$min_discount)
#>  [1]  14.31  25.20  25.20  29.90  29.90 100.00 100.00  71.30  66.24  71.30
#> [11] 100.00  51.00  51.00  51.15
(max_discount <- optim_data$max_discount)
#>  [1]  39.75  30.60  30.60  39.10  39.10 200.00 200.00 179.80 155.52 179.80
#> [11] 200.00 174.25 174.25 190.65
(mrp <- optim_data$mrp)
#>  [1] 159 180 180 230 230 500 500 310 288 310 500 425 425 465
(discount <- numeric(length = 14L))
#>  [1] 0 0 0 0 0 0 0 0 0 0 0 0 0 0

## objective function
obj <- function(discount) {
  sales_value <- (discount/mrp) * coeff
  return(sum(sales_value))
}

## constraint
con <- function(discount) {
  sales <- (discount/mrp)*coeff
  spend <- (sales/(mrp-discount))*discount
  f = NULL
  f = rbind(f, sum(spend)-100) # 100 is spend budget
  return(list(ceq = f, c = NULL))
}

## optimize 
result <- solnl(X = discount, objfun = obj, confun = con, 
                lb = min_discount, ub = max_discount)
#> Error in solnl(X = discount, objfun = obj, confun = con, lb = min_discount, : object 'lambda' not found

Created on 2020-07-03 by the reprex package (v0.3.0)

Issue:

  1. It constantly throwing error message "object 'lambda' not found" and I am clueless on how to solve the issue.
  2. How to solve non-linearity optimization problem shared in R?? Is there other way to solve the problem??

Details: Excel solution

  • pre-setup(input data) in excel

enter image description here

  • post setup (after running excel solver)

enter image description here

desertnaut
  • 57,590
  • 26
  • 140
  • 166
nikn8
  • 1,016
  • 8
  • 23
  • could you share the Excel file to help understanding of the constraints / calculations? – Waldi Jul 08 '20 at 08:29
  • sure... just wondering, do we have option of sharing file here? however, I have shared the data and all calculations I believe. – nikn8 Jul 08 '20 at 08:32
  • I guess you'll need to chose a file upload service outside SO. Playing around with the xl Solver could help better understand the calculations and the way to the result. – Waldi Jul 08 '20 at 08:44
  • @Neel you don't have `constant` in your objective / constraint function. Can you elaborate? – slava-kohut Jul 08 '20 at 13:48

2 Answers2

2

I have never dealt with NlcOptim, but I do have some experience with nloptr(link). Here is how to setup the problem (pay attention to comments below):

optim_data$constant <- c(30,60,-10,34,-23,54,-34,-56,23,45,-71,19,29,39)

# this is minimized, therefore "-", max_budget is a dummy variable
sales_value <- function(discount, discount_coef, mrp, max_budget, constant){
  -sum(discount * discount_coef + constant)
}  

# g(x) <= 0
constraint <- function(discount, discount_coef, mrp, max_budget, constant){
  sum((discount * discount_coef + constant)/(mrp-discount)*discount) - max_budget
}


# mean of the bounds as an initial guess
init_guess <- rowMeans(optim_data[,4:5])

sol <- nloptr(x0 = init_guess,
              eval_f = sales_value,
              lb = optim_data$min_discount, # lower bound
              ub = optim_data$max_discount, # upper bound
              eval_g_ineq = constraint, # g <= 0
              opts = list("algorithm" = "NLOPT_LN_COBYLA", "print_level" = 2, "maxeval" = 2000),
              discount_coef = optim_data$discount_coef,
              mrp = optim_data$mrp,
              max_budget = 100,
              constant = optim_data$constant)

which gives the following solution after 1010 iterations (matches Excel):

  > sol$solution
 [1]  14.31000  25.20000  30.60000  29.90000  39.10000 100.00000 164.94972
 [8] 164.57111  66.24000  71.30000 200.00000  82.56430  51.00000  77.32753

I use the mean of the boundary values as an initial guess. Hope this helps.

slava-kohut
  • 4,203
  • 1
  • 7
  • 24
  • Thanks much for sharing your experience... really appreciate. Just a question, would you be able to give some explanation on choosing 'NLOPT_LN_COBYLA' algo, instead of other derivative algos. – nikn8 Jul 10 '20 at 05:57
  • Also, Could you please help me with your experience, if **other regression models** (random forest or gbms) could be used, like linear regression I have used in example, **as objective function** in **nloptr** ?? – nikn8 Jul 10 '20 at 06:15
  • @Neel, I had some experience with this algorithm, this is why I chose it. You can use any algorithm as long as it doesn't require analytic derivatives. – slava-kohut Jul 10 '20 at 12:20
  • @Neel You can use any non-linear optimization routine to optimize the objective function with respect to the model parameters. If you have an explicit expression for the objective function as a function of model parameters, you can minimize it, unconstrained or with constaints. This is what is going on behind the scene of `gbm`, random forest, neural nets, etc. – slava-kohut Jul 10 '20 at 12:23
  • True, if we have explicit expression, then we can use any optimization algorithm. As we know, linear regression provides coeff, but other models doesn't give exclusively the relation(equation or coeff) between predictors and response... and hence the issue. Actually I found something [here](https://stackoverflow.com/questions/54833739/can-i-use-a-machine-learning-model-as-the-objective-function-in-an-optimization) but couldn't understand exactly. hope you get the idea, what I am looking for. – nikn8 Jul 10 '20 at 12:28
  • @Neel All models provide an expression for the response, but sometimes it is so complicated that it is omitted in textbooks. Also, this expression depends on the parameters the user picks as part of model setup (e.g., number of trees, tree depth). – slava-kohut Jul 10 '20 at 12:37
  • @Neel take a look at my answer [here](https://stackoverflow.com/questions/62395844/how-to-fit-a-data-set-to-an-specific-function-by-trial-and-error-or-a-better-spe/62396762#62396762). Model coefficients were obtained by using non-linear optimization. – slava-kohut Jul 10 '20 at 12:39
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/217603/discussion-between-neel-and-slava-kohut). – nikn8 Jul 10 '20 at 12:45
2

This finds the same result as the Xl Solver :

optim_data$constant <- c(30,60,-10,34,-23,54,-34,-56,23,45,-71,19,29,39)

obj <- function(discount) {
  sales_value <- (discount * optim_data$discount_coef) + optim_data$constant
  return(-sum(sales_value)) # looking for minimum
}

con <- function(discount) {
  sales_value <- (discount * optim_data$discount_coef) + optim_data$constant
  spend_value = (sales_value/(optim_data$mrp-discount))*discount
  return(list(ceq = NULL, c = sum(spend_value)-100))
}

library(NlcOptim)
solnl(X= optim_data$min_discount, objfun = obj, confun  =con , lb = optim_data$min_discount , ub = optim_data$max_discount )
#> $par
#>            [,1]
#>  [1,]  14.31000
#>  [2,]  25.20000
#>  [3,]  30.60000
#>  [4,]  29.90000
#>  [5,]  39.10000
#>  [6,] 100.00000
#>  [7,] 164.95480
#>  [8,] 164.56988
#>  [9,]  66.24000
#> [10,]  71.30000
#> [11,] 200.00000
#> [12,]  82.55170
#> [13,]  51.00000
#> [14,]  77.33407
#> 
#> $fn
#> [1] -481.6475

Note that the objective function should be negative as solnl is looking for a minimum.
The constraint should be c instead of ceq as it is an inequality : spend-100<0

Waldi
  • 39,242
  • 6
  • 30
  • 78
  • @Neel, regarding your suggested edit : X is the starting vector of parameter values, so I chose optim_data$min_discount because a vector of zeros would be below lower bounds so that the algorithm can't use it and will probably start searching with lower bound. – Waldi Jul 09 '20 at 13:10
  • Thank you. I just realized, by checking at [R documentation of it](https://www.rdocumentation.org/packages/NlcOptim/versions/0.6/topics/solnl). – nikn8 Jul 09 '20 at 14:06
  • Could you please help me with your experience, if other regression models (random forest or gbms) could be used, like linear regression I have used in example, as objective function in NlcOptim or any other library you are aware of?? – nikn8 Jul 10 '20 at 09:57
  • As far as I know, regression, gradient boosted regression trees, ... are themselves optimisation procedures which on simple problems converge to the same solution. I doubt that optimization of an already optimized solution will give better results. – Waldi Jul 10 '20 at 12:17
  • If you have closely seen my linear equation *discount* is a predictor and *sales* is my response. the linear equation gives me relation between them, which I can use as objective function to *maximise my sales* and find the optimal discount for each product by involving the spend constraints. If I am using RF or GBM, then I can't get the relationship and hence the optimal depth. Hope you understand my concern. – nikn8 Jul 10 '20 at 12:41