Optimization Modeling Using R (Timothy R. Anderson) (Bibis - Ir)
Optimization Modeling Using R (Timothy R. Anderson) (Bibis - Ir)
Modeling Using R
Chapman & Hall/CRC Series in Operations
Research
Series Editors:
Malgorzata Sterna, Bo Chen, Michel Gendreau, and Edmund Burke
Rational Queueing
Refael Hassin
Timothy R. Anderson
First edition published 2023
by CRC Press
6000 Broken Sound Parkway NW, Suite 300, Boca Raton, FL 33487-2742
Reasonable efforts have been made to publish reliable data and information, but the author and pub-
lisher cannot assume responsibility for the validity of all materials or the consequences of their use.
The authors and publishers have attempted to trace the copyright holders of all material reproduced
in this publication and apologize to copyright holders if permission to publish in this form has not
been obtained. If any copyright material has not been acknowledged please write and let us know so
we may rectify in any future reprint.
Except as permitted under U.S. Copyright Law, no part of this book may be reprinted, reproduced,
transmitted, or utilized in any form by any electronic, mechanical, or other means, now known or
hereafter invented, including photocopying, microfilming, and recording, or in any information
storage or retrieval system, without written permission from the publishers.
For permission to photocopy or use material electronically from this work, access www.copyright.
com or contact the Copyright Clearance Center, Inc. (CCC), 222 Rosewood Drive, Danvers, MA
01923, 978-750-8400. For works that are not available on CCC please contact mpkbookspermis-
[email protected]
Trademark notice: Product or corporate names may be trademarks or registered trademarks and are
used only for identification and explanation without intent to infringe.
DOI: 10.1201/9781003051251
Typeset in LM Roman
by KnowledgeWorks Global Ltd.
Publisher’s note: This book has been prepared from camera-ready copy provided by the authors
To Carrie, Trent, and Paige for their constant support and patience while
Dad worked late nights on this book.
Contents
List of Tables xv
Preface xix
Author xxiii
1 Introduction 1
1.1 What is Operations Research . . . . . . . . . . . . . . . . . . . 1
1.2 Purpose of this Book . . . . . . . . . . . . . . . . . . . . . . . . 1
1.3 Range of Operations Research Techniques . . . . . . . . . . . . 2
1.4 Relationship between Operations Research and Analytics . . . 3
1.5 Importance of Optimization . . . . . . . . . . . . . . . . . . . . 3
1.6 Why R? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.7 Conventions Used in this Book . . . . . . . . . . . . . . . . . . 6
vii
viii Contents
4 Sensitivity Analysis 71
4.1 Base Case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
4.2 Shadow Prices . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
4.2.1 Extraction and Interpretation . . . . . . . . . . . . . . . 72
4.2.2 Example of Adding an Hour to Assembly . . . . . . . . 74
4.2.3 Shadow Prices of Underutilized Resources . . . . . . . . 75
4.3 Reduced Costs of Variables . . . . . . . . . . . . . . . . . . . . 76
4.3.1 Reduced Cost of Ants . . . . . . . . . . . . . . . . . . . 77
4.3.2 Reduced Price of Bats . . . . . . . . . . . . . . . . . . . 79
4.4 Using Sensitivity Analysis to Evaluate a New Product . . . . . 81
4.5 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
C Troubleshooting 247
C.1 Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
C.2 Model Building . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
C.2.1 Define and Formulate before Implementing . . . . . . . 247
C.2.2 Failing to Look for Past Optimization Models . . . . . . 248
C.2.3 Misrendering of PDF . . . . . . . . . . . . . . . . . . . . 248
C.2.4 Blank Lines in LaTeX . . . . . . . . . . . . . . . . . . . 249
C.2.5 Problems with PDF Creation . . . . . . . . . . . . . . . 250
C.3 Implementation Troubleshooting . . . . . . . . . . . . . . . . . 252
C.3.1 Errors in a Piped Model . . . . . . . . . . . . . . . . . . 252
Contents xi
Bibliography 271
Index 273
List of Figures
xiii
xiv List of Figures
xv
xvi List of Tables
This book covers using R for doing optimization, a key area of operations
research, which has been applied to virtually every industry. The focus is on
linear and mixed integer optimization.
Pedagogically, since the late 1990s, I had been a proponent of teaching intro-
ductory optimization using spreadsheets as a way to bridge the barrier be-
tween data and applications as well as teaching better spreadsheet practices.
While this worked well, the disconnect with algebraic modeling was always a
problem. Excel add-ins enabling algebraic tools were rich but not seamless. In
2018, I decided to switch to R for a variety of reasons:
xix
xx Preface
Intended Audience
This book is written for people with at least a passing familiarity with R
or another programming environment, but Appendix A helps jumpstart the
motivated reader to jump in without a background. Some familiarity with
mathematics is used throughout the book at the level of subscripts and sum-
mations, but refreshers are provided in Appendix B. It is assumed that the
reader is willing to use R and get their hands dirty with experimenting.
Instructor Notes
This book has been used multiple times for a ten-week graduate course on op-
erations research emphasizing optimization. It can be used for graduate and
undergraduate courses for people without a background in optimization and
varying levels of mathematical backgrounds. The focus is on applications (for-
mulating, implementing, and interpreting rather than algorithms. The book
Preface xxi
Acknowldegments
I would like to thank many people for their contributions, collaborations, and
assistance over the years. All errors are my fault, though.
• Dirk Schumacher, author of the ompr package used throughout this book
• Dr. Dong-Joon Lim, applications and methodological work in DEA
• Dr. Gerry Williams, application of DEA to construction contracting
• Dr. Janice Forrester, application of DEA to the energy industry
• Dr. Scott Leavengood, application of DEA to wood products
• Dr. Oliver (Lane) Inman, early work on TFDEA
• Dr. Maoloud Dabab, many suggestions over time
• Dr. K. Louis Luangkesorn, author of the first vignette on using glpk
in R
• Dr. Chester Ismay, contributions to the Portland and broader R com-
munity
• Dr. Jili Hu, rich interactions during his sabbatical in Portland
• Dr. Nina Chaichi, many suggestions over the years
• Tom Shott, primary author of the TFDEA package
• Aurobindh Kalathil Kumar, PhD student, many suggestions over time
• Kevin van Blommestein, earlier DEA & R work
• William (Ike) Eisenhauer, LaTeX formulation improvements
• Andey Nunes, coding improvements
• Christopher Davis, graphical example of LP
• Thanh Thuy Nguyen, fixed charge example
• Roland Richards, formatting assistance
• Caroline Blackledge, contributed to appendix
• Alexander Keller, contributed to appendix
• Shahram Khorasanizadeh, contributed to appendix
• Jose Banos, formatting assistance
• Jon Syverson, frequent and thoughtful feedback on early drafts
• Dawei Zhang, further editorial work on the book
• Navdeep Singh, assistance with formatting and additional exercises
• Ketsaraporn Kaewkhiaolueang, assistance with proofreading
xxiii
1
Introduction
DOI: 10.1201/9781003051251-1 1
2 1 Introduction
• Optimization
• Simulation
• Queuing Theory
• Markov Chains
• Inventory Control
• Forecasting
• Game Theory
• Decision Theory
Each of these topics can require a full book on its own. The focus of this book
is on the most widely used operations research technique, optimization, and
more specifically, linear and mixed-integer optimization.
1.4 Relationship between Operations Research and Analytics 3
The field of Operations Research significantly predates that of the term analyt-
ics, but they are closely linked. This is strongly demonstrated by the leading
professional organization for operations research, INFORMS (The Institute
for Operations Research and Management Science), developing a professional
certification for analytics. This certification process had leadership from the
industry representing the diverse users of Operations Research techniques.
1.6 Why R?
DOI: 10.1201/9781003051251-2 7
8 2 Introduction to Linear Programming
A simple LP now is to find the production plan of products that results in the
most profit. In order to do so, we need to define certain key items:
• the goal(s)
• the decisions
• the limitations
Let’s start with the goal(s). In this case, the production manager is simply
trying to make as much profit as possible. While cost cutting is also a goal for
many organizations, in this case and many applications, profit maximization is
appropriate. Maximizing profit is the referred to as the objective of the model.
People new to linear programming will often think of the decisions as the
amount of each resource to use. Instead, the decisions in this case would be
how much to make of each particular product. This drives the resource usage,
and the resource usage is a byproduct of these decisions. These decisions can
take on a range of values and are, therefore, called decision variables.
The decision variables are then combined in some way to reflect the perfor-
mance with respect to the organization’s objective. The equation combining
the decision variables to reflect this is then the objective function. For now,
we will assume that there is a single objective function, but we will allow for
multiple objectives in Chapter 8.
Lastly, what is limiting the organization from even better performance? There
are typically many limits such as the number of customers, personnel, supplier
capacity, etc. In this case, we focus on a set of resource limits based on staffing
in different centers and the supply of sensors. Since these limitations constrain
the possible values of decision variables, they are called constraints.
Every optimization model can be thought of a collection of:
Let’s put things together in the context of this application. In the base case,
our objective function is to maximize profit. We can’t express it, though, until
we define our decision variables. It is good practice to very clearly and precisely
define decision variables. While this case is very straightforward, the definition
of variables can get much more complicated as we move into richer and larger
models.
2.3 Graphically Solving a Linear Program 9
Our objective function and constraints can now be written as the optimization
model shown in the following formulation.
Note that since the objective function and each constraint is a simple linear
function of the decision variables, this is what we call a linear programming
model or LP for short. It would not be linear if any nonlinear function is made
of the decision variables. For example, squaring a decision variable, using
conditional logic based on the variable value, multiplying two variables, or
dividing a variable by a function of a variable. These and other issues would
then require using nonlinear programming or NLP. NLP is also widely used
but has limitations.
Linear programming: (LP, also called linear optimization) is a method to
achieve the best outcome (such as maximum profit or lowest cost) in a math-
ematical model whose requirements are represented by linear relationships.
It is impressive the number of situations that can be modeled well using linear
programming. Keeping to the world of linear programming, in general, allows
for finding the very best solution to very big problems in a short amount of
time. For example, it is common for practitioners to be analyzing real-world
problems with hundreds of thousands of decision variables and constraints.
Given that the linear program has two variables, it can be represented in
two-dimensions making it straightforward to draw and visualize. Before we
analyze it using R, we will walk through a graphical representation.
10 2 Introduction to Linear Programming
The constraints are differentiated by color, and the diagonal lines of the same
color drawn off the constraint are a hatching line indicating the direction of
the inequality constraint. In this case, each of the constraints are less than
or equal to constraints and are indicating that this constraint includes the
line and the region under the line. If it had been a greater than or equal to
constraint, the hatching would have been rising above the constraint line. If
it had been an exact equality constraint rather than an inequality constraint,
it would have been just the line with no hatching above or below.
In each figure, we illustrate the feasible region as a grayed oval, but it is the
whole region.
Since both products require two hours of testing and there are 480 hours
available, the Green Testing constraint connects producing just 240 Ants and
just 240 Bats.
We now draw our fourth and final constraint – Sensors. This constraint is done
in yellow, so it may be more difficult to see, but it goes from an Ants only
production plan of 600 units to a bats only production plan of 120.
This now results in a feasible region that satisfies all of the constraints simul-
taneously. Every point in this region is feasible or possible in that it does not
violate any of the constraints. There is an infinite number of possible points
in this feasible region.
Given that we are doing linear programming and are trying to maximize or
minimize a certain objective function, the optimal solution will be at a corner
12 2 Introduction to Linear Programming
point or more formally, a vertex. In our example, we can narrow our attention
from the full feasible region to just the five vertices or candidate solutions.
Lastly, we can pick from among these possible solutions by drawing parallel,
equal profit lines radiating out from the origin to find the last line that still
touches the feasible region. The figure to the right shows one line with a profit
of $1400. A second line is drawn that last touches the feasible region is at
$1980.
Graphically, we could read the number of Ants and the number of Bats to
produce. Accuracy would be limited to the drawing skills and tools used.
Rather than relying on this graphical approach for determining the coordi-
nates of each point drawn, each point in a two-dimensional space is defined
by the intersection of two lines. In this case, the optimal solution (and all of
the other candidate solutions too) can be defined by the intersection of two
constraint lines, rewritten as exact equalities. These can be solved as a system
of equations with two equations and two unknowns. The optimal solution here
2.3 Graphically Solving a Linear Program 13
is defined by the intersection of the Assembly (Red) and the Testing (Green)
constraints. Just solve 3𝐴 + 6𝐵 = 900 and 2𝐴 + 2𝐵 = 480 for 𝐴 and 𝐵.
Our first formulation named the variables individually and directly entered the
data in the formulation. We refer to this as an explicit formulation. We will
implement our first R LP model using explicit variables and data consistent
with the first formulation.
First, let us load the required packages using the library command, and then
we will move on to the actual implementation.
The quietly=TRUE option will not display standard messages returned when
loading libraries.
This code chunk uses the library command to load a series of useful
packages.1 The first line, library (kableExtra), enhances knitr's built-in
kable function to generate better-formatted tables. For more details, see
Appendix C. The following lines similarly load packages providing the
optimization functions that we will be relying on frequently. The ROIpackage
is the R Optimization Interface for connecting various optimization solvers
to R. The ROI.plugin.glpk provides a connection between the glpksolver and
R through ROI. While this would be sufficient, we are going to make use of
the omprpackage by Dirk Schumacher to provide algebraic representations for
linear programs. The ompr package also requires a connector to ROI, aptly
named ompr.roi.
Now we move on to implement and solve the linear program. Let’s go through
it step by step.
1 As noted earlier, if these packages are not preinstalled, you may need to install them
The first line initializes an empty model using the MIPModel command and
stores it in model0. We could pick any valid R object name in place of model0,
but we are using this to indicate that it is our initial model. The term MIP
used in the function call is an acronym for Mixed Integer Program. This is a
more general case of LP and will be discussed in greater detail later. We can
see that the model is empty by simply displaying the summary of model0.
model0
The summary states that there are no constraints or variables. Next, we will
add variables.
The first line takes model0 and adds the Ants variable to it, creating an en-
hanced model0a. Note that the continuation of the first line specifies the type
of variable and whether it has a bound. The Ants variable is continuous (as
compared to integer or binary, which we will get to in chapter 6) and non-
negative. The variable is made non-negative by setting a zero lowerbound
(lb=0). The lowerbound can be set to other values such as a minimum pro-
duction level of ten. Also, upperbounds for variables can be set using ub as a
parameter.
The Bats variable is added in the same way to model0a creating model0b. Let’s
check the new model.2
2 We are creating a series of models here as we keep adding one element at a time and
appending a letter to model0 to differentiate them. The result is that we will have many
16 2 Introduction to Linear Programming
model0b
Next, we can add the objective function. We set the objective function as well
as declaring it to be a max rather than a min function.
model0c<-set_objective(model0b,7*Ants+12*Bats,”max”)
model0g
incrementally enhanced versions of model0. Other than for demonstration purposes in our
first model, there is no need to incrementally save each version of the file. We could replace
each of the assignments by just resaving it to model0.
2.4 Implementing and Solving with ompr 17
Our model has the three core elements of an optimization model: variables,
constraints, and an objective function. Let’s go ahead and solve model0g. The
LP can be solved using different LP engines – we’ll use glpk for now. The
results of the solved model will be assigned to result0. glpk is the R package
name for the “GNU Linear Programming Kit”, which is intended for solving
large-scale linear programming (LP), mixed integer programming (MIP), and
other related problems.
We built up the model line by line incrementally taking the previous model,
adding a new element, and storing it in a new model. Note that we could keep
placing them into the original model if we had wished such as the following.
In this case, we take the previous command’s output, model0, add an element
to it, and then store it back in the same object, model0. We keep doing it for
each element until we are done. This is equivalent to assigning the value of 2
to a. We then multiply a by 3 and assign the result again to a.
result0$solution
## Ants Bats
## 180 60
Notice that we used two ways to get the solution of decision variables from ompr.
In the first case, we can get all the decision variable values as a component of
our result object, result0. When ompr solves a problem, it creates an object
with a number of components. After solving a model successfully in the console
and saving the result to an object, you can see all the components by typing
the object name and a dollar sign $. For example, using the command Run All
Code Chunks Above and then typing result0$ will show the following options.
• model: Saves the model that was solved into the resultobject.
• objective_value: Gives the optimal objective function value found.
• status: States whether the problem is optimal or not optimal.
• solution: An alphabetical listing of variables and their values in the
optimal solution.
kbl(res0, booktabs=T,
caption=”Base Case Two Variable Solution”) |>
kableExtra::kable_styling(latex_options = ”hold_position”)
It might be helpful to see what the model looks like before examining the
results.
2.4 Implementing and Solving with ompr 19
model0
extract_constraints(model0)
## $matrix
## 4 x 2 sparse Matrix of class ”dgCMatrix”
##
## [1,] 1 4
## [2,] 3 6
## [3,] 2 2
## [4,] 2 10
##
## $sense
## [1] ”<=” ”<=” ”<=” ”<=”
##
## $rhs
## [1] 800 900 480 1200
This should look familiar. Let’s see if we can arrange to make it look more
similar to what we had given the model. Note that the term RHS represents
the right-hand side of the constraint.
constr0<-extract_constraints(model0)
# Extract the constraints from model
constr00<- cbind(as.matrix(constr0$matrix), # Get matrix of data
as.matrix(constr0$sense), # Get inequalities
as.matrix(constr0$rhs)) # Get right hand sides
rownames(constr00) <-c ('Machining','Assembly','Testing','Sensors')
kableExtra::kbl(constr00, booktabs=T, align='cccc',
20 2 Introduction to Linear Programming
In each step, we are simply taking the output of the previous step and feeding
it in as the first term of the following step. It is almost like the output is
a bucket that is then passed as a bucket into the next step. Rather than
carrying a bucket from one step into the next one, a plumber might suggest
a pipe connecting one step to the next. This is done so often that we refer to
it as piping and a pipe operator. The pipe symbol, represented by |> will
let us do this more compactly and efficiently. It takes a little getting used to
but is a better way to build the model. Also, note that the piping operator
requires R version 4.1.0. or higher.
Here is the equivalent process for building the base case implementation using
a piping operator without all the intermediate partial models being built.
Notice how it is a lot more concise. We will typically use this approach for
model building, but both approaches are functionally equivalent.
Piping’s major drawback is that it treats all of the piped code as one line of
code, and it can, therefore, make debugging much harder. The first line creates
the basic model. The |> serves as a pipe symbol at the end of each line. It
basically means take product of the previous command and feed it in as the
first argument of the following command. While it may shorten each line of
code and may be faster, there is a drawback in that the piped commands are
treated as one very long line of code making it harder to find where an error
occurs.
Let’s check to see the status of the solver. Did it find the optimal solution?
We do this by extracting the solver status from the result object.
print(solver_status(result0))
## [1] ”optimal”
Furthermore, we can do the same thing to extract the objective function value.
print(objective_value(result0))
## [1] 1980
objective function (i.e. maximum possible profit), but it does not tell us what
decisions give us this profit.
Since the LP solver found an optimal solution, let’s now extract the solution
values of the decision variables that give us this profit.
print(get_solution(result0, Ants))
## Ants
## 180
print(get_solution(result0, Bats))
## Bats
## 60
In summary, our optimal production plan is to make a mix of Ant and Bat
drones. More specifically, we should produce 180 Ants and 60 Bats to generate
a total profit of 1980. Given the situation, this is the optimal or most profitable
possible production plan.
We will now extend the previous model to account for a third product, the
Cat drone. The goal is still to find the most profitable production plan. See
the table to the right with a summary of the new situation.
2.5 Adding a Third Product (Variable) 23
Let’s check to see the status of the solver and the results.
print(solver_status(result1))
## [1] ”optimal”
result1$objective_value
## [1] 2225
result1$solution
There are several special cases where a linear program does not give the simple,
unique solution that we might expect. These are:
2.6 Linear Programming Special Cases 25
• No feasible solution
• Multiple optima
• Redundant constraint
• Unbounded solution
Now, let’s look at how we would modify the earlier formulation to come up
with each of these situations.
Let’s assume that the sales manager comes in and says that we have a con-
tractual requirement to deliver 400 Ants to customers. This results in the
following LP. 3
Now let’s extend our formulation with this change. In this case, we are going to
simply add the new constraint to model1 and create a new model, model1infeas
to solve.
model1infeas <-
add_constraint(model1, Ants >= 400) #THIS IS THE NEW CHANGE
Note that the constraint on the minimum number of Ants could also be imple-
mented by changing the lower bound on the Ants variable to be 400 instead
of zero.
3 Itcan be helpful to highlight a part of a LaTeX formulation. In this case, we can use
\textcolor{red}{before the part that we want to turn red and use a closing }. You can also
review the RMD file to see how it is done.
26 2 Introduction to Linear Programming
print(solver_status(result1infeas))
## [1] ”infeasible”
result1infeas$solution
Notice that since the solver status was infeasible, the values for the decision
variables are not feasible and, therefore, cannot be considered a reliable (or
possible) production plan. This highlights why the solver’s status should al-
ways be confirmed to be “Optimal” before results are discussed.
There are a couple of ways of creating situations for multiple optima. One
situation is to have a decision variable be identical or a linear multiple of
another variable. In this case, each Cat now consumes exactly double the
resources as an Ant and generates double the profit of an Ant. The new LP
is shown in the following formulation.
The implementation can be simplified again since we are only changing the
objective function; let’s change the objective function in model1 and save it to
model2a.
2.6 Linear Programming Special Cases 27
print(solver_status(result2a))
## [1] ”optimal”
kbl(res2a, booktabs=T,
caption=”First Solution of Multiple Optima Case”) |>
kableExtra::kable_styling(latex_options = ”hold_position”)
Okay. When I ran it, all the production was focused on a mix of Ants and Bats
28 2 Introduction to Linear Programming
## [1] ”optimal”
Again, a product mix is made but now instead and Ants and Bats, the mix
is made up of Bats and Cats with exactly the same level of profit. This is
an instance of multiple optima. Let’s try one more situation by adding a
constraint forcing the number of Ants to be 60 and solving.
## [1] ”optimal”
Let’s summarize these results more clearly by displaying them in a single table.
Each of three solutions has exactly the same profit but different production
plans to generate this profit. Furthermore, while we are listing three, there
are actually many more solutions. You can force the system to find other
solutions by setting the number of Ants to a number between 0 and 180
or Cats between 0 and 90. The number of solutions is infinite if we explore
fractional solutions. More generally, when there are two alternate optimal in a
linear program with continuous variables, there is actually an infinite number
of other optimal solutions between them.
While our first optimization found a solution focused more on Ants, and our
second found a solution that emphasized Cats, this is dependent upon the
particular solver’s algorithmic implementation. The user can consider this to
be relatively arbitrary as there is no difference between them based on the
objective function.
Which solution is the best? Within the limits of this problem, we can’t distin-
guish between them and they are equally good. If an application area expert
or the end decision maker prefers one solution over the other-refinements in
costs or profits could be included directly, or additional constraints could be
added. Sometimes considering these two otherwise equal solutions will elicit a
response such as, “With all other things being equally, we prefer this alterna-
tive because of future market positioning.” This could represent an additional
or secondary goal, which is addressed using goal programming as discussed in
Chapter 8.
30 2 Introduction to Linear Programming
For the redundant constraint, a new constraint for painting is created. Let’s
assume each item is painted and requires one liter of paint. We have 500 liters.
The corresponding constraint is then added to the model.
Now we can implement the model. Rather than building the model from
scratch, let’s just add this one constraint to a previously built model.
## [1] ”optimal”
result3$solution
This constraint was redundant because the other constraints would keep us
from ever having 500 drones or, therefore, ever needing 500 liters of paint. In
other words, there is no way that this constraint could ever be binding at
any solution regardless of what the objective function is. More precisely, the
elimination of a redundant constraint does not change the size of the feasible
region at all.
2.6 Linear Programming Special Cases 31
Note that not all non-binding constraints at an optimal solution are redundant.
Deleting a non-binding constraint and resolving won’t change the optimal
objective function value. On the other hand, for a different objective function,
that non-binding constraint might become binding, and therefore, different
solutions would be found if it were deleted.
Challenge 1: Can you use a calculator to simply estimate the maximum
number of Ants that could be made? Bats? Cats?
Challenge 2: How would you modify the formulation to find the most total
drones that could be produced irrespective of profit?
As with other cases, there are multiple ways of triggering this condition. For
the unbounded solution, instead of at most a certain amount of resources can
be used, the constraints are changed to at least that amount of each resource
must be used. This doesn’t make a lot of sense in the setting of this application.
Perhaps a cynic would say that in a cost-plus business arrangement or a
situation where the factory manager has a limited purview and doesn’t see
issues such as downstream demand limits and cost impacts, it results in this
kind of myopic perspective. More commonly, an unbounded solution might be
a sign that the analyst had simply reversed one or more inequalities or the
form of the objective (max vs. min).
Let’s make this change to the model and the implementation by simply chang-
ing each ≤ to a ≥ for each constraint.
Now let’s see what is reported from trying to solve this model.
print(solver_status(result4))
## [1] ”infeasible”
result4$solution
The solver status reports that the problem is infeasible rather than unbounded,
but by inspection, the solution is feasible in that it satisfies all of the con-
straints of ≥ and therefore, the LP is feasible.
Infeasible vs. Unbounded. This is a known issue in ompr as of 0.8.1 and
reported on github. It is caused by not being able to distinguish between the
different status conditions for situations other than being solved to guaranteed
optimality. This is caused by the variety of status codes used by different LP
solvers (glpk, symphony, and others) passing a numerical status code to ROI.
Each solver has defined their status codes in different ways.) The result is
that you should read the ompr status of “infeasible” to indicate that is not
assured of being an optimal solution. The misinterpretation of solver status
is an outcome of the independent toolchain from the solver (glpk to ROI to
ompr.)
This is another good reminder that it is important to always check the status
of the solver.4
4 ompr 1.0 implemented a fix to change the solver status to “error” to indicate that
it did not solve the problem to optimality. Detailed information is available from re-
sult4$additional_solver_output
2.7 Abstracting the Production Planning Model 33
Subscripting makes for a more succinct and compact way of expressing the
same concept.
Similarly, the resources: Machining, Assembly, Testing, and Sensors resources
are numbered as 1, 2, 3, and 4, respectively. Note that we do not need to
separate the resources by units; the first three are in units of hours, while the
last is a count of sensors.
Here we are talking about abstracting the model in terms of variable names
and notation. In the next chapter, we will continue with generalizing the
model’s number of products and resources.
In this book, we will focus on solving linear programs using the Simplex
method. While we don’t cover the algorithm of the Simplex method, it is
well implemented in a variety of robust packages that we can readily use for
modeling purposes. Conceptually, the Simplex method can be thought of as
traversing the edges of a multi-dimensional space. In general, it is usually a
very quick and efficient process for even very large problems. There are alter-
natives to solving optimization problems.
First, Karmarkar developed an approach called Interior Points Algorithm that
instead of following the edges, instead cuts through the middle of the multi-
dimensional region. On certain problems, this can be significantly faster. Often
a standard linear programming solver will include an option to use an interior
points approach.
Another very important way of solving optimization problems is using heuris-
tic methods. These approaches cover a range of techniques but in general,
unlike the Simplex and Interior Points method approaches do not guaran-
tee optimality. These include evolutionary or genetic algorithms, simulated
annealing, and gradient search (hill-climbing). These approaches can be par-
ticularly helpful on nonlinear problems or complex integer programming prob-
lems.
Changing the method of solving an optimization problem can often be done
at the implementation stage after the model is formulated. The approach of
formulating the model will often stay the same regardless of the approach used
for solving.
2.9 Exercises 35
2.9 Exercises
Exercise 2.1 (Adding a Dog Drone). Your company has extended produc-
tion to allow for producing the dog drone and is now including a finishing
department that primes and paints the drones.
Medium- Available
Light Medium Dark Dark (hours)
Price $5.0 $5.2 $5.55 $6
(/𝑙𝑏)|$8|$10|$9|$11|||𝑉 𝑎𝑟𝑖𝑎𝑏𝑙𝑒𝐶𝑜𝑠𝑡(/lb)
Arrange (lbs/hr) 10 10 10 10 4000
Roast (lbs/hr) 6 4 2 8 2000
Cool (lbs/hr) 3 2 2 2 1480
Degas (lbs/hr) 3 4 2 4 1200
Packaging (lbs/hr) 4 4 2 3 1200
2.9 Exercises 37
DOI: 10.1201/9781003051251-3 39
40 3 More Linear Programming Models
We could further generalize this by eliminating the hard coding of the number
of products and resource constraints. Instead we define the number of products
and resources as NProd and NResources, respectively.
Let’s introduce a very convenient shorthand symbol, ∀, is read as “for all.” It
can be interpreted to mean “repeat by substituting for all possible values of
this subscript.” In the constraint, it means to repeat the constraint line with
𝑗 = 1, again with 𝑗 = 2, and so on for as many values of j as make sense in
the model.
We also will use it in the non-negativity constraint to avoid having to list out
every variable, x_1, x_2, etc. In other words, given that i is used consistently
for the three products, then 𝑥𝑖 ≥ 0 ∀ 𝑖 is equivalent to 𝑥𝑖 ≥ 0 , 𝑖 = 1, 2, 3 or
even 𝑥1 ≥ 0, 𝑥2 ≥ 0, 𝑥3 ≥ 0.
The payoff is modest when there are 3 products and 4 constraints but when
there are hundreds of products and thousands of constraint, the benefit is
tremendous. Furthermore, the generalized, algebraic model doesn’t change
when a new product is added or one is discontinued, it is only a change of
data that is fed into the model. The result is that the ∀ symbol can simplify
the description of complex models when index ranges are clear.
We can then rewrite the previous formulation as the following, more general
formulation.
𝑁𝑃 𝑟𝑜𝑑
Max: ∑ 𝑃𝑖 𝑥𝑖
𝑖=1
𝑁𝑃 𝑟𝑜𝑑
subject to ∑ 𝑅𝑖,𝑗 𝑥𝑖 ≤ 𝐴𝑗 ∀ 𝑗
𝑖=1
𝑥𝑖 ≥ 0 ∀ 𝑖
A helpful convention is to use capital letters for data and lower case letters
for variables. Some people will swap this around and use capital letters for
variables and lower case letters for data – it doesn’t matter as long as a model
is consistent. It gives a quick visual cue for the reader as to whether each item
is a variable or constraint.
More complex models often run out of letters that make sense. A common
approach in these models is to use a superscript.
For example, perhaps labor cost for each worker, w, could have different values
for both normal and overtime rates. Rather than separate data terms for such
closely related concepts, we might denote regular hourly labor cost for worker
𝑅 𝑂
w as 𝐶𝑤 and for overtime as 𝐶𝑤 . Another option is to use a Greek letter such
as 𝜃. We’ll see a few Greek letters used in chapter 5.
Again, this highlights why it is very important to clearly define all the data and
variables used in the model. Think of it as building a language for describing
the particular model. It is frequently an iterative process that it is updated
as the model is refined and developed. Frequently a definitions section will
warrant its own section or subsection and should precede the formulation.
Let’s implement a four product model requiring varying levels of five limited
resources. We will start our implementation by creating the appropriate data
structures in R.
NProd <- 4
NResources <- 5
ProdNames <- lapply(list(rep(”Prod”,NProd)),paste0,1:NProd)
# Product names: Prod1, Prod2, ...
Profit <- matrix(c(20, 14, 3, 16),
ncol=NProd,dimnames=c(”Profit”,ProdNames))
42 3 More Linear Programming Models
ResNames<- lapply(list(rep(”Res”,NResources)),
paste0,1:NResources)
# Resource names: Res1, Res2, ...
Resources <- matrix(c( 1, 3, 2, 3, 2, 3, 4, 3, 5, 7,
2, 2, 1, 2, 2, 2, 2, 2, 4, 6),
ncol=NProd,
dimnames=c(ResNames,ProdNames))
Available <- matrix(c(800, 900, 480, 1200, 500),
ncol=1,dimnames=c(ResNames,”Available”))
This should match the data that we hard coded into the R linear programming
model in the previous chapter.
Similarly, we can display the resources used by each product and the amount
of each resource available alongside each other.
We have used the cbind function to do a column binding of the data. In this
way the representation is more visually intuitive.
To ensure that we know how to access the data, if we want to see how the
amount of the first resource used by the second product, you can enter Re-
sources[1,2] in R Studio’s console, which is then evaluated as 3.
Using inline code is a great feature of RMarkdown. In the text of your RMark-
down, anything enclosed by a pair of single tick marks will be shown as a code
chunk. If it starts with the letter r, it will be passed instead to R for evalua-
tion. This allows for discussing results in a reproducible manner. For example,
rather than discussing a result giving a value of 42.00 in the text and a later
3.2 The Algebraic Model 43
rerun of the analysis has an updated table of results showing the result to
be 42.42, by using inline code evaluation, it will always show the up to date
result.
Now, let’s begin building our optimization model.
First, we’ll start by loading the packages that we are using. I’ll typically do
this as at the beginning of an RMarkdown document but for demonstration
purposes, I’ll put it here at the beginning of our model implementation. It is
important to note that when knitting an RMarkdown document, it will run as
a fresh environment without having items in memory from your environment
and packages loaded. When you run all code chunks, it will retain the currently
loaded packages.
We will continue with the code to build the model in a generic format. Note
that in my ompr model, I generally like to give each linear programming
variable in ompr a V prefix to differentiate it from a regular R data object that
might exist elsewhere in my R environment. Conflicts between R objects and
ompr variables are a common problem for modelers and this helps to avoid
the problem, assuming that you don’t have a lot of other R objects that start
with V. For more discussion about this issue and other common problems that
are particularly common in optimization modeling using R, see Appendix C.
solve_model(with_ROI(solver = ”glpk”))
prodmodel
## Status: optimal
## Objective value: 4800
This is useful to know but we are really interested in how to generate this
profit. To do this, we need to extract the values of the variables.
Let’s examine how the resources are consumed. To do this, we can multiply
the amount of each product by the amount of each resource used for
that product. For the first product, this would be a term by term sum
of each product resulting in 240, which is less than 800. We can do this
manually for each product. Another approach is to use the command,
Resources[1,]%*%t(results.products). This command will take the first row
of the Resources matrix and multiplies it by the vector of results.
Note how we used both an inline r expression and an inline executable r code
chunk. Inline code chunks can be inserted into text by using a single tick at
the beginning and end of the chunk instead of the triple tick mark for regular
code chunks. Also, the inline r code chunk starts with the letter r to indicate
that it is an r command to evaluate. A common use for this might be to show
the results of an earlier analysis.
One thing to note is that the first row of Resources is by definition a row
vector and result.products is also a row vector. What we want to do is do
a row vector multiplied by a column vector. In order to do this, we need to
convert the row vector of results into a column vector. This is done by doing
a transpose which changes the row to a column. This is done often enough
that the actual function in R is just a single letter, t.
We can go one further step now and multiply the matrix of resources by the
column vector of production.
This section covered a lot of concepts including defining the data, setting
names, using indices in ompr, building a generalized ompr model, extracting
decision variable values, and calculating constraint right-hand sides. If you
find this a little uncomfortable, try doing some experimenting with the model.
It may take some experimenting to get familiar and comfortable with this.
46 3 More Linear Programming Models
Let’s modify the above model. We can do this by simply changing the data
that we pass into the model and rebuilding the model.
Let’s change the number of sensors required for an Ant to 5. Recall that this is
the first product and the fourth resource. This is how we can change the value.
Resources[4,1] <- 5
# Set value of the 4th row, 1st column to 5
# In our example, this is the number of sensors needed per Ant
Now we will rebuild the optimization model. Note that simply changing the
data does not change the model.
prodmodel
3.3 Common Linear Programming Applications 47
## Status: optimal
## Objective value: 4800
Specific blend limitations arise in many situations. Recall from our three
variable case in Chapter 2 that the vast majority of drones produced were Cat
models. Let’s assume that we can’t have more than 40% of total production
made up of Cats. Let’s start by building our original three-variable model. In
our original case, this would be expressed as the following.
48 3 More Linear Programming Models
Let’s rebuild our three variable implementation and review the results to see
if it is violates our blending requirement.
kbl(base_case_res, booktabs=T,
caption=”Production Plan for Base Case”) |>
kable_styling(latex_options = ”hold_position”)
These results clearly violate the required production mix for Cats. Let’s now
work on adding the constraint that Cats can’t make up more than 40% of the
total production.
3.3 Common Linear Programming Applications 49
𝐶𝑎𝑡𝑠
≤ 0.4
𝐴𝑛𝑡𝑠 + 𝐵𝑎𝑡𝑠 + 𝐶𝑎𝑡𝑠
Alas, this is not a linear function since we are dividing a variable by a function
of variables so we need to clear the denominator. For demonstration purposes,
we will write out each step.
We like to get all the variables on the left side so let’s move them over.
Now we can just this to the original formulation. The result is the following
formulation.
ModelBlending<- add_constraint(model1,
-0.4*Ants -0.4*Bats +0.6*Cats <= 0)
resBlending<- solve_model(ModelBlending,
with_ROI(solver = ”glpk”))
50 3 More Linear Programming Models
Okay, now let’s put both side by side in a table to show the results.
rownames(base_case_res)<-”Base Model”
rownames(blendres)<-”with Constraint”
comparative <- rbind(base_case_res,round(blendres, 2))
colnames (comparative) <- c(”Ants”,”Bats”,”Cats”)
kbl(comparative, booktabs=T,
caption =
”Compare Baseline and Production Plan with Blending Constraint”)|>
kable_styling(latex_options = ”hold_position”)
TABLE 3.7 Compare Baseline and Production Plan with Blending Con-
straint
The table clearly shows that the blending constraint had a major impact on
our product mix.
3
Max: ∑ 𝑃𝑖 𝑥𝑖 [Maximize Profit]
𝑖=1
3
s.t. ∑ 𝑅𝑖,𝑗 𝑥𝑖 ≤ 𝐴𝑗 ∀ 𝑗 [Resource Limits]
𝑖=1
𝑥𝑖 ≥ 0 ∀ 𝑖
Consider the case of Trevor’s Trail Mix Company. Trevor creates a variety of
custom trail mixes for health food fans. He can use a variety of ingredients
displayed in following table.
Let’s go ahead and build a model in the same way as we had done earlier for
production planning.
NMix <- 4
NCharacteristic <- 4
MixNames <- lapply(list(rep(”Mix”,NMix)),paste0,1:NMix)
52 3 More Linear Programming Models
TTMix <-cbind(MixChar,CharMin)
kbl(TTMix, booktabs=T,
caption=”Data for Trevor Trail Mix Company”) |>
kable_styling(latex_options = ”hold_position”)
Hint: You might need to add a total amount to make! Modify the numbers
until it runs.
Now let’s build our model.
results.trailmix
## Status: optimal
## Objective value: 4426.667
In this formulation we need to make sure that we don’t ship out more than
the capacity of each supply node.
Similarly, we need to ensure that we don’t take in more than demand capacity
at any destination.
Min ∑ ∑ 𝐶𝑖,𝑗 𝑥𝑖,𝑗
𝑖 𝑗
𝑥𝑖,𝑗 ≥ 0 ∀ 𝑖, 𝑗
If we simply run this model, as is, the minimum cost plan would be to just
do nothing! The cost would be zero. In reality, even though we are focused on
costs in this application, there is an implied revenue and, therefore, profit (we
hope!) that we aren’t directly modeling. We are likely to instead be wanting to
3.4 Allocation Models 55
ship all of the product that we can at the lowest possible cost. More precisely,
what we want to do is instead determine if the problem is supply limited or
demand limited. This is a simple matter of comparing the net demand vs. the
net supply and making sure that the lesser is satisfied completely.
Then Demand
If… Situation is: Source Constraints Constraints
∑𝑖 𝑆𝑖 < ∑𝑗 𝐷𝑗 Supply ∑𝑗 𝑥𝑖,𝑗 = 𝑆𝑖 ∑𝑖 𝑥𝑖,𝑗 ≤ 𝐷𝑗
Constrained
∑𝑖 𝑆𝑖 > ∑𝑗 𝐷𝑗 Demand ∑𝑗 𝑥𝑖,𝑗 ≤ 𝑆𝑖 ∑𝑖 𝑥𝑖,𝑗 = 𝐷𝑗
Constrained
∑𝑖 𝑆𝑖 = ∑𝑗 𝐷𝑗 Balanced ∑𝑗 𝑥𝑖,𝑗 = 𝑆𝑖 ∑𝑖 𝑥𝑖,𝑗 = 𝐷𝑗
At this point, it may be a good idea to look at the arcs and see if you can
expect which routes are likely to be heavily used and which are likely to be
unused. You can also do this by looking at the matrix of transportation costs.
56 3 More Linear Programming Models
res.transp <-solve_model(
transportationmodel, with_ROI(solver =”glpk”))
res.transp$status
## [1] ”optimal”
Now we can display the table of optimal transportation decisions from supply
points to destinations cleanly.1
1 ompr 1.0 changed the order of the solution results. Adding the option byrow=T to the
matrix command fixes this.
58 3 More Linear Programming Models
Now we could put everything together. We’ll differentiate the costs to ship
𝑆 𝐷
from supply and to destination now as 𝐶𝑖,𝑗 and 𝐶𝑗,𝑘 , respectively.
𝑆 𝐷
Min ∑ ∑ 𝐶𝑖,𝑗 𝑥𝑖,𝑗 + ∑ ∑ 𝐶𝑗,𝑘 𝑦𝑗,𝑘
𝑖 𝑗 𝑗 𝑘
The directions of the supply and demand constraint inequalities must again
be considered as to whether the problem is supply or demand constrained, lest
we find an optimal solution of doing nothing.
3.4 Allocation Models 59
Real world applications applications often also require modeling a time dimen-
sion which leads us to issue production and inventory planning.
We can start by defining our decision variables. We will need a set of decision
variables for the amount to produce of each product p in each period t. Let’s
define that as 𝑥𝑝,𝑡 .
Similarly, we will define a variable for the inventory at the end of each period,
𝑦𝑝,𝑡 .
The production cost is simply cost per product multiplied by the number of
𝑀
products produced or ∑𝑝 ∑𝑡 𝐶𝑝,𝑡 ⋅ 𝑥𝑝,𝑡 .
𝐼
The inventory carrying cost follows the same structure, ∑𝑝 ∑𝑡 𝐶𝑝,𝑡 ⋅ 𝑦𝑝,𝑡 .
𝑀 𝐼
We can then combine them as ∑𝑝 ∑𝑡 (𝐶𝑝,𝑡 ⋅ 𝑥𝑝,𝑡 + 𝐶𝑝,𝑡 ⋅ 𝑦𝑝,𝑡 )
Sometimes care must be taken for the “boundary conditions” of the first or
last time period. In our case, we have a value defined the beginning inventory
of 𝐵𝑝 . For product 1, we would have 𝐵1 + 𝑥1,1 − 𝐷1,1 = 𝑦1,1 . For product 2,
it would be 𝐵2 + 𝑥2,1 − 𝐷2,1 = 𝑦2,1 and so on for other products. We would
generalize this then as 𝐵𝑝 + 𝑥𝑝,1 − 𝐷𝑝,1 = 𝑦𝑝,1 .
For periods after the first (𝑡 > 1)) Essentially the ending inventory is
equal to the beginning inventory plus the inflow (production) minus the
outflow (demand). For product p in time period t, this then becomes
𝑦𝑝,𝑡−1 + 𝑥𝑝,𝑡 − 𝐷𝑝,𝑡 = 𝑦𝑝,𝑡 .
𝑀 𝐼
Min ∑ ∑ (𝐶𝑝,𝑡 ⋅ 𝑥𝑝,𝑡 + 𝐶𝑝,𝑡 ⋅ 𝑦𝑝,𝑡 )
𝑝 𝑡
Any linear program with inequality constraints can be converted into what is
referred to as standard form. First, all strictly numerical terms are collected
or moved to the right-hand side and all variables are on the left-hand side.
3.4 Allocation Models 61
The last step is where all of the inequalities are replaced by strict equality
relations. The conversion of inequalities to equalities warrants a little further
explanation. This is done by introducing a new, non-negative “slack” variable
for each inequality. If the inequality is a ≤, then the slack variable can be
thought of as filling the left-hand side to make it equal to the right-hand side
so the slack variable is added to the left-hand side. If the inequality is ≥, then
the slack variable is the amount that must be absorbed from the left-hand
side to make it equal the right-hand side.
max 2 ⋅ 𝑥 + 3 ⋅ 𝑦
s.t.: 10 ⋅ 𝑥 + 2 ⋅ 𝑦 ≤ 110
2 ⋅ 𝑥 + 10 ⋅ 𝑦 ≥ 50
2⋅𝑥=4⋅𝑦
𝑥, 𝑦 ≥ 0
max 2 ⋅ 𝑥 + 3 ⋅ 𝑦
s.t.: 10 ⋅ 𝑥 + 2 ⋅ 𝑦 + 𝑠1 = 110
2 ⋅ 𝑥 + 10 ⋅ 𝑦 − 𝑠2 = 50
2⋅𝑥−4⋅𝑦 =0
𝑥, 𝑦, 𝑠1 , 𝑠2 ≥ 0
The standard form now consists of a system of equations, generally with far
more variables (both regular and slack) than equations. The simplex algorithm
makes use of the fact that a system of equations and unknowns can be solved
efficiently. The Simplex algorithm solves for as many variables (termed basic
variables) as there are equations and sets the remaining variables to zero (non-
basic variables). It then systematically swaps a variable from the set of basic
variables and non-basic variables until it can no longer find an improvement.
The actual algorithm for the Simplex algorithm is beyond the scope of what
we will cover in this book.
Let’s examine the solution to our example though.
62 3 More Linear Programming Models
## Status: optimal
## Objective value: 35
Notice that we had three equations and four variables (both regular and slack
variables.) This meant that at every iteration of the simplex algorithm, it
would set one variable equal to zero and solve for the other three variables
because solving three equations with three unknowns is very easy. This is
consistent with our solution, where 𝑠1 is zero and the other three variables
all had values.
3.5 Vector and Matrix Forms of LPs 63
x y s1 s2
Optimal Values 10 5 0 20
max 𝐶 ⋅ 𝑥
s.t.: 𝐴 ⋅ 𝑥 ≤ 𝐵
𝑥≥0
Let’s examine building our first two variable LP model in this way using the
lpsolveAPI package.
64 3 More Linear Programming Models
library(lpSolveAPI)
lps.model <- make.lp(0, 2) # Make empty 2 var model
add.constraint(lps.model, c(1,4), ”<=”, 800)
add.constraint(lps.model, c(3,6), ”<=”, 900)
add.constraint(lps.model, c(2,2), ”<=”, 480)
add.constraint(lps.model, c(2,10), ”<=”, 1200)
set.objfn(lps.model, c(-7,-12))
name.lp(lps.model, ”Simple LP”)
name.lp(lps.model)
# plot.lpExtPtr(lps.model)
solve(lps.model)
## [1] 0
get.primal.solution(lps.model, orig=TRUE)
While the graph may not be as elegant our earlier versions, it shows how the
problem can be visualized.
Earlier in the chapter, we created data structures for the generalized version
of the optimization model. Recall that earlier in this chapter we created a
Resources matrix of the resources used by each product – this corresponds
to our 𝐴 matrix. We also created a single column matrix, Available, for the
3.5 Vector and Matrix Forms of LPs 65
amount of each resource available, which is our 𝐵 vector. The Profit single
row matrix serves as our 𝐶 vector. The last item that we need to do is to
specify a direction for each inequality.
We could rewrite our formulation then as the following.
max 𝑃 𝑟𝑜𝑓𝑖𝑡 ⋅ 𝑥
s.t. 𝑅𝑒𝑠𝑜𝑢𝑟𝑐𝑒𝑠 ⋅ 𝑥 ≤ 𝐴𝑣𝑎𝑖𝑙𝑎𝑏𝑙𝑒
𝑥≥0
Notice that calling the solver directly such as in this way using Rglpk does
not require naming decision variables. A decision variable is created for each
column of the matrix. Directly accessing the solver allows for all the same
features that we use through ompr such as placing bounds on the variables,
changing to a minimization objective function, and more such as advanced
solving options. Other LP solving engines available in R such as RSymphony
and lpSolve have similar syntax for solving. Let’s look at the results. As
usual, we start by examining the status.
66 3 More Linear Programming Models
res2$status
## [1] 0
The status returned by Rglpk is zero. This may sound bad but the package’s
help file indicates that returning a status value of zero means that no problems
occurred, and it found an optimal solution. We can then proceed to examine
the objective function value.
res2$optimum
## [1] 4800
This objective function value may look familiar. Now let’s look at the solution
in terms of optimal decision variable values.
res2$solution
## [1] 240 0 0 0
Most of these LP solving engines can also allow models to be built up in-
crementally as well by creating an empty model, then adding an objective
function and adding constraints, bounds on variables as separate lines of R
code.
While working with the solver directly has the benefit of avoiding ambiguities
such as the difficulty of parsing solution status correctly, creating a single A
matrix for optimization problems with multiple sets of decision variables and
decision variables that may have 3 or more subscripts can be very tricky, hard
to maintain, and even harder to debug. Also, the results require just as much
or more care in unpacking as the solution variable values are returned as a
single vector, x containing all the separate decision variables. Think of having
three triple subscripted variables, u, v, and y with each subscript having 12
values. The resulting x vector will have 3 ⋅ 123 = 5184 elements, each of which
must always be considered in exactly the same order for each constraint or
building of the A matrix.
At this point, it may be apparent that a chief benefit of ompr is that allows the
modeler to deal with the model at a higher level and forces the computer to
handle the detailed accounting translating the model into a lower level solvable
3.6 Exercises 67
structure and then translating the solution back into the original terms. These
conversions are analogous to using a high-level computer language versus a low
level assembly code. A talented programmer can write a program in assembly
code to do anything that could be done in a high-level language but the cost
in terms of time and effort is typically prohibitive.
The investment of time and effort to use a lower level, more direct access
to the solver engine may be warranted when developing a package that does
optimization. For example, packages for doing Data Envelopment Analysis
require doing many linear programs. These are typically done using the lower
level engines such as lpSolveAPI, Rglkp, lpSolveAPI, or other tools rather than
ompr but the platform decision should be made carefully. A more indepth
example of building optimization models using lower level access calls is in
DEA Using R.
Since each low level solver may have different choices made in terms of the
name or format of parameters to be passed for the LP as well as a different
naming convention of elements being passed back, a translation layer has
been developed, ROI which stands for the R Optimization Interface. There
is the core ROI package and a specific ROI interface for each LP solver such
as ROI.plugin.glpk. ROI can then be used to simplify switching between LP
solvers. This is demonstrated in Chapter 8.
3.6 Exercises
Formulate an explicit model for the above application that solves this trans-
portation problem to find the lowest cost way of transporting as much as
product as we can to distributors. Hint: You might choose to define variables
based on the first letter of source and destination so XCP is the amount to
ship from Chicago to PDX.
Implement and solve the model using ompr. Be sure to interpret and discuss
the solution as to why it makes it sense.
Hint: Feel free to use my LaTeX formulation for the general transportation
model and make change(s) to reflect your case.
Implement and solve the model using ompr. Be sure to discuss the solution as
to why it makes sense.
Exercise 3.3 (Convert LP to Standard Form). Convert the three variable
LP represented in Table 3.13 into standard form.
Max:
𝑃 𝑟𝑜𝑓𝑖𝑡 = 20 ⋅ 𝐴𝑛𝑡𝑠 + 10 ⋅ 𝐵𝑎𝑡𝑠 + 16 ⋅ 𝐶𝑎𝑡𝑠
S.t.
6 ⋅ 𝐴𝑛𝑡𝑠 + 3 ⋅ 𝐵𝑎𝑡𝑠 + 4 ⋅ 𝐶𝑎𝑡𝑠 ≤ 2000
8 ⋅ 𝐴𝑛𝑡𝑠 + 4 ⋅ 𝐵𝑎𝑡𝑠 + 4 ⋅ 𝐶𝑎𝑡𝑠 ≤ 2000
6 ⋅ 𝐴𝑛𝑡𝑠 + 3 ⋅ 𝐵𝑎𝑡𝑠 + 8 ⋅ 𝐶𝑎𝑡𝑠 ≤ 1440
40 ⋅ 𝐴𝑛𝑡𝑠 + 20 ⋅ 𝐵𝑎𝑡𝑠 + 16 ⋅ 𝐶𝑎𝑡𝑠 ≤ 9600
𝐶𝑎𝑡𝑠 ≤ 200
𝐴𝑛𝑡𝑠, 𝐵𝑎𝑡𝑠, 𝐶𝑎𝑡𝑠 ≥ 0
Exercise 3.4 (Solve Standard Form). Implement and solve the standard
form of the LP using R. Be sure to interpret the solution and discuss how it
compares to the solution from the original model.
3.6 Exercises 69
Exercise 3.7 (Staff Scheduling). The need for nurses has increased rapidly
during the Covid pandemic. Specific Hospital has determined their need for
nurses throughout the day. They expect the following numbers of nurses to
be needed over the typical 24 hour period. The nurses work eight hour shifts,
starting at each 4 hour time period specified in the table.
hph_needs <- matrix (c(50, 140, 260, 140, 100, 80), ncol=1,
dimnames = c(list(c(”midnight - 4 AM”,
”4 AM - 8 AM”,
”8 AM - noon”,
”noon - 4 PM”,
”4 PM - 8 PM”,
”8 PM - midnight”)),
list(c(”Required Nurses”))))
kbl (hph_needs, booktabs=T, caption=”Staff Scheduling”) |>
kableExtra::kable_styling(latex_options = ”hold_position”)
Required Nurses
midnight - 4 AM 50
4 AM - 8 AM 140
8 AM - noon 260
noon - 4 PM 140
4 PM - 8 PM 100
8 PM - midnight 80
The company has decided to use the personnel from the Engine shop in other
shops in the Press shop and the Paint shop, split equally. Also, it has decided to
store the assembled vehicles in the plant until Engine Shop is functional again.
The maximum capacity of the storage of cars is 10000. Current manpower and
production information is as given in the table.
Make required change to the given table and Create an optimization model to
find a production plan with max profit for all four car models – Alpha, Beta,
Gamma, and Theta. Make sure to consider the max storage limit of 10000
cars.
4
Sensitivity Analysis
We can get a lot more than just the objective function value and the decision
variable values from a solved linear program. In particular, we can potentially
explore the impact of changes in constrained resources, changes in the ob-
jective function, forced changes in decision variables, and the introduction of
additional decision variables.
The implementation that we did earlier for production planning was straight-
forward.
DOI: 10.1201/9781003051251-4 71
72 4 Sensitivity Analysis
In the base case, we are producing Ants and Cats but not Bats to generate a
total profit of $2225.
There are many resources, some are fully used, while some are not fully utilized.
How do we prioritize the importance of each resource? For example, if the
factory manager could add a worker to one department, which should it be?
Conversely, if an outside task came up where should she draw the time from?
4.2 Shadow Prices 73
We could modify the model and rerun. For complex situations, this may be
necessary. On the other hand, we could also use sensitivity analysis to explore
the relative value of the resources.
Let’s begin by examining the row duals, also known as shadow prices.
rduals1 <-as.matrix(get_row_duals(Base3VarModel))
dimnames(rduals1)<-list(c(”Machining”, ”Assembly”,
”Testing”, ”Sensors”),
c(”Row Duals”))
kbl(format(rduals1,digits=4), booktabs=T,
caption=”Shadow Prices of Constrained Resources”) |>
kable_styling(latex_options = ”hold_position”)
Row Duals
Machining 0.25
Assembly 2.25
Testing 0.00
Sensors 0.00
The row duals or shadow prices for testing is zero. This means that the
marginal value of one additional hour of testing labor time is $0. This makes
sense if you examine the amount of testing time used and realize that the
company is not using all of the 480 hours available. Therefore adding more
testing hours certainly can’t help improve the production plan.
On the other hand, all of the assembly time (resource) is used in the optimal
solution. The shadow price of an hour of assembly time is $2.25. This means
that for every hour of additional assembly time within certain limits, the
objective function will increase by $2.25 also.
All of the 900 hours of labor available in the assembly center are consumed
by the optimal production plan. Increasing the assembly hours available may
allow the company to change the production plan and increase the profit.
While you could rerun the model with increased assembly hours to determine
the new optimal production plan but if you only want to know the change
in the optimal objective function value, you can determine that from the
shadow price of the assembly constraint. Each additional hour (within a
certain range) will increase the profit by $2.25.
74 4 Sensitivity Analysis
This potential for increased profit can’t be achieved by simply increasing the
resource – it requires a modified production plan to utilize this increased
resource. To find the production plan that creates this increased profit, let’s
solve a modified linear program.
Let’s test the numerical results from the Shadow Price table by adding an
hour of labor to the assembly department. The model is represented in the
following formulation.
Max: 7 ⋅ 𝐴𝑛𝑡𝑠 + 12 ⋅ 𝐵𝑎𝑡𝑠 + 5 ⋅ 𝐶𝑎𝑡𝑠
s.t. 1 ⋅ 𝐴𝑛𝑡𝑠 + 4 ⋅ 𝐵𝑎𝑡𝑠 + 2 ⋅ 𝐶𝑎𝑡𝑠 ≤ 800
3 ⋅ 𝐴𝑛𝑡𝑠 + 6 ⋅ 𝐵𝑎𝑡𝑠 + 2 ⋅ 𝐶𝑎𝑡𝑠 ≤ 901
2 ⋅ 𝐴𝑛𝑡𝑠 + 2 ⋅ 𝐵𝑎𝑡𝑠 + 1 ⋅ 𝐶𝑎𝑡𝑠 ≤ 480
2 ⋅ 𝐴𝑛𝑡𝑠 + 10 ⋅ 𝐵𝑎𝑡𝑠 + 2 ⋅ 𝐶𝑎𝑡𝑠 ≤ 1200
𝐴𝑛𝑡𝑠, 𝐵𝑎𝑡𝑠, 𝐶𝑎𝑡𝑠 ≥ 0
solve_model(with_ROI(solver = ”glpk”))
These results confirmed that adding one hour of Testing time results in a
new production plan that generates an increased profit of $2.25, exactly as
expected.
While it is easy to look at an individual resource when we are looking at
problems with only a couple of constraints, the shadow prices can be very
helpful in larger problems with dozens, hundreds, or thousands of resources
where questions might come up of trying to evaluate or prioritize limited
resources.
The shadow price on sensors is zero (as was also the case for testing hours).
This means that even a large increase in the number of sensors would not
affect the maximum profit or the optimal production plan. Essentially there
is plenty of sensors available, having more would not allow a better profit plan
to be possible. Let’s confirm this as well with a numerical example by adding
10,000 more sensors.
In the same way that it was done for adjusting assembly hours earlier, the
next chunk shows how to examine the case of a huge increase in the number
of sensors.
76 4 Sensitivity Analysis
Even this massive increase of sensors does not result in any increase in profit
or change the production plan.
Next, we move on to the reduced costs of variables. The reduced cost for a
variable is the per unit marginal profit (objective function coefficient) minus
the per unit value (in terms of shadow prices) of the resources used by a unit
4.3 Reduced Costs of Variables 77
in production. The reduced costs is also often referred to as the column du-
als. The concept and use of reduced costs frequently may require rereading
several times. The mathematical details rely on the structure of linear pro-
gramming and the Simplex method. We won’t go into detail on the origin of
this mathematically in detail here.
Let’s start by examining the Ants. The reduced cost for Ants is the per unit
marginal profit minus the per unit value (in terms of shadow prices) of the
resources used by a unit in production.
Let’s extract the reduced costs from the results just as we did for the shadow
prices. Note that the reduced costs are referred to as column duals in ompr.
cduals1 <-as.matrix(get_column_duals(Base3VarModel) )
dimnames(cduals1)<-list(c(”Ants”, ”Bats”, ”Cats”),
c(”Column Duals”))
Column Duals
Ants 0.0
Bats −2.5
Cats 0.0
These results are interesting. The reduced costs of variables that are between
simple upper and lower bounds will be zero. Again, the reduced cost of a
variable is the difference between the value of the resources consumed by the
product and the value of the product in the objective function. All of our
product’s variables have simple lower bounds of zero and no upper bounds.
Ants and Cats have zero reduced cost while Bats have a negative reduced cost.
Let’s see if this is consistent with the interpretation of reduced costs.
Let’s start by examining the shadow prices of the resources along with the
number of each resource used in the production of a single Ant.
ant_res_used<-cbind(rduals1,c(1,3,2,2))
colnames(ant_res_used)<-c(”Row Duals”, ”Resources Used”)
ant_res_used <- rbind(ant_res_used,
78 4 Sensitivity Analysis
c(”TOTAL”,t(rduals1)%*%c(1,3,2,2)))
kbl(format(ant_res_used, digits=4), booktabs=T,
caption=”Resources Used by an Ant and shadow prices of resources”)
Taking the product in each row and adding them up will give the marginal
value of the resources consumed by an incremental change in production of
Ants. In this case, the marginal value is 1 ⋅ 0.25 + 3 ⋅ 2.25 + 2 ⋅ 0 + 2 ⋅ 0 = 7.
Since the profit per Ant (from the objective function coefficient on Ants) is
also $7, they are in balance and the difference between them is zero, which
is why the reduced cost for Ants is zero. This can be thought of saying that
the marginal benefit is equal to the marginal cost of a very small forced
change in the value of the Ants variable. The value of the resources used
in producing an Ant equals that of the profit of an Ant at the optimal solution.
cat_res_used<-cbind(rduals1,c(2,2,1,2))
colnames(cat_res_used)<-c(”Row Duals”, ”Resources Used”)
cat_res_used <- rbind(cat_res_used,
c(”TOTAL”,t(rduals1)%*%c(2,2,1,2)))
kbl(format(cat_res_used, digits=4), booktabs=T,
caption=”Resources Used by a Cat and their Shadow Prices”) |>
kable_styling(latex_options = ”hold_position”)
In this case, using the columns from Table 4.6 we can calculate the marginal
value as 2 ⋅ 0.25 + 2 ⋅ 2.25 + 1 ⋅ 0 + 2 ⋅ 0 = 5. Since the profit per Cat (from the
objective function coefficient on Cats) is also $5, they are in balance and the
difference between them is zero, which is why the reduced cost for the Cats
variable is also zero.
4.3 Reduced Costs of Variables 79
The situation is more interesting for Bats. The production plan does not call
for producing any Bats. This means that it is sitting right at the lower bound
of zero Bats. This hints that perhaps we would like to make even less than
zero Bats if we could and that being forced to make even one Bat would be
costly. A reduced cost of $ − 2.5 means that the opportunity cost of resources
used in producing a Bat is $2.5 more than the profit of producing a single Bat
In other words, we would expect that if we force the production of a single
Bat, the over all production plan’s profit will go down by $2.5.
Let’s go ahead and test this interpretation by again looking at the value of
the resources consumed in the production of a Bat and the amount of each
resource used.
bat_res_used<-cbind(rduals1,c(4,6,2,10))
colnames(bat_res_used)<-c(”Row Duals”, ”Resources Used”)
bat_res_used <- rbind(bat_res_used,
c(”TOTAL”,t(rduals1)%*%c(4,6,2,10)))
Notice that the values based on shadow prices of the resources used by a Bat
are 4 ⋅ 0.25 + 6 ⋅ 2.25 + 2 ⋅ 0 + 10 ⋅ 0 = 14.5. Alas, the profit for each Bat is
80 4 Sensitivity Analysis
just $12 which means that forcing the production of a single Bat will decrease
the production plan’s profit by $12 − 14.5 = −2.5. In other words, the impact
on the objective function is $ − 2.5, which is the same as the reduced price of
Bats.
Now let’s test it. We will modify the formulation to set a lower bound on the
number of Bats to be 1. Note that we do this in case by setting the lb option
in the add_variable to be 1. Also, if we had a demand constraint for Bats, we
could also be accommodated this by setting the upper bound (ub).
add_constraint(1*Ants+4*Bats+2*Cats<=800) |>
add_constraint(3*Ants+6*Bats+2*Cats<=900) |>
add_constraint(2*Ants+2*Bats+1*Cats<=480) |>
add_constraint(2*Ants+10*Bats+2*Cats<=1200) |>
solve_model(with_ROI(solver = ”glpk”))
Let’s compare the results for the new production plan and the original base
case looking at the Table 4.9.
making one Bat meant that we had fewer of the precious, limited resources,
decreasing overall profit due to limiting our possible production of Ants and
Cats.
This meant that the number of Ants and Cats were changed resulting in a
lower total profit even though a Bat is on its own profitable.
Another way to view the reduced costs for Bats is to think of it as an
opportunity cost for not being able to further change the production of Bats
due the simple bound. Think of it as a cost for being pinned at the simple
upper or lower bound (often 0). A non-zero reduced cost means that the
optimizer would like to relax or loosen the lower bound and the value is
how much better the objective would be with a unit change of one relaxing
the bound. For example, in our case, with bats being pinned at zero at the
optimal solution and a reduced cost of bats of −2.5, this means that if we
relaxed the non-negativity from 𝐵𝑎𝑡𝑠 ≥ 0 to instead be 𝐵𝑎𝑡𝑠 ≥ −1 and
re-solve, then the objective function would improve by 2.5. In other words,
relaxing the simple lower bound would change the production plan to enable
profit to increase by 2.5.
Let’s consider a design proposal for a Dog drone. The Dog has a projected
profit of $20 each and uses 8 hours of machining time, 12 of assembly, and 4
of testing. Each dog drone also uses 4 sensors.
dog_res_used<-cbind(rduals1,c(8,12,4,4))
colnames(dog_res_used)<-c(”Row Duals”, ”Resources Used”)
dog_res_used <- rbind(dog_res_used,
c(”TOTAL”,t(rduals1)%*%c(8,12,4,4)))
82 4 Sensitivity Analysis
4.5 Exercises
Exercise 4.1 (Adding Eels). Your company has extended production to allow
for producing aquatic Eels and is now including a finishing department that
primes and paints the drones.
e. Examine and reflect upon the reduced costs and shadow prices.
Discuss which step’s available hours need to increases/decrease to
make the most profit.
5
Data Envelopment Analysis
5.1 Introduction
This chapter walks through how DEA works and then shows how to imple-
ment the model in R using two very different approaches. Over the years, I
have built DEA models in many languages and platforms: Pascal, LINDO,
LINGO, Excel Macros, Excel VBA, GAMS, AMPL, XPress-MOSEL, and
GLPK among others.
In this chapter, we will use a few functions from an R package, TRA, that I have
created. This package is for doing a range of functions related to Technology,
Research, and Analytics. The TRA package is not posted on CRAN but is
instead available from github. You can download a current version from github.
The result is that you will need to install it using the devtools package.
1 This chapter is drawn from an introduction chapter in the book, Data Envelopment
Analysis Using R by the same author. More details on DEA are available from that book.
This book is also available via github at https://github.com/prof-anderson
𝑔𝑖𝑡ℎ𝑢𝑏.𝑐𝑜𝑚/𝑝𝑟𝑜𝑓 − 𝑎𝑛𝑑𝑒𝑟𝑠𝑜𝑛
DOI: 10.1201/9781003051251-5 85
86 5 Data Envelopment Analysis
library (devtools)
devtools::install_github(”prof-anderson/TRA”)
library (TRA)
In contrast, outputs are the “good” items that are being produced by the
actions of the producers. Examples of outputs might include automobiles
produced, customers served, or graduating students.
Let’s start by creating a simple application – assume that you are a regional
manager of a grocery chain. You have four stores and would like to assess the
performance of the store managers using number of employees, x, as an input
and the weekly sales, y, as an output.
As the manager responsible for the stores in the region, there are many ques-
tions that you may want answered:
The figure for drawing input-output diagrams is worth a little discussion and
explanation.
This follows three steps to work for both HTML & PDF:
Note that it assumes that there is a subdirectory for images – if there isn’t a
subdirectory for images, you can create one or edit the path.
x <- matrix(c(10,20,30,50),ncol=1,
dimnames=list(LETTERS[1:4],”x”))
y <- matrix(c(75,100,300,400),ncol=1,
dimnames=list(LETTERS[1:4],”y”))
temp<-cbind(storenames,x,y)
colnames(temp)<-c(”Store Name”, ”Employees (x)”, ”Sales (y)”)
The above commands create matrices that hold the data and have named
rows and columns to match. The <- symbol is a key function in R and means
to assign what is in the right to the object on the left.
For benchmarking, we want to know which ones are doing the best job.
Can you tell which stores represent the best tradeoff between inputs and
outputs? None of the stores are strictly dominated by any of the other stores.
Dominance would be producing more outputs using less input so let’s move
on to looking at it graphically.
library(Benchmarking, quietly=TRUE)
dea.plot(x, y, RTS=”crs”, ORIENTATION=”in-out”,
5.3 Graphical Analysis 89
txt=LETTERS[1:length(x)],
add=FALSE, wx=NULL, wy=NULL, TRANSPOSE=FALSE,
xlab=”Employees (x)”, ylab=”Sales (y)”,
fex=1, GRID=TRUE, RANGE=FALSE, param=NULL)
400
D
300
C
Sales (y)
200
100
B
A
0
0 10 20 30 40 50 60
Employees (x)
Note that the function call dea.plot from Benchmarking uses options from
the standard r function plot. This includes items such as axis labeling
xlab and ylab along with the figure title main. It also provides DEA
specific options such as RTS, ORIENTATION, add, wx, wy, and more. The var-
ious options for plot and dea.plot can be found in their respective help pages.
This chart clearly shows that store C (Trader Carrie’s) has the best ratio
of sales (output, y) to employees (input, x). The diagonal line represents
an efficiency frontier of best practices that could be achieved by scaling up
or down store C. As the input is scaled up or down, it is assumed that the
output of store C would be scaled up or down by the same value. We will
revisit this assumption in a later section but for now, think of this as saying
that store C cannot enjoy economies of scale by getting larger or suffer
from diseconomies of scale by getting smaller, so it is referred to as constant
returns to scale or CRS.
The same steps can be followed for analyzing stores A, C, and D resulting in
efficiencies of 75%, 100%, and 80%, respectively.
occur for each input while maintaining B’s level of output. Similarly, a value
of 𝜃 = 0.9 indicates a 10% reduction in input usage could be obtained.
Min 𝜃 [Efficiency]
S.t.: 10𝜆𝐴 + 20𝜆𝐵 + 30𝜆𝐶 + 50𝜆𝐷 ≤ 20𝜃 [Input]
75𝜆𝐴 + 100𝜆𝐵 + 300𝜆𝐶 + 400𝜆𝐷 ≥ 100 [Output]
𝜃, 𝜆𝐴 , 𝜆𝐵 , 𝜆𝐷 , 𝜆𝐷 ≥ 0
To solve for other stores, we would simply change the values on the right-hand
side of the constraints and solve again. This means that we solve as many
linear programs as there are units to evaluate. We are in effect iterating for
k=1,…,Number of Units or with the four store case, k=1,…,4.
This can be easily expanded to the multiple input and multiple output case
by defining 𝑥𝑖,𝑗 to be the amount of the i’th input used by unit j and 𝑦𝑟,𝑗
to be the amount of the r’th output produced by unit j. For simplicity, this
example will focus on the one input and one output case rather than the m
input and s output case but the R code explicitly allows for 𝑚, 𝑠 > 1. To
make the code more readable, I will use a slightly different convention 𝑁 𝑋
or NX instead of m to refer to the number of inputs (x’s) and 𝑁 𝑌 or NY to
be the number of outputs (y’s) instead of s. Also, the normal mathematical
convention is to use n to denote the number of Decision Making Units
(DMUs) so I will use 𝑁 𝐷 or ND to indicate that in the R code.
Rather than jumping straight to the linear program, let’s take a step back
and focus on the construction of the target of performance.
By the same token, the amount of the r’th output produced by the target is
𝑁𝐷
∑𝑗=1 𝑦𝑟,𝑗 𝜆𝑗 .
𝑁𝐷
∑ 𝑥𝑖,𝑗 𝜆𝑗 ≤ 𝑥𝑖,𝑘 ∀ 𝑖 [Use no more input than k]
𝑗=1
𝑁𝐷
∑ 𝑦𝑟,𝑗 𝜆𝑗 ≥ 𝑦𝑟,𝑘 ∀ 𝑟 [At least as much output]
𝑗=1
𝜆𝑗 ≥ 0 ∀ 𝑗
The two most common approaches to finding the best target are the input-
oriented and output-oriented models. In the output-oriented model, the first
(input) constraint is satisfied while trying to exceed the second constraint
(output) by as much possible. This focus on increasing the output is then
called an output orientation.
In this chapter, we will focus on satisfying the second constraint while trying
to improve upon the first by as much as possible. In other words, we will
satisfy the second (output) constraint but try to form a target that uses as
little input as possible. The focus on reducing inputs gives it the name of the
input-oriented model.
In fact, we will go one step further and say that we want to find the maximum
possible input reduction in k’s input or conversely, the minimum amount
of the input that could be used by the target while still producing the
same or more output. We do this by adding a new variable, 𝜃, which is the
radial reduction in the amount of DMU k’s input. We want to find how
low we can drive this by minimizing 𝜃. Let’s define the proportion of the
studied unit’s input needed by the target as 𝜃. A value of 𝜃 = 1 then means
no input reduction can be found in order to produce that unit’s level of output.
min 𝜃
𝑁𝐷
s.t.: ∑ 𝑥𝑖,𝑗 𝜆𝑗 ≤ 𝜃𝑥𝑖,𝑘 ∀ 𝑖
𝑗=1
𝑁𝐷
∑ 𝑦𝑟,𝑗 𝜆𝑗 ≥ 𝑦𝑟,𝑘 ∀ 𝑟
𝑗=1
𝜆𝑗 ≥ 0 ∀ 𝑗
Expressing the target on the left and the actual unit’s value and radial reduc-
tion on the right is conceptually straightforward to understand. Some opti-
mization software requires collecting all the variables on the left and putting
constants on the right-hand side of the inequalities. This is easily done and is
shown in the following linear program for completeness but a benefit of ompr
is that we can specify the model in the original format.
min 𝜃
𝑁𝐷
s.t.: ∑ 𝑥𝑖,𝑗 𝜆𝑗 − 𝜃𝑥𝑖,𝑘 ≤ 0 ∀ 𝑖
𝑗=1
𝑁𝐷
∑ 𝑦𝑟,𝑗 𝜆𝑗 ≥ 𝑦𝑟,𝑘 ∀ 𝑟
𝑗=1
𝜆𝑗 ≥ 0 ∀ 𝑗
Until recently, R did not have the ability to do algebraic modeling optimiza-
tion but a few new packages have provided support for this. In particular, ompr,
provides an algebraic perspective that matches closely to the summation rep-
resentation of a linear program shown earlier. Don’t worry, if you want to see
94 5 Data Envelopment Analysis
the data structure format approach, that is covered in the DEA Using R book.
Let’s define some data structures for holding our data and results.
We’re going to use our data from earlier but first we will load a collection
5.5 Creating the LP – The Algebraic Approach 95
Now that we have loaded all of the packages that we use as building blocks,
we can start constructing the model.
We are going to start by building a model for just one DMU, in this case, the
second DMU (B).
𝜃𝐶𝑅𝑆 𝜆𝐴 𝜆𝐵 𝜆𝐶 𝜆𝐷
Optimal results for DMU B 0.5 0 0 0.3333 0
The above table follows a few convenient conventions. First, the 𝜃, gets a
superscript to indicate that it for the CRS or constant returns to scale model.
This is sometimes labeled as CCR after Charnes, Cooper, and Rhodes (Rhodes,
1978). Later we will cover other models including the variable returns to scale
model, sometimes labeled BCC after Banker, Charnes, and Cooper (Banker
et al., 1984).
Another convention is to embed the orientation in the table. This an input-
oriented model where the primary focus is on achieving efficiency through
input reduction. Output-orientation is also very common where the primary
goal is for a unit to increase output with the requirement of not using any
more input.
The results in the table indicate that DMU B has an efficiency score of 50%.
The target of performance is made of DMU C scaled down by a factor of 0.33.
These results match the graphical results from earlier. This 50% efficiency
score means that if B were using best practices as demonstrated by the other
units, it would only need 50% of the inputs to produce the same output.
Another item to note is that we are using some of the flexibility of kable in
our table. In particular, we are able to use LaTeX in our in column headings
as long as we set escape=F. Note from the code chunk that the actual use
requires an extra slash.
5.5 Creating the LP – The Algebraic Approach 97
Let’s now extend it to handle multiple inputs, NX, and outputs, NY. Of course
this doesn’t have any impact on our results just yet since we are still only using
a single input and output but we now have the structure to accommodate the
more general case of DEA. To provide a little variety, we’ll change it to the
first DMU, A, to give a little more variety.
𝜃𝐶𝑅𝑆 𝜆𝐴 𝜆𝐵 𝜆𝐶 𝜆𝐷
Optimal results for DMU A 0.75 0 0 0.25 0
Again, the results match what would be expected the graphical analysis.
98 5 Data Envelopment Analysis
Now we should extend this to handle all four of the decision making units. A
key new function that we use here is the for command to loop the previous
code that we had used for analyzing A and B separately. Notably, we assign
the results to matrices at the end of each loop.
print(c(”DMU=”,k,solver_status(result)))
results.efficiency[k] <- get_solution(result, vtheta)
results.lambda[k,] <- t(as.matrix(as.numeric(
get_solution(result, vlambda[j])[,3] )))
}
Success! This indicates each of the four linear programs was solved to opti-
mality. By itself, it doesn’t help much though. We need to now display each
column of results. Lambda, 𝜆, reflects the way that a best target is made for
that unit.
CCR-IO 𝜆𝐴 𝜆𝐵 𝜆𝐶 𝜆𝐷
A 0.75 0 0 0.2500 0
B 0.50 0 0 0.3333 0
C 1.00 0 0 1.0000 0
D 0.80 0 0 1.3333 0
CCR-IO 𝜆𝐶
A 0.75 0.2500
B 0.50 0.3333
C 1.00 1.0000
D 0.80 1.3333
The results match those observed graphically but let’s discuss them. These
results indicate that only DMU C is efficient. Rescaled versions of C could
produce the same level of output of A, B, or D, while using just 25%, 50%,
and 20% less input, respectively. The targets of performance for A and B are
constructed by scaling down unit C to a much smaller size, as shown by the
values of 𝜆. In contrast, D’s performance is surpassed by a 33% larger version
of C.
It is an important step of any DEA study to carefully examine the results.
In this case, it might be argued that for certain applications, C’s business
practices do not readily scale, and therefore, could not be assumed to operate
at a much smaller or larger size. Recall our original application of stores. It
might be that practices employed by store C, Trader Carrie’s, do not easily
scale to larger or smaller operations. Al’s Pantry (store A) is a much smaller
operation and just trying to keep the store in operation with ten employees
may be a major challenge. Perhaps all of the stores offer 24 hour a day oper-
ation. During the slow time after midnight, Trader Carrie’s could have just
one employee working. Meanwhile Al’s Pantry could not stay in business with
one third of an employee working at 3 AM!
100 5 Data Envelopment Analysis
minimize 𝜃
𝑛
subject to ∑ 𝜆𝑗 = 1
𝑗=1
𝑛
∑ 𝑥𝑖,𝑗 𝜆𝑗 − 𝜃𝑥𝑖,𝑘 ≤ 0 ∀ 𝑖
𝑗=1
𝑛
∑ 𝑦𝑟,𝑗 𝜆𝑗 ≥ 𝑦𝑟,𝑘 ∀ 𝑟
𝑗=1
𝜆𝑗 ≥ 0 ∀𝑗
To incorporate this, we can add another constraint to our previous model and
solve it. Let’s define a parameter, “RTS” to describe which returns to scale
assumption we are using and only add the VRS constraint when RTS=“VRS”.
The other very common returns to scale option is constant returns to scale or
CRS, which is what we have used up to this point. For CRS, you can delete the
VRS constraint, but it may be helpful in some implementations to maintain
a consistent model size for reading out sensitivity information. To maintain
the number of rows (constraints) we can make it a redundant constraint by
𝑛
constraining the sum of 𝜆 to be greater than or equal to zero, ∑𝑗=1 𝜆𝑗 ≥ 0
Since 𝜆’s are by definition non-negative, the sum of 𝜆’s is also non-negative,
and therefore, the constraint is superfluous or redundant.
5.6 Returns to Scale 101
RTS<-”VRS”
for (k in 1:ND) {
𝜃𝑉 𝑅𝑆 𝜆𝐴 𝜆𝐵 𝜆𝐶 𝜆𝐷
A 1.0000 1.0000 0 0.0000 0
B 0.6111 0.8889 0 0.1111 0
C 1.0000 0.0000 0 1.0000 0
D 1.0000 0.0000 0 0.0000 1
Notice that the efficiencies have generally increased or stayed the same.
Whereas earlier three out of four DMUs were inefficient, now three out of
102 5 Data Envelopment Analysis
four are efficient. One way of thinking of returns to scale is whether doubling
the inputs should be expected to result in doubling the outputs that should
be achieved. Another way to think of it is whether it is fair to think of scaling
up or down an efficient significantly to set a performance target for a much
bigger or smaller unit. For example, would it be fair to compare a small conve-
nience store such as Al’s Pantry or to use a common realworld example, 7-11,
to a CostCo store scaled down by a factor of a 100? Much more could be said
about returns to scale.
400
D
300
C
Y
200
100
B
A
0
0 10 20 30 40 50 60
The Constant Returns to Scale, CRS, model is often referred to in the DEA
literature by CCR after Charnes, Cooper, and Rhodes (Charnes et al., 1978).
The Variable Returns to Scale, VRS, model is also referred to as the BCC
model after Banker, Charnes, and Cooper. In addition to the CRS and VRS
models, two other common approaches are Increasing Returns to Scale (IRS)
and Decreasing Returns to Scale (DRS). Technically, IRS is sometimes more
precisely referred to as non-decreasing returns to scale. Similarly, DRS cor-
responds to non-increasing returns to scale. A less commonly used approach
is Free Disposal Hull (FDH). In the case of FDH, DMUs are only compared
against other individual units. Since it makes use of binary variables and is
less commonly used in DEA, we will not implement it in the chapter.
5.6 Returns to Scale 103
RTS<-”DRS”
for (k in 1:ND) {
DRS-IO 𝜆𝐴 𝜆𝐵 𝜆𝐶 𝜆𝐷
A 0.75 0 0 0.2500 0
B 0.50 0 0 0.3333 0
C 1.00 0 0 1.0000 0
D 1.00 0 0 0.0000 1
Simply changing from RTS<-”DRS” to RTS<-”IRS” in the first line allows to now
evaluate the Increasing Returns to Scale case.
IRS-IO 𝜆𝐴 𝜆𝐵 𝜆𝐶 𝜆𝐷
A 1.0000 1.0000 0 0.0000 0
B 0.6111 0.8889 0 0.1111 0
C 1.0000 0.0000 0 1.0000 0
D 0.8000 0.0000 0 1.3333 0
400
D
300
C
Y
200
100
B
A
0
0 10 20 30 40 50 60
400
D
300
C
Y
200
100
B
A
0
0 10 20 30 40 50 60
functions with a preview of the imported data along with R code that can be
copied to an R script or RMarkdown document.
library(readr)
univ_lic_2007 <- read_csv(”univ_lic_2007.csv”, show_col_types=FALSE)
Next, it is always helpful to look over the dataset. The names of the columns
will be long and awkward for displaying results. Let’s abbreviate the names
to make it more manageable.
utt2007 <- utt2007 [,-5] # Drop the fifth column (Patents Filed)
Rather than using the full dataset. We are going to simplify the data a little.
First, we limit it to just the first 25 universities instead of the full set of 54.
Second, we will drop the measure of patents filed.
Now, let’s prepare our data for the upcoming analyses.
5.7 Multiple Inputs and Multiple Outputs 107
Now, let’s move on to the DEA input-output diagram. We use the total
research spending as an input and then five separate outputs for the various
dimensions of technology transfer success measured by AUTM. This can be
visualized with our input-output diagram.
108 5 Data Envelopment Analysis
RTS<-”CRS”
for (k in 1:ND) {
UTT_CCR.Res<-cbind(res.efficiency, res.lambda)
Note that the results are different from those in the UTT paper because the
model is different and the data is trimmed. This model is an input-oriented
model with constant returns to scale. In contrast, the UTT paper uses an
output-oriented variable returns to scale model with a weight restriction re-
lationship. Recall that the data is trimmed to the first 25 universities and
patents filed is dropped as less important than patents issued.
Every DMU is compared to one or more of the efficient DMUs. In this case
there are three efficient DMUs. These are New York University (NYU), Uni-
versity of Wisconsin at Madison (UW-Madison), and California Institute of
110 5 Data Envelopment Analysis
Technology (Cal Tech). The poscol function from the TRA package trimmed
out all of the unused columns in the matrix of lambdas. This then left us
with just the efficiency scores and the three columns with values of 𝜆 for each
of these efficient universities. Given the length of the names of the names of
the universities, it would still exceed text size. The kableExtra package has an
option of scale_down which makes it easy to fit a table in the printable area.
The University of California System, UC-System, receives an efficiency score of
0.4663. It has a target made up of approximately 0.107 of New York University
(NYU), 1.066 of the University of Wisconsin at Madison, 1.186 of Cal Tech
that produces similar output using only 47% of the input. This means that if
the UC-System were operating efficiently as defined in this model, it would be
able to produce the same or more outputs as it actually did using only 47% of
the input (Research Funding) as it actually used. This target of performance
is made up more more than two other full universities and part of a third.
Note that these values are rounded using the digits=4 from kable. The result
of this is it masks odd some values of 𝜆 that could warrant a more careful
look. In particular, there may be very small positive or negative numbers in
the table that are shown as 0.0000. These numbers are due to computational
5.7 Multiple Inputs and Multiple Outputs 111
RTS<-”VRS”
for (k in 1:ND) {
𝜃𝐶𝑅𝑆 𝜃𝑉 𝑅𝑆
New York University 1.000000 1.000000
U of California System 0.466286 1.000000
U of Wisconsin at Madison 1.000000 1.000000
Stanford 0.699625 0.732835
U of Minnesota 0.824059 0.874977
U of Florida 0.878757 0.907647
Michigan State University 0.792484 0.906014
Wake Forest University 0.592206 1.000000
U of Colorado System 0.544260 0.571191
U of Rochester 0.754255 0.850801
U of Massachusetts 0.505347 0.691422
MIT 0.785563 1.000000
U of Washington 0.444039 0.475481
Emory University 0.432986 0.646545
Harvard 0.420787 0.513422
U. of Utah 0.594784 0.827146
Florida State U. 0.398852 0.790456
State U. of New York Research Foundation 0.415255 0.469800
U. of Texas Southwestern Medical Center at Dallas 0.531787 0.763226
Case Western Reserve U. 0.512656 0.772125
U. of Iowa Research Foundation 0.616331 0.860436
U. of Michigan at Ann Arbor 0.629340 0.655179
California Tech 1.000000 1.000000
Washington U. in St. Louis 0.799824 0.894644
U. of Chicago UCTech 0.371733 0.629332
• Switching from CRS to VRS will never hurt the efficiency of a DMU. From
an optimization perspective, think of this as adding a constraint which will
increase or leave unchanged the objective function value from a minimization
linear program.
• Another way to view the impact of adding a returns to scale assumption
is that this makes it easier for a DMU to find some way of being best or
efficient. They can do this by now having the largest of any output or the
smallest of input as well as many other ways.
• At a simple level, the CCR (CRS) efficiency score can be thought of as a
combination of how efficient someone is considering their operational and
5.8 Extracting Multiplier Weights from Sensitivity Analysis 113
size efficiency. The BCC (VRS) efficiency is how efficient they are given
their current operating size.
• The use of drop=FALSE in the subsetting of UTT_CCR.Res enables retain-
ing the original structure of the matrix, which allows the column names to
remain. The default is drop=TRUE which simplifies the data object into a
form that doesn’t have a column name.
The following process can be used for extracting the shadow prices (row duals)
from the linear program results. This extraction is based upon knowing the
structure of the model that was built.
𝑣Research Spending 𝑢Licensing Income 𝑢Licenses and Options 𝑢Startups 𝑢Patents Issued
0.00307 0 0.00835 0 0.00342
RTS<-”CRS”
for (k in 1:ND) {
𝜃𝐶𝑅𝑆 𝑣Research Spending 𝑢Licensing Income 𝑢Licenses and Options 𝑢Startups 𝑢Patents Issued
1.00000 0.00409 0.00528 0.01416 0.00000 0.00000
0.46629 0.00036 0.00040 0.00096 0.00000 0.00065
1.00000 0.00131 0.00000 0.00493 0.00000 0.00000
0.69963 0.00144 0.00101 0.00496 0.02340 0.00000
0.82406 0.00194 0.00136 0.00668 0.03151 0.00000
0.87876 0.00234 0.00164 0.00803 0.03792 0.00000
0.79248 0.00307 0.00215 0.01056 0.04987 0.00000
0.59221 0.00727 0.01227 0.00000 0.00000 0.01904
0.54426 0.00175 0.00123 0.00602 0.02841 0.00000
0.75426 0.00327 0.00411 0.00000 0.08796 0.00000
0.50535 0.00289 0.00203 0.00995 0.04697 0.00000
0.78556 0.00097 0.00000 0.00351 0.01577 0.00000
0.44404 0.00120 0.00084 0.00412 0.01946 0.00000
0.43299 0.00307 0.00215 0.01055 0.04982 0.00000
0.42079 0.00169 0.00119 0.00582 0.02748 0.00000
0.59478 0.00345 0.00242 0.01187 0.05602 0.00000
0.39885 0.00487 0.00823 0.00000 0.00000 0.01277
0.41526 0.00141 0.00099 0.00484 0.02286 0.00000
0.53179 0.00318 0.00358 0.00848 0.00000 0.00577
0.51266 0.00381 0.00267 0.01310 0.06185 0.00000
0.61633 0.00320 0.00000 0.00938 0.00000 0.00578
0.62934 0.00133 0.00000 0.00479 0.02152 0.00000
1.00000 0.00257 0.00000 0.00755 0.00000 0.00465
0.79982 0.00240 0.00000 0.00703 0.00000 0.00434
0.37173 0.00307 0.00346 0.00819 0.00000 0.00558
• Multiplier weights can be quite small because in general, they are multiplied
by inputs and the product is typically less than one. They may appear to
round to zero but still be significant.
• Multiplier weights are not unique for strongly efficient DMUs. This is ex-
plored in more detail in the multiplier chapter of DEA Using R and in other
DEA specific publications.
• Multiplier weights are usually unique for inefficient DMUs and should match
the results obtained using either the row duals of the envelopment model or
those obtained directly from the multiplier model.
• People new to DEA are often “offended” by units that place zero weight on
certain inputs or on certain outputs as was done by nursing homes B and
D. Approaches such as weight restrictions exist to deal with this directly
should this be a problem but should be carefully considered.
116 5 Data Envelopment Analysis
Situations can arise where units may appear to be radially efficient but can
still find opportunities to improve one or more inputs or outputs. This is
defined as being weakly efficient.
In order to accommodate this, we need to extend the simple radial model
by adding variables to reflect nonradial slacks. We do this by converting the
model’s input and output constraints from inequalities into equalities by ex-
plicitly defining slack variables.
minimize 𝜃
𝑁𝐷
subject to ∑ 𝑥𝑖,𝑗 𝜆𝑗 − 𝜃𝑥𝑖,𝑘 + 𝑠𝑥𝑖 = 0 ∀ 𝑖
𝑗=1
𝑁𝐷
∑ 𝑦𝑟,𝑗 𝜆𝑗 − 𝑠𝑦𝑟 = 𝑦𝑟,𝑘 ∀ 𝑟
𝑗=1
𝜆𝑗 , 𝑠𝑥𝑖 , 𝑠𝑦𝑟 ≥ 0 ∀ 𝑗, 𝑖, 𝑟
𝜆𝑗 , 𝑠𝑥𝑖 , 𝑠𝑦𝑟 ≥ 0 ∀ 𝑗, 𝑖, 𝑟
5.9 Slack Maximization 117
The first phase can take the form of any of our earlier linear programs without
the 𝜖 and sum of slacks in the objective function. The second phase is the
following where 𝜃∗ is the optimal value from phase one and 𝜃 is then held
constant in the second phase. This is implemented by adding a constraint,
𝜃 = 𝜃∗ to the second phase linear program.
𝜃 = 𝜃∗
𝜆𝑗 , 𝑠𝑥𝑖 , 𝑠𝑦𝑟 ≥ 0 ∀ 𝑗, 𝑖, 𝑟
RTS<-”CRS”
for (k in 1:ND) {
lb = 0) |>
add_variable(vtheta, type = ”continuous”) |>
add_variable(xslack[i], i = 1:NX, type = ”continuous”,
lb=0) |>
add_variable(yslack[r], r = 1:NY, type = ”continuous”,
lb=0) |>
add_constraint(sum_expr(vlambda[j] * xdata[j,i] +
xslack[i], j = 1:ND)
- vtheta * xdata[k,i]==0, i = 1:NX,
.show_progress_bar=FALSE ) |>
add_constraint(sum_expr(vlambda[j] * ydata[j,r] -
yslack[r], j = 1:ND)
==ydata[k,r], r = 1:NY,
.show_progress_bar=FALSE )
if (RTS==”VRS”) {LPSlack<-
add_constraint(LPSlack, sum_expr(vlambda[j],
j = 1:ND) == 1) }
#Returns to Scale
result <- solve_model(LPSlack, with_ROI(solver = ”glpk”))
# The following are key steps to slack maximization
phase1obj <- get_solution(result, vtheta)
# Get Phase 1 objective value
add_constraint(LPSlack, vtheta==phase1obj)
# Passing result from phase 1 to phase 2
set_objective(LPSlack, sum_expr(
xslack[i], i=1:NX)+sum_expr(
yslack[r], r=1:NY), ”max”)
# Modify the objective function for phase 2
result <- solve_model(LPSlack, with_ROI(solver = ”glpk”))
kbl(head(cbind(res.efficiency,poscol(res.lambda[1:25,]))),
booktabs=T, escape=F,
caption=”Selected Input-oriented Efficiency Results
with Slack Maximization”) |>
kable_styling(latex_options = ”hold_position”)
Over the years, there have been several R packages developed for doing DEA.
Each package is often developed by a researcher or team for their specific
needs. Some continue to be enhanced over time. At this point the reader will
have an understanding of many of the basics of the implementation and have a
greater appreciation of how the packages. The following packages are currently
available through CRAN.
• Benchmarking by Bogetoft and Otto stands out for the comprehensive toolset
and documentation in their book.
• deaR provides some interesting classic datasets along with both standard and
less commonly used variations of DEA.
• DJL incorporates a variety of DEA techniques and is officially an abbreviation
of distance measurement based judgement and learning. Perhaps not too
coincidentally, it matches the author’s, Dong-Joon Lim’s, initials.
• MultiplierDEA was developed by Aurobindh Kalathil Kumar from Portland
State University and emphasizes the multiplier model of DEA.
• nonparaeff draws its name from the nonparametric nature of DEA.
• rDEA stands for Robust DEA.
In each case, the notation for calling DEA varies. This includes the way that
data is structured, how returns to scale is specified, whether a second phase
for slack maximization is conducted, and more. The result is that reading the
help file for a package is necessary.
“All models are wrong but some are useful” – George Box, 1978
120 5 Data Envelopment Analysis
At its simplest, the analyst should try to build a model that reflects the most
important resources used in the production of the unit’s most important out-
puts. No DEA input-model is perfect and they all represent tradeoffs between
data availability, accuracy, and other issues.
The subject of DEA model building is involved and arguably as much an art
as it is a science. Economists will argue (correctly) that the production func-
tion axioms that underlie theoretical foundations of DEA require that inputs
can have tradeoffs between them in the production of the outputs and that
outputs also have tradeoffs between them. Also, that all inputs contribute to
all outputs. In other words, a benchmarking study of hospitals that had as one
one of the inputs heart surgeons and as one of the outputs heart transplants
performed but another output such as kidney transplants could be problem-
atic.
Data Envelopment Analysis is by its very name data driven. This highlights
the fact that data is needed for conducting the analysis. Unlike multiple regres-
sion in which the need for more observations as a function of the number of
independent variables is well known, there is no simple sufficiency test for DEA
models. Common heuristics of needing at least three times as many DMUs
as the input-output model complexity is generally helpful. (Note that model
complexity is sometimes interpreted as number of inputs plus the number of
outputs or as the product of the number of inputs and outputs.)
Since DMUs can be efficient by being the best on any ratio of an output to an
input, the higher the number of inputs and outputs, the less discrimination
between DMUs is typically found. This curse of dimensionality means that
highly complex models may find most or all DMUs to be efficient. Forced fixes
to adjust for this often lead to difficulties.
Correlations among inputs and correlations among outputs do not generally
affect the efficiency score. In effect, two highly correlated inputs tend to behave
like one and do not increase the curse of dimensionality although they might
have unintended consequences elsewhere and violate production economics
ideas. Also, they may cause multiplier weights to be less interpretable.
Note that if an input or an output is inaccurate, this affects the accuracy of
the final efficiency scores.
Furthermore, DEA is an extreme point technique. As such, it is important
that the frontier points be valid instances. A bad data point (DMU) can
have a profound affect on the efficiency of many other units. As such, the
analyst should carefully review the efficient points. (Techniques such as super-
efficiency can be used to highlight and prioritize efficient DMUs worth detailed
examination.)
5.11 DEA Model Building 121
The issues of returns to scale and input vs. output-orientation should be care-
fully justified in terms of the particular application being examined.
With respect to returns to scale, it is straightforward to examine results and
consider what makes the most sense in the application. Does it really make
sense to scale up or down a unit to an extreme extent without distorting their
actual operations?
In terms of orientation, is the primary goal to increase outputs or decrease
inputs? Note that there are DEA models that allow for both simultaneously
but input and output orientation remain the most commonly used orienta-
tions.
a measure of workload for the hospital, this clearly a bad modeling choice.
There are DEA models that can accommodate “bad outputs” such as toxic
byproducts in manufacturing or energy product. Another option would be to
emphasize the lives saved.
In short, a DEA application should always bring together both application
area expertise and familiarity with DEA to ensure meaningful results.
5.13 Exercises
Exercise 5.1 (Graphically Adding DMU E). Add a fifth unit, E, to the first
example that produces 400 units output using 30 units of input. Graphically
evaluate all five units for their efficiency scores and lambda values. Interpret
the solution in terms of who is doing well, who is doing poorly, and who
should be learning from whom.
Exercise 5.2 (Adding DMU E Using R). Examine the new unit, E, using R.
Interpret the solution in terms of who is doing well, who is doing poorly, and
who should be learning from whom.
Exercise 5.3 (Looping). Wrap a for loop around the model from the
previous exercise to examine every unit. Discuss results.
5.13 Exercises 123
Exercise 5.4 (Bigger Data). Use a bigger data set and conduct an analysis
& interpretation (more inputs, outputs, and units.)
Exercise 5.5 (Compare Results). Check results against a DEA package (ex.
DEAMultiplier, Benchmarking, nonparaeff).
Storage Lens
Cost($) Space(GB) Resolution(MP) Thickness(mm)
Phone A 250 16 12 4
Phone B 225 16 8 5
Phone C 300 32 16 4.5
Phone D 275 32 8 4
Up until this point, we have always assumed that variables could take on frac-
tional (or non-integer) values. In other words, they were continuous variables.
Sometimes the values conveniently turned out to be integer-valued, in other
cases, we would brush off the non-integer values of the results.
Allowing for integer values opens up many more important areas of applica-
tions as we will see. Let’s look at two numerical examples. The first shows a
case where integrality makes little difference, the second where it has a major
impact.
Unfortunately, there is no such thing as a free lunch-adding and integrality can
make problems much more computationally demanding. We will go through
an example of how many optimization routines do integer optimization to
demonstrate why it can be more difficult algorithmically even if looks trivially
easy from the perspective of the change in the omprfunction.
In this case, the fractional value of Cats would be only somewhat concerning.
We wouldn’t actually ship an 80% complete Cat drone to a customer-at least
I hope not. The difference between 374 and 375 is relatively small over a
month of production. This is a difference of much less than 1% and none of
the numbers specified in the model appear to be reported to more than two
significant digits or this level of precision. The result is that we could specify
the answer as 374.8 and be satisfied that, while it is our actual best guess or
might reflect a chair half finished for the next month, it isn’t a major concern.
For the sake of illustration, let’s show how we would modify the model to be
integers. If we had wanted to modify the problem to force the amount of each
item to be produced to be an integer value, we can modify our formulation
and the implementation with only a few small changes.
6.1 Example of Minor Integrality Impact 127
model1
We can see that it now has three integer variables replacing the variables of the
same name that were continuous so let’s move on to solving it and comparing
our results.
1 ompr 1.0 adds an enforced variable checking in the model building. This now requires
rebuilding the model rather than just redefining the same named variable.
128 6 Mixed Integer Optimization
Notice that in this case, there was a small adjustment to the production plan
and a small decrease to the optimal objective function value. This is not always
the case, sometimes the result can be unchanged. In other cases, it may be
changed in a very large way. In still others, the problem may in fact even
become infeasible.
At first glance, this looks the same as our earlier drone eample but the last
constraint cannot be solved directly strictly using linear programming. It is
instead referred to as an integer linear program or ILP.
Again, we could try solving it with and without the imposition of the integral-
ity constraint.
6.2 Example of Major Integality Impact 129
All we need to do is repeat the same process with changing the variable type
to integer instead of continuous.
x1 x2 Profit
LP Solution 2.238 2.698 12.571
IP Solution 1.000 3.000 11.000
Simply rounding down did not yield an optimal solution. Notice that produc-
tion of product 1 went down while product 2 went up. The net impact was a
profit reduction of almost 10%.
The simplicity of making the change in the model from continuous to integer
variables hides or understates the complexity of what needs to be done com-
putationally to solve the optimization problem. This small change can have
major impacts on the computational effort needed to solve a problem. Solving
linear programs with tens of thousands of continuous variables is very quick
and easy computationally. Changing those same variables to general integers
can make it very difficult. Why is this the case?
At its core, the basic problem is that the Simplex method and linear program-
ming solves a system of equations and unknowns (or variables). It is trivial
to solve one equation with one unknown such as 5𝑥 = 7. It is only slightly
harder to solve a system of two equations and two unknowns. Doing three
equations and three unknowns requires some careful algebra but is not hard.
In general, solving n equations and n unknowns is readily solvable. On the
other hand, none of this algebraic work of solving for unknown variables can
require that the variables take on integer values. Even with only one equation
(say 𝐶 ⋅ 𝑥 = 12), one unknown variable (𝑥), and one constant (𝐶), x will only
6.3 The Branch and Bound Algorithm 131
To solve this using branch and bound, we relax the integrality requirements
and solve what is called the LP Relaxation. To do this, we simply remove the
integrality requirement.
x1 x2 Profit
LP Solution 2.238 2.698 12.571
6.3.2 Subproblem I
Alas, this production plan from the LP Relaxation is not feasible from the
perspective of the original integer problem because it produces 2.238 of prod-
uct 1 and 2.698 of product 2. If both of these variables had been integer we
could have declared victory and been satisfied that we had easily (luckily?)
found the optimal solution to the original integer programming problem so
quickly.
Instead, we will need to proceed with the branch and bound process. Since
both of the variables in the LP relaxation have fractional values, we need
to start by choosing which variable to use for branching first. Algorithmic
researchers would focus on how to best pick a branch but for our purposes
to improve solution speed, but it doesn’t really matter for illustration so let’s
arbitrarily choose to branch on 𝑥1 .
6.3 The Branch and Bound Algorithm 133
For the branch and bound algorithm, we want to include two subproblems that
exclude the illegal value of 𝑥1 = 2.238 as well as everything else between 2 and
3. We say that we are branching on 𝑥1 to create two subproblems. The first
subproblem (I) has an added constraint of 𝑥1 ≤ 2.0 and the other subproblem
(II) has an added constraint of 𝑥1 ≥ 3.0.
x1 x2 obj_val
2 2.778 12.333
Looking over the results, we now get an integer value for 𝑥1 but not for 𝑥2 .
We repeat the same process by creating subproblems from Subproblem I by
branching off of 𝑥2 .
Choosing which subproblem to examine next is one of the areas that large
scale integer programming software and algorithms specialize in and provide
options. One way to think of it is to focus on searching down a branch and
134 6 Mixed Integer Optimization
bound tree deeply or to search across the breadth of the tree. For this example,
let’s go deep which means jumping ahead to Subproblem III but we’ll return
to Subproblem II later.
Since 𝑥2 is now a non-integer solution, we will create branches with bounds
(or constraints) on 𝑥2 in the same manner as before. Subproblem III has an
added constraint of 𝑥2 ≤ 2.0 and Subproblem IV has 𝑥2 ≥ 3.0.
To simplify the implementation, I can simply change the upper and lower
bounds on variables rather than adding separate variables.
x1 x2 obj_val
2 2 10
This results in integer values for both 𝑥1 and 𝑥2 so it is feasible with respect
to integrality in addition to of course satisfying, the production constraints.
It does generate less profit than the LP relaxation. While it is feasible, it
doesn’t prove that it is optimal though. We need to explore the other potential
branches.
6.3 The Branch and Bound Algorithm 135
6.3.4 Subproblem IV
Next, let’s look at Subproblem IV. This problem adds the bound of 𝑥2 ≥ 3.0
to Subproblem I. Notice that in the MIPModel implementation the variable for
𝑥1 (Vx1) has an upperbound of 2, (ub=2) in order to implement the bounding
constraint for Subproblem I and the lower bound on 𝑥2 of 3 in the variable
declarations. Notice from the earlier graphical examples, there is no feasible
region (blue area) that has 𝑥2 ≥ 3. By visual inspection, we expect this to be
an infeasible subproblem but let’s confirm it.
x1 x2 obj_val
1.333 3 11.667
6.3.5 Subproblem V
x1 x2 obj_val
1 3.111 11.333
6.3.6 Subproblem VI
In this case, we our retaining our previous bounds from Subproblems I and
IV while adding a bound for 𝑥1 . The result is that our bounds on 𝑥1 is 2 ≤
𝑥1 ≤ 2 or in other words, 𝑥1 = 2 while 𝑥2 ≥ 3. A careful look at the previous
6.3 The Branch and Bound Algorithm 137
figure suggest there is no feasible solution in this situation but let’s solve the
corresponding LP to confirm this.
## [1] ”infeasible”
x1 x2 obj_val
2 3 13
The ompr status value of this solved LP indicates that Subproblem VI is in-
feasible. It still returned values that were used when it determined that the
138 6 Mixed Integer Optimization
problem was infeasible, which is why it gave the results in the previous table.
From here on out, it is a good reminder to check the status. Note that it can
be used as an in-line evaluated expression to simply say was it feasible or not?
Yes, it was infeasible.
x1 x2 obj_val
1 3 11
This solution is all integer valued and the objective function is better than our
previous integer valued solution. The result is that we have a new candidate
optimal solution. Since there are still branches to explore, we can’t declare
victory.
6.3 The Branch and Bound Algorithm 139
Setting a lower bound of 𝑥2 ≥ 4 makes the problem infeasible. We can see from
the drawings of the feasible region that the feasible region does not extend
to that height at all regardless of what 𝑥1 is. We can confirm by solving
Subproblem VIII though since we don’t generally have the luxury of looking
at the feasible region. Again, the results table highlights that care must be
taken to not give credence to values returned from an infeasible solution.
## [1] ”infeasible”
x1 x2 obj_val
1 4 14
6.3.9 Subproblem II
rownames(LPSubII_res) <- ””
x1 x2 obj_val
Vx1 3 0.667 8
At this point, our first reaction may be to breathe deeply and do the same
branch and bound off of 𝑥2 . On the other hand, if we step back and take
note that the objective function value is 10.0, while optimal for Subproblem
II, it is less than what we found from a feasible, fully integer-valued solution
to Subproblem III and the even better Subproblem VII. Given that adding
6.4 Computational Complexity 141
x1 x2 obj_val
1 3 11
constraints can’t improve an objective function value, we can safely trim all
branches below Subproblem II.
Given that we no longer have any branches to explore, we can declare that we
have found the optimal solution. The optimal solution can now be definitively
stated to be what we found from Subproblem VII.
Before this, we could only say that it was feasible solution and candidate to
be optimal since no better integer feasible solution had been found.
Let’s summarize the results of the series of LPs solved.
x1 x2 Profit
LP Relaxation 2.238 2.698 12.571
Subproblem I 2.000 2.778 12.333
Subproblem III 2.000 2.000 10.000
Subproblem IV 1.333 3.000 11.667
Subproblem V 1.000 3.111 11.333
Subproblem VI – – Infeasible
Subproblem VII 1.000 3.000 11.000
Subproblem VIII – – Infeasible
Subproblem II 3.000 0.667 8.000
The Branch and Bound process is often characterized as a tree. Each sub-
problem is a circle and represents a separate linear program with its objective
function value and decision variable values. The arcs or branches connecting
to a new node show the added constraint.
to typically find a solution using branch and bound. Small and medium size
problems can generally be solved quickly but worst case scenarios have a
combinatorial explosion.
Worst case scenarios for Branch and Bound may approach that of full enu-
meration but in practice performs much better. A variety of sophisticated
algorithmic extensions have been added by researchers over the years but
solving large scale integer programming problems can still be quite difficult.
One option to deal with long solving times is to set early termination condi-
tions such as running for a certain number of seconds. If it is terminated early,
it may report the best feasible solution found so far and a precise bound as
to how much better an as yet unfound solution might be based on the best re-
maining open branch. This difference between the best feasible solution found
so far and the theoretically possible best solution to be found gives a gap that
an analyst can set. This acceptable gap is sometimes referred to as a subop-
timality tolerance factor. In other words, what percentage of suboptimality is
the analyst willing to accept for getting a solution more quickly. For example,
a suboptimality tolerance factor of 10% would tell the software to terminate
the branch and bound algorithm if a feasible solution is found, and it is cer-
tain that regardless of how much more time is spent solving, it is impossible
to improve upon this solution by more than 10%.
In practice, even small suboptimality tolerance factors like 1% can allow big
problems to be solved quickly and are often well within the margin of error
for model data. In other cases, organizations may be interested in finding any
feasible solution to large, vexing problems.
Note that our Acme example only specified two digits of accuracy for resource
usage and may have only one digit of accuracy for profitability per product.
Note that in our earlier example, if we had a wide enough suboptimality
tolerance, say 30% and followed the branch for 𝑥1 ≥ 3.0 first rather than
𝑥1 ≤ 2.0, we might have terminated with a suboptimal solution.
Binary variables are a special case of general integer variables. Rather than
variables taking on values such as 46 and 973, acceptable values are limited
to just 0 and 1. This greatly reduces the possible search space for branch and
bound since you know that in any branch, you will never branch on a single
variable more than once. On the other hand, with a large number of variables,
the search space can still be very large. If the previous case with 1000 variables
were binary, a full enumeration list would rely on a list of 21000 or one followed
by about 300 zeros) possible solutions. While better than the case of integers,
it is still vastly more than the number of atoms in the universe.
144 6 Mixed Integer Optimization
Inv NPV
A 12 2.0
B 24 3.6
C 20 3.2
D 8 1.6
E 16 2.8
The important thing is that binary variables give us a lot of rich opportunities
to model complex, real world situations.
Examples include assigning people to projects, go-no go decisions on projects,
and much more.
Let’s explore the impact of binary restrictions with another example. The
world famous semiconductor company, Outtel has a choice of five major R&D
projects:
The key data is described as follows. NPV is Net Present Value of the project
factoring in costs. Inv is the capital expenditures or investment required for
each project. The company has $40 Billion in capital available to fund a port-
folio of projects. Let’s set up the data as matrices in R.
These projects all carry their own expenses and engineering support. There
are limits to both the capital investment and engineering resources available.
To start, consider all of these project to be independent.
6.5 Binary Variables and Logical Relations 145
Exercise 6.1 (Create and Solve Model). Formulate and solve a basic, naive
model that allows projects to repeated and partially completed. Implement
and solve your model. (Hint: You don’t need binary variables yet.) What is
the optimal decision? Is this result surprising? Does this make sense in terms
of the application?
Exercise 6.2 (No Project is Repeated). Now, let’s explore one aspect of
moving towards binary variables. What constraint or constraints are needed
to ensure no project is done more than once, while allowing partial projects
to still be funded and incur both proportionate costs and benefits. How does
this solution compare to that of above?
Exercise 6.3 (No Partial Projects). What change is needed to prevent partial
funding of projects. How does this solution compare to that of above? Can
you envision a situation where it might make sense to have partially funded
projects? Could this be similar to corporate joint ventures where investments
and benefits are shared across multiple entities?
Now that you have binary constraints, let’s explore the impact of logical re-
strictions with a series of separate scenarios. In each of these cases, make
sure that you implement the relationship as a linear relationship. This means
that you cannot use a function such as an “If-then”, “OR”, “Exclusive OR”,
or other relationship. Furthermore, you can’t multiply variables together or
divide variables into variables.
Let’s assume that projects A and B require the focused effort of Outtel’s top
chip architects and Outtel has decided, therefore, that it is not possible to do
both projects.
Therefore, they want to ensure that at most one of the two projects can be
pursued. What needs to be added to enforce this situation?
Let’s start with a truth table summarizing the interaction between projects
(variable values) and the “Top Chip Architects” relationship.
TABLE 6.16 Relationships between Projects A, B, and the Need for Chip
Architects
Project A Project B
𝑦𝐴 𝑦𝐵 Violates “Top Chip Architects” Relationship
0 0 No
0 1 No
1 0 No
1 1 Yes
146 6 Mixed Integer Optimization
Now we need to create a constraint that blocks the situations that violate the
relationship (𝑦𝐴 = 𝑦𝐵 = 1 ) but allows the other situations to be unblocked.
Recall that you want to have a linear constraint or constraints that would
be added to the model. In this case, we can use 𝑦𝐴 + 𝑦𝐵 ≤ 1. It is a good
exercise to check this relationship against the truth table.
In each of the following sections create a linear relationship that models this
situation.
Fixed charge models are a special case of integer programming models where
situations where product cost has a fixed cost component and a marginal (per
6.6 Fixed Charge Models 147
In our example, we need to connect or link the two decision variables of how
much to produce and whether to produce for each of the products.2
Let’s explore an example of a fixed charge problem. Widget Inc. is re-
evaluating their product production mix. As the plant manager, you are
responsible for determining what products the company should manufacture.
Since the company is leasing equipment, there are setup costs for each prod-
uct. You need to determine the mix that will maximize profit for the company.
The profit for each Widget is shown below, as well as the setup costs (if you
decide to produce any Widgets at all.) The materials you have sourced allow
you to only produce each Widget up to its capacity.
To produce each Widget, the hours required at each step of the manufacturing
process are shown below. Also shown are the availability (in hours) of the
equipment at each step.
Decision Variables:
Constraints:
## Status: optimal
## Objective value: 8845
Our analysis found an optimal solution with a profit of 8845. At first glance,
this may look good but let’s examine the results in more detail.
fc_base_summary <-
cbind(fc_base_res$objective_value,
t(as.matrix(fc_base_res$solution)))
colnames(fc_base_summary)<-
c(”Net Profit”, ”$w_1$”,”$w_2$”,”$w_3$”,
”$y_1$”,”$y_2$”,”$y_3$”)
kbl (fc_base_summary, booktabs=T, escape=F,
caption = ”Base Case Solution for Fixed Charge Problem”) |>
kable_styling(latex_options = ”hold_position”) |>
footnote (”Note that no setups are incurred.”)
Net Profit 𝑤1 𝑤2 𝑤3 𝑦1 𝑦2 𝑦3
8845 0 307 231 0 0 0
Note:
Note that no setups are incurred.
the production setups. It may not be surprising that the optimization model
chooses to avoid “paying” the fixed charge for production. This is a “penalty”
in the objective function. What we need is a way of connecting the amount to
produce of a widget and the decision to produce any of that widget. In reality,
it would be necessary to pay the setup costs for the second and third products
(1500 + 2500) bringing the total profit down to 5345. Since the model is not
factoring in the production setup cost, perhaps a higher net profit could be
found if we could directly account this in our product offerings.
What we need is a way to connect, associate, or dare I say “link” each produc-
tion decision, 𝑤, with its setup decision, 𝑦. In fact, this connection is called a
linking constraint and is quite common in mixed integer programming prob-
lems.
Let’s return to the idea of the truth table – let’s focus on widget 2.
Amount to
Produce Decision to
𝑤2 Produce 𝑦2 Interpretation of situation
0 0 OK – choosing to do nothing
0 1 OK – perhaps not a “smart” option but not
impossible or unheard of
1 0 impossible – can’t produce even a single
sellable product without doing a setup
1 1 OK
42 0 impossible
42 1 OK
2 ⋅ 𝑤1 + 1 ⋅ 0 + 3 ⋅ 0 ≤ 1000
1 ⋅ 𝑤1 + 5 ⋅ 0 + 2 ⋅ 0 ≤ 2000
2 ⋅ 𝑤1 + 1 ⋅ 0 + 1 ⋅ 0 ≤ 1000
3 ⋅ 𝑤1 + 2 ⋅ 0 + 1 ⋅ 0 ≤ 1500
𝑤1 ≤ 500
0 ≤ 1000
0 ≤ 750
2 ⋅ 𝑤1 ≤ 1000
1 ⋅ 𝑤1 ≤ 2000
2 ⋅ 𝑤1 ≤ 1000
3 ⋅ 𝑤1 ≤ 1500
𝑤1 ≤ 500
1000
𝑤1 ≤ = 500
2
𝑤1 ≤ 2000
1000
𝑤1 ≤ = 500
2
1500
𝑤1 ≤ = 500
3
𝑤1 ≤ 500
𝑀1 = 500.
We can follow the same process for setting Big M values for widget 2. We
start by setting 𝑤1 = 𝑤3 = 0. We know that 𝑤2 must be no larger than the
most restrict constraint. I’ll skip rewriting the constraints and jump a little
ahead.
Again we can follow the same process for 𝑤3 to find a small Big M value.
Given that we have already created a model without the Big M constraints,
we can simply add the constraints to the model. We’ll skip the piping operator
and just add the constraint directly to the previous model, fc_base_model.
## Status: optimal
## Objective value: 6500
Our model was able to find an optimal solution with an objective value of
6500. Our optimal production plan is shown below. As we can see, our model
returned a production plan with only one model of widget being produced.
Interesting, the objective function value has gone down significantly. Let’s look
this over in more detail and compare it to the results that we had when we
avoided paying for setups.
Net Profit 𝑤1 𝑤2 𝑤3 𝑦1 𝑦2 𝑦3
Base Case w/o Setup 8845 0 307 231 0 0 0
Base Case with Setup 5345 0 307 231 0 0 0
Optimal Fixed Charge 6500 500 0 0 1 0 0
Notice that the high setup costs have caused us to focus our production plan-
ning decisions. Rather than spreading ourselves across three different product
lines, we are producing as many of widget 1 as we can.
6.7 Model Results and Interpretation 155
As can be seen, we used all of our Pre-processing hours, Assembly hours, and
Quality Assurance hours. There is a significant amount of time available in
Machining though.
Further analysis could examine alternatives such as redesigning widget 2 and
3 to be less resource intensive in production to see at what point we would
choose to produce them.
Exercise 6.8 (Redesign Widget 3). The design team has an idea of how to
use a previous manufacturing fixture which could eliminate the setup cost for
Widget 3. How much would you produce of Widget 3 if there were no setup
(fixed) cost for production of Widget 3? Of course this still keeps a cost for
Widgets 1 and 2. Experiment with the above model to find the lowest setup
cost for Widget 3 where you still choose to not produce any of Widget 3.
Exercise 6.9 (Reducing Fixed Costs). How would production change if the
setup costs were cut in half due to implementing lean production approaches?
Exercise 6.10 (Adding a Fourth Widget). Consider adding a new widget 4
which needs 4, 3, 2, and 5 hours for Pre-processing, Machining, Assembly, and
Quality Assurance, respectively. Widget 4 uses the same setup as Widget 3
which means no repeated setup cost for Widget 4 if widget 3 is already being
produced.
Solve the model to obtain the production plan where both Widget 3 and
Widget 4 are produced.
7
More Integer Programming Models
7.1 Overview
This chapter consists of a collection of rich MILP models that can be used
for inspiration on products. Some of these cases are expansions of Dirk Schu-
macher’s omprvignettes. These make for excellent resources demonstrating a
variety of features such as creation of simulated data and visualization of
results. I strongly recommend reading the original. These vignettes can be
viewed from the package documentation, Dirk Schumacher’s github web site1 ,
or downloading his github repository.
• All text from Dirk Schumacher’s articles are set in block quotes.
• Code chunks are generally based on Dirk Schumacher’s articles unless stated
otherwise.
• The LaTeX formulations are generally based on Dirk Schumacher’s LaTeX
with some modifications to support LaTeX environments.
Thus there are two decisions that need to made at once: where
and if to build warehouses and the assignment of customers to
warehouses. This simple setting also implies that at least one
warehouse must be built and that any warehouse is big enough
to serve all customers.
As a practical example: you run the logistics for an NGO and
want to regularly distribute goods to people in need. You identi-
fied a set of possible locations to set up your distribution hubs,
but you are not sure where to build them. Then such a model
might help. In practice however you might need to incorporate
additional constraints into the model. Let’s start by defining the
decision variables. Each possible location of a warehouse, 𝑗 can
have a warehouse be built or not be built. We will use 𝑦𝑗 = 1 to
indicate that warehouse 𝑗 is built. Conversely, 𝑦𝑗 = 0 indicates
that we are not building a warehouse at location 𝑗. Since a ware-
house is either built or not built, a binary variable is appropriate
for 𝑦𝑗 .
Now, that we have a handle on the variables, let’s move on to the constraints.
Each customer must be assigned to one and only one warehouse. For illus-
tration, this means that one of the variables 𝑥1,𝑗 must be set to one and the
others are zero. To enforce this constraint, we can simply add the 𝑥 vari-
ables for warehouse 1 for each of the warehouses. We could do this with
𝑥1,1 + 𝑥1,2 + … + 𝑥1,𝑚 and requiring it to be one. We could rewrite this
𝑚
using a summation as ∑ 𝑥1,𝑗 = 1. That constraint is limited to just cus-
𝑗=1
tomer 1 though. We don’t want to write this constraint out separately for
each customer so we can generalize this by repeating it for all 𝑛 customers as
𝑚
∑ 𝑥𝑖,𝑗 = 1, 𝑖 = 1, … , 𝑛.
𝑗=1
We also have a cost factor for each warehouse that we choose to build/operate.
Again, if we define fixedcost𝑗 as the fixed cost for building warehouse 𝑗, the
𝑚
cost of our decisions is simply, ∑ fixedcost𝑗 ⋅𝑦𝑗 .
𝑗=1
𝑛 𝑚 𝑚
min ∑ ∑ transportcost𝑖,𝑗 ⋅𝑥𝑖,𝑗 + ∑ fixedcost𝑗 ⋅𝑦𝑗
𝑖=1 𝑗=1 𝑗=1
𝑚
s.t. ∑ 𝑥𝑖,𝑗 = 1, 𝑖 = 1, … , 𝑛
𝑗=1
𝑥𝑖,𝑗 ≤ 𝑦𝑗 , 𝑖 = 1, … , 𝑛, 𝑗 = 1, … , 𝑚
𝑥𝑖,𝑗 ∈ {0, 1} 𝑖 = 1, … , 𝑛, 𝑗 = 1, … , 𝑚
𝑦𝑗 ∈ {0, 1} 𝑗 = 1, … , 𝑚
Rather than typing in fixed data or loading it from data file, Dirk simply
generated a collection of random data for testing purposes. This highlights the
advantage of R in that we have a rich collection of numerical tools available
that we can leverage.
The first thing we need is the data. In this article we will simply
generate artificial data.
We assume the customers are located in a grid with Euclidian
160 7 More Integer Programming Models
distances. Let’s explain the functions used for this data genera-
tion. The first command sets the random number seed. In gen-
eral, computers don’t actually generate random numbers, they
generate what is called pseudo random numbers according to a
particular algorithm in a sequence. The starting point will set a
sequence that appears to be random and behaves in a relatively
random way. Much more can be said but this is setting as the
first random number, 1234. It could just as easily have been 4321
or any other integer.
Let’s unpack this further.
The grid size is set to 1000. You can think of this as a map for a square region
that is 1000 kilometers in both horizontal (East-West) and vertical (North-
South) directions for visualization purposes. We are then randomly placing
customers on the map.
Next, we set the number of customers to be 200 and the number of warehouses
to be 40. The size of this problem can have a tremendous impact on solving
speed.3
3 In
particular, I recommend using a much smaller problem size (fewer customers and
warehouses) if you are using a slow computer or a shared resource such as a server or
cloud-based service like RStudio.cloud. Integer programming problems can quickly consume
tremendous amounts of computer processing time which can have budgetary impacts or
other problems.
7.2 Revisiting the Warehouse Location Problem 161
Choosing to open a warehouse entails a fixed cost that varies for each ware-
house, j, defined earlier as 𝑓𝑖𝑥𝑒𝑑𝑐𝑜𝑠𝑡𝑗 or fixedcost[j] in R. The warehouse
locations and fixed costs are again generated randomly as described by Dirk
Schumacher.
8
4
0
Cost
162 7 More Integer Programming Models
## [1] 648
A table of the randomly generated locations is not terribly helpful for under-
standing the locations of customers or warehouses though. Let’s build a map
by plotting both customers and warehouses together. Black dots are customers
and red dots are possible warehouse locations.
7.2 Revisiting the Warehouse Location Problem 163
id x y
1 114 661
2 622 528
3 609 317
4 623 768
5 861 526
6 640 732
Note that I have modified the variables names for x and y to be Vx and Vy
in the code chunk to remove confusion over x and y representing location
or variables. The prefix of V is meant to suggest that it is a variable. It also
helps prevent name space conflicts with other R objects in my environment.
# minimize cost
set_objective(sum_expr(transportcost(i, j) * Vx[i, j],
i = 1:n, j = 1:m) +
164 7 More Integer Programming Models
model
The number of x (or Vx) variables is 𝑚 ⋅ 𝑛 = 40 ⋅ 200 or 8000 and the number
of y (or Vy) variables is 𝑚 or 20, the total number of variables is 8040 which
matches the model summary. A similar calculation can be done on the
constraints. The number of variables and constraints make this a non-trivial
sized MIP problem. Fortunately, solving still turns out to be pretty easy on
a personal computer but be cautious of solving this on a cloud instance. In
fact, preparing the LP to be solved using ompr can take a significant amount
of time and this is an area of ompr being worked upon.
Now that have prepared the model, we are now ready to work on solving
the model. We will start with using glpk as we have done in previous examples.
result
## Status: optimal
## Objective value: 60736
166 7 More Integer Programming Models
We can see that the result was solved to optimality and the objective function
value of We can summarize the information by simply extracting a the objec-
tive function value. This can be done with inline R code. For example, we can
say the following.
We solved the problem with an objective value of 60736.
With 8040 variables, it isn’t always terribly useful to just list out all of the
variables.
We can do some processing to extract the non-zero x (or Vx) variables from
those with zero values. In order to do so, Dirk uses the dplyr package. This
package provides a tremendous number of functions for data management. To
illustrate how this is done, let’s review line by line how this code chunk works.
The next command matching <- result |> does a few things. It uses the
piping operator |> to pass the result object into being used by the following
command and then everything that gets done later in this piped sequence
will be assigned to matching.
The command filter(value > .9) |> takes the previous command’s 2000
variable values and only keeps the ones for which the value is larger than
0.9. Since these are binary variables, they should all be exactly 1.0 but
just in case there is any numerical anomaly where a value is passed as a
value close to one, such as 0.9999999, it is best to test with some tolerance.
This command then only passes to the next command the 100 non-zero values.
The last command in this code chunk select(i, j) says to only select (or
retain) the columns named i and j. Notice that this does not pass the actual
value of zero or one – we know it is one! Also, note that it does not include
the piping operator |> which means that it is the end of this piped sequence.
Let’s review what the two versions of the results look like.
variable i j value
Vx 1 1 0
Vx 2 1 0
Vx 3 1 0
Vx 4 1 0
Vx 5 1 0
Vx 6 1 0
Notice that in the table of raw results, the first six entries (obtained using the
head function) did not show any customers assigned to a warehouse. This isn’t
surprising given that only one in twenty values are non-zero. It is common in
many binary programming cases for the vast majority of variables to have a
value of zero.
The processed results table shows that the matching object simply lists six
customers and to which warehouse it is assigned. It has cut out the vast
majority of rows (decision variables) with zero values.
This clean and simple listing from matching is interesting and will then be
used for adding lines to the earlier ggplot model.
168 7 More Integer Programming Models
i j
4 3
5 3
6 3
10 3
11 3
14 3
The last step is to add the assignments to the previous plot we generated.
Dirk used one single code chunk to do more processing of results, focused
on warehouses, and then create the final plot. I’ll break up the steps into
separate code chunks just for the sake of illustrating how they work and
showing the intermediate products. This is a useful chance to see how to
manage results.
Notice that this gives the beginning and ending coordinates of what will soon
be lines to show the connections between customers and warehouses.
id n
3 67
24 61
28 72
id x y costs n
3 757 745 4511 67
24 105 517 1184 61
28 426 393 4061 72
p +
geom_segment(data=plot_assignment,
aes(x=x.y, y=y.y, xend=x.x, yend=y.x)) +
geom_point(data = plot_warehouses,
color = ”red”, size=3, shape=17) +
ggrepel::geom_label_repel(data = plot_warehouses,
aes(label = paste0(
”fixed costs:”,costs, ”;
customers: ”, n)),
size = 2, nudge_y = 20) +
ggtitle(paste0(
”Optimal Warehouse Locations and Customer Assignment”),
”Big red triangles show warehouses that will be built.
Light red are unused warehouse locations.
Lines connect customers to their assigned warehouses.”)
fixed costs:4511;
customers: 67
fixed costs:1184;
customers: 61
fixed costs:4061;
customers: 72
sum(fixedcost[unique(matching$j)])
## [1] 9756
The above function cleverly uses the warehouse ID column from matching
(matching$j) to make list unique and get rid of duplicates. Recall that ware-
houses will be listed 100 times since every customer has a warehouse listed
in matching. Next, it uses these ID numbers to extract fixed-cost values and
adds them up. Note that when you see a command such as this in R, it can
often work to run them one at time in the console to see how they work. To
demonstrate this, see how each statement builds upon the previous.
matching$j
unique(matching$j)
## [1] 3 24 28
## [1] 9756
This warehouse customer assignment problem was discussed in detail for mul-
tiple reasons.
One of the big benefits of ompr is that it separates the process of formulating
the optimization model from that of solving it and thereby let’s us easily
switch to other solvers. Up until this point, we have used the glpk solver. Now
that we have a nontrivial optimization model, the Warehouse Location Model,
let’s use it.
The emphasis in this section is not model building but comparing the use of
different solvers. Since we have the model already defined as an object, simply
named model, we can easily pass it to the different solvers for optimizing. There
is a large collection of other solvers that can be used with R.6
We’ll demonstrate the performance for illustration purposes. Again, do not
run the following code chunks in this section on a cloud-based service such
as Rstudio.cloud, unless you are certain that imposing a non-trivial computa-
tional load is acceptable.
We have been using glpk for all of our earlier examples. For the sake of com-
parison, let’s start off with it too. We’ll set the verbose=TRUE option to be
passed through ROI to the glpk solver. The exact options available for each
solver and their available options varies. We will use the tictoc package to
help us collect data on timing. Note that timing can vary even if the same
problem is solved on the same computer twice in rapid succession.
print(solver_status(result_glpk))
## [1] ”optimal”
The more verbose output provides additional details on the solving. For ex-
ample, it indicates that the Simplex method was used by indicating a Simplex
optimizer was used before then passing the LP relaxation solution to the in-
teger optimization. In general, the glpk solver worked. We’ll take the results
and combine them with other shortly.
Let’s move on to testing the symphony solver. Symphony is a solver with a long
history of development.
7.3 Solving MIPs with Different Solvers 175
tic(”symphony”)
result_symphony <- solve_model(
model, with_ROI(solver = ”symphony”, verbosity = -1))
symphonytime <- toc()
print(solver_status(result_symphony))
## [1] ”optimal”
Several items should be noted from the above code and output.
One item is that parameters can be passed directly to solvers. This is why
verbose = TRUE was used for glpk but symphony uses verbosity = -1. Differing
levels of verbosity gives much more granularity than a simple TRUE/FALSE.
Setting verbosity = 0 will give rich detail that an analyst trying to improve
solution speed may find useful or for debugging why a model did not work
but explaining the report is beyond the scope of this text.
Other options can be passed to the symphony solver such as time_limit,
gap_limit, and first_feasible which all allow for optimization runs to be
finished before it has solved the model to known optimality. These early ter-
mination options can be very helpful when working with large integer pro-
gramming problems.
The time_limit option has a default value of −1 meaning no time limit. It
should be an integer to indicate the number of seconds to run before stopping.
The node_limit option has a default value of −1 meaning no limit on the num-
ber of nodes (in an MIP problem, the number of linear programs). It should
be an integer to indicate the number of nodes examined before stopping.
176 7 More Integer Programming Models
tic(”symphony”)
result_symphony_ff <- solve_model(
model, with_ROI(solver = ”symphony”,
verbosity = -1, first_feasible=TRUE))
symphonytimeff <- toc()
print(solver_status(result_symphony_ff))
## [1] ”infeasible”
Several interesting and important things should be noted here. First, ompr
status indicates that the problem is infeasible. Just as we saw in Chapter 2’s
unbounded case, ompr interprets the status code from the solver by way of
ROI as not being solved to optimality and concludes that the problem is not
feasible.
This is a known issue in ompr and ompr.ROIas of version 0.8.1. It highlights
that “infeasible” status from the ompr should be thought of as meaning that
an optimal solution was not found for some reason such as being told to
terminate after the first feasible solution was found, time limit reached, node
limit reached, the MIP was infeasible, the MIP was unbounded, or some other
issue.
A second thing to note that in my randomly generated instance, the very first
feasible solution it found, happens to have the same objective function value
as the optimal solution found to optimality earlier by glpk and symphony.
7.3 Solving MIPs with Different Solvers 177
This is similar to the very simple, two variable integer programming problem
that we examined using a branch and bound tree in the previous chapter but
stopped before searching down the rest of the tree. Symphony just doesn’t
have confirmation yet that this first feasible solution is truly optimal yet.
Thirdly, on my computer it sometimes took less time to solve to optimality
than for when it stopped early with just the initial feasible solution.
This demonstrates the variability of solving time and that while the warehouse
optimization problem has quite a few variables, in its current structure, it is
not a particularly difficult integer programming problem.
The lpsolve package has a long history too and is widely used. Let’s test it
and see how it performs.
tic(”lpsolve”)
result_lpsolve <- solve_model(
model, with_ROI(solver = ”lpsolve”, verbose=FALSE))
lpsolvetime <- toc()
print(solver_status(result_lpsolve))
## [1] ”optimal”
1. Get a license to gurobi (free licenses are available for academic use)
2. Download and install the gurobi software
3. Follow directions to have software access license key
4. Install gurobi’s R package. Note that the package file is not in
CRAN and will need to be installed directly from the gurobi in-
stalled files on your local computer. (For example in my windows
install, it was in C:\\gurobi900\win64\R directory.)
5. Install the roi.plugin.gurobi package. Note that this may need to
be installed from a github account as it is not officially supported
by Gurobi.
A code chunk is provided without evaluation (by setting a code chunk option
of eval=FALSE) for several reasons:
• Avoid issues of reproducibility for people that don’t have access to gurobi
• Avoid potential of breaking working code due license expiration
• Maintain the philosophy of open-source tools
• Performance testing of gurobi under these circumstances might not do justice
to the performance benefits of gurobi on much more complex optimization
problems.
tic(”gurobi”)
result_gurobi <- solve_model(
model, with_ROI(solver = ”gurobi”))
gurobitime <- toc()
print(solver_status(result_gurobi))
gurobitime1 <- c(”gurobi”,
gurobitime$toc - gurobitime$tic,
result_gurobi$status,
result_lpsolve$objective_value)
The ROI.plugin for each respective solver general accepts a subset of all the
7.3 Solving MIPs with Different Solvers 179
parameters or options that a solver can use. This may mean some trial and
error is involved. The Gurobi documentation7 provides a comprehensive list
of parameters.
I’ve tested the following commonly used parameters with Gurobi and can
confirm that they appear to work.
The gurobi solver has far more capabilities than are presented here. It provides
it’s own algebraic modeling language and as an integrated solution is able to
avoid the issue of misinterpreting solving codes. For people with very large
scale optimization problems that they are solving on a frequent basis, it may be
worth investigating these other options. The ompr tool and algebraic modeling
language used within R will make the transition straightforward.
Now let’s compare how three major open source optimization solvers per-
formed.
7 https://www.gurobi.com/documentation/9.0/refman/parameters.html#sec:Parameters
180 7 More Integer Programming Models
The most important thing to note is that each solver states that it found
the same value for an objective function. While they may vary in how it
is achieved if there are multiple optima (in other words, different values for
decision variables x and y), this means that they all found a correct solution.
Of course it would be a big problem if they claimed to have optimal solutions
with different objective function values.
Note that time will vary significantly from computer to computer and may
also depend upon the other tasks and the amount of memory available. Per-
formance will change for different solver settings and for different problems.
Also, performance time will change for different random instances – simply
changing the random number seed will create different run-time results. This
problem is probably too small to show off the advantages of gurobi and
running gurobi through third party tool interfaces such as ompr and ROI will
also be slower than a more native format.
The ROI package lists 19 different solvers with ROI plugins. Three commer-
cial solvers, C-Plex, GUROBI, and MOSEK are worth highlighting. These
programs may be substantially faster on large MIP problems and may make
versions available for academic use. Others are for quadratic optimization or
special classes of optimization problems.
After running these chunks dozens of times and finding the results to be
quite stable across the various solvers tested, using a code chunk option
cache=TRUE may be beneficial. Caching means that it will save and reuse
7.3 Solving MIPs with Different Solvers 181
the results unless things are changed. If it determines that things have
changed, it will rerun that chunk. This can save a lot of time in knitting
the chapter or rebuilding the book. This is a good application area for
demonstrating the benefits of using the chunk option for caching. The
current application requires about a minute to do each full size customer
warehouse optimization run. Caching just the four runs can save a lot of
knitting time. RStudio and knitr do a sophisticated check for changes which
can invalidate the previously cached results and will then rerun the code
chunk and save the results for future cached knits as appropriate. More
demanding needs can use the granularity of numerical options for caching as
compared to simply TRUE/FALSE and additional options. These might be re-
quired in detailed performance testing or numerical performance comparisons.
While caching can be very helpful with large runs, I have also run into diffi-
culty where data or a code chunk has changed in a way that I know should
affect results and yet the results didn’t change. In fact, when running all the
code chunks, the correct results would be shown. After too long a time spent
debugging, I realized that it was a problem with the knitted file using old
cached results rather than actually rerunning the current results. Technically,
I had expected the previous cached results to be considered invalidated. In
general, I would recommend not using knitr’s caching of results unless com-
putation time or resources become a problem and the user is willing to learn
a little about the issue of caching.
Significant computer time can be used for large models but a variety of ques-
tions can be examined such as the following.
60000
package
lpSolve
lpSolveAPI
40000 Rglpk
Downloads
clpAPI
DEoptim
Rsymphony
glpkAPI
alabama
20000 scs
We will focus our attention on the ROI packages that emphasize linear pro-
gramming. This means dropping packages such as ROI.plugin.nloptr and
ROI.plugin.quadprog.
Also, commercial software that is not available on CRAN such as gurobi and
XPress, are not included.
6000
package
ROI.plugin.glpk
Downloads
ROI.plugin.symphony
4000
ROI.plugin.lpsolve
ROI.plugin.scs
ROI.plugin.clp
ROI.plugin.alabama
2000
It is interesting to see that the symphony and glpk ROI plugins were relatively
equal in downloads until about 2015 when glpk started to open a lead in
usage. This of course does not mean that glpk is better than the others but
is interesting to note. The spikes in downloads could be driven by other
packages that use this particular solver, visibility from vignettes, courses,
other publications, or package updates.
2000
1500
Downloads
package
1000 ompr
500
9
4
6 5
9
9 8 5
1 6 7 8
1 5 9 2
3
9 4 3
After spending a significant amount of time and CPU cycles on the serious
topic of warehouse optimization, let’s take a break with a more lighthearted
application of the classic Sudoku puzzle game. For those unfamiliar with
Sukoku, the core idea is to fill a 9 by 9 grid, using every digit from 1 to
9 once in each column, once in each row, and once in each 3x3 cluster. A
limited number of “hints” are provided in terms of digits that are shown,
often around 20 to 30 digits. Feel free to work on this Sudoku puzzle hand.
Using optimization, you can solve this or any other Sudoku model!
from the ‘SudokuAlt’ package from Bill Venables, one of the Godfathers of R.
Let’s start with Dirk’s explanation, with permission, of the model.
can just add up all of the digit possibilities for that cell and set it equal to one.
9
∑ 𝑥1,1,𝑘 = 1
𝑘=1
This would still entail writing a similar constraint for each of the other 80
cells such as (𝑖 = 1; 𝑗 = 2), (𝑖 = 1; 𝑗 = 3), and so on. Instead, this is a perfect
opportunity to use the ∀ to repeat this for all values of i and j.
9
∑ 𝑥𝑖,𝑗,𝑘 = 1, ∀𝑖, 𝑗
𝑘=1
We then need to ensure that in each row, 𝑖, each digit 𝑘 only appears in one
of the columns.
9
∑ 𝑥𝑖,𝑗,𝑘 = 1, ∀𝑗, 𝑘
𝑖=1
We then need to ensure that in each column, 𝑗, each digit only appears in of
the rows.
9
∑ 𝑥𝑖,𝑗,𝑘 = 1, ∀𝑖, 𝑘
𝑗=1
If we are clever, we may note that the top left corner of each cluster is a row
or column value of 1, 4, or 7.
We could extend this constraint to handle each set of clusters. We’ll use a
counter across for each cluster by row, using 𝑟. Similarly, we’ll use 𝑐 for cluster.
3+3𝑟 3+3𝑐
∑ ∑ 𝑥𝑖,𝑗,𝑘 = 1, 𝑘 = 1, … , 9 𝑟 = 0, 1, 2, 𝑐 = 0, 1, 2,
𝑖=1+3𝑟 𝑗=1+3𝑐
max 0
9
s.t.: ∑ 𝑥𝑖,𝑗,𝑘 = 1 𝑖 = 1, … , 𝑛, 𝑗 = 1, … , 𝑛
𝑘=1
𝑛
∑ 𝑥𝑖,𝑗,𝑘 = 1 𝑗 = 1, … , 𝑛, 𝑘 = 1, … , 9
𝑖=1
𝑛
∑ 𝑥𝑖,𝑗,𝑘 = 1 𝑖 = 1, … , 𝑛, 𝑘 = 1, … , 9
𝑗=1
3+𝑟 3+𝑐
∑ ∑ 𝑥𝑖,𝑗,𝑘 = 1, 𝑘 = 1, … , 9 𝑟 = 0, 3, 6, 𝑐 = 0, 3, 6,
𝑖=1+𝑟 𝑗=1+𝑐
𝑥𝑖,𝑗,𝑘 ∈ {0, 1} 𝑖 = 1, … , 𝑛, 𝑗 = 1, … , 𝑛, 𝑘 = 1, … , 9
We are now ready to implement the model. As always, clearly defining vari-
ables and constraints is important. A triple subscripted variable often makes
188 7 More Integer Programming Models
for a tricky model to both formulate and implement. Also, that last constraint
may take careful examination.
n <- 9
Sudoku_model <- MIPModel() |>
# no objective
set_objective(0) |>
We will use glpk to solve the above model. Note that we haven’t fixed any
numbers to specific values. That means that the solver will find a valid Sudoku
without any prior hints.
7.4 Solving Sudoku Puzzles using Optimization 189
I’ve made a couple of minor changes to Dirk’s code chunks. In the implementa-
tion, I replaced the x variable in ompr with Vx to avoid R name space collisions
with previously defined R data structures named x in my environment. Sec-
ondly, I switch the sx and sy to r and c to represent moving over by rows and
columns in the tricky last constraint for clusters.
If you want to solve a specific Sudoku you can fix certain cells to specific
values. For example here we solve a Sudoku that has the sequence from 1 to
9 in the first 3x3 matrix fixed.
190 7 More Integer Programming Models
1 2 3 4 5 6 7 8 9
1 2 3 8 4 6 9 5 7
4 5 6 1 9 7 3 8 2
7 8 9 3 5 2 1 4 6
2 3 4 7 1 8 6 9 5
8 9 1 5 6 3 2 7 4
5 6 7 4 2 9 8 3 1
3 4 5 6 8 1 7 2 9
6 7 2 9 3 4 5 1 8
9 1 8 2 7 5 4 6 3
Now, let’s try printing it nicely using the sudokuAlt package from Bill Venables,
available from CRAN.
plot(as.sudoku(as.matrix(Solution_specific, colGame=”grey”)))
1 2 3 8 4 6 9 5 7
4 5 6 1 9 7 3 8 2
7 8 9 3 5 2 1 4 6
2 3 4 7 1 8 6 9 5
8 9 1 5 6 3 2 7 4
5 6 7 4 2 9 8 3 1
3 4 5 6 8 1 7 2 9
6 7 2 9 3 4 5 1 8
9 1 8 2 7 5 4 6 3
Have at it. Any Sudoku puzzle can be solved using this model as long as
there is a feasible solution. Note that if there are two or more solutions to
the puzzle based on the hints, this model will find one of them and does not
indicate whether there might be other solutions.
192 7 More Integer Programming Models
The intention here is not to imply that optimization is the only way or the
best way to solve Sudoku problems. There are algorithmic approaches for
solving Sudoku that can be more computationally efficient than using a general
purpose integer programming system and in fact people have implemented
such algorithms in R and other languages. The purpose of this example was
to show how a nontraditional problem can be framed and implemented.
7.5 Exercises
that obtained earlier. Ensure that your data for customers and warehouses is
the same for the two cases.
This problem size should be cloud-friendly but ensure that you are solving the
appropriate sized problem before running.
Exercise 7.2 (Warehouse-Max and Min Customers-Small). Run the analysis
for the smaller warehouse optimization but with just 10 customers and 3
warehouses. Extend and solve the warehouse customer assignment problem
to have each warehouse that is built be assigned a maximum of 4 customers
and a minimum of 2 customers. Show plots of both solutions. Compare the
solution to that obtained earlier using tables as appropriate. Ensure that
your data for customers and warehouses is the same for the two cases.
This problem size should be cloud-friendly but ensure that you are solving
the appropriate sized problem before running.
This problem size should not be be run on a cloud or remote server without
full understanding of load implications. Note that this will be somewhat
computationally intensive and is best done on a local computer rather than
the cloud. Doing it on a personal computer may require on the order of five
minutes of processing time.
of number of warehouses used and the total cost. Discuss and interpret the
results. Note that this will be computationally intensive and should not
be done on the cloud. Doing it on a personal computer may require on the
order of five minutes of processing time.
Exercise 7.7 (Sudoku-First Puzzle). Solve the Sudoku puzzle from the be-
ginning of this section using R.
Exercise 7.8 (Sudoku-Bottom Row). Solve a Sudoku model using optimiza-
tion where the bottom row is 9 numbered down to 1.
Exercise 7.9 (Sudoku-Right Column). Solve a Sudoku model using optimiza-
tion where the rightmost column is 9 numbered down to 1.
Exercise 7.10 (Sudoku-Center Cluster). Solve a Sudoku model using opti-
mization where the center 3x3 cluster is made up of the numbers from 9 to 1.
(First row is 9, 8, 7; second row is 6, 5, 4; bottom row is 3, 2, 1)
Exercise 7.11 (Find a Sudoku to Solve). Find a Sudoku puzzle from a
newspaper or elsewhere and solve it using optimization.
Exercise 7.12 (Sudoku-First Puzzle). Solve the Sudoku problem from the
beginning of this section.
9
4
6 5
9
9 8 5
1 6 7 8
1 5 9 2
3
9 4 3
people use to solve them are based on incrementally reasoning out what values
each cell must contain. The result is that the ambiguity of multiple optima
may cause problems for people to solve. While the optimization model can
solve these without difficulty, it is only going to find one, arbitrary solution.
Simply resolving is likely to give you the same solution even when there are
multiple optima. Consider variations and apply them to find multiple solutions
to a problem. For an example of one with multiple optimal solutions, the most
extreme case would be starting with a blank board.
Let’s revisit our drone manufacturing example but accounting for time. In
this case, we’ll look at producing ants and bats over time while accounting for
196 7 More Integer Programming Models
with varying weekly demand, inventory carrying costs, setup costs, marginal
production costs, and beginning inventory. The planning horizon is four weeks.
Let’s start by by showing the data.
nTime <- 4
mDemandA <- 150 # Used for generating demand
mDemandB <- 250 # Upper limit for random number generator
fcostA <- 80; fcostB <- 140 # Setup cost for product
invcostA <- 1.5; invcostB <- 2.0 # Weekly carry cost per unit
Our demand over time for Ants and Bat is then the following.
Our goal is to create a production plan that will meet all demand at the lowest
total cost.
Let’s start by defining our data. Instead of Ants and Bats, we’ll abbreviate it
to just A and B.
Data:
𝐼
• 𝐶𝐴 = The weekly inventory carrying cost for each Ant carried from one
week to the next.
7.6 Production Planning over Time 197
𝐼
• 𝐶𝐵 = The weekly inventory carrying cost for each Bats carried from one
week to the next.
𝑃
• 𝐶𝐴 = The marginal production cost for each Ant.
𝑃
• 𝐶𝐵 = The marginal production cost for each Bat.
𝑆
• 𝐶𝐴 = The setup cost for producing Ants in any given week.
𝑆
• 𝐶𝐵 = The setup cost for producing Bats in any given week.
𝐼
• 𝐵𝐴 = Inventory of Ants at the beginning of week 1.
𝐼
• 𝐵𝐵 = Inventory of Bats at the beginning of week 1.
• 𝐷𝑡,𝐴 = Demand for Ants in week t.
• 𝐷𝑡,𝐵 = Demand for Bats in week t.
• 𝐿𝑃 = Maximum Limit on production of Ants and Bats in any given week.
• 𝐿𝐼 = Limit on maximum inventory of Ants and Bats in any given week.
Decision variables:
Let’s start with some easy relationships. We know that we can’t exceed total
production capacity in any given week. All we need to do is add the two
production variables in week t together and constrain it to not exceed the
production capacity. For week 1, we would have the following:
𝑥1,𝐴 + 𝑥1,𝐵 ≤ 𝐿𝑃
We can then generalize this for all weeks in the following manner.
𝑥𝑡,𝐴 + 𝑥𝑡,𝐵 ≤ 𝐿𝑃 , ∀𝑡
We can then implement an upper limit on total inventory in the same way.
𝑧𝑡,𝐴 + 𝑧𝑡,𝐵 ≤ 𝐿𝐼 , ∀𝑡
The inventory balance constraints follow a similar form for each week other
than week 1, so it is straightforward to generalize this to account for other
time periods after the initial one, 𝑡 = 1.
For week 1, the previous inventory is fixed rather than a variable and can be
framed as the following.
𝑥𝑡,𝐴 ≤ 𝐿𝑃 ⋅ 𝑦𝑡,𝐴 ∀ 𝑡
The linking constraint for Bats follows the same form.
𝑥𝑡,𝐵 ≤ 𝐿𝑃 ⋅ 𝑦𝑡,𝐵 ∀ 𝑡
Our goal is to minimize the total cost. Let’s look at the total cost with respect
Ants. It is a combination production cost, setup costs, and carrying costs.
4
∑ 𝐶𝑡𝑃 ⋅ 𝑥𝑡,𝐴 + 𝐶𝑡𝑆 ⋅ 𝑦𝑡,𝐴 + 𝐶𝑡𝐼 ⋅ 𝑧𝑡,𝐴
𝑡=1
This was just the costs for Ants (A) over time. We can create a similar function
for Bats (B) and then since these are costs, we want minimize the sum of the
two giving us our objective function.
4
𝑃 𝑆 𝐼
min ∑(𝐶𝐴 ⋅ 𝑥𝑡,𝐴 + 𝐶𝐴 ⋅ 𝑦𝑡,𝐴 + 𝐶𝐴 ⋅ 𝑧𝑡,𝐴 +
𝑡=1
𝑃 𝑆 𝐼
𝐶𝐵 ⋅ 𝑥𝑡,𝐵 + 𝐶𝐵 ⋅ 𝑦𝑡,𝐵 + 𝐶𝐵 ⋅ 𝑧𝑡,𝐵 )
4
𝑃 𝑆 𝐼
min ∑(𝐶𝐴 ⋅ 𝑥𝑡,𝐴 + 𝐶𝐴 ⋅ 𝑦𝑡,𝐴 + 𝐶𝐴 ⋅ 𝑧𝑡,𝐴 +
𝑡=1
𝑃 𝑆 𝐼
𝐶𝐵 ⋅ 𝑥𝑡,𝐵 + 𝐶𝐵 ⋅ 𝑦𝑡,𝐵 + 𝐶𝐵 ⋅ 𝑧𝑡,𝐵 )
s.t.: 𝑧1,𝐴 = 𝐵𝐴 + 𝑥1,𝐴 − 𝐷1,𝐴
𝑧1,𝐵 = 𝐵𝐵 + 𝑥1,𝐵 − 𝐷1,𝐵
𝑧𝑡,𝐴 = 𝑧𝑡−1,𝐴 + 𝑥𝑡,𝐴 − 𝐷𝑡,𝐴 , ∀𝑡 > 1
𝑧𝑡,𝐵 = 𝑧𝑡−1,𝐵 + 𝑥𝑡,𝐵 − 𝐷𝑡,𝐵 , ∀𝑡 > 1
𝑥𝑡,𝐴 + 𝑥𝑡,𝐵 ≤ 𝐿𝑃 , ∀𝑡
𝑧𝑡,𝐴 + 𝑧𝑡,𝐵 ≤ 𝐿𝐼 , ∀𝑡
𝑥𝑡,𝐴 ≤ 𝐿𝑃 ⋅ 𝑦𝑡,𝐴 ∀𝑡
𝑥𝑡,𝐵 ≤ 𝐿𝑃 ⋅ 𝑦𝑡,𝐵 ∀𝑡
𝑥𝑡,𝐴 𝑥𝑡,𝐴 , 𝑧𝑡,𝐴 𝑧𝑡,𝐵 ≥ 0, ∀ 𝑡
𝑦𝑡,𝐴 , 𝑦𝑡,𝐵 ∈ {0, 1} ∀ 𝑡
We’ll start by initializing the model and creating the decision variables.
Now let’s define the objective function. We are not selling prices
200 7 More Integer Programming Models
Let’s start with our constraints for linking production, inventory, and demand
for the first week. The inventory at the end of week 1 is simply the beginning
inventory at the start of week 1 (beg_invA) plus the amount produced minus
the demand.
Note that this assumes all demand must be satisfied in the same week. This
model could be extended to allow for backlogged demand or other variations.
This handled the first week. Now we need to do the same for all of the following
weeks. We take the ending inventory from the previous week, add the new
production, and then subtract the demand to give us the ending inventory for
both products.
Now we need to use our create our linking constraints to connect our produc-
tion and decision to produce products. We will use the Big M method and the
maximum production production value as our Big M value.
promod
## [1] ”optimal”
prores$objective_value
202 7 More Integer Programming Models
## [1] 8856.5
This production planning over time example can be further enhanced in many
directions to tailor it to specific applications. Some of the ways that it can be
enhanced include the following:
you had been responsible for creating the plan and presented the recommen-
dation to the Vice President of Supply Chain Management who stated that
they don’t want to pay for the construction of three warehouses and that at
most two warehouses should be built.
8.1 Introduction
Up until this point, we assumed that there would be a single, clear objective
function. Often we have more complex situations where there are multiple
conflicting objectives. In our earlier production planning case, we might have
additional objectives besides maximizing profit such as minimizing environ-
mental waste or longer term strategic positioning. In the case of our capital
budgeting problem, we can envision a range of additional considerations be-
yond simple expected net present value maximization. In the warehouse site
selection problem from Chapter 7, we can envision other considerations such as
political placement in certain states, being prepared for future varying growth
levels in different regions, and other issues which could further influence a
simple minimum cost solution.
Let’s begin with a simple example. Recall the example of multiple optima
from Chapter 2. We had adjusted the characteristics of Cats so that there
were multiple optima with different mixes of Ants and Cats. The LP solver
found an initial solution that only produced Ants, and we had to force the LP
solver to find the Cats oriented production.
Let’s reframe this problem where the company’s primary goal is to maximize
profit but the Cat product is more prestigious than the Ant product and
emphasizing it will benefit the company in long term market positioning. The
company doesn’t want to hurt profit but holding everything else equal, wants
to then maximize Cat production.
The first step is to make the primary goal more direct. The objective function
is now a fourth variable, Profit that is a direct function of the other three
variables. The following formulation would then be considered the first phase.
Max 𝑃 𝑟𝑜𝑓𝑖𝑡
s.t.: 𝑃 𝑟𝑜𝑓𝑖𝑡 = 7 ⋅ 𝐴𝑛𝑡𝑠 + 12 ⋅ 𝐵𝑎𝑡𝑠 + 14 ⋅ 𝐶𝑎𝑡𝑠
1 ⋅ 𝐴𝑛𝑡𝑠 + 4 ⋅ 𝐵𝑎𝑡𝑠 + 2 ⋅ 𝐶𝑎𝑡𝑠 ≤ 800
3 ⋅ 𝐴𝑛𝑡𝑠 + 6 ⋅ 𝐵𝑎𝑡𝑠 + 6 ⋅ 𝐶𝑎𝑡𝑠 ≤ 900
2 ⋅ 𝐴𝑛𝑡𝑠 + 2 ⋅ 𝐵𝑎𝑡𝑠 + 4 ⋅ 𝐶𝑎𝑡𝑠 ≤ 480
2 ⋅ 𝐴𝑛𝑡𝑠 + 10 ⋅ 𝐵𝑎𝑡𝑠 + 4 ⋅ 𝐶𝑎𝑡𝑠 ≤ 1200
𝐴𝑛𝑡𝑠, 𝐵𝑎𝑡𝑠, 𝐶𝑎𝑡𝑠 ≥ 0
The optimal objective function value from this LP is 1980 as shown in Chapter
2. Now we modify the above formulation to keep the Profit fixed at 1980 and
maximize the production of Cats.
Max 𝐶𝑎𝑡𝑠
s.t.: 𝑃 𝑟𝑜𝑓𝑖𝑡 = 7 ⋅ 𝐴𝑛𝑡𝑠 + 12 ⋅ 𝐵𝑎𝑡𝑠 + 14 ⋅ 𝐶𝑎𝑡𝑠
1 ⋅ 𝐴𝑛𝑡𝑠 + 4 ⋅ 𝐵𝑎𝑡𝑠 + 2 ⋅ 𝐶𝑎𝑡𝑠 ≤ 800
3 ⋅ 𝐴𝑛𝑡𝑠 + 6 ⋅ 𝐵𝑎𝑡𝑠 + 6 ⋅ 𝐶𝑎𝑡𝑠 ≤ 900
2 ⋅ 𝐴𝑛𝑡𝑠 + 2 ⋅ 𝐵𝑎𝑡𝑠 + 4 ⋅ 𝐶𝑎𝑡𝑠 ≤ 480
2 ⋅ 𝐴𝑛𝑡𝑠 + 10 ⋅ 𝐵𝑎𝑡𝑠 + 4 ⋅ 𝐶𝑎𝑡𝑠 ≤ 1200
𝑃 𝑟𝑜𝑓𝑖𝑡 = 1980
𝐴𝑛𝑡𝑠, 𝐵𝑎𝑡𝑠, 𝐶𝑎𝑡𝑠 ≥ 0
Rather than directly stating the solution value of 1980, a more general ap-
proach would use the term, 𝑃 𝑟𝑜𝑓𝑖𝑡∗ to denote the optimal solution from the
first Phase and the new Phase 2 constraint would then be 𝑃 𝑟𝑜𝑓𝑖𝑡 = 𝑃 𝑟𝑜𝑓𝑖𝑡∗ .
This would then give a revised solution that emphasizes Cat production in
so far as it doesn’t detract from Profit. In other words, maximizing Profit
preempts maximizing Cat production.
8.3 Policies for Houselessness 207
Let’s use an example that is a pressing issue for many cities – homelessness.
Note that is often better characterized as houselesness and the term will be
used interchangeably for the sake of this illustration.
The City of Bartland has a problem with houselessness. Two ideas have been
proposed for dealing with the houselessness problem. The first option is to
build new, government subsidized tiny homes for annual cost of $10𝐾 which
would serve one adult 90% of the time and a parent with a child 10% of the
time. Another option is to create a rental subsidy program which costs $25𝐾
per year per unit which typically serves a single adult (15%), two adults (20%),
an adult with one child (30%), an adult with two children (20%), two adults
with one child (10%), and two adults with two children (5%).
Bartland’s official Chief Economist has estimated that this subsidy program
would tend to increase housing prices in a very tight housing market by an
average of 0.001%. The Bartland City Council has $1000𝐾 available to reap-
propriate from elsewhere in the budget and would like to find the best way
to use this budget to help with the houselessness problem. Both programs
require staff support – in particular 10% of a full time equivalent staff mem-
ber to process paperwork, conduct visits, and other service related activities.
There are seven staff members available to work on these activities.
Let’s summarize the data for two programs. Let’s focus on expected numbers
of people served for each policy intervention.
One group on the city council wants to serve as many people (both children
and adults) as possible while keeping under the total budget limit.
The second group feels that adults are responsible for their own situation but
wants to save as many children from houselessness as possible within budget
limits.
As usual, start by thinking of the decision variables. In this case, let’s define
H to be number of tiny homes to be built and R to be the rental housing
subsidies provided. Of course these should be non-negative variables. We could
use integer variables or continuous variables.
Next, let’s look at our constraints and formulate them in terms of the decision
variables. We have two constraints. The first one for the budget is simply:
10 ⋅ 𝐻 + 25 ⋅ 𝑅 ≤ 1000. The second is to ensure we have sufficient staff support,
0.1 ⋅ 𝐻 + 0.1 ⋅ 𝑅 ≤ 7.
Now, let’s think about our objectives. The first group wants to serve as many
people as possible so the objective function is max 1.1 ⋅ 𝐻 + 2.25 ⋅ 𝑅.
Similarly, since the second group is focused on children, their objective func-
tion is
max 0.1 ⋅ 𝐻 + 0.9 ⋅ 𝑅.
Alas, linear programming models and the Simplex method only allows for a
single objective function. Let’s start by solving from the perspective of the
first group.
Now, let’s examine the second group’s model that has an objective of maxi-
mizing the expected number of children served.
So which group has the better model? The objective function value for group
1’s model is higher, but it is in different units (people served) versus group 2’s
model of children served.
Both group’s have admirable objectives. We can view this as a case of goal
programming. By definition, we know that these are the best values that can
be achieved in terms of that objective function. Let’s treat these optimal values
as targets to strive for and measure the amount by which fail to achieve these
targets. We’ll define target 𝑇1 = 100 and 𝑇2 = 36.
In order to do this, we need to use deviational variables. These are like slack
variables from the standard form of linear programs. Since the deviations can
only be one sided in this case, we only need to have deviations in one direction.
We will define 𝑑1 as the deviation in goal 1 (Maximizing people served) and
𝑑2 as the deviation in goal 2 (Maximizing children served).
Let’s now create the modified formulation.
min 𝑑1 + 𝑑2
s.t. 10 ⋅ 𝐻 + 25 ⋅ 𝑅 ≤ 1000
0.1 ⋅ 𝐻 + 0.1 ⋅ 𝑅 ≤ 7
1.1 ⋅ 𝐻 + 2.25 ⋅ 𝑅 + 𝑑1 = 𝑇1 = 100
0.1 ⋅ 𝐻 + 0.9 ⋅ 𝑅 + 𝑑2 = 𝑇2 = 36
𝐻, 𝑅 ∈ {0, 1, 2, …}
𝑑1 , 𝑑 2 ≥ 0
The deviation variables have different units though. One way to accommodate
this would be to minimize the sum of percentages missed.
8.3 Policies for Houselessness 211
𝑑1 𝑑
min + 2
𝑇1 𝑇2
s.t. 10 ⋅ 𝐻 + 25 ⋅ 𝑅 ≤ 1000
0.1 ⋅ 𝐻 + 0.1 ⋅ 𝑅 ≤ 7
1.1 ⋅ 𝐻 + 2.25 ⋅ 𝑅 + 𝑑1 = 𝑇1
0.1 ⋅ 𝐻 + 0.9 ⋅ 𝑅 + 𝑑2 = 𝑇2
𝐻, 𝑅 ∈ {0, 1, 2, …}
𝑑1 , 𝑑 2 ≥ 0
## Status: optimal
## Objective value: 0.1
## Status: optimal
## Objective value: 0.08
on drug addiction treatment, policing practices, and more. We did not factor
in the Chief Economist’s impact on housing prices. We’ll leave these issues to
future work.
Try to give some thoughts as to how to set this up before moving on to seeing
our formulation.
To provide some space before we discuss the formulation, let’s show the data.
Rather than providing a data table that must be retyped, let’s use a dataset
already available in R so you can simply load the state data. Note that you
can grab the population in 1977 in terms of thousands.
214 8 Goal Programming
data(state)
Customers <- state.x77[,1]
kbl (head (Customers), booktabs=T,
caption=”Number of Customers for First Six States.”) |>
kable_styling(latex_options = ”hold_position”)
x
Alabama 3615
Alaska 365
Arizona 2212
Arkansas 2110
California 21198
Colorado 2541
Presumably you have created your own formulation. If so, your model will
likely differ from what follows in some ways such as naming conventions for
variables or subscripts. That is fine. The process of trying to build a model is
important.
Now, we need to ensure that every state is mailed to in one of the eight weeks.
We simply need to add up the variable for each state’s decision to mail in
8
week 1, 2, 3, …, up to 8. Mathematically, this would be ∑ 𝑥𝑠,𝑤 = 1, ∀ 𝑠.
𝑤=1
50 8
It is useful to take a moment to reflect on why ∑ ∑ 𝑥𝑠,𝑤 = 50 is not
𝑠=1 𝑤=1
sufficient to ensure that all 50 states get mailed to during the eight week
planning period.
Combined with the variable 𝑥𝑠,𝑤 being defined to be binary, this is sufficient
to ensure that we have a feasible answer but not necessarily a well-balanced
8.4 Mass Mailings 215
min 𝑄
8
s.t. ∑ 𝑥𝑠,𝑤 = 1, ∀ 𝑠
𝑤=1
50
𝑄 ≥ ∑ 𝐶𝑠 ⋅ 𝑥𝑠,𝑤 ∀ 𝑤
𝑠=1
𝑥𝑠,𝑤 ∈ {0, 1} ∀ 𝑠, 𝑤
res_Mail1
## Status: infeasible
## Objective value: 26834
Note that the messages from Symphony indicate that the solution found was
feasible while ompr interprets the status as infeasible. This is a bug that we
have discussed earlier. Turning on an option for more messages from the solver
such as verbose=TRUE for verbose=TRUE or verbosity=0 for symphony can give
confirmation that the final status is not infeasible.
Another useful to thing to note is that solving this problem to optimality can
take a long time despite having fewer binary variables than some of our earlier
examples. Using glpk with verbose=FALSE means that the MIP is solved with
no progress information displayed and makes it look like the solver is hung.
8.4 Mass Mailings 217
Turning on more information (increasing the verbosity) helps explain that the
Solver is working, it is just taking a while, on my computer I let it run 20
minutes without making further progress than a feasible solution it had found
quickly.
In fact, I realized that the feasible solution found was very close to the best
remaining branches so perhaps this solution was optimal, but it was taking a
very long time to prove that it was optimal. In any case, it is probably good
enough. Often data may only be accurate to ±5% so spending extensive time
trying to get significantly more accurate results is not very productive. This
suggests setting stopping options such as a time limit, number of LPs, or a
good enough setting. For this problem, I chose the latter option.
We solved this with a mixed integer programming problem gap limit of 1.5%
meaning that while we have not proven this solution to be optimal, we do
know that it is impossible to find a solution more than 1.5% better. From a
branch and bound algorithm perspective, this means that while we have not
searched down fully or pruned every branch, we know that no branch has the
potential of being more than 1.5% better than the feasible solution that we
have already found.
Now let’s move on to discussing the results. We will start with filtering out
all the variables that have zero values so we can focus on the ones of interest
– the states that are assigned to each week. Also, notice that a dplyr function
was used to add state names to the data frame.
s w
Indiana 14 1
Maryland 20 1
Minnesota 23 1
Nevada 28 1
North Carolina 33 1
South Carolina 40 1
kbl(text_tbl, booktabs=T,
caption=”Display of States by Week”) |>
kable_styling(latex_options = c(”hold_position”)) |>
column_spec(1, bold = T) %>%
column_spec(2, width = ”30em”)
Since this is state level data, let’s look at a map of the schedule.
library (ggplot2)
library (maps)
mapx = data.frame(region=tolower(rownames(assigned1a)),
week=assigned1a[,”w”],
stringsAsFactors=F)
Weeks States
Week 1 Indiana, Maryland, Minnesota, Nevada, North Carolina, South
Carolina, Tennessee
Week 2 Alabama, Arkansas, Delaware, Iowa, Montana, South Dakota,
Texas, Washington
Week 3 Alaska, Massachusetts, Missouri, Oregon, Pennsylvania, Utah,
Vermont
Week 4 Arizona, Hawaii, Idaho, Illinois, Kentucky, Maine, New
Hampshire, West Virginia, Wisconsin
Week 5 Colorado, Georgia, Ohio, Oklahoma, Rhode Island, Virginia
Week 6 Florida, New York, Wyoming
Week 7 California, Louisiana, New Mexico
Week 8 Connecticut, Kansas, Michigan, Mississippi, Nebraska, New Jersey,
North Dakota
Note that this leaves off Alaska and Hawaii for visualization. For completeness,
Alaska is in week 3 and Hawaii is in week 4.
45
colour
white
40
week
8
y
6
35
4
2
30
25
50 50
∑ 𝐶𝑠 ⋅ 𝑥𝑠,𝑤−1 ≥ ∑ 𝐶𝑠 ⋅ 𝑥𝑠,𝑤 , 𝑤 ∈ {2, … , 8}
𝑠=1 𝑠=1
We could then extend our ompr model, Mail1 with the additional constraints.
While it might be thought that adding constraints limits the search space of
possible solutions and might speed up solution, in this case it slows down
solving speed tremendously. Despite having having less than half the binary
variables of applications from Chapter 8 and the same number the earlier mail
problem, this problem turns out to be most computationally demanding so
far. The branch and bound process from symphony takes hours to solve.
8.4 Mass Mailings 221
This highlights that large or complex optimization problems may take extra
effort and attention. Analysts can make judicious tradeoffs of potentially sub-
optimal results, solving time, and data accuracy. For our purpose, to speed
up the solution time, you could do an early termination option such as setting
the gap_limit to 15% by adding gap_limit=15. Setting an appropriate termina-
tion parameter based on gap, number of iterations, or time can be particularly
helpful while developing a model. It also underscores that the benefits of im-
plementing and testing algebraic models with smaller sized problems before
expanding to the full sized problems. By separating the data from the model,
we can easily just shrink or expand the problem without affecting the formula-
tion or the implementation. In this case, we could shrink the problem by using
fewer weeks and states by changing the upper limits (Weeks<-10; States<-10)
used in the model, resulting in 30 binary variables instead of 400.
We could further modify the model to eliminate Q since the workload in week
1 would essentially be the heaviest workload (tallest nail.) This highlights
that in optimization and particularly integer programming, there are often
many different ways to implement models for the same application. This
222 8 Goal Programming
This application and model can be adjusted to fit a wide variety of other
situations such as:
8.5 Exercises
A.1 Purpose
This Appendix provides a very brief introduction to the basics of R from the
perspective of use for optimization modeling. This is not meant to be com-
prehensive R tutorial. There is an ever expanding collection of such materials
available.
This Appendix helps a reader new to R quickly get started. I strongly recom-
mend using RStudio. I also like books such as R in a Nutshell (Adler, 2012)
or R in Action (Kabacoff, 2011).
I usually find the best way for me to learn a new tool is to roll up my sleeves
and jump right in. This book can be approached in that way – just be ready
for a little more experimentation. This book is available on Github and the R
Markdown files are available for use. Code fragments are shown quite liberally
for demonstrating how things work. While this may be a bit verbose it is
meant to enable readers to jump in at various points of the book.
While code fragments can be copied from the R markdown files for this book,
it is often best to physically retype many of the code fragments shown as that
gives time to reflect on what each statement is doing.
Let’s define some conventions to be used throughout the book. First, let me be
clear, the goal of this section is not to provide a comprehensive introduction
to R. Entire books are written on that subject. My goal here is to simply make
sure that everyone can get started productively in the material covered later.
Since this book is focused on using R for operations research, we will focus on
the capabilities that are needed for this area and introduce additional features
and functions as needed. If you are already comfortable with R, RStudio, and
RMarkdown, you may skip the remainder of this section.
Begin by ensuring that you have access to or installed R and RStudio. Both
are available for Windows, Mac, and Linux operating systems as well as being
available as web services.
Now, let’s assume that you are running RStudio.
In this book, I will frequently show code fragments called chunks to show
R code. In general, these code chunks can be run by simply retyping the
command(s) in the Console.
a <- 7
6*a
## [1] 42
Yes, not surprisingly, 6 ∗ 7 is 42. Notice that if we don’t assign that result to
something, it gives an immediate result to the screen.
We will often be using or defining data that has more than one element. In
fact, R is designed around more complex data structures. Let’s go ahead and
define a matrix of data.
b<-matrix(c(1,2,3,4,5,6,7,8))
By default, it is assuming that the matrix has one column which means that
every data value is in a separate row. The c function is a concatenate operator
meaning that it combines all the following items together.
Let’s look at b now to see what it contains.
A.2 Getting Started with R 227
## [,1]
## [1,] 1
## [2,] 2
## [3,] 3
## [4,] 4
## [5,] 5
## [6,] 6
## [7,] 7
## [8,] 8
Let’s instead define this matrix to have four columns and two rows.
b<-matrix(c(1,2,3,4,5,6,7,8), ncol=4)
b
Notice that since there are eight elements, we only needed to tell R that the
matrix has four columns, and it then knew that there would be only two rows.
Of course we could have set the number of rows to be two for the same result.
This is still a little ambiguous. Let’s give the rows and columns names. For
now we will simply name them Row and Col.
b<-matrix(c(1,2,3,4,5,6,7,8), ncol=4,
dimnames=c(list(c(”Row1”, ”Row2”)),
list(c(”Col1”, ”Col2”,”Col3”,”Col4”))))
Remark (RStudio Console). The RStudio console can save typing by pressing
the up arrow key to view previous command(s) which can then be further
edited.
Okay, this command has a lot more going on. The term dimnames is a
parameter that contain names for rows and columns. One thing to note is
that this line fills up more space than a single line, so it rolls over to multiple
line. The dimnames parameter will get get two concatenated (combined) lists.
The first list is a combined list of two text strings “Row1” and “Row2”. The
228 A A Very Brief Introduction to R
This table is still not that “nice” looking. Let’s use a package that does a
nicer job of formatting tables. To do this, we will use an extra package. Up
to this point, everything that we have done just simply uses standard built in
functions of R. The package that we will use is kable but there are plenty of
others available such as pander, kable, xtable, and huxtable. While kable is a
function of knitr, it is significantly enhanced by the kableExtra package. For
more information on the specifics of creating rich tables, see Appendix D.
Let’s start by loading the kableExtra package.
library (kableExtra)
If R indicates that you don’t have the kableExtra package installed on your
computer, you can press the “Install” command under the Packages tab and
then type in kableExtra or use the install.packages command from RStudio.
The kbl is a shorthand way of entering the kable function that is provided by
kableExtra. A lot of the power of R comes from the thousdands of packages
developed by other people so you will often be installing and loading packages.
Notice the hash symbol is used to mark a comment in the above code chunk.
It is also used to “comment out” a command that I don’t need to use at
the current time. Using comments to explain what a command is doing can
be helpful to anyone that needs to revisit your code in the future, including
yourself!
The table looks very nice in the book. Let’s explain in detail how the table is
generated. This code chunk has a lot going and is worth a careful look.
A.2 Getting Started with R 229
• The first line knitr::kable means that we should run the kable function
from within knitr. In general, as long as the library is loaded and the same
function is not defined by two different loaded libraries, you don’t need to
specify where the function is being run from.
• The booktabs=T sets a format for clean published table style by setting the
option to T for TRUE.
• The second line provides a caption.
• The last part of the second line warrants a little attention. It is the new pipe
operator and requires R version 4.1.0 or higher. The pipe operator basically
says pass this commands output into the beginning of the next command.
We’ll use it a lot for table generation. Prior to R version 4.1.0, people that
used pipes in R often used the %>% operator from a separate package such
as magrittr. For most users, including us, the built-in |> will suffice.
• The last command, kable_styling, has an option for hold_position which
holds the placement closer to where it is called, keeping LaTeX from second
guessing where the ideal location would be for the table. Note that is also
specifying its source package, in this case, kableExtra. There are a lot of
other options in kable_styling that we will use in later chapters as needed.
• For your own use, you could shorten all of this to just kable (b). On the other
hand, kableExtra has one more small trick up its sleeve. It provides a shorter
name for the kable function of just kbl. When space is at premium, this
shortcut is handy. It also serves as a helpful check to ensure that kableExtra
is loaded.
c<-a*b
Now let’s do a transpose operation on the original matrix. What this means
that it converts rows into columns and columns into rows.
d<-t(b)
Row1 Row2
Col1 1 2
Col2 3 4
Col3 5 6
Col4 7 8
Note:
Row and column names were changed at the same time.
Notice that row and column names are also transposed. The result is that we
now have rows labeled as columns and columns labeled as rows! This is only
a problem given that we used the words rows and columns in the names. If
these had been more descriptive such as weeks and product names, it would
have been a good thing to change them at the same time.
Now we have done some basic operations within R.
We could try this, but we aren’t quite there yet.
## Row1 Row2
## Col1 1 2
## Col2 3 4
## Col3 5 6
## Col4 7 8
Try renaming the rows and columns for d. As a hint, you could use dimnames
or colnames and rownames.
A.2 Getting Started with R 231
Let’s look at the original matrix, b and what we can do with it. Let’s grab
the second row, third column and last element.
Col1 2
Col2 4
Col3 6
Col4 8
The as.matrix function is used to convert the object into a matrix so that
kable can display it well.
Row1 5
Row2 6
Grabbing the third column is not terribly interesting but operations such as
this will often be quite useful.
Let’s take the first row of b and combine it with the second row of c to form
232 A A Very Brief Introduction to R
a new matrix, e. Since these are rows that are going to be combined, we will
use a command, rbind, to bind these rows together.
Notice that temp4 has inherited the column names but lost the row names.
Let’s set the row names for this matrix.
We could combine all of matrix b and matrix c together using row binding or
column binding. Table Let’s view the results of binding using columns (cbind)
and rows (rbind).
temp5<- cbind(b,c)
Data organizing is a less glamorous part of the job for practicing analytics
professionals but can consume a majority of the workday. There is a lot more
that can be said about data wrestling but scripting the data cleansing in terms
R commands will make the work more repeatable, consistent, and in the end
save time.
A.3 Exercises
Exercise A.1 (Creating a Matrix of Daily Demand for Four Weeks). As-
sume that your company’s product has a demand of 10 widgets on Monday,
increasing by five units for every day through Sunday. Build a matrix of four
weeks of demand where each row is a separate week. Each Monday starts over
with the same demand. The rows should be named Week1, Week2, etc. The
columns should have names corresponding to the day of the week.
Exercise A.2 (Creating a Matrix of Demand for Two Products). Assume that
your company’s product has a demand of 10 widgets on Monday, increasing
by five units for every day through Sunday. Gadgets have a demand of 20 on
Monday, increasing by 3 units a day through Sunday. Build a matrix showing
each product as a separate row. Rows should have names for the products and
columns for the days of the week.
Exercise A.3 (Creating a Matrix of Monthly Demand). Consider some top
selling online products for the year 2021: toys, shoes, pens and pencils, deco-
rative bottles, drills, cutters, and GPS navigation systems. Create your own
assumed starting counts per item for throughout year by increasing each item
sales by 50 units per month, starting from January till December.
Exercise A.4 (Displaying Selective Data from the Matrix). In the above
matrix, grab the columns and rows to show the data only for pens and pencils
during the months of June, August and October.
234 A A Very Brief Introduction to R
Exercise A.5 (Creating Separate Demands for Products). In the above ma-
trix, demand for toys and shoes has been increased by 20 and 10 units, respec-
tively. Create a matrix for only these products for the first 6 months. Display
the matrix twice–once without formatting and a second time using an R pack-
age that provides better table formatting (pander, kable via knitr, huxtable
or similar tool. Be sure to provide a table caption.)
B
Introduction to Math Notation
B.1 Purpose
∑
𝑥
The starting value of the variable may be given below ∑ as well. In this case,
the summation notation below tells us that the initial value of 𝑥 will be equal
to 1.
∑
𝑥=1
In some cases, we may also wish to designate an ending value of the variable,
which we can include above the ∑. The summation notation below also tells
us that the final value of 𝑥 will be equal to 5.
5
∑
𝑥=1
In all dealings with summation notation, variables will only take integer values,
beginning and ending at any values provided within the summation notation.
Note that some summations may use an “ending” value of ∞, which would
involve the summation of an infinite number of values.
Let’s look at a basic summation problem.
5
∑ 2𝑥
𝑥=1
The summation above means that we will take the 𝑥 values, starting at 𝑥 = 1,
and multiply the value of 𝑥 by 2. We will continue to do this for each integer
value of 𝑥 until we have reached our ending value of 5. Then we will sum all
of our results (five of them, in this case) to produce one final value for the
summation.
5
∑ 2𝑥 = 2 ⋅ 1 + 2 ⋅ 2 + 2 ⋅ 3 + 2 ⋅ 4 + 2 ⋅ 5
𝑥=1
= 2 + 4 + 6 + 8 + 10
= 30
entered in the middle with regular text. Entering a regular summation in the
paragraph causes problems.
Let’s try the opposite now by showing what an in-line summation looks like
when entered on its own line.
3
∑𝑥=0 𝑥2 − 4𝑥 + 1
Again, in this case, the summation is not quite right. Given that vertical space
is no longer at a premium, it is just harder to read than the regular summation.
The simple rule of thumb in Rmarkdown is to use a double-dollar sign for
equations that are entered on their own line and a single dollar sign for symbols
that are entered as text.
238 B Introduction to Math Notation
B.5 Sums
To display the ∑ symbol in LaTex, use the command \sum_{lower}^{upper}.
The lower and upper limits of summation after the \sum are both optional.
The upper and sometimes the lower are omitted when the interpretation
would be unambiguous. The summation expression can be added using the
command:
𝑢𝑝𝑝𝑒𝑟
∑
𝑙𝑜𝑤𝑒𝑟
Sum limits can be written to appear above and below the operator using the
same notation as for superscript and subscript.
𝑛
𝐶𝐹𝑡
∑
𝑡=0
(1 + 𝑟)𝑡
B.6 Delimiters
For illustration purposes, let’s at the code for the right-hand side.
\left( \frac{n(n-1)}{2}\right)^2 = \frac{n^2(n-1)^2}{4}
If we had not used the \left and \right the original equation would have
looked like this.
𝑛
𝑛(𝑛 − 1) 2 𝑛2 (𝑛 − 1)2
(∑ 𝑖)2 = ( ) =
𝑖=1
2 4
B.7 Summary of Mathematical Notations 239
Below are some common mathematical functions that are often used.
Of course there are many more mathematical terms, Greek letters, and more
but this table can serve as a common quick reference for things that may come
up in frequently in optimization modeling.
240 B Introduction to Math Notation
While the * is used for multiplication in R, it both takes more space and is
not considered formal mathematical notation. In LaTeX you should instead
use \\cdot in place of the asterisk. In other words, ⋅ in place of ∗. The result
is the following:
5
∑ (10 − 𝑖) 𝑥𝑖 = 9 ⋅ 𝑥1 + 8 ⋅ 𝑥2 + 7 ⋅ 𝑥3 + 6 ⋅ 𝑥4 + 5 ⋅ 𝑥5
𝑖=1
Product 1
Design 11
Materials 12
Production 13
Packaging 14
Distribution 15
For example, let’s imagine that the itemized cost for the production of a
product from start to finish is that, which is given in the table below.
# Load library
library(kableExtra,quietly = TRUE)
We might wish to determine the total cost to produce the product from start
to finish. We can extract the data from our cost matrix and use summation
to find the total cost.
5
∑ 𝑥𝑖
𝑖=1
= 𝑥 1 + 𝑥2 + 𝑥3 + 𝑥4 + 𝑥5
= 11 + 12 + 13 + 14 + 15
= 65
”Packaging”, ”Distribution”)
colnames(M2)<-list(”Product 1”, ”Product 2”, ”Product 3”)
kbl(M2, booktabs=T, caption=
”Itemized Production Costs for Three Products”)
We might wish to determine the cost to produce each of the three products
from start to finish. We could show this with the following summation nota-
tion.
5
∑ 𝑥𝑖,𝑗 ∀ 𝑗
𝑖=1
This notation indicates that we are summing the cost values in the 𝑖 rows for
each product in column 𝑗. Note that the symbol ∀ shown in the summation
above translates to the phrase “for all.” The summation expression above can
be interpreted as “the sum of all values of 𝑥𝑖,𝑗 , starting with an initial value
of 𝑖 = 1, 𝑓𝑜𝑟 𝑎𝑙𝑙 values of 𝑗.” The expression will result in 𝑗 summations.
5
∑ 𝑥𝑖,𝑗 ∀ 𝑗
𝑖=1
= 𝑥1,1 + 𝑥2,1 + 𝑥3,1 + 𝑥4,1 + 𝑥5,1
= 11 + 12 + 13 + 14 + 15
= 65 Cost for Product 1
𝐴𝑁 𝐷
B.10 Double Summation 243
𝐴𝑁 𝐷
We can see that the summation expression resulted in three summation values
since 𝑗, can take on values of 1, 2, or 3. These summation values are 65, 115,
and 165, representing the total cost from start to finish to produce Product
1, Product 2, and Product 3, respectively.
For some projects or models, we may need to add one summation into another.
This procedure is called “double summation.” Consider the following double
summation expression: \sum_{i=1}^3\sum_{j=1}^4 (i+j)
3 4
∑ ∑(𝑖 + 𝑗)
𝑖=1 𝑗=1
Note that the expression contains two ∑ symbols, indicating a double sum-
mation. The double summation would expand as shown below.
3 4
∑ ∑(𝑖 + 𝑗)
𝑖=1 𝑗=1
= (1 + 1) + (2 + 1) + (3 + 1)
+ (1 + 2) + (2 + 2) + (3 + 2)
+ (1 + 3) + (2 + 3) + (3 + 3)
+ (1 + 4) + (2 + 4) + (3 + 4)
244 B Introduction to Math Notation
B.12 Exercises
Exercise B.1 (Expand-Summation1). Write all terms and calculate the sum-
mation for the following:
4
𝐴. ∑𝑥 + 3
𝑥=1
5
𝐵. ∑ 8𝑥 − 1
𝑥=0
5 2
𝐵. ∑ ∑(3 ⋅ 𝑖 − 𝑗)
𝑖=1 𝑗=1
C.1 Overview
The goal of this chapter is not to cover all errors that might arise while using
R or RMarkdown. The goal of this Appendix is to demonstrate and discuss
some common errors that arise in building optimization models using R. The
error messages may not always be clear and here we provide a review. It is
good practice to carefully look over the error message when you get one to see
if you can find a clue as to where the problem might be.
All of these problems have stumped me or my students at some point. Whether
the problems arise while doing optimization with ompr, creating tables to dis-
play these results, expressing the mathematical model using LaTeX, or creat-
ing the PDF for disseminating results, all of these errors can occur in other
situations as well.
This appendix uses a mix of both real, working code along with images of
non-working code and the errors they generate. Images are used so that this
writeup can itself be properly knitted without errors.
One of the most common errors is to jump straight into coding a model into
ompr before really understanding the model. This is the equivalent of trying to
build a house without a plan. There will be a lot of wasted time and effort as
well as making things difficult for anyone to assist. Algebraic linear program-
ming models are generally only a few lines long and will often look similar to
related models. Changes at this point are also very easy to make. If the model
is good – then it is a matter of just making sure that the implementation
matches the plan (formulation.)
Software engineers have a similar perspective that the cost of bug fixes goes up
by a factor of 10 at every stage of development from specification development,
to prototyping, to coding, to end user testing.
As an example, new modelers often get confused between which items are
pieces of data to be fed into the model and which things are decision variables.
Frequently, I will see people that jump too quickly to implementation going
back and forth often using the same item as both data and as an optimization
variable. Without this being clear, no implementation regardless of how clever,
can succeed.
Just because a PDF is knitted or rendered does not mean that it is correct. A
variety of common errors often creep in such as unrendered section headings
from the RMarkdown file. Consider the following text from an RMarkdown
document.
end of sentence.
##Section Heading
Beginning of next sentence.
This fragment has three problems, each of which may cause the author’s in-
tended 2nd level heading to not be recognized as a heading.
None of these will cause an error message to appear and they can easily occur
by accident while editing a document, so it is just good practice to look over
the PDF for this or similar issues.
C.2 Model Building 249
Put simply, blank lines are not allowed in LaTeX equations but this problem
can be one of the most puzzling ones that readers have seen. The problem
is that everything looks right and knits to HTML, but it doesn’t knit to
PDF. Part of the confusion is caused by the LaTeX processor being different
from RStudio’s Math rendering system, which is used for previewing in an
RMarkdown document. The result is that something may be visible in the
preview and look correct but not render in the PDF because of differing
strictness in the implementation of parsing of the math notation.
Again, the error message may not be obvious but googling would often find a
hint as to the source of the error.
If you had really intended to add a blank line before the non-negativity con-
straints, simply add the LaTeX linebreak code of a double slash, \\.
250 C Troubleshooting
Max: 20 ⋅ 𝐴 + 14 ⋅ 𝐵
s.t.:
6 ⋅ 𝐴 + 2 ⋅ 𝐵 ≤ 200
𝐴, 𝐵 ≥ 0
Note that LaTeX may return a lot of warnings that do not represent prob-
lems but are provided out of an abundance of caution. For example, see the
following:
LaTeX Warning: Command \textellipsis invalid in math mode on input line
1324.
The line number refer to the intermediate .tex file rather than the original
.Rmd file. When LaTeX fails, it is sometimes necessary to look at both the
.log and .tex files to see where the error is occurring.
Knitting to PDF uses the LaTeX environment so a variety of other issues may
arise. While sometimes these are spotted late in a project, the source is often
at early stage of work so I am including it in the Model Building section.
C.2 Model Building 251
library(rmarkdown)
rmarkdown::render(”02-A_First_LP.Rmd”, ”pdf_document”)
# Renders as a regular PDF document
Another possible problem is having the PDF file open from having previously
created it. This will typically generate a message about being unable to write
the PDF file. The solution is to simply ensure that the PDF is not open in
any other program.
A related issue is that there could be a temporary file for debugging purposes
that needs to be deleted before it can be done. I often see this with a Markdown
file (.md), not to be confused with the RMarkdown file (.Rmd).
Avoid using an underscore, _, in the code chunk label for any code chunk
that involves a kable or graphical figure. This can cause strange errors when
knitting to PDF. While the underscore is valid for a code chunk name, its
importance in LaTeX causes problems – it is best to just a dash.
Some PDF errors are caused by the strict enforcement of LaTeX requirements.
If the .tex file is generated but the .pdf is incomplete or missing, you can
sometimes overcome these problems by using latexmk to rerun the Tex engine
on the PDF. This is not meant to be a cure-all and is only meant for late stage
errors. For example, this book sometimes requires running latexmk OMUR.tex
-pdf -f -interaction=nonstopmode after building the pdf_book generates a
LaTeX error at the bibliography stage. Of course tracking down the LaTeX
error is preferred.
If there is a code chunk that is causing fatal errors, you can always use the
code chunk option of eval=FALSE in order to temporarily turn off the execution
while working on other parts of the PDF and model.
252 C Troubleshooting
Unpiping a piped model is easy. The first step is to remove all of the pipe
operators %>% or |>. Second, every ompr command after the model is initialized
needs to be modified to name the model that is being enhanced. Lastly,
every line needs to say to what the enhanced model is being assigned. The
following figure is an unpiped version of the earlier code chunk that has the
same error as the earlier screen capture, but it is now much easier to find the
error as the reader only has to look at one line of code – the line to the right
of the red vertical bar.
In general, since the model name or variant may occur twice in every line of
an unpiped ompr model, keeping a short model name such as m0 instead of
model0 would help keep the code easier to read.
Let’s now review the error that was generated in the previous section. This
is a common ompr error. Of course an object must exist before it can be used
in R. It might be confusing or difficult to identify the error though.
A student told me that they spent hours trying to identify the error in the
following code chunk. Using a piped object might misattribute the line or the
source error to the beginning of the piped command. If you see this error,
read the previous section on dealing with piped objects.
The message suggests that perhaps the user forgot to create a variable using
add_variable that was used in the objective function or a constraint. Another
possibility is that the user has a problem with a data object used in the ompr
model. The result is that users may still have difficulty finding the source of
the problem.
The console reflects what I did to help in debugging. These are the steps that
I followed:
In this case, I then scrolled up and confirmed that the student had defined
NSupply, NDest, and all the other data items used in the linear program except
C.3 Implementation Troubleshooting 255
for Cost. This will also help capture an inconsistent spelling such as Cost
vs. cost or Costs.
Another problem for a student occurred with in the following code chunk of
an ompr linear programming model generated an ’Error: unexpected symbol
in:‘ message.
256 C Troubleshooting
It is an important feature of ompr that it has access to all of the data objects
that you have defined. This enables rich models to be built without specifically
passing each piece of data into a function. This has a critical requirement that
you should avoid using the same name for something as an ompr variable and
an R object. This can happen when you want to have a LP variable be used
for the same purpose in the rest of our R code or perhaps because the variable
name is used elsewhere in your work for other purposes. Simple variable names
such as x or y are particularly likely to find multiple uses and therefore conflicts.
Hint: Sometimes it may be helpful to clear your environment to avoid other
conflicts. This may help resolve some other inscrutable errors.
This example illustrates what happens when you have an object in your general
R environment with the same name as an ompr model variable.
Here is a very simple linear program formulated and solved using ompr. Again,
it uses the old piping operator instead of the new |> operator.
result1
## Status: optimal
## Objective value: 1400
Now, let’s redo this with an identical LP, but renaming the variables from A
and B to C and D. To trigger this problem, we will define C to be a matrix.
Since this LP is identical to the previous one other than changing names, it
should work. Alas, we get an error message.
Notice that the error message may not actually refer to the variables causing
the problem. In our example, variable C is causing the problem, but it refers
to x. This is because the error is actually occurring deeper in the ompr code
which has already transformed the actual variables into a more abstract
notation.
258 C Troubleshooting
If you run all chunks, you may get the following error message. It doesn’t
make clear which variable is generating the error but does list the ompr
variables so that you can perhaps narrow them down.
C.3 Implementation Troubleshooting 259
Since the problem is having the same name for objects inside and outside of
ompr an an easy solution is to adopt a convention that differentiates ompr
variables. I have adopted the convention of prefixing all ompr variables with a
capital V to suggest that it is a mathematical programming variable. Readers
can then easily differentiate which items in constraints or the objective
function are variables (specific to ompr) and which are sources of data from
outside of ompr. The resulting model is shown below. When expressing the
model mathematically for readers, I can then omit the V prefix.
You will often find examples of code that is helpful. It is important to try to
read what the code is doing before using it. For example, a reader tried using
my TRA package for drawing DEA input-output diagrams and used a code
example that I had. They did not realize that it was writing to a subdirectory
named images that existed in my project but not theirs.
This is far from a comprehensive list but these items may be helpful for general
purpose debugging.
• Read through the error message to try to decode where the problem is. For
example, it tells you pretty clearly if there are two code chunks with the
same name.
• Check for spelling, capitalization, or singular/plural errors in variable names.
The human reader’s eye tends to be more forgiving of inconsistencies than
computers.
• Try stepping through the code by evaluating one code chunk (or even line)
at a time to narrow down where an error may be occurring.
• Use the console to examine run code and display variable values directly.
• Do a web search for the error message, leaving out items specific to your
code such as variables or code chunk names.
• Narrow down where to look for problems by line numbers or code chunk
names.
• Check your parentheses carefully – it is easy to get a parenthesis misplaced.
• If you are getting a LaTeX error, look at the TeX log file. You can scroll
down to the very end to see where things went wrong. This message may
give more clues to where the error arose.
• See if you can create a “minimal” example that reproduces the same problem
in a new file. This can be very helpful for others to see where your problem is
and not read through many lines of working code. As you trim it down in a
second file, you might also have a Eureka moment where you see it yourself.
• If you have a complex statement that does three or four things in one line,
try working from inside out by running parts of the command in the console
to make sure that they each generate the results that you intend.
• Inspect the data that you are using. Perhaps the data is not of the right
form.
• Caching analysis results in RStudio can be helpful for advanced users with
C.5 Getting Help 261
After going through the above ideas, you may still be stuck. We’ve all been
there before in working with computers. Given the large number of people
using R, it is likely that many other people have had the same problem
before so a good search will probably find a discussion of the problem and
resolution. I often use google to search for the generalized text snippets of
error messages, leaving out things unique to my case such as a variable name.
Sometimes add in the words “R”, “package” or the specific package name
used as appropriate. Using the search qualifiers of + or - can be very helpful
in finding specific items. For example a search of boxplot +r -python will find
pages that discuss boxplots and R but exclude any page that mentions python.
After going through these efforts, you may still not find a solution. The R
community is a great resource for getting answers to problems but there is a
common practice that is important to emphasize. When you ask for help, you
are typically requesting a person that you don’t know to review your work
for free.
Another tip for making the reproducible example easier to use simplified
variable names. Again, the reader doesn’t really care about your specific
model but this makes it shorter and easier to read. Of course, this might
cause (or fix?) name conflicts or collisions.
262 C Troubleshooting
Including the code in a way that can be easily run is often helpful. If you are
using rstudio.cloud, you may even create a simple project that someone can
jump into for debugging purposes. I’ve done this with students, but it would
not work well for posting a link into an open Internet forum.
If you have posted the question or emailed someone and there were suggestions
given, a response is usually appreciated or in a forum post, may be helpful for
a future person that has the same problem.
D
Making Good Tables
• Ease of use
• Widely used
• Well documented, including use with bookdown
• Straightforward table resizing
There are more options in kable and kableExtra than we are covering here
but this covers everything that was used in the book.
We will use kbl throughout this Appendix and book. If you are getting an
error that kbl function is not recognized, you will need to load the kableExtra
package.
C1 C2
1 3
2 4
Note:
General comments about the table.
1
Footnote 1;
2
Footnote 2;
a
Footnote A;
b
Footnote B;
*
Footnote Symbol 1;
†
Footnote Symbol 2
The following code chunk seems like it should work but row.names generates
an error because it wants a TRUE or FALSE (or their shorthand equivalents of T
and F) rather than the row names. The code chunk was set to eval=FALSE in
order to avoid causing an error. The lesson is that row names are not always
handled in R in the same way as column names or as even consistently in
different settings.
kable (m,
col.names=c(”C1”, ”C2”),
row.names=c(”R1”, ”R2”))
# Works for col.names but not row.names
# The following demonstrates use of row.names
Since col.names works well, there is no reason to spend more time on it. Let’s
instead show how to replace row names for a table. The following example gives
bad row names to a matrix that I want to replace and use with an better set
of row names. It then generates four tables showing these situations. The last
two options give good results.
m <- matrix(1:4, 2, 2)
C1 C2
Bad row name 1 1 3
Bad row name 2 2 4
D.4 Setting Row and Column Names in Kable 267
C1 C2
1 3
2 4
C1 C2
Good row name 1 1 3
Good row name 2 2 4
rownames(m)<-GR
kbl (m, booktabs=T,
caption=”Replacing row names in matrix before kable”,
col.names=c(”C1”, ”C2”), row.names=T) |>
kable_styling (latex_options = ”hold_position”)
C1 C2
Good row name 1 1 3
Good row name 2 2 4
placement near the code chunk. If ”hold_position” is not passed, LaTeX will
float the table to what it considers a good place relative to the rest of the text
that might be well away from the actual intent.
To summarize:
kbl (cbind(as.matrix(GR),m),
caption=”Default format using kable.”,
col.names=c(””, ”C1”, ”C2”),
row.names=F) |>
kable_styling(latex_options = ”hold_position”)
C1 C2
Good row name 1 1 3
Good row name 2 2 4
C1 C2
Good row name 1 1 3
Good row name 2 2 4
m <- matrix(1:4, 2, 2)
GR <- c(”$\\phi$”, ”$\\omega$”)
𝜃𝐶𝑅𝑆 𝜆𝐴
𝜙 1 3
𝜔 2 4
270 D Making Good Tables
Large tables can be problematic. Kable can scale a table to fit the page width
easily using the scale_down option in kableExtra via the kable_styling and
latex_options.
NWeeks <- 24
mbig <- matrix(1:(2*NWeeks), ncol= NWeeks, byrow=TRUE,)
rownames (mbig) <- c(”Widgets”, ”Gadgets”)
kbl(mbig,booktabs=T,
caption=”Scaling Table to Fit Page”)|>
kable_styling(latex_options =
c(”hold_position”, ”scale_down”))
Widgets 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
Gadgets 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
On the other hand, sometimes this is a sign that the display of this information
needs to be rethought: - transposing the table to be taller rather than wider,
- not displaying certain columns or rows with less information value, - using a
figure of some form rather than a table, - turning the table (or page) sideways,
- sticking the table into an appendix or as a file for download, or - providing
a summary table of means, standard deviations, or other information.
Bibliography
271
Index
273
274 Index