Example 24: Ranking on fuzzy numbers¶
Two experts' intuitionistic ratings of four suppliers go through rank_fuzzy, score-first with crisp CODAS, WASPAS and CoCoSo, and score-last with their fuzzy versions.
Source¶
examples/24_fuzzy_ranking.py
"""Score first or score last: CODAS, WASPAS and CoCoSo on an intuitionistic matrix.
Two experts rate four suppliers on price (a cost), quality and delivery with
intuitionistic numbers (mu, nu). `rank_fuzzy` aggregates them and then either
- scores every aggregated number first and ranks the crisp matrix with the
crisp method (`method="codas"`, `"waspas"`, `"cocoso"`), or
- hands the aggregated *fuzzy* matrix to the fuzzy method
(`method="fuzzy_codas"`, `"fuzzy_waspas"`, `"fuzzy_cocoso"`), which weights,
sums and multiplies the numbers with intuitionistic arithmetic and scores
only at the end.
The crisp ratio methods need positive entries, so every run uses
`score="normalized"`, (1 + mu - nu) / 2. The fuzzy WASPAS is run both ways of
combining its weighted sum and weighted product: as numbers
(`combine="fuzzy"`) and as scores (`combine="scores"`).
"""
from bulanik import IFN
from bulanik.mcdm import DecisionMatrix, rank_fuzzy
alternatives = ["Atlas", "Borealis", "Cedar", "Delta"]
criteria = ["price", "quality", "delivery"]
types = ["cost", "benefit", "benefit"]
weights = {"price": 0.40, "quality": 0.35, "delivery": 0.25}
# ratings [expert][alternative][criterion]; for price a high mu means "expensive"
expert1 = [
[IFN(0.60, 0.30), IFN(0.70, 0.20), IFN(0.60, 0.30)],
[IFN(0.40, 0.50), IFN(0.60, 0.30), IFN(0.40, 0.50)],
[IFN(0.50, 0.40), IFN(0.80, 0.10), IFN(0.50, 0.40)],
[IFN(0.70, 0.20), IFN(0.50, 0.40), IFN(0.80, 0.10)],
]
expert2 = [
[IFN(0.50, 0.40), IFN(0.70, 0.20), IFN(0.70, 0.20)],
[IFN(0.40, 0.40), IFN(0.50, 0.40), IFN(0.50, 0.40)],
[IFN(0.60, 0.30), IFN(0.70, 0.20), IFN(0.40, 0.50)],
[IFN(0.80, 0.10), IFN(0.60, 0.30), IFN(0.70, 0.20)],
]
matrix = DecisionMatrix(alternatives, criteria, [expert1, expert2])
runs = {
"codas (score first)": dict(method="codas"),
"fuzzy_codas": dict(method="fuzzy_codas"),
"waspas (score first)": dict(method="waspas"),
"fuzzy_waspas, fuzzy": dict(method="fuzzy_waspas", combine="fuzzy"),
"fuzzy_waspas, scores": dict(method="fuzzy_waspas", combine="scores"),
"cocoso (score first)": dict(method="cocoso"),
"fuzzy_cocoso": dict(method="fuzzy_cocoso"),
}
results = {name: rank_fuzzy(matrix, weights, types, score="normalized", **kw)
for name, kw in runs.items()}
print("fuzzy ranking table (1 = best)")
print(f"{'run':<22}" + "".join(f"{a:>10}" for a in alternatives))
for name, r in results.items():
position = {a: i + 1 for i, a in enumerate(r.ranking)}
print(f"{name:<22}" + "".join(f"{position[a]:>10}" for a in alternatives))
print()
print("scores (larger is better; compare within a method, not across methods)")
for name, r in results.items():
print(f"{name:<22}" + "".join(f"{r.scores[a]:>10.4f}" for a in alternatives))
fw = results["fuzzy_waspas, fuzzy"]
print()
print("fuzzy_waspas keeps the numbers: Q1 (weighted sum) and Q2 (weighted product) of Cedar")
q1, q2 = fw.extras["Q1"]["Cedar"], fw.extras["Q2"]["Cedar"]
print(f" Q1 = IFN({q1.mu:.4f}, {q1.nu:.4f}), Q2 = IFN({q2.mu:.4f}, {q2.nu:.4f})")
assert all(sorted(r.ranking) == sorted(alternatives) for r in results.values())
Output¶
Run when this page is built:
fuzzy ranking table (1 = best)
run Atlas Borealis Cedar Delta
codas (score first) 2 1 3 4
fuzzy_codas 3 4 1 2
waspas (score first) 1 3 2 4
fuzzy_waspas, fuzzy 1 3 2 4
fuzzy_waspas, scores 1 3 2 4
cocoso (score first) 1 3 2 4
fuzzy_cocoso 1 3 2 4
scores (larger is better; compare within a method, not across methods)
codas (score first) 0.1357 0.1816 0.0572 -0.3746
fuzzy_codas -0.0124 -0.0220 0.0398 -0.0055
waspas (score first) 0.8584 0.8099 0.8150 0.7397
fuzzy_waspas, fuzzy 0.6020 0.5464 0.5793 0.4910
fuzzy_waspas, scores 0.6009 0.5464 0.5775 0.4853
cocoso (score first) 3.4985 1.6353 2.7110 1.3283
fuzzy_cocoso 2.1570 1.9680 2.0701 1.7275
fuzzy_waspas keeps the numbers: Q1 (weighted sum) and Q2 (weighted product) of Cedar
Q1 = IFN(0.5565, 0.3253), Q2 = IFN(0.4864, 0.4075)
What to notice¶
- WASPAS and CoCoSo give the same order score-first and score-last here. CODAS does not: score-first it picks Borealis, score-last Cedar. The crisp method turns a cost into a ratio of scores and measures distances on the scored matrix;
fuzzy_codastakes the complement of the cost rating and measures distances between the weighted numbers. combine="fuzzy"andcombine="scores"differ from the third decimal on, because a fuzzy mix of two close numbers scores close to the mix of their scores. They agree on the order in this example, though they need not in general.- Scores from different methods live on different scales, so compare the ranks across methods and the scores only within one method.
extras["Q1"]andextras["Q2"]keep the weighted sum and the weighted product asIFNnumbers.
Go further¶
- Guide: More ranking methods
- API:
fuzzy_codas,fuzzy_waspas,fuzzy_cocoso,rank_fuzzy
Run it yourself: python examples/24_fuzzy_ranking.py.