Source code for skcriteria.core.methods

#!/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, QuatroPe
# All rights reserved.

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

"""Core functionalities of scikit-criteria."""

# =============================================================================
# IMPORTS
# =============================================================================รง

import abc
import copy
import inspect

from .data import DecisionMatrix
from ..utils import doc_inherit

# =============================================================================
# BASE DECISION MAKER CLASS
# =============================================================================


[docs]class SKCMethodABC(metaclass=abc.ABCMeta): """Base class for all class in scikit-criteria. Notes ----- All estimators should specify: - ``_skcriteria_dm_type``: The type of the decision maker. - ``_skcriteria_parameters``: Availebe parameters. - ``_skcriteria_abstract_class``: If the class is abstract. If the class is *abstract* the user can ignore the other two attributes. """ _skcriteria_abstract_class = True def __init_subclass__(cls): """Validate if the subclass are well formed.""" is_abstract = vars(cls).get("_skcriteria_abstract_class", False) if is_abstract: return decisor_type = getattr(cls, "_skcriteria_dm_type", None) if decisor_type is None: raise TypeError(f"{cls} must redefine '_skcriteria_dm_type'") cls._skcriteria_dm_type = str(decisor_type) params = getattr(cls, "_skcriteria_parameters", None) if params is None: raise TypeError(f"{cls} must redefine '_skcriteria_parameters'") params = frozenset(params) signature = inspect.signature(cls.__init__) has_kwargs = any( p.kind == inspect.Parameter.VAR_KEYWORD for p in signature.parameters.values() ) params_not_in_signature = params.difference(signature.parameters) if params_not_in_signature and not has_kwargs: raise TypeError( f"{cls} defines the parameters {params_not_in_signature} " "which is not found as a parameter in the __init__ method." ) cls._skcriteria_parameters = params def __repr__(self): """x.__repr__() <==> repr(x).""" cls_name = type(self).__name__ parameters = [] if self._skcriteria_parameters: for pname in sorted(self._skcriteria_parameters): pvalue = getattr(self, pname) parameters.append(f"{pname}={repr(pvalue)}") str_parameters = ", ".join(parameters) return f"{cls_name}({str_parameters})"
[docs] def get_parameters(self): """Return the parameters of the method as dictionary.""" the_parameters = {} for parameter_name in self._skcriteria_parameters: parameter_value = getattr(self, parameter_name) the_parameters[parameter_name] = copy.deepcopy(parameter_value) return the_parameters
[docs] def copy(self, **kwargs): """Return a deep copy of the current Object.. This method is also useful for manually modifying the values of the object. Parameters ---------- kwargs : The same parameters supported by object constructor. The values provided replace the existing ones in the object to be copied. Returns ------- A new object. """ asdict = self.get_parameters() asdict.update(kwargs) cls = type(self) return cls(**asdict)
# ============================================================================= # SKCTransformer ABC # =============================================================================
[docs]class SKCTransformerABC(SKCMethodABC): """Abstract class for all transformer in scikit-criteria.""" _skcriteria_dm_type = "transformer" _skcriteria_abstract_class = True @abc.abstractmethod def _transform_data(self, **kwargs): """Apply the transformation logic to the decision matrix parameters. Parameters ---------- kwargs: The decision matrix as separated parameters. Returns ------- :py:class:`dict` A dictionary with all the values of the decision matrix transformed. """ raise NotImplementedError()
[docs] def transform(self, dm): """Perform transformation on `dm`. Parameters ---------- dm: :py:class:`skcriteria.data.DecisionMatrix` The decision matrix to transform. Returns ------- :py:class:`skcriteria.data.DecisionMatrix` Transformed decision matrix. """ data = dm.to_dict() transformed_data = self._transform_data(**data) transformed_dm = DecisionMatrix.from_mcda_data(**transformed_data) return transformed_dm
[docs]class SKCMatrixAndWeightTransformerABC(SKCTransformerABC): """Transform weights and matrix together or independently. The Transformer that implements this abstract class can be configured to transform `weights`, `matrix` or `both` so only that part of the DecisionMatrix is altered. This abstract class require to redefine ``_transform_weights`` and ``_transform_matrix``, instead of ``_transform_data``. """ _skcriteria_abstract_class = True _skcriteria_parameters = ["target"] _TARGET_WEIGHTS = "weights" _TARGET_MATRIX = "matrix" _TARGET_BOTH = "both" def __init__(self, target): if target not in ( self._TARGET_MATRIX, self._TARGET_WEIGHTS, self._TARGET_BOTH, ): raise ValueError( f"'target' can only be '{self._TARGET_WEIGHTS}', " f"'{self._TARGET_MATRIX}' or '{self._TARGET_BOTH}', " f"found '{target}'" ) self._target = target @property def target(self): """Determine which part of the DecisionMatrix will be transformed.""" return self._target @abc.abstractmethod def _transform_weights(self, weights): """Execute the transform method over the weights. Parameters ---------- weights: :py:class:`numpy.ndarray` The weights to transform. Returns ------- :py:class:`numpy.ndarray` The transformed weights. """ raise NotImplementedError() @abc.abstractmethod def _transform_matrix(self, matrix): """Execute the transform method over the matrix. Parameters ---------- matrix: :py:class:`numpy.ndarray` The decision matrix to transform Returns ------- :py:class:`numpy.ndarray` The transformed matrix. """ raise NotImplementedError() @doc_inherit(SKCTransformerABC._transform_data) def _transform_data(self, matrix, weights, **kwargs): transformed_mtx = matrix transformed_weights = weights if self._target in (self._TARGET_MATRIX, self._TARGET_BOTH): transformed_mtx = self._transform_matrix(matrix) if self._target in (self._TARGET_WEIGHTS, self._TARGET_BOTH): transformed_weights = self._transform_weights(weights) kwargs.update( matrix=transformed_mtx, weights=transformed_weights, dtypes=None ) return kwargs