AHP is a method allowing individuals or groups to make complex decisions. The core concept of AHP is that alternatives are always compared pairwise (and not, say, by giving a score, or sorting alternatives). AHP is used in many fields, from finance to criminal investigation.
For a general introduction to AHP, you might be interested in this.
Another good introduction is here
For a more formal introduction to the subject, see: Saaty, T. L. (2001). Decision Making for Leaders: The Analytic Hierarchy Process for Decisions in a Complex World, New Edition 2001 (3 Revised). Pittsburgh, PA: RWS Publications.
This package is built on top of the data.tree package, and uses R6 reference classes. To learn more about these, you might be interested in the Introduction:
library(R6)
vignette('Introduction', package = 'R6')
Even though this is not required, the data.tree Introduction vignette might also be helpful:
library(data.tree)
vignette(package = 'data.tree')
This vignette follows a well-known AHP example that you can also find e.g. on Wikipedia. In this example, the Jones family wants to buy a new car and uses AHP to decide systematically which car they should buy. Even though a few details are different from the original article, it is recommended to consider this vignette as an extension of that article, and not as a self sufficient document.
When deciding which car to buy, the family considers the following criteria: cost, safety, style, and capacity. Additionally, some of the criteria have sub-criteria.
The Jones’ have a shortlist of 6 cars.
We can draw the hierarchy like this:
Using the ahp package, the AHP hierarchy tree can be defined in an ahp file. An ahp file is a text file in yaml format, following a few conventions. For example:
## #########################
## # Alternatives Section
## #
##
## Alternatives: &alternatives
## # Here, we list all the alternatives, together with their attributes.
## # We can use these attributes later in the file when defining
## # preferenceFunctions. The attributes can be quantitative or
## # qualitative.
## Accord Sedan:
## price: 20360
## mpg: 31
## passengers: 5
## cargo: 14
## curb weight: 3289
## safety class: Midsize Car
## crash rating: 4 in Side Impact Front
## residual value: 0.52
## Accord Hybrid:
## price: 31090
## mpg: 35
## passengers: 5
## cargo: 14
## curb weight: 3501
## safety class: Midsize Car
## crash rating: 4 in Side Impact Front
## residual value: 0.46
## Pilot:
## price: 27595
## mpg: 22
## passengers: 8
## cargo: 87.6
## curb weight: 4264
## safety class: Midsize SUV
## crash rating: 4 in Rollover
## residual value: 0.4
## CR-V:
## price: 20700
## mpg: 27
## passengers: 5
## cargo: 72.9
## curb weight: 3389
## safety class: Small SUV
## crash rating: 4 in Rollover
## residual value: 0.55
## Element:
## price: 18980
## mpg: 25
## passengers: 4
## cargo: 74.6
## curb weight: 3433
## safety class: Small SUV
## crash rating: 3 in Rollover
## residual value: 0.48
## Odyssey:
## price: 25645
## mpg: 26
## passengers: 8
## cargo: 147.4
## curb weight: 4385
## safety class: Minivan
## crash rating: All 5 Stars
## residual value: 0.48
##
## #
## # End of Alternatives Section
## #####################################
##
## #####################################
## # Goal Section
## #
##
##
## Goal:
## # The goal spans a tree of criteria and the alternatives
## name: Buy Car
## preferences:
## # preferences are defined pairwise
## # 1 means: A is equal to B
## # 9 means: A is highly preferrable to B
## # 1/9 means: B is highly preferrable to A
## - [Cost, Safety, 3]
## - [Cost, Style, 7]
## - [Cost, Capacity, 3]
## - [Safety, Style, 9]
## - [Safety, Capacity, 1]
## - [Style, Capacity, 1/7]
## children:
## Cost:
## preferences:
## - [Purchase Price, Fuel Cost, 2]
## - [Purchase Price, Maintenance Cost, 5]
## - [Purchase Price, Resale Value, 3]
## - [Fuel Cost, Maintenance Cost, 2]
## - [Fuel Cost, Resale Value, 2]
## - [Maintenance Cost, Resale Value, 1/2]
## children:
## Purchase Price:
## preferences:
## - [Accord Sedan, Accord Hybrid, 9]
## - [Accord Sedan, Pilot, 9]
## - [Accord Sedan, CR-V, 1]
## - [Accord Sedan, Element, 1/2]
## - [Accord Sedan, Odyssey, 5]
## - [Accord Hybrid, Pilot, 1]
## - [Accord Hybrid, CR-V, 1/9]
## - [Accord Hybrid, Element, 1/9]
## - [Accord Hybrid, Odyssey, 1/7]
## - [Pilot, CR-V, 1/9]
## - [Pilot, Element, 1/9]
## - [Pilot, Odyssey, 1/7]
## - [CR-V, Element, 1/2]
## - [CR-V, Odyssey, 5]
## - [Element, Odyssey, 6]
## children: *alternatives
## # We don't need to retype the alternatives here. Instead
## # we can simply make a reference to the alternatives anchor
## # defined in the alternatives section of the file.
## Fuel Cost:
## # Alternatively to the pairwise preferences, you
## # can define a preference function. This function
## # is in R syntax, and needs to have two arguments.
## # The Calculate method will pass all combinations
## # of alternatives to this function, and the function
## # is expected to return the pairwise preference, i.e.
## # a number between 1/9 and 9.
## preferenceFunction:
## function(a1, a2) min(9, max(1/9, a1$mpg/a2$mpg))
## children: *alternatives
## Maintenance Cost:
## preferences:
## - [Accord Sedan, Accord Hybrid, 1.5]
## - [Accord Sedan, Pilot, 4]
## - [Accord Sedan, CR-V, 4]
## - [Accord Sedan, Element, 4]
## - [Accord Sedan, Odyssey, 5]
## - [Accord Hybrid, Pilot, 4]
## - [Accord Hybrid, CR-V, 4]
## - [Accord Hybrid, Element, 4]
## - [Accord Hybrid, Odyssey, 5]
## - [Pilot, CR-V, 1]
## - [Pilot, Element, 1.2]
## - [Pilot, Odyssey, 1]
## - [CR-V, Element, 1]
## - [CR-V, Odyssey, 3]
## - [Element, Odyssey, 2]
## children: *alternatives
## Resale Value:
## preferenceFunction: >
## GetResalePreference <- function(a1, a2) {
## if (a1$`residual value` < a2$`residual value`) return (1/GetResalePreference(a2, a1))
## ratio <- a1$`residual value` / a2$`residual value`
## if (ratio < 1.05) return (1)
## if (ratio < 1.1) return (2)
## if (ratio < 1.15) return (3)
## if (ratio < 1.2) return (4)
## if (ratio < 1.25) return (5)
## return (5)
## }
## children: *alternatives
## Safety:
## preferences:
## - [Accord Sedan, Accord Hybrid, 1]
## - [Accord Sedan, Pilot, 5]
## - [Accord Sedan, CR-V, 7]
## - [Accord Sedan, Element, 9]
## - [Accord Sedan, Odyssey, 1/3]
## - [Accord Hybrid, Pilot, 5]
## - [Accord Hybrid, CR-V, 7]
## - [Accord Hybrid, Element, 9]
## - [Accord Hybrid, Odyssey, 1/3]
## - [Pilot, CR-V, 2]
## - [Pilot, Element, 9]
## - [Pilot, Odyssey, 1/8]
## - [CR-V, Element, 2]
## - [CR-V, Odyssey, 1/8]
## - [Element, Odyssey, 1/9]
## children: *alternatives
## Style:
## preferences:
## - [Accord Sedan, Accord Hybrid, 1]
## - [Accord Sedan, Pilot, 7]
## - [Accord Sedan, CR-V, 5]
## - [Accord Sedan, Element, 9]
## - [Accord Sedan, Odyssey, 6]
## - [Accord Hybrid, Pilot, 7]
## - [Accord Hybrid, CR-V, 5]
## - [Accord Hybrid, Element, 9]
## - [Accord Hybrid, Odyssey, 6]
## - [Pilot, CR-V, 1/6]
## - [Pilot, Element, 3]
## - [Pilot, Odyssey, 1/3]
## - [CR-V, Element, 7]
## - [CR-V, Odyssey, 5]
## - [Element, Odyssey, 1/5]
## children: *alternatives
## Capacity:
## preferences:
## - [Cargo Capacity, Passenger Capacity, 1/5]
## children:
## Cargo Capacity:
## preferenceFunction: >
## CargoPreference <- function(a1, a2) {
## if (a1$cargo < a2$cargo) return (1/CargoPreference(a2, a1))
## ratio <- a1$cargo / a2$cargo
## if (ratio < 3) return (1)
## if (ratio < 8) return (2)
## return (3)
## }
## children: *alternatives
## Passenger Capacity:
## preferences:
## - [Accord Sedan, Accord Hybrid, 1]
## - [Accord Sedan, Pilot, 1/2]
## - [Accord Sedan, CR-V, 1]
## - [Accord Sedan, Element, 3]
## - [Accord Sedan, Odyssey, 1/2]
## - [Accord Hybrid, Pilot, 1/2]
## - [Accord Hybrid, CR-V, 1]
## - [Accord Hybrid, Element, 3]
## - [Accord Hybrid, Odyssey, 1/2]
## - [Pilot, CR-V, 2]
## - [Pilot, Element, 6]
## - [Pilot, Odyssey, 1]
## - [CR-V, Element, 3]
## - [CR-V, Odyssey, 1/2]
## - [Element, Odyssey, 1/6]
## children: *alternatives
##
## #
## # End of Goal Section
## #####################################
Let’s look at the various elements of this file.
The first section is a list of alternatives. Note that they have properties. For example:
Alternatives: &alternatives
Accord Sedan:
price: 20360
mpg: 31
passengers: 5
cargo: 14
curb weight: 3289
safety class: Midsize Car
crash rating: 4 in Side Impact Front
residual value: 0.52
Accord Hybrid:
price: 31090
mpg: 35
passengers: 5
...
You are entirely free regarding the type and format of information you store in your alternatives. Some models can do without any properties at all. However, often you would like to define your preferences as a function of properties. Later, you will see how you can define functions that refer to these properties to derive the pairwise preferences of alternatives with respect to a specific criteria.
The second section of the ahp file is the Goal. Here, you define your criteria tree, your pairwise preferences, and - if applicable - your preference functions.
Criteria are grouped hierarchically. Each criteria has at least the following sections:
An example of a criteria which has explicit pairwise preference is the
Cost:
preferences:
- [Purchase Price, Fuel Cost, 2]
- [Purchase Price, Maintenance Cost, 5]
- [Purchase Price, Resale Value, 3]
- [Fuel Cost, Maintenance Cost, 2]
- [Fuel Cost, Resale Value, 2]
- [Maintenance Cost, Resale Value, 1/2]
children:
Purchase Price:
preferences:
...
Here, we say, for instance, that Purchase Price is slightly more important to Fuel Cost. Purchase Price is, however, much more important than maintenance cost.
To keep the ahp file short, you can reference to the alternatives defined above. In order for this to work, your alternatives need to be defined as an anchor, using the & sign:
Alternatives: &alternatives
Accord Sedan:
price: 20360
...
Note that this is a standard YAML feature.
You can then later refer to the anchor using the * reference feature:
Style:
preferences:
- [Accord Sedan, Accord Hybrid, 1]
- [Accord Sedan, Pilot, 7]
- [Accord Sedan, CR-V, 5]
- [Accord Sedan, Element, 9]
- [Accord Sedan, Odyssey, 6]
- [Accord Hybrid, Pilot, 7]
- [Accord Hybrid, CR-V, 5]
- [Accord Hybrid, Element, 9]
- [Accord Hybrid, Odyssey, 6]
- [Pilot, CR-V, 1/6]
- [Pilot, Element, 3]
- [Pilot, Odyssey, 1/3]
- [CR-V, Element, 7]
- [CR-V, Odyssey, 5]
- [Element, Odyssey, 1/5]
children: *alternatives
Instead of defining the preferences explicitly in a pairwise manner, you can define a preference function. For example, you can define
Fuel Cost:
preferenceFunction:
function(a1, a2) min(9, max(1/9, a1$mpg/a2$mpg))
children: *alternatives
...
Note that, in this case, we have a field preferenceFunction instead of preferences. The function itself is a standard R function, and the arguments a1 and a2 are pairwise combinations of the criteria, i.e. in this case, the alternatives.
Here, we simply say that the AHP preference is the ratio of the mpg (i.e. miles per gallon) a car uses. And since we store properties mpg on the alternatives, we can reference them directly here. We want the ratio to be between 1/9 and 9, as these are the typical AHP acceptable preferences.
You can even define a named preference function that you can use for recursion:
Resale Value:
preferenceFunction: >
GetResalePreference <- function(a1, a2) {
if (a1$`residual value` < a2$`residual value`) return (1/GetResalePreference(a2, a1))
ratio <- a1$`residual value` / a2$`residual value`
if (ratio < 1.05) return (1)
if (ratio < 1.1) return (2)
if (ratio < 1.15) return (3)
if (ratio < 1.2) return (4)
if (ratio < 1.25) return (5)
return (5)
}
children: *alternatives
Loading the file is done by calling LoadFile:
library(ahp)
ahpFile <- system.file("extdata", "car.ahp", package="ahp")
carAhp <- LoadFile(ahpFile)
carAhp, the return value of LoadFile, is a data.tree structure. Its structure is the same as the one of the file. If you are interested in solving AHP problems programmatically, all you need to do is to create a tree with that structure, using the data.tree functions.
library(data.tree)
print(carAhp, filterFun = isNotLeaf)
## levelName
## 1 Buy Car
## 2 ¦--Cost
## 3 ¦ ¦--Purchase Price
## 4 ¦ ¦--Fuel Cost
## 5 ¦ ¦--Maintenance Cost
## 6 ¦ °--Resale Value
## 7 ¦--Safety
## 8 ¦--Style
## 9 °--Capacity
## 10 ¦--Cargo Capacity
## 11 °--Passenger Capacity
Calculate(carAhp)
print(carAhp, "weight")
## levelName weight
## 1 Buy Car 1.00000000
## 2 ¦--Cost 0.51007502
## 3 ¦ ¦--Purchase Price 0.48805379
## 4 ¦ ¦ ¦--Accord Sedan 0.24617542
## 5 ¦ ¦ ¦--Accord Hybrid 0.02455426
## 6 ¦ ¦ ¦--Pilot 0.02455426
## 7 ¦ ¦ ¦--CR-V 0.24617542
## 8 ¦ ¦ ¦--Element 0.36575063
## 9 ¦ ¦ °--Odyssey 0.09279001
## 10 ¦ ¦--Fuel Cost 0.25153610
## 11 ¦ ¦ ¦--Accord Sedan 0.18674699
## 12 ¦ ¦ ¦--Accord Hybrid 0.21084337
## 13 ¦ ¦ ¦--Pilot 0.13253012
## 14 ¦ ¦ ¦--CR-V 0.16265060
## 15 ¦ ¦ ¦--Element 0.15060241
## 16 ¦ ¦ °--Odyssey 0.15662651
## 17 ¦ ¦--Maintenance Cost 0.09986256
## 18 ¦ ¦ ¦--Accord Sedan 0.35819852
## 19 ¦ ¦ ¦--Accord Hybrid 0.31325042
## 20 ¦ ¦ ¦--Pilot 0.08369953
## 21 ¦ ¦ ¦--CR-V 0.09953251
## 22 ¦ ¦ ¦--Element 0.08791282
## 23 ¦ ¦ °--Odyssey 0.05740619
## 24 ¦ °--Resale Value 0.16054755
## 25 ¦ ¦--Accord Sedan 0.23408904
## 26 ¦ ¦--Accord Hybrid 0.11101029
## 27 ¦ ¦--Pilot 0.03808108
## 28 ¦ ¦--CR-V 0.36023288
## 29 ¦ ¦--Element 0.12829335
## 30 ¦ °--Odyssey 0.12829335
## 31 ¦--Safety 0.23435219
## 32 ¦ ¦--Accord Sedan 0.21624385
## 33 ¦ ¦--Accord Hybrid 0.21624385
## 34 ¦ ¦--Pilot 0.07515991
## 35 ¦ ¦--CR-V 0.03603025
## 36 ¦ ¦--Element 0.02229468
## 37 ¦ °--Odyssey 0.43402745
## 38 ¦--Style 0.04051875
## 39 ¦ ¦--Accord Sedan 0.35785923
## 40 ¦ ¦--Accord Hybrid 0.35785923
## 41 ¦ ¦--Pilot 0.03886101
## 42 ¦ ¦--CR-V 0.15508522
## 43 ¦ ¦--Element 0.02275249
## 44 ¦ °--Odyssey 0.06758283
## 45 °--Capacity 0.21505404
## 46 ¦--Cargo Capacity 0.16666667
## 47 ¦ ¦--Accord Sedan 0.09193018
## 48 ¦ ¦--Accord Hybrid 0.09193018
## 49 ¦ ¦--Pilot 0.19640904
## 50 ¦ ¦--CR-V 0.19640904
## 51 ¦ ¦--Element 0.19640904
## 52 ¦ °--Odyssey 0.22691251
## 53 °--Passenger Capacity 0.83333333
## 54 ¦--Accord Sedan 0.13636364
## 55 ¦--Accord Hybrid 0.13636364
## 56 ¦--Pilot 0.27272727
## 57 ¦--CR-V 0.13636364
## 58 ¦--Element 0.04545455
## 59 °--Odyssey 0.27272727
GetDataFrame(carAhp)
## Weight Odyssey Accord Sedan CR-V Accord Hybrid Element Pilot Consistency
## 1 Buy Car 100.0% 21.8% 21.6% 16.3% 15.1% 14.7% 10.6% 7.4%
## 2 ¦--Cost 51.0% 5.7% 12.3% 11.7% 5.8% 12.5% 3.0% 1.5%
## 3 ¦ ¦--Purchase Price 24.9% 2.3% 6.1% 6.1% 0.6% 9.1% 0.6% 6.8%
## 4 ¦ ¦--Fuel Cost 12.8% 2.0% 2.4% 2.1% 2.7% 1.9% 1.7% 0.0%
## 5 ¦ ¦--Maintenance Cost 5.1% 0.3% 1.8% 0.5% 1.6% 0.4% 0.4% 2.3%
## 6 ¦ °--Resale Value 8.2% 1.1% 1.9% 2.9% 0.9% 1.1% 0.3% 3.2%
## 7 ¦--Safety 23.4% 10.2% 5.1% 0.8% 5.1% 0.5% 1.8% 8.1%
## 8 ¦--Style 4.1% 0.3% 1.5% 0.6% 1.5% 0.1% 0.2% 10.2%
## 9 °--Capacity 21.5% 5.7% 2.8% 3.1% 2.8% 1.5% 5.6% 0.0%
## 10 ¦--Cargo Capacity 3.6% 0.8% 0.3% 0.7% 0.3% 0.7% 0.7% 0.4%
## 11 °--Passenger Capacity 17.9% 4.9% 2.4% 2.4% 2.4% 0.8% 4.9% 0.0%
Note that, thanks to the formattable package, the columns are numerics, even though they are formatted as percentages.
Another way to display the AHP analysis is with the ShowTable function, which displays an html table with color codings to help interpret the numbers:
ShowTable(carAhp)
| Weight | Odyssey | Accord Sedan | CR-V | Accord Hybrid | Element | Pilot | Consistency | |
|---|---|---|---|---|---|---|---|---|
| Buy Car | 100.0% | 21.8% | 21.6% | 16.3% | 15.1% | 14.7% | 10.6% | 7.4% |
| Cost | 51.0% | 5.7% | 12.3% | 11.7% | 5.8% | 12.5% | 3.0% | 1.5% |
| Purchase Price | 24.9% | 2.3% | 6.1% | 6.1% | 0.6% | 9.1% | 0.6% | 6.8% |
| Fuel Cost | 12.8% | 2.0% | 2.4% | 2.1% | 2.7% | 1.9% | 1.7% | 0.0% |
| Maintenance Cost | 5.1% | 0.3% | 1.8% | 0.5% | 1.6% | 0.4% | 0.4% | 2.3% |
| Resale Value | 8.2% | 1.1% | 1.9% | 2.9% | 0.9% | 1.1% | 0.3% | 3.2% |
| Safety | 23.4% | 10.2% | 5.1% | 0.8% | 5.1% | 0.5% | 1.8% | 8.1% |
| Style | 4.1% | 0.3% | 1.5% | 0.6% | 1.5% | 0.1% | 0.2% | 10.2% |
| Capacity | 21.5% | 5.7% | 2.8% | 3.1% | 2.8% | 1.5% | 5.6% | 0.0% |
| Cargo Capacity | 3.6% | 0.8% | 0.3% | 0.7% | 0.3% | 0.7% | 0.7% | 0.4% |
| Passenger Capacity | 17.9% | 4.9% | 2.4% | 2.4% | 2.4% | 0.8% | 4.9% | 0.0% |