Skip to content

Example 22: Fuzzy DEMATEL

Three experts' triangular influence matrices are converted by CFCS, analysed with DEMATEL, and the factor weights drive a WISP ranking.

Source

examples/22_fuzzy_dematel.py
"""Fuzzy DEMATEL: three experts' triangular matrices, CFCS, causes and effects, then WISP.

Each expert states how strongly factor i influences factor j with a triangular fuzzy
number. `cfcs` (Opricovic and Tzeng 2003) converts the three matrices into one crisp
matrix; `dematel` then finds the total-relation matrix, the cause and effect groups
and factor weights. The factor weights (`as_weights()`) become the criteria weights
of a WISP ranking of three projects.
"""

from bulanik import TFN
from bulanik.mcdm import dematel, wisp
from bulanik.mcdm.dematel import cfcs

factors = ["quality", "cost", "delivery", "service"]

# linguistic influence words as triangular numbers
NO, LOW, MID, HIGH = TFN(0, 0, 0.25), TFN(0, 0.25, 0.5), TFN(0.25, 0.5, 0.75), TFN(0.5, 0.75, 1)
VERY = TFN(0.75, 1, 1)

expert_1 = [
    [NO, MID, HIGH, HIGH],
    [LOW, NO, MID, LOW],
    [LOW, LOW, NO, MID],
    [LOW, NO, LOW, NO],
]
expert_2 = [
    [NO, HIGH, MID, HIGH],
    [MID, NO, MID, LOW],
    [LOW, MID, NO, HIGH],
    [NO, LOW, LOW, NO],
]
expert_3 = [
    [NO, MID, VERY, MID],
    [LOW, NO, HIGH, NO],
    [NO, LOW, NO, MID],
    [LOW, LOW, NO, NO],
]

crisp = cfcs([expert_1, expert_2, expert_3])
result = dematel(crisp, factors)

print("total relation T (rounded):")
for name, row in zip(factors, result.total):
    print(f"  {name:<9}" + "".join(f"{x:8.3f}" for x in row))
print("prominence D+R:", {k: round(v, 3) for k, v in result.prominence.items()})
print("relation D-R  :", {k: round(v, 3) for k, v in result.relation.items()})
print("causes:", ", ".join(result.causes))
print("effects:", ", ".join(result.effects))

weights = result.as_weights()
print("weights:", {k: round(v, 4) for k, v in weights.weights.items()})

projects = ["Project X", "Project Y", "Project Z"]
scores = [
    [8, 6, 7, 5],  # quality, cost (cost-type: lower is better), delivery, service
    [7, 4, 8, 7],
    [9, 7, 5, 6],
]
ranked = wisp(scores, weights, ["benefit", "cost", "benefit", "benefit"],
              alternatives=projects, criteria=factors)
print("WISP ranking:", " > ".join(ranked.ranking))

assert abs(sum(weights.weights.values()) - 1.0) < 1e-9
assert result.causes == ("quality", "cost") and result.effects == ("delivery", "service")
expected_w = {"quality": 0.2877, "cost": 0.2332, "delivery": 0.2551, "service": 0.224}
assert all(abs(weights.weights[k] - v) < 5e-5 for k, v in expected_w.items())
assert ranked.ranking == ("Project Y", "Project X", "Project Z")
assert set(result.causes) | set(result.effects) == set(factors)
assert abs(sum(result.relation.values())) < 1e-9
# a TFN matrix handed to dematel directly is defuzzified by CFCS with one expert
single = dematel(expert_1, factors)
assert single.operators["defuzzification"] == "cfcs"

Output

Run when this page is built:

total relation T (rounded):
  quality     0.234   0.540   0.678   0.661
  cost        0.285   0.222   0.499   0.354
  delivery    0.207   0.312   0.232   0.461
  service     0.157   0.188   0.219   0.155
prominence D+R: {'quality': 2.995, 'cost': 2.622, 'delivery': 2.84, 'service': 2.35}
relation D-R  : {'quality': 1.23, 'cost': 0.097, 'delivery': -0.415, 'service': -0.912}
causes: quality, cost
effects: delivery, service
weights: {'quality': 0.2877, 'cost': 0.2332, 'delivery': 0.2551, 'service': 0.224}
WISP ranking: Project Y > Project X > Project Z

What to notice

  • cfcs takes the minimum and maximum per cell across the experts, converts each expert, then averages.
  • A factor is a cause when its net relation D - R is positive, an effect otherwise.
  • result.as_weights() makes the DEMATEL weights usable by every ranking method.

Go further

Run it yourself: python examples/22_fuzzy_dematel.py.