Weighting methods¶
A weighting method turns judgements or data into criteria weights that sum to 1.
bulanik has four, all returning a WeightingResult,
and a fifth, the best-worst method, that needs an optimiser and so lives in the
LumiX and sezgi bridges.
| Method | Function | Input | Kind |
|---|---|---|---|
| AHP | ahp |
crisp pairwise comparison matrix | subjective |
| Fuzzy AHP | fuzzy_ahp |
matrix of triangular numbers | subjective |
| SWARA | swara |
comparative importances of an ordered list | subjective |
| Entropy | entropy |
decision matrix (alternatives by criteria) | objective |
| BWM | bwm_linear, BWMProblem, FuzzyBWMProblem |
best-to-others and others-to-worst vectors | subjective |
Subjective methods encode what the decision makers think; objective ones read the
weights off the data. Every result carries weights (a dict), ranking (criteria,
largest weight first), operators (the choices behind the numbers) and, for the
AHP-based methods, consistency_ratio. Pass a result straight to a
ranking method as its weights.
plot.weights. AHP and SWARA are judgement-based and agree
on the order; entropy ignores judgement and rewards the criterion whose values differ
most between the four suppliers, here delivery. The AHP panel shows its consistency
ratio in the corner.AHP¶
Saaty's analytic hierarchy process asks for a square matrix \(A\) in which \(a_{ij}\) says how many times more important criterion \(i\) is than \(j\) (the usual scale is 1 to 9), with \(a_{ji} = 1/a_{ij}\) and ones on the diagonal. The weights are the principal eigenvector of \(A\) normalised to sum 1; bulanik finds it by power iteration.
The matrix is rarely perfectly consistent (\(a_{ij} a_{jk} = a_{ik}\)), so AHP also reports how far it is from consistent. With the principal eigenvalue \(\lambda_{\max}\) and order \(n\),
where \(RI_n\) is Saaty's random index, the average \(CI\) of random reciprocal matrices of order \(n\). The code uses this table:
| \(n\) | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|
| \(RI\) | 0 | 0 | 0.58 | 0.90 | 1.12 | 1.24 | 1.32 | 1.41 | 1.45 | 1.49 |
Hence matrices larger than 10 are rejected (there is no random index beyond 10), and for \(n \le 2\) the ratio is 0 (every 1x1 and 2x2 reciprocal matrix is consistent). A ratio below about 0.1 is conventionally acceptable.
from bulanik.mcdm import ahp
names = ["cost", "quality", "speed"]
r = ahp([[1, 3, 5], [1/3, 1, 2], [1/5, 1/2, 1]], names)
print({k: round(v, 3) for k, v in r.weights.items()})
print(r.ranking, round(r.consistency_ratio, 3), r.operators)
# cost beats quality 3:1 and quality beats speed 3:1, but speed beats cost 3:1: a cycle
cyclic = ahp([[1, 3, 1/3], [1/3, 1, 3], [3, 1/3, 1]], names)
print({k: round(v, 3) for k, v in cyclic.weights.items()}, round(cyclic.consistency_ratio, 3))
{'cost': 0.648, 'quality': 0.23, 'speed': 0.122}
('cost', 'quality', 'speed') 0.003 {'weights': 'eigenvector', 'consistency': 'saaty_ri'}
{'cost': 0.333, 'quality': 0.333, 'speed': 0.333} 1.149
The cyclic matrix has equal weights and a ratio far above 0.1: AHP will always give you
some weights, so check consistency_ratio before trusting them.
Fuzzy AHP¶
fuzzy_ahp takes the same matrix of triangular
numbers (TFN), so each judgement can carry its own uncertainty. It
follows Buckley's (1985) geometric-mean method:
- The fuzzy geometric mean of row \(i\) is \(\tilde r_i = (\prod_j \tilde a_{ij})^{1/n}\), taken componentwise on \((l, m, u)\).
- The fuzzy weight is \(\tilde w_i = \tilde r_i \otimes (\sum_k \tilde r_k)^{-1}\).
- Each \(\tilde w_i\) is defuzzified with
defuzzification(a TFN score name:"centroid", the default,"graded_mean"or"mean") and the crisp values are normalised to sum 1.
The result keeps the fuzzy weights in fuzzy_weights. The consistency ratio is Saaty's,
computed on the defuzzified matrix with the table above. The matrix must be
reciprocal in the fuzzy sense, \(\tilde a_{ji} = 1/\tilde a_{ij}\) (relative tolerance
\(10^{-9}\)), with every lower bound positive.
SAATY_TFN and LinguisticScale.reciprocal build such
entries from the words of the 1-9 scale.
from bulanik import TFN
from bulanik.mcdm import fuzzy_ahp
from bulanik.scales import SAATY_TFN
one = SAATY_TFN["1"]
three, five = SAATY_TFN["3"], SAATY_TFN["5"]
matrix = [
[one, three, five],
[SAATY_TFN.reciprocal("3"), one, three],
[SAATY_TFN.reciprocal("5"), SAATY_TFN.reciprocal("3"), one],
]
r = fuzzy_ahp(matrix, ["cost", "quality", "speed"])
print({k: round(v, 3) for k, v in r.weights.items()})
print("CR:", round(r.consistency_ratio, 3), r.operators)
print("cost, fuzzy:", r.fuzzy_weights["cost"])
g = fuzzy_ahp(matrix, ["cost", "quality", "speed"], defuzzification="graded_mean")
print({k: round(v, 3) for k, v in g.weights.items()})
{'cost': 0.629, 'quality': 0.263, 'speed': 0.107}
CR: 0.082 {'weights': 'buckley_geometric_mean', 'defuzzification': 'centroid', 'consistency': 'saaty_ri'}
cost, fuzzy: TFN(l=0.43062425834913853, m=0.636985571744757, u=0.9185188009830095)
{'cost': 0.633, 'quality': 0.261, 'speed': 0.106}
SWARA¶
Step-wise weight assessment ratio analysis (Kersuliene et al. 2010) needs no matrix: rank the criteria from most to least important, then for each criterion say how much less important it is than the one before it. With these comparative importances \(s_2, \dots, s_n\) the code computes
An \(s_j\) of 0 means "as important as the previous one", and 0.3 means "30% less
important". The names are given most important first and there are \(n - 1\) values.
The importances may be crisp (non-negative) or all fuzzy (any fuzzy number kind); fuzzy
ones are scored first, with score= or the kind's default, and operators["score"]
records which. There is no consistency ratio.
from bulanik import TFN
from bulanik.mcdm import swara
r = swara([0.3, 0.2], ["a", "b", "c"])
print({k: round(v, 3) for k, v in r.weights.items()}, r.operators)
fuzzy = swara([TFN(0.2, 0.3, 0.4), TFN(0.1, 0.2, 0.3)], ["a", "b", "c"])
print({k: round(v, 3) for k, v in fuzzy.weights.items()}, fuzzy.operators)
print(swara([TFN(0.2, 0.3, 0.4)], ["a", "b"], score="mean").operators)
{'a': 0.415, 'b': 0.319, 'c': 0.266} {'score': 'crisp'}
{'a': 0.415, 'b': 0.319, 'c': 0.266} {'score': 'centroid'}
{'score': 'mean'}
Entropy¶
The entropy method is objective: it uses only the decision matrix. With \(p_{ij} = x_{ij} / \sum_i x_{ij}\) for \(m\) alternatives, criterion \(j\) has entropy and divergence
with \(0 \ln 0 = 0\). A criterion on which the alternatives are all alike has entropy near 1, carries little information and gets little weight; one on which they differ a lot gets more. Entropy says nothing about what matters to you, only about what discriminates, so it is often combined with, not substituted for, a subjective method.
from bulanik.mcdm import entropy
r = entropy([[1, 9], [2, 8], [3, 7]], ["a", "b"])
print({k: round(v, 3) for k, v in r.weights.items()}) # a (1, 2, 3) differs more, relatively, than b (9, 8, 7)
print(r.operators, r.consistency_ratio)
{'a': 0.944, 'b': 0.056}
{'weights': 'shannon_entropy'} None
Best-worst method¶
The best-worst method (BWM, Rezaei 2015) asks for only \(2n - 3\) comparisons: the best
criterion \(B\) against every other (\(a_{Bj}\)) and every other against the worst criterion
\(W\) (\(a_{jW}\)), on a 1 to 9 scale. The weights are those that best reproduce the
comparisons, which is an optimisation problem, solved by the
LumiX bridge (linear form) or the sezgi bridge
(nonlinear and fuzzy forms). Its input checks and two consistency indices are
solver-free, so they live in bulanik.mcdm.bwm:
bwm_consistency_index(a_bw)is Rezaei's largest attainable deviation \(\xi\) for a best-over-worst comparison \(a = a_{BW}\), \(CI(a) = \bigl((1 + 2a) - \sqrt{8a + 1}\bigr)/2\), andfuzzy_bwm_consistency_index(a_bw)is Guo and Zhao's for a triangular \(\tilde a_{BW}\) with upper bound \(u\), \(CI(u) = \bigl((1 + 2u) + \sqrt{8u + 1}\bigr)/2\) (the larger root, so it is never 0).
from bulanik.mcdm.bwm import bwm_consistency_index, fuzzy_bwm_consistency_index
from bulanik.scales import FUZZY_BWM_TFN
print("a_BW ", *range(1, 10), sep="\t")
print("CI(a) ", *[f"{bwm_consistency_index(a):.2f}" for a in range(1, 10)], sep="\t")
print()
for term in FUZZY_BWM_TFN.terms:
tfn = FUZZY_BWM_TFN[term]
print(f"{term} {str(tfn):<44} CI = {fuzzy_bwm_consistency_index(tfn):.2f}")
a_BW 1 2 3 4 5 6 7 8 9
CI(a) 0.00 0.44 1.00 1.63 2.30 3.00 3.73 4.47 5.23
EI TFN(l=1.0, m=1.0, u=1.0) CI = 3.00
WI TFN(l=0.6666666666666666, m=1.0, u=1.5) CI = 3.80
FI TFN(l=1.5, m=2.0, u=2.5) CI = 5.29
VI TFN(l=2.5, m=3.0, u=3.5) CI = 6.69
AI TFN(l=3.5, m=4.0, u=4.5) CI = 8.04
The consistency ratio of a BWM result is \(\xi / CI\), which is why the ratio does not exist for \(a_{BW} = 1\) in the crisp model (see the sezgi bridge).
Pitfalls¶
AHP matrices must be exactly reciprocal. The check is a relative tolerance of
\(10^{-9}\) on \(a_{ij} a_{ji} = 1\), the diagonal must be 1, entries must be positive and the
order at most 10. Write 1/3, not 0.33.
A fuzzy AHP entry must be a TFN with \(l > 0\). Plain numbers and other number kinds
are rejected, and so is a \(\tilde a_{ji}\) that is not the reciprocal of \(\tilde a_{ij}\).
SWARA wants the criteria sorted by you. The function does not sort: the first name is
taken as the most important. With fuzzy importances a negative score is an error; use
score="normalized" if the default score can go negative.
Entropy needs variation. Fewer than 2 alternatives, a negative entry, a column that sums to 0, or a matrix whose columns are all constant is an error (no criterion discriminates).
A small consistency_ratio is a necessary check, not proof. It says the judgements
do not contradict each other, not that they are right.
from bulanik import TFN
from bulanik.mcdm import ahp, entropy, fuzzy_ahp, swara
def show(make):
try:
print(make())
except Exception as error:
print(f"{type(error).__name__}: {error}")
show(lambda: ahp([[1, 3], [0.33, 1]])) # not reciprocal
show(lambda: ahp([[2, 1], [1, 2]])) # diagonal is not 1
show(lambda: ahp([[1, 0], [0, 1]])) # not positive
show(lambda: ahp([[1.0] * 11 for _ in range(11)])) # no random index beyond 10
show(lambda: ahp([[1, 3], [1/3, 1]], ["a", "a"])) # duplicate names
show(lambda: fuzzy_ahp([[1, 3], [1/3, 1]])) # not TFN entries
show(lambda: fuzzy_ahp([[TFN(1, 1, 1), TFN(2, 3, 4)], [TFN(1, 1, 1), TFN(1, 1, 1)]])) # not reciprocal
show(lambda: swara([0.3], ["a", "b", "c"])) # needs n - 1 = 2 values
show(lambda: swara([0.3, TFN(0.1, 0.2, 0.3)], ["a", "b", "c"])) # crisp and fuzzy mixed
show(lambda: swara([-0.5], ["a", "b"]))
show(lambda: entropy([[1, 2], [1, 2]])) # no criterion discriminates
show(lambda: entropy([[1, 2]])) # one alternative
ValueError: AHP matrix must be reciprocal: a[0][1]*a[1][0] != 1
ValueError: AHP diagonal must be 1
ValueError: AHP entries must be positive
ValueError: AHP supports at most 10 criteria (no random index beyond 10), got 11
ValueError: criteria names must be unique
TypeError: fuzzy AHP needs TFN entries, got int
ValueError: fuzzy AHP matrix must be reciprocal: a[1][0] != 1/a[0][1]
ValueError: expected 2 comparative importances for 3 criteria, got 1
ValueError: SWARA comparative importances must be all crisp or all fuzzy, not a mix
ValueError: comparative importance must be >= 0, got -0.5; for fuzzy inputs try score="normalized"
ValueError: no criterion discriminates between the alternatives
ValueError: entropy weighting needs at least 2 alternatives
See also¶
- More weighting methods for CRITIC, PIPRECIA,
ahp_fuzzy_numbersand DEMATEL. - Ranking: pass a result straight to
topsis,vikor,waspas,edas. - LumiX bridge and sezgi bridge: the best-worst method.
- Aggregation to combine several experts' judgements before weighting,
Sets and scales for
SAATY_TFNand friends. - API:
bulanik.mcdm,bulanik.plot.weights. - Examples: 03 fuzzy AHP, 04 SWARA and TOPSIS.
- Saaty (1980), The Analytic Hierarchy Process, McGraw-Hill; Buckley (1985), "Fuzzy hierarchical analysis", Fuzzy Sets and Systems 17(3); Kersuliene, Zavadskas and Turskis (2010), "Selection of rational dispute resolution method by applying new step-wise weight assessment ratio analysis (SWARA)", Journal of Business Economics and Management 11(2); Shannon (1948), "A mathematical theory of communication", Bell System Technical Journal 27; Hwang and Yoon (1981), Multiple Attribute Decision Making, Springer (the entropy procedure; see also Zeleny 1982); Rezaei (2015), "Best-worst multi-criteria decision-making method", Omega 53; Guo and Zhao (2017), "Fuzzy best-worst multi-criteria decision-making method and its applications", Knowledge-Based Systems 121.