Source code for skcriteria.agg.edas

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# License: BSD-3 (https://tldrlegal.com/license/bsd-3-clause-license-(revised))
# Copyright (c) 2016-2021, Cabral, Juan; Luczywo, Nadia
# Copyright (c) 2022-2025 QuatroPe
# All rights reserved.

# =============================================================================
# DOCS
# =============================================================================

"""Evaluation based on Distance from Average Solution - EDAS.

The EDAS method evaluates alternatives by comparing them to an average solution
benchmark. It calculates two key metrics: Positive Distance from Average (PDA)
for performance exceeding the average, and Negative Distance from Average (NDA)
for performance below average. These measures capture how each alternative
deviates from the mean performance across all criteria.

The final appraisal combines these deviations through a weighted, normalized
scoring process. After computing weighted sums of PDA and NDA for each
alternative, the method normalizes these values and averages them to produce
a comprehensive evaluation score.

"""

# =============================================================================
# IMPORTS
# =============================================================================

from ..utils import hidden

with hidden():
    import numpy as np

    from ._agg_base import RankResult, SKCDecisionMakerABC
    from ..core import Objective
    from ..utils import doc_inherit, rank


# =============================================================================
# EDAS
# =============================================================================


def _any_zeroes(matrix):
    """Aux function to avoid division by zero."""
    result = matrix
    if np.any(result == 0):
        result = np.add(result, 1e-5)
    result = np.where(np.equal(result, 0), -1e-6, result)
    return result


def _distance_from_avg(matrix, objectives, avg):
    """Aux function to calculate PDA and NDA."""
    pda = np.zeros_like(matrix, dtype=float)
    nda = np.zeros_like(matrix, dtype=float)

    # Determine if the objective is beneficial or not
    is_beneficial = np.equal(objectives, Objective.MAX.value)

    # Avoid division by zero
    divisor = _any_zeroes(avg)

    diff_from_avg = np.subtract(matrix, avg)
    max_zero_diff = np.maximum(0, diff_from_avg)
    neg_max_zero_diff = np.maximum(0, np.multiply(-1, diff_from_avg))

    # Calculate PDA
    pda_filtered = np.where(is_beneficial, max_zero_diff, neg_max_zero_diff)
    pda = np.divide(pda_filtered, divisor)

    # Calculate NDA
    nda_filtered = np.where(is_beneficial, neg_max_zero_diff, max_zero_diff)
    nda = np.divide(nda_filtered, divisor)

    return pda, nda


def _normalize_sum_pda_nda(pda, nda):
    """Normalize the sums of PDA and NDA."""
    max_pda = np.max(pda)
    max_nda = np.max(nda)

    # Avoid division by zero
    divisor = max_pda if max_pda != 0 else 1e-5
    result_pda = np.divide(pda, divisor)

    # Avoid division by zero
    divisor = max_nda if max_nda != 0 else 1e-5
    result_nda = np.subtract(1, np.divide(nda, divisor))

    return result_pda, result_nda


[docs] def edas(matrix, weights, objectives): """Execute EDAS without any validation.""" # Determine the average solution for each criteria average_solution = np.mean(matrix, axis=0) # Calculate the positive (PDA) and distance (NDA) from average pda, nda = _distance_from_avg(matrix, objectives, average_solution) # Determine the weighted sum of PDA and NDA for all alternatives sum_pda = np.sum(np.multiply(pda, weights), axis=1) sum_nda = np.sum(np.multiply(nda, weights), axis=1) # Normalize the values of weighted sums for all alternatives normal_sum_pda, normal_sum_nda = _normalize_sum_pda_nda(sum_pda, sum_nda) # Calculate the appraisal score for all alternatives score = np.multiply(0.5, np.add(normal_sum_pda, normal_sum_nda)) return rank.rank_values(score, reverse=True), score
[docs] class EDAS(SKCDecisionMakerABC): """Rank alternatives using EDAS method. The Evaluation based on Distance from Average Solution (EDAS) method ranks alternatives by comparing their performance to the average solution across all criteria. For each alternative, it calculates Positive (PDA) and Negative (NDA) distances from average values, which are then weighted, normalized, and combined into a final appraisal score. References ---------- :cite:p:`keshavarz2015multi` """ _skcriteria_parameters = [] @doc_inherit(SKCDecisionMakerABC._evaluate_data) def _evaluate_data(self, matrix, weights, objectives, **kwargs): rank, score = edas(matrix, weights, objectives) return rank, {"score": score} @doc_inherit(SKCDecisionMakerABC._make_result) def _make_result(self, alternatives, values, extra): return RankResult( "EDAS", alternatives=alternatives, values=values, extra=extra )