Source code for pysensmcda.compromise.improved_borda

# Copyright (C) 2024 Bartosz Paradowski, Jakub Więckowski

import numpy as np
from scipy.stats import rankdata
from ..validator import Validator
from ..utils import memory_guard

[docs] def vector_normalization(x: np.ndarray, cost: bool=True) -> np.ndarray: """ Parameters: ---------- x: ndarray Vector of numbers to be normalized. cost: bool, optional, cost=True Type of normalization. If True normalize as cost criterion, if False normalize as profit criterion. Returns: ------- ndarray Normalized vector. Example: ------- >>> normalized_matrix = matrix.copy() >>> for i in range(criteria_number): >>> cost = True if types[i] == -1 else False >>> normalized_matrix[:, i] = normalization(matrix[:, i], cost) """ if cost: return 1 - (x / np.sqrt(sum(x ** 2))) return x / np.sqrt(np.sum(x ** 2))
[docs] @memory_guard def improved_borda(preferences: np.ndarray, preference_types: np.ndarray | list= [], normalization: callable = vector_normalization, utility_funcs: list[callable] = [], norm_types: np.ndarray | list = []) -> np.ndarray: """ Improved borda was presented along Probabilistic Linguistic MULTIMOORA, where authors used specific utility functions. This implementation relyes on the concept proposed by author, however it does provide freedom for the user. Parameters: ------------ preferences: ndarray Preferences for alternatives in rows that will be further compromised. Columns designates methods / criteria. preference_types: list | ndarray, optional, default=[] List of types of methods, changes direction of evaluation: -1 for the ascending ranking, 1 for the descending ranking. Defaults to descending for all. normalization: callable, optional, default=vector_normalization Function to normalize utility functions results. `See vector_normalization` for further information. utility_funcs: list[callable], optional, default=[] List of utility functions for each of criterion. If provided, must align with number of criteria in preference matrix. norm_types: list | ndarray, optional, default=[] Changes type of normalization if needed, -1 for cost, 1 for profit. Returns: --------- ndarray Compromised ranking. Example: ---------- >>> matrix = np.random.random((8,5)) >>> criteria_num = matrix.shape[1] >>> weights = np.ones(criteria_num)/criteria_num >>> types = np.ones(criteria_num) >>> preferences = np.array([topsis(matrix, weights, types), vikor(matrix, weights, types)]).T >>> >>> compromise_ranking = improved_borda(preferences, [1, -1]) """ Validator.is_type_valid(preferences, np.ndarray, 'preferences') Validator.is_type_valid(preference_types, (list, np.ndarray), 'preference_types') Validator.is_callable(normalization, 'normalization') Validator.is_callable(utility_funcs, 'utility_funcs') Validator.is_type_valid(norm_types, (list, np.ndarray), 'norm_types') Validator.is_in_list(norm_types, [-1, 1], 'norm_types') alternatives_num, methods_num = preferences.shape if not preference_types: preference_types = np.ones(methods_num) if not norm_types: norm_types = np.ones(methods_num) Validator.is_shape_equal(len(preference_types), methods_num, custom_message="Number of columns in 'preferences' and length of 'preference_types' are different") Validator.is_shape_equal(len(norm_types), methods_num, custom_message="Number of columns in 'preferences' and length of 'norm_types' are different") if utility_funcs: Validator.is_shape_equal(len(utility_funcs), methods_num, custom_message="Number of columns in 'preferences' and length of 'utility_funcs' are different") util_prefs = preferences.copy() for idx, util_func in enumerate(utility_funcs): util_prefs[:, idx] = util_func(preferences[:, idx]) norm_prefs = util_prefs.copy() for i in range(methods_num): cost = True if norm_types[i] == -1 else False norm_prefs[:, i] = normalization(util_prefs[:, i], cost) rankings = rankdata(preferences * -1 * preference_types, axis=0) IBS = np.zeros(alternatives_num) an = alternatives_num for i in range(methods_num): if preference_types[i] == -1: IBS -= norm_prefs[:, i] * ((rankings[:, i])/((an*(an+1))/2)) else: IBS += norm_prefs[:, i] * ((an - rankings[:, i] + 1)/((an*(an+1))/2)) compromise_ranking = rankdata(IBS * -1) return compromise_ranking