# MARKETING OPTIMIZATION R-SCRIPT # Bob Agnew, raagnew1@gmail.com, www.raagnew.com # The algorithm imbedded in this script optimizes the assignment of offers to # prospects, subject to stipulated offer quantity constraints and the limit of # at most one offer per prospect. This demo problem, with a million # prospects and three offers, solves quickly on my home PC. This script can be # adapted for a different number of offers and alternative equality or inequality # constraints, as long as they can be linearized, although the dual must be # formulated for solution. Inequality constraints would require a different # R solver (nlminb) which incorporates lower bounds. # # Offer vector contains the optimal offers; zero indicates no offer to a prospect # date() # Start time stamp # Number of Prospects n <- 1000000 # Initialize prospect offer and profit at zero, i.e., no offer. Profit <- Offer <- rep(0,n) # Simulate Prospect Profits for Three Offers # Different profit vectors will be generated for each execution, which has the # advantage of showing that solution is robust across various instances. p1 <- 100*runif(n,0,1) - 10 p2 <- .6*p1 + .4*(60*runif(n,0,1) - 6) p3 <- .4*p1 + .6*(40*runif(n,0,1) - 4) # Ordinarily, offer profit "scores" would be input from an external text file using the # scan function, i.e., inp <- scan("infile", list(0,0,0)); p1 <- inp[[1]]; # p2 <- inp[[2]]; p3 <- inp[[3]]. You can check this out by saving an instance of the # simulated vectors via write(rbind(p1,p2,p3), file = "infile", ncolumns = 3) and then # use a different version of this script with scan rather than simulation. # Mean Offer Profits c(mean(p1),mean(p2),mean(p3)) # Stipulated Offer Quantities; 400000 get no offer b <- c(300000,200000,100000) # Dual Function dual <- function(u) { d1 <- p1 - u[1]; d2 <- p2 - u[2]; d3 <- p3 - u[3] v <- pmax(d1,d2,d3,rep(0,n)) y <- b%*%u + sum(v) y } # Dual Minimization using Nonlinear Minimization Function out <- nlm(dual,c(0,0,0)) # Dual Minimization Output out # Keep dual minimum and estimates. mindual <- out$minimum u <- out$estimate d1 <- p1 - u[1]; d2 <- p2 - u[2]; d3 <- p3 - u[3] v <- pmax(d1,d2,d3) ord <- order(v, decreasing = TRUE) s <- c(0,0,0) h <- 100000000 for (j in 1:sum(b)) { k <- s < b m <- ord[j] i <- which.max(c(k[1]*d1[m]-(1-k[1])*h,k[2]*d2[m]-(1-k[2])*h,k[3]*d3[m]-(1-k[3])*h)) Offer[m] <- i Profit[m] <- c(p1[m],p2[m],p3[m])[i] s[i] <- s[i] + 1 } # Dual Minimum (Upper Bound) mindual # Primal Optimum (Maximum Profit) sum(Profit) # Note that we are extremely close, in some instances right on. # Offer Quantities s # Offer Profits c(sum(Profit[Offer == 1]),sum(Profit[Offer == 2]),sum(Profit[Offer == 3])) # Offers to first 100 prospects Offer[1:100] # In actual application, you would export a text file containing the optimal # prospect offers, i.e., write(Offer, file = "outfile", ncolumns = 1). date() # End time stamp