├── .gitignore ├── .readthedocs.yaml ├── LICENSE ├── README.md ├── docs ├── Makefile ├── conf.py ├── example_crispyn.ipynb ├── example_pyrepo_mcda.ipynb ├── example_pyrepo_mcda_update.ipynb ├── example_pyrepo_mcda_update2.ipynb ├── index.rst ├── make.bat ├── requirements.txt └── usage.rst ├── examples ├── criteria_electric_cars.csv ├── data.csv ├── dataset_cars.csv ├── dataset_waste.csv ├── electric_cars_2021.csv ├── example1criteria.csv ├── example2criteria.csv ├── example_crispyn.ipynb ├── example_pyrepo_mcda.ipynb ├── example_pyrepo_mcda_update.ipynb ├── example_pyrepo_mcda_update2.ipynb ├── example_v_1_3 │ └── SHARES_2022_POPULATION.csv ├── main_crispyn.py ├── main_pyrepo_mcda.py ├── main_pyrepo_mcda_update.py ├── main_pyrepo_mcda_update2.py ├── main_visualizations.py ├── results │ ├── TOPSIS_boxplot.eps │ ├── bar_chart_Distance metrics.eps │ ├── bar_chart_MCDA methods.eps │ ├── bar_chart_MCDA methods1.eps │ ├── bar_chart_MCDA methods1.png │ ├── bar_chart_MCDA methods2.eps │ ├── bar_chart_MCDA methods2.png │ ├── bar_chart_weights_Alternative.eps │ ├── bar_chart_weights_Alternative.pdf │ ├── bar_chart_weights_Alternatives.eps │ ├── bar_chart_weights_Alternatives.pdf │ ├── bar_chart_weights_Weighting methods.eps │ ├── bar_chart_weights_Weighting methods.pdf │ ├── boxplot_weights.eps │ ├── boxplot_weights.pdf │ ├── correlations_Pearson.eps │ ├── correlations_WS.eps │ ├── correlations_r_s.eps │ ├── correlations_r_w.eps │ ├── correlations_r_w1.eps │ ├── correlations_r_w1.png │ ├── correlations_r_w2.eps │ ├── correlations_r_w2.png │ ├── dataset1.csv │ ├── dataset2.csv │ ├── heatmap_smaa.eps │ ├── heatmap_smaa.pdf │ ├── heatmap_weights.eps │ ├── heatmap_weights.pdf │ ├── radar_chart_C4.eps │ ├── results.csv │ ├── results_1.csv │ ├── results_2.csv │ ├── robustness_1.eps │ ├── robustness_2.eps │ ├── robustness_3.eps │ ├── robustness_4.eps │ ├── robustness_C1.csv │ ├── robustness_C1.eps │ ├── robustness_C2.csv │ ├── robustness_C2.eps │ ├── robustness_C3.csv │ ├── robustness_C3.eps │ ├── robustness_C4.csv │ ├── robustness_C4.eps │ ├── sensitivity_hist_CRADIS_C_4_weight_values_bar.eps │ ├── sensitivity_hist_TOPSIS_C_4_weight_values_bar.eps │ ├── sensitivity_lineplot_CRADIS_C_1_percentage.eps │ ├── sensitivity_lineplot_CRADIS_C_1_value.eps │ ├── sensitivity_lineplot_CRADIS_C_2_percentage.eps │ ├── sensitivity_lineplot_CRADIS_C_2_value.eps │ ├── sensitivity_lineplot_CRADIS_C_3_percentage.eps │ ├── sensitivity_lineplot_CRADIS_C_3_value.eps │ ├── sensitivity_lineplot_CRADIS_C_4_percentage.eps │ ├── sensitivity_lineplot_CRADIS_C_4_value.eps │ ├── sensitivity_lineplot_TOPSIS_C_1_percentage.eps │ ├── sensitivity_lineplot_TOPSIS_C_1_value.eps │ ├── sensitivity_lineplot_TOPSIS_C_2_percentage.eps │ ├── sensitivity_lineplot_TOPSIS_C_2_value.eps │ ├── sensitivity_lineplot_TOPSIS_C_3_percentage.eps │ ├── sensitivity_lineplot_TOPSIS_C_3_value.eps │ ├── sensitivity_lineplot_TOPSIS_C_4_percentage.eps │ └── sensitivity_lineplot_TOPSIS_C_4_value.eps ├── results_smaa │ ├── ai.csv │ ├── cw.csv │ └── fr.csv ├── results_update2 │ ├── df_comparative.csv │ ├── heatmap.eps │ ├── heatmap.pdf │ └── vmcm_results.csv └── visualizations.py ├── pyproject.toml ├── requirements.txt ├── requirements_python_vermin.txt ├── requirements_src.txt ├── setup.py ├── src └── pyrepo_mcda │ ├── __init__.py │ ├── additions.py │ ├── compromise_rankings.py │ ├── correlations.py │ ├── distance_metrics.py │ ├── mcda_methods │ ├── __init__.py │ ├── ahp.py │ ├── aras.py │ ├── cocoso.py │ ├── codas.py │ ├── copras.py │ ├── cradis.py │ ├── edas.py │ ├── mabac.py │ ├── marcos.py │ ├── mcda_method.py │ ├── multimoora.py │ ├── promethee.py │ ├── prosa_c.py │ ├── pvm.py │ ├── saw.py │ ├── spotis.py │ ├── topsis.py │ ├── vikor.py │ ├── vikor_smaa.py │ ├── vmcm.py │ └── waspas.py │ ├── normalizations.py │ ├── sensitivity_analysis_weights_percentages.py │ ├── sensitivity_analysis_weights_values.py │ └── weighting_methods.py └── tests ├── test_correlations.py ├── test_mcda_methods_1.py ├── test_mcda_methods_2.py └── test_weighting_methods.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | .vscode 113 | 114 | # Spyder project settings 115 | .spyderproject 116 | .spyproject 117 | 118 | # Rope project settings 119 | .ropeproject 120 | 121 | # mkdocs documentation 122 | /site 123 | 124 | # mypy 125 | .mypy_cache/ 126 | .dmypy.json 127 | dmypy.json 128 | 129 | # Pyre type checker 130 | .pyre/ 131 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for Sphinx projects 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Set the OS, Python version and other tools you might need 8 | build: 9 | os: ubuntu-22.04 10 | tools: 11 | python: "3.11" 12 | # You can also specify other tool versions: 13 | # nodejs: "20" 14 | # rust: "1.70" 15 | # golang: "1.20" 16 | 17 | # Build documentation in the "docs/" directory with Sphinx 18 | sphinx: 19 | configuration: docs/conf.py 20 | # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs 21 | # builder: "dirhtml" 22 | # Fail on all warnings to avoid broken references 23 | # fail_on_warning: true 24 | 25 | # Optionally build your docs in additional formats such as PDF and ePub 26 | # formats: 27 | # - pdf 28 | # - epub 29 | 30 | # Optional but recommended, declare the Python requirements required 31 | # to build your documentation 32 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 33 | python: 34 | install: 35 | - requirements: docs/requirements.txt -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 energyinpython 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyrepo-mcda 2 | 3 | The Python 3 library for Multi-Criteria Decision Analysis. 4 | 5 | ## Installation 6 | 7 | ``` 8 | pip install pyrepo-mcda 9 | ``` 10 | 11 | ## Usage 12 | 13 | `pyrepo-mcda` can be used to rank alternatives after providing their performance values in the two-dimensional decision matrix `matrix` 14 | with alternatives in rows and criteria in columns, and criteria weights `weights` and types `types` in vectors. 15 | All criteria weights must sum to 1. Criteria types are equal to 1 for profit criteria and -1 for cost criteria. The TOPSIS method returns a 16 | vector with preference values `pref` assigned to alternatives. To rank alternatives according to TOPSIS preference values, we have to sort them 17 | in descending order because, in the TOPSIS method, the best alternative has the highest preference value. The alternatives are ranked using 18 | the `rank_preferences` method provided in the `additions` module of the `pyrepo-mcda` package. Parameter `reverse = True` means that alternatives 19 | are sorted in descending order. Here is an example of using the TOPSIS method: 20 | 21 | ```python 22 | import numpy as np 23 | from pyrepo_mcda.mcda_methods import TOPSIS 24 | from pyrepo_mcda import distance_metrics as dists 25 | from pyrepo_mcda import normalizations as norms 26 | from pyrepo_mcda.additions import rank_preferences 27 | 28 | matrix = np.array([[256, 8, 41, 1.6, 1.77, 7347.16], 29 | [256, 8, 32, 1.0, 1.8, 6919.99], 30 | [256, 8, 53, 1.6, 1.9, 8400], 31 | [256, 8, 41, 1.0, 1.75, 6808.9], 32 | [512, 8, 35, 1.6, 1.7, 8479.99], 33 | [256, 4, 35, 1.6, 1.7, 7499.99]]) 34 | 35 | weights = np.array([0.405, 0.221, 0.134, 0.199, 0.007, 0.034]) 36 | types = np.array([1, 1, 1, 1, -1, -1]) 37 | 38 | topsis = TOPSIS(normalization_method=norms.vector_normalization, distance_metric=dists.euclidean) 39 | pref = topsis(matrix, weights, types) 40 | rank = rank_preferences(pref, reverse = True) 41 | print(rank) 42 | ``` 43 | 44 | ## License 45 | 46 | `pyrepo-mcda` was created by Aleksandra Bączkiewicz. It is licensed under the terms of the MIT license. 47 | 48 | ## Documentation 49 | 50 | Documentation of this library with instruction for installation and usage is 51 | provided [here](https://pyrepo-mcda.readthedocs.io/en/latest/) -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | 3 | # -- Project information 4 | 5 | project = 'pyrepo-mcda' 6 | copyright = '2023, energyinpython' 7 | author = 'Aleksandra Bączkiewicz' 8 | 9 | release = '0.1' 10 | version = '0.1.5' 11 | 12 | # -- General configuration 13 | 14 | extensions = ['autoapi.extension', 15 | 'nbsphinx', 16 | 'sphinx_rtd_theme', 17 | ] 18 | autoapi_type = 'python' 19 | autoapi_dirs = ["../src"] # location to parse for API reference 20 | 21 | intersphinx_mapping = { 22 | 'python': ('https://docs.python.org/3/', None), 23 | 'sphinx': ('https://www.sphinx-doc.org/en/master/', None), 24 | } 25 | intersphinx_disabled_domains = ['std'] 26 | 27 | templates_path = ['_templates'] 28 | 29 | # -- Options for HTML output 30 | 31 | html_theme = 'sphinx_rtd_theme' 32 | 33 | # -- Options for EPUB output 34 | epub_show_urls = 'footnote' 35 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to pyrepo-mcda documentation! 2 | ======================================== 3 | 4 | pyrepo-mcda is Python 3 library for Multi-Criteria Decision Analysis. 5 | This library includes: 6 | 7 | - MCDA methods: 8 | 9 | - ``TOPSIS`` 10 | - ``CODAS`` 11 | - ``MABAC`` 12 | - ``MULTIMOORA`` 13 | - ``MOORA`` 14 | - ``VIKOR`` 15 | - ``WASPAS`` 16 | - ``EDAS`` 17 | - ``SPOTIS`` 18 | - ``AHP`` 19 | - ``ARAS`` 20 | - ``COPRAS`` 21 | - ``CRADIS`` 22 | - ``MARCOS`` 23 | - ``PROMETHEE II`` 24 | - ``PROSA C`` 25 | - ``SAW`` 26 | - ``VIKOR_SMAA`` 27 | - ``VMCM`` 28 | - ``COCOSO`` 29 | - ``PVM`` 30 | 31 | - Distance metrics: 32 | 33 | - ``euclidean`` (Euclidean distance) 34 | - ``manhattan`` (Manhattan distance) 35 | - ``hausdorff`` (Hausdorff distance) 36 | - ``correlation`` (Correlation distance) 37 | - ``chebyshev`` (Chebyshev distance) 38 | - ``std_euclidean`` (Standardized Euclidean distance) 39 | - ``cosine`` (Cosine distance) 40 | - ``csm`` (Cosine similarity measure) 41 | - ``squared_euclidean`` (Squared Euclidean distance) 42 | - ``bray_curtis`` (Sorensen or Bray-Curtis distance) 43 | - ``canberra`` (Canberra distance) 44 | - ``lorentzian`` (Lorentzian distance) 45 | - ``jaccard`` (Jaccard distance) 46 | - ``dice`` (Dice distance) 47 | - ``bhattacharyya`` (Bhattacharyya distance) 48 | - ``hellinger`` (Hellinger distance) 49 | - ``matusita`` (Matusita distance) 50 | - ``squared_chord`` (Squared-chord distance) 51 | - ``pearson_chi_square`` (Pearson chi square distance) 52 | - ``squared_chi_square`` (Sqaured chi square distance) 53 | 54 | - Correlation coefficients: 55 | 56 | - ``spearman`` (Spearman rank correlation coefficient) 57 | - ``weighted_spearman`` (Weighted Spearman rank correlation coefficient) 58 | - ``pearson_coeff`` (Pearson correlation coefficient) 59 | - ``WS_coeff`` (Similarity rank coefficient - WS coefficient) 60 | 61 | - Methods for normalization of decision matrix: 62 | 63 | - ``linear_normalization`` (Linear normalization) 64 | - ``minmax_normalization`` (Minimum-Maximum normalization) 65 | - ``max_normalization`` (Maximum normalization) 66 | - ``sum_normalization`` (Sum normalization) 67 | - ``vector_normalization`` (Vector normalization) 68 | - ``multimoora_normalization`` (Normalization method dedicated for the MULTIMOORA method) 69 | 70 | - Objective weighting methods for determining criteria weights required by Multi-Criteria Decision Analysis (MCDA) methods: 71 | 72 | - ``equal_weighting`` (Equal weighting method) 73 | - ``entropy_weighting`` (Entropy weighting method) 74 | - ``std_weighting`` (Standard deviation weighting method) 75 | - ``critic_weighting`` (CRITIC weighting method) 76 | - ``gini_weighting`` (Gini coefficient-based weighting method) 77 | - ``merec_weighting`` (MEREC weighting method) 78 | - ``stat_var_weighting`` (Statistical variance weighting method) 79 | - ``cilos_weighting`` (CILOS weighting method) 80 | - ``idocriw_weighting`` (IDOCRIW weighting method) 81 | - ``angle_weighting`` (Angle weighting method) 82 | - ``coeff_var_weighting`` (Coefficient of variation weighting method) 83 | 84 | - Stochastic Multicriteria Acceptability Analysis Method - SMAA combined with VIKOR (``VIKOR_SMAA``) 85 | 86 | - Methods for determination of compromise rankings based on several rankings obtained with different MCDA methods: 87 | 88 | - ``copeland`` (the Copeland method for compromise ranking) 89 | - ``dominance_directed_graph`` (Dominance Directed Graph for compromise ranking) 90 | - ``rank_position_method`` (Rank Position Method for compromise ranking) 91 | - ``improved_borda_rule`` (Improved Borda Rule method for compromise for MULTIMOORA method) 92 | 93 | - Methods for sensitivity analysis: 94 | 95 | - ``Sensitivity_analysis_weights_percentages`` (Method for sensitivity analysis considering percentage modification of criteria weights) 96 | - ``Sensitivity_analysis_weights_values`` (Method for sensitivity analysis considering setting different values as chosen criterion weight) 97 | 98 | - additions: 99 | 100 | - ``rank_preferences`` (Method for ordering alternatives according to their preference values obtained with MCDA methods) 101 | 102 | Check out the :doc:`usage` section for further information, including 103 | how to :ref:`installation` the project. 104 | 105 | .. note:: 106 | 107 | This project is under active development. 108 | 109 | Contents 110 | --------- 111 | 112 | .. toctree:: 113 | :maxdepth: 2 114 | 115 | usage 116 | example_pyrepo_mcda 117 | example_pyrepo_mcda_update 118 | example_crispyn 119 | example_pyrepo_mcda_update2 120 | autoapi/index 121 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx-autoapi 2 | nbsphinx 3 | sphinx_rtd_theme -------------------------------------------------------------------------------- /examples/criteria_electric_cars.csv: -------------------------------------------------------------------------------- 1 | Cj,Name,Unit,Type 2 | C1,Max speed,mph,1 3 | C2,Battery capacity,kWh,1 4 | C3,Electric motor,kW,1 5 | C4,Maximum torque,Nm,1 6 | C5,Horsepower,hp,1 7 | C6,EPA Fuel Economy Combined,MPGe,1 8 | C7,EPA Fuel Economy City,MPGe,1 9 | C8,EPA Fuel Economy Highway,MPGe,1 10 | C9,EPA range,miles,1 11 | C10,"Turning Diameter / Radius, curb to curb",feet,-1 12 | C11,Base price,USD,-1 13 | -------------------------------------------------------------------------------- /examples/data.csv: -------------------------------------------------------------------------------- 1 | Ai,C1,C2,C3,C4 2 | A1,45,3600,45,0.9 3 | A2,25,3800,60,0.8 4 | A3,23,3100,35,0.9 5 | A4,14,3400,50,0.7 6 | A5,15,3300,40,0.8 7 | A6,28,3000,30,0.6 8 | weights,0.2857,0.3036,0.2321,0.1786 9 | type,1,-1,1,1 10 | -------------------------------------------------------------------------------- /examples/dataset_cars.csv: -------------------------------------------------------------------------------- 1 | Ai,C1,C2,C3,C4,C5,C6,C7,C8,C9,C10,C11 2 | A1,155.3,74,340,673,456,111,115,106,244,39.8,65440 3 | A2,162.2,79.5,247,639,283,113,118,107,263,38.8,60440 4 | A3,112.5,68,198,430,266,98,105,91,230,38.1,56575 5 | A4,90.1,66,150,360,201.2,120,131,109,259,34.8,32495 6 | A5,99.4,77,150,310,201.2,97,102,90,260,36.4,45635 7 | A6,89.5,40,110,320,147.5,111,123,99,226,34.8,28425 8 | A7,124.3,95,125,247,187.7,78,78,77,222,40,84595 9 | A8,155.3,79.2,160,300,214.6,79,79,80,227,38.4,105150 10 | A9,162.2,100,205,420,502.9,120,124,115,402,40.3,96440 11 | A10,96.3,39.2,100,395,134.1,120,132,108,258,34.8,35245 12 | A11,162.2,100,205,420,502.9,98,103,93,371,40.8,127940 13 | A12,102.5,38.3,101,295,136.1,133,145,121,170,34.8,34250 14 | Type,1,1,1,1,1,1,1,1,1,-1,-1 15 | -------------------------------------------------------------------------------- /examples/dataset_waste.csv: -------------------------------------------------------------------------------- 1 | Alternative,C1,C2,C3,C4,C5,C6,C7,C8,C9,C10,C11,C12,C13,C14 2 | A1 (AD),682.56,20,8.4167,8.4167,370.2,17.811,127.27,7.55,0.0951,20.092,0.003483,8.4167,8.1467,8.4167 3 | A2 (LFGR),69.1,10,8.4167,8.4167,16.417,4.1029,110.06,4.62,0.1156,148.41,0.10328,9.6667,2.9167,1.4167 4 | A3 (INC),882.43,26,9.6667,1.4167,733.11,2.3826,11.565,6.96,0.1005,1169.8,366.6,1.4167,0.4167,1.4167 5 | A4 (GAS),2842.2,30,1.4167,1.4167,1393.5,81.03,323.95,10.99,0.0626,288.62,0.2882,1.4167,1.4167,1.4167 6 | A5 (PYR),434.12,15,1.4167,1.4167,7.0315,18.37,247.49,5.99,0.0501,358.56,16.1,1.4167,1.4167,1.4167 7 | Type,1,1,1,1,-1,-1,1,-1,-1,-1,-1,-1,1,1 8 | -------------------------------------------------------------------------------- /examples/electric_cars_2021.csv: -------------------------------------------------------------------------------- 1 | Ai,Name,C1 Max speed [mph],C2 Battery [kWh],C3 Electric motor [kW] Front,C4 Torque [Nm] Front,C5 Mechanical horsepower [hp],C6 EPA Fuel Economy Combined [MPGe],C7 EPA Fuel Economy City [MPGe],C8 EPA Fuel Economy Highway [MPGe],C9 EPA range [miles],"C10 Turning Diameter / Radius, curb to curb [feet]",C11 Base price [$] 2 | A1,Tesla Model Y,155.3,74,340,673,456,111,115,106,244,39.8,65440 3 | A2,Tesla Model 3,162.2,79.5,247,639,283,113,118,107,263,38.8,60440 4 | A3,Ford Mustang Mach-E,112.5,68,198,430,266,98,105,91,230,38.1,56575 5 | A4,Chevrolet Bolt EV and EUV,90.1,66,150,360,201.2,120,131,109,259,34.8,32495 6 | A5,Volkswagen ID.4,99.4,77,150,310,201.2,97,102,90,260,36.4,45635 7 | A6,Nissan Leaf,89.5,40,110,320,147.5,111,123,99,226,34.8,28425 8 | A7,Audi e-tron and e-tron Sportback,124.3,95,125,247,187.7,78,78,77,222,40,84595 9 | A8,Porsche Taycan,155.3,79.2,160,300,214.6,79,79,80,227,38.4,105150 10 | A9,Tesla Model S,162.2,100,205,420,502.9,120,124,115,402,40.3,96440 11 | A10,Hyundai Kona Electric,96.3,39.2,100,395,134.1,120,132,108,258,34.8,35245 12 | A11,Tesla Model X,162.2,100,205,420,502.9,98,103,93,371,40.8,127940 13 | A12,Hyundai Ioniq Electric,102.5,38.3,101,295,136.1,133,145,121,170,34.8,34250 14 | Type,,1,1,1,1,1,1,1,1,1,-1,-1 15 | -------------------------------------------------------------------------------- /examples/example1criteria.csv: -------------------------------------------------------------------------------- 1 | Cj,Criterion Name,Unit of Measurement,Preference Direction,Weight of Criterion,Preference function for PROMETHEE II and PROSA-C,Preference Threshold (p) for PROMETHEE II and PROSA-C 2 | C1,Investment cost,mln PLN,Min,20,V-shape,7280 3 | C2,Payback period,years,Min,5,V-shape,4 4 | C3,Distance from power stations,km,Min,5,V-shape,13.4 5 | C4,Mean sea water depth,m,Min,1.67,V-shape,7.4 6 | C5,Undersea geological condition,points,Min,1.67,V-shape,3 7 | C6,Employment,number,Max,11.67,V-shape,1662 8 | C7,Conflict with fisheries,points,Min,11.67,V-shape,3 9 | C8,Density of shipping traffic,points,Min,5,V-shape,3 10 | C9,Distance from shore,km,Max,5,V-shape,13.8 11 | C10,Influence on protected areas,points,Min,16.67,V-shape,3 12 | C11,CO2 reduction,tonnes,Max,8.33,V-shape,766240 13 | C12,SO2 reduction,tonnes,Max,8.33,V-shape,17820 14 | -------------------------------------------------------------------------------- /examples/example2criteria.csv: -------------------------------------------------------------------------------- 1 | Cj,Criterion Name,Unit of Measurement,Preference Direction 2 | C1,yearly amount of energy generated,(MWh),Max 3 | C2,average wind speed at the height of 100 m,(m/s),Max 4 | C3,distance from power grid connection,(km),Min 5 | C4,power grid voltage on the site of connection and its vicinity,(kV),Max 6 | C5,distance from the road network,(km),Min 7 | C6,location in Natura 2000 protected area,[0;1],Min 8 | C7,social acceptance,(%),Max 9 | C8,investment cost,(PLN),Min 10 | C9,operational costs per year,(PLN),Min 11 | C10,profits from generated energy per year,(PLN),Max 12 | -------------------------------------------------------------------------------- /examples/example_v_1_3/SHARES_2022_POPULATION.csv: -------------------------------------------------------------------------------- 1 | Country,C1,C2,C3,C4,C5,C6,C7,C8,C9,C10,C11,C12,C13,C14,C15,C16,C17,C18 2 | Belgium,0.258046668410569,9.997550230369844,5.089075266945743,1.842369587134993,1.5635826829636321,0.09032848061948645,0.28131682109154343,0.032662323635394265,6.567265640312136,12.291719915511116,0.6965883169904894,1.9935954265343263,1.5692160332625702,0.42437939258314716,0.01680642361178358,18.346316815410518,14.998710080366697,6.423807073959966 3 | Bulgaria,5.1696547445896925,1.7748688282989007,2.632538670848993,0.0018758603566606917,0.1586082679223394,0.013862299711197807,0.11985215525453737,0.003859827426396821,2.6766653194787438,15.98901389207124,0.23219982871607095,2.2497542308110163,2.2497542308110163,0.0,0.0,9.599972090691871,18.4709679589094,2.4901050514136918 4 | Czechia,1.8005210338179052,0.5438897322137053,2.147238147834679,2.17436363968303,2.2458762006015762,0.00879052846104774,0.19548756906510756,0.00949899060608991,3.4434401357763416,27.35222407546393,2.633863025755115,3.673275336091421,3.2441200596346365,0.42915527721747887,0.0,8.698111666513102,33.659362441113934,3.657217223984656 5 | Denmark,0.02127250157489163,27.043304599364593,3.224470111110733,8.230938652437592,2.7270950383252006,0.374281606627825,0.47743030806582876,0.0,4.132515638248243,21.82584305225916,34.92497916035291,6.439140989406512,4.486096623432345,1.9530443659741685,0.0,40.39536898433962,63.18996320712634,4.984227552260863 6 | Germany,2.0511418775112893,13.338057259162392,6.229444763132374,1.059245267292032,4.242636123516234,0.11295469403772289,0.5348409649521289,0.0,3.5069252332649072,17.597310317929775,2.456059970308441,2.0493786546493364,1.4047598941549206,0.6446187598937224,0.2766994606877576,26.272729629630167,22.379448405737808,4.154720892326842 7 | Estonia,0.2203770042859417,4.623617240177926,3.847944848910794,9.705091515517392,0.29769276683516094,0.03771247683579167,0.0234797880456166,0.0,3.0704963920900803,33.58622491732968,28.379322824216324,8.525365649093406,5.883934327029064,2.641431319811743,0.0,18.63353111137141,70.49091338313075,3.1316886572718348 8 | Greece,4.181076896248889,8.998586621594981,5.8691948560687015,0.042606794443708296,0.425640479027192,0.00528627283054274,0.05474408974297935,0.0,1.5368971418333575,11.504421354097055,0.0,4.425490618255715,4.425490618255715,0.0,0.0,19.457075281301275,15.929911971396729,1.5969275038428143 9 | Spain,5.4643193679963815,11.613328398501858,6.475727944740793,0.8358472927215297,0.35278465515481,0.041522888958090746,0.25466319353533845,0.01799851460883906,3.047400039462067,8.93824798753051,0.0,2.5535170161347733,2.5052945516099974,0.048222465726473825,0.002404842392809564,24.427823072060985,11.494169845385562,3.3615846370576636 10 | France,7.592206640669173,5.270612974363111,2.48839727177327,0.5921756016202575,0.815666685894057,0.044378090631730274,0.25899329435550855,0.036785099523845836,4.567672836743617,13.37274646623033,2.873919294317938,6.046557431515314,5.61461292426876,0.43194450709921667,0.24285377893731466,16.418902690619134,22.536076971442906,4.907829321181033 11 | Croatia,15.184459047123413,5.040415759501127,0.338167208700504,1.6020087124139601,1.0728293475010389,0.014616671365933039,0.28993725197776976,0.041214220938014995,0.5437167701670376,27.640735985376608,2.739275598379724,0.3655995425011748,0.3655995425011748,0.0,0.0,22.892111930569957,30.74561113117685,0.889484914319299 12 | Ireland,1.2975291102536677,18.948653615688844,0.2519990258900981,0.03661062080188079,0.6257127757211259,0.10515744291901746,0.03235974070376229,2.777310057462405e-07,4.556849291028228,3.5120319707257153,0.0,1.9829010332798156,1.962604793197792,0.020296239726292707,0.0,21.022987689337796,5.494933004005531,4.694366753465017 13 | Italy,7.00542686393744,3.0636253470748573,4.096230072868038,0.6347714798474196,2.7670016481243573,0.033239416960148135,0.3093032690609049,0.15715129425508834,2.664686757524331,12.196838443511552,0.632395915489467,4.648763581474566,4.619066045472064,0.02969753717139685,0.5223150908367427,17.067361427086738,18.00031302656899,3.1643807375463644 14 | Cyprus,0.0,2.223186907334435,5.720158383119359,0.0,0.5497200521717024,0.0,0.0,0.0,2.750067701626497,16.81924394139526,0.10600031612514577,5.3721146616853,5.284557676811779,0.08755698443138922,3.9649904786643155,8.49306534174123,26.262349406712683,2.750067701626497 15 | Latvia,13.26255078882819,0.7333401618653161,0.1892087921303239,2.529070073575628,1.1443015497209925,0.08396688478305024,0.15806164343249152,0.0056258399675437705,0.8398742481035656,53.78292918539021,22.62846166640988,0.0,0.0,0.0,0.0,17.61081699815061,76.41139086779364,1.0874219928274291 16 | Lithuania,1.337485713104571,5.818576192855448,1.0479945741230037,1.2067259160555355,0.9634195733567879,0.03590219529735944,0.007909605851465325,0.012914903289310968,4.2622981199558945,22.217008301502712,20.658567507888456,2.2562215778485943,1.7642423864878023,0.4919791913607921,0.0,10.317475265484866,45.131797385457865,4.319024824679134 17 | Luxembourg,1.3178575390031253,4.40703605687662,3.6806449580645713,3.57508894060555,1.2130228471777837,0.0880747676236487,0.26045064820567804,0.0,19.78603420840196,6.693179267954453,15.95703371723141,0.8062650929582876,0.36322789383898596,0.4430371991193018,0.0,13.84512492620821,23.456478074735397,20.13455962764004 18 | Hungary,0.21224607168327825,0.5775400459902508,4.19938426939388,1.2983303433477722,0.237834950216792,0.01289233387105597,0.1241679696893697,0.001056748677109426,3.1262223901100317,17.43208733400007,1.785414744127625,0.7977610720806357,0.7550683717944352,0.042692700306842496,0.0,6.387218629147869,20.015263148660182,3.2567984200656204 19 | Malta,0.0,0.0009572705582460444,4.778298822775164,0.0,0.12195297607736323,0.01615773200427663,0.0,0.0,2.419904651122615,1.1181950288979616,0.0,3.573181236575548,3.573181236575548,0.0,5.217499219726242,4.8850513368306485,9.908875484431954,2.4360623835107904 20 | Netherlands,0.042324838886200596,10.457535260733643,8.348237099753778,2.8050264651629,1.2807982315854676,0.21592364208712433,0.22278054294912666,0.0,3.4897460494971426,6.070048028864388,2.3359005335327723,2.783449651610808,1.9708824370097968,0.8125672146010112,0.0,22.495217709704324,11.189398216281901,3.928450234874483 21 | Austria,40.56493833507315,7.053217992925437,3.631032821397741,0.784970035735888,0.4634427464567322,0.20314750679062057,1.3932803834399403,0.9261822386612034,4.490053563181088,36.949436820360205,3.437998750185017,5.323936845920042,5.323936845920042,0.0,0.0,49.97499179467841,45.71137241423783,7.012663691850108 22 | Poland,0.539700645119387,4.413354922521727,1.9368599582415293,1.3831496080985723,0.3955653976722701,0.0062637690930011725,0.12575828151882038,0.0005282377649451294,3.262207979607133,19.861180727085763,1.511228244606952,1.4087006852660284,0.9448017996104665,0.4638988853844838,0.0,8.53608024188609,22.781109658043054,3.2545884832379373 23 | Portugal,10.470575206321612,11.27686474803715,2.9229253436181963,2.943600749494641,0.6341706599528866,0.01945088538087461,0.2181076916032605,0.0027722180319592984,3.295089026879914,19.193324901502525,0.0,8.213937048362054,8.213937048362054,0.0,0.0,28.007805918870886,27.407261948898583,3.535419821519271 24 | Romania,7.11894530931017,3.0266171289363686,0.8978530278790208,0.06714417095904913,0.011694916122947383,0.043540645615284375,0.1974492923837814,0.004737625495242079,2.9338654075853134,17.76719888795851,0.4319678821874596,0.0,0.0,0.0,0.0,10.876526991924097,18.199166772351568,2.93847084527704 25 | Slovenia,18.60376130658036,0.024419823698022952,2.6343497048187627,0.6625955461802029,0.4370064478592242,0.03723339918753975,0.3464510512628252,0.009647746561755521,3.728320993935022,22.75777992387931,2.403695284693287,2.8459046279862186,2.8459046279862186,0.0,0.0,21.96880063402272,28.007379839406223,4.121653190994599 26 | Slovakia,6.738527848393807,0.008327030392778864,1.0283882533241873,1.6596604272682711,0.6977218765226199,0.00875923388396662,0.182849007233502,0.05182546712686892,3.1035683215596337,18.044690605500346,2.8923730769910163,1.587942212577226,1.587942212577226,0.0,0.0,9.88919172717892,22.5250058880765,3.3470020306503825 27 | Finland,22.782619284201964,20.003185099565787,0.6080366667561845,18.17203796662762,1.3329816343594303,0.2451958802798941,0.42251958431510095,0.0,9.837068045530106,89.97171912683677,38.71840334621369,11.707808790209365,7.825212547544347,3.8825962426650173,0.7004923596505631,62.23114518637529,141.09842362651514,10.504783510665812 28 | Sweden,55.65195245536736,27.836187304146467,1.6288178832156595,9.24595208186197,1.4697712853579195,0.6929145147214123,1.6296663211614335,0.1911488316571833,16.033410601621114,57.15130549889087,31.452890811097934,15.503777723733455,5.647566847800193,9.856210875933261,0.4499815061260049,93.31895134154828,104.55795552109645,18.54714027289237 29 | Iceland,320.7739738151432,0.131142003944207,0.0,0.0,135.20145683166422,1.392213946120644,0.0,0.0,6.418638770172865,21.66529479758032,230.65951712700132,0.0,0.0,0.0,0.0,454.7143586145308,252.32481193255512,7.810852716825073 30 | Norway,230.69038628492223,21.83892744508568,0.2739734708134342,0.0,0.32485792596497504,3.1725204238683054,1.1832783384052774,0.2630916208778549,6.416864629778794,15.816397489894513,3.644987300171236,11.519480744737129,6.143843358948034,5.375637385789095,0.0,248.50925483892968,30.98086554217578,11.035755013114557 31 | -------------------------------------------------------------------------------- /examples/main_crispyn.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import time 3 | 4 | import numpy as np 5 | import pandas as pd 6 | import matplotlib.pyplot as plt 7 | import matplotlib 8 | import seaborn as sns 9 | 10 | from pyrepo_mcda.mcda_methods import VIKOR 11 | from pyrepo_mcda.mcda_methods import VIKOR_SMAA 12 | from pyrepo_mcda.additions import rank_preferences 13 | from pyrepo_mcda import correlations as corrs 14 | from pyrepo_mcda import normalizations as norms 15 | from pyrepo_mcda import weighting_methods as mcda_weights 16 | 17 | 18 | # Functions for visualizations 19 | 20 | def plot_barplot(df_plot, x_name, y_name, title): 21 | """ 22 | Display stacked column chart of weights for criteria for `x_name == Weighting methods` 23 | and column chart of ranks for alternatives `x_name == Alternatives` 24 | Parameters 25 | ---------- 26 | df_plot : dataframe 27 | dataframe with criteria weights calculated different weighting methods 28 | or with alternaives rankings for different weighting methods 29 | x_name : str 30 | name of x axis, Alternatives or Weighting methods 31 | y_name : str 32 | name of y axis, Ranks or Weight values 33 | title : str 34 | name of chart title, Weighting methods or Criteria 35 | Examples 36 | ---------- 37 | >>> plot_barplot(df_plot, x_name, y_name, title) 38 | """ 39 | 40 | list_rank = np.arange(1, len(df_plot) + 1, 1) 41 | stacked = True 42 | width = 0.5 43 | if x_name == 'Alternatives': 44 | stacked = False 45 | width = 0.8 46 | elif x_name == 'Alternative': 47 | pass 48 | else: 49 | df_plot = df_plot.T 50 | ax = df_plot.plot(kind='bar', width = width, stacked=stacked, edgecolor = 'black', figsize = (9,4)) 51 | ax.set_xlabel(x_name, fontsize = 12) 52 | ax.set_ylabel(y_name, fontsize = 12) 53 | 54 | if x_name == 'Alternatives': 55 | ax.set_yticks(list_rank) 56 | 57 | ax.set_xticklabels(df_plot.index, rotation = 'horizontal') 58 | ax.tick_params(axis = 'both', labelsize = 12) 59 | 60 | plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc='lower left', 61 | ncol=4, mode="expand", borderaxespad=0., edgecolor = 'black', title = title, fontsize = 11) 62 | 63 | ax.grid(True, linestyle = '--') 64 | ax.set_axisbelow(True) 65 | plt.tight_layout() 66 | plt.show() 67 | 68 | 69 | def draw_heatmap(data, title): 70 | """ 71 | Display heatmap with correlations of compared rankings generated using different methods 72 | Parameters 73 | ---------- 74 | data : dataframe 75 | dataframe with correlation values between compared rankings 76 | title : str 77 | title of chart containing name of used correlation coefficient 78 | Examples 79 | ---------- 80 | >>> draw_heatmap(data, title) 81 | """ 82 | 83 | plt.figure(figsize = (6, 4)) 84 | sns.set(font_scale=1.0) 85 | heatmap = sns.heatmap(data, annot=True, fmt=".2f", cmap="RdYlBu", 86 | linewidth=0.5, linecolor='w') 87 | plt.yticks(va="center") 88 | plt.xlabel('Weighting methods') 89 | plt.title('Correlation coefficient: ' + title) 90 | plt.tight_layout() 91 | plt.show() 92 | 93 | def draw_heatmap_smaa(data, title): 94 | """ 95 | Display heatmap with correlations of compared rankings generated using different methods 96 | Parameters 97 | ---------- 98 | data : dataframe 99 | dataframe with correlation values between compared rankings 100 | title : str 101 | title of chart containing name of used correlation coefficient 102 | Examples 103 | ---------- 104 | >>> draw_heatmap(data, title) 105 | """ 106 | 107 | sns.set(font_scale=1.0) 108 | heatmap = sns.heatmap(data, annot=True, fmt=".2f", cmap="RdYlBu_r", 109 | linewidth=0.05, linecolor='w') 110 | plt.yticks(rotation=0) 111 | plt.ylabel('Alternatives') 112 | plt.tick_params(labelbottom=False,labeltop=True) 113 | 114 | plt.title(title) 115 | plt.tight_layout() 116 | plt.show() 117 | 118 | 119 | def plot_boxplot(data): 120 | """ 121 | Display boxplot showing distribution of criteria weights determined with different methods. 122 | Parameters 123 | ---------- 124 | data : dataframe 125 | dataframe with correlation values between compared rankings 126 | Examples 127 | --------- 128 | >>> plot_boxplot(data) 129 | """ 130 | 131 | df_melted = pd.melt(data) 132 | plt.figure(figsize = (7, 4)) 133 | ax = sns.boxplot(x = 'variable', y = 'value', data = df_melted, width = 0.6) 134 | ax.grid(True, linestyle = '--') 135 | ax.set_axisbelow(True) 136 | ax.set_xlabel('Criterion', fontsize = 12) 137 | ax.set_ylabel('Different weights distribution', fontsize = 12) 138 | plt.tight_layout() 139 | plt.show() 140 | 141 | 142 | # Create dictionary class 143 | class Create_dictionary(dict): 144 | 145 | # __init__ function 146 | def __init__(self): 147 | self = dict() 148 | 149 | # Function to add key:value 150 | def add(self, key, value): 151 | self[key] = value 152 | 153 | 154 | 155 | # main 156 | def main(): 157 | 158 | matrix = np.array([[155.3, 74.0, 340, 673, 456.0, 111, 115, 106, 244, 39.8, 65440], 159 | [162.2, 79.5, 247, 639, 283.0, 113, 118, 107, 263, 38.8, 60440], 160 | [112.5, 68.0, 198, 430, 266.0, 98, 105, 91, 230, 38.1, 56575], 161 | [90.1, 66.0, 150, 360, 201.2, 120, 131, 109, 259, 34.8, 32495], 162 | [99.4, 77.0, 150, 310, 201.2, 97, 102, 90, 260, 36.4, 45635], 163 | [89.5, 40.0, 110, 320, 147.5, 111, 123, 99, 226, 34.8, 28425], 164 | [124.3, 95.0, 125, 247, 187.7, 78, 78, 77, 222, 40.0, 84595], 165 | [155.3, 79.2, 160, 300, 214.6, 79, 79, 80, 227, 38.4, 105150], 166 | [162.2, 100.0, 205, 420, 502.9, 120, 124, 115, 402, 40.3, 96440], 167 | [96.3, 39.2, 100, 395, 134.1, 120, 132, 108, 258, 34.8, 35245], 168 | [162.2, 100.0, 205, 420, 502.9, 98, 103, 93, 371, 40.8, 127940], 169 | [102.5, 38.3, 101, 295, 136.1, 133, 145, 121, 170, 34.8, 34250]]) 170 | 171 | types = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1]) 172 | 173 | 174 | 175 | # matrix = np.array([[80, 16, 2, 5], 176 | # [110, 32, 2, 9], 177 | # [130, 64, 4, 9], 178 | # [185, 64, 4, 1], 179 | # [135, 64, 3, 4], 180 | # [140, 32, 3, 5], 181 | # [185, 64, 6, 7], 182 | # [110, 16, 3, 3], 183 | # [120, 16, 4, 3], 184 | # [340, 128, 6, 5]]) 185 | 186 | # types = np.array([-1, 1, 1, 1]) 187 | 188 | # Symbols for alternatives Ai 189 | list_alt_names = [r'$A_{' + str(i) + '}$' for i in range(1, matrix.shape[0] + 1)] 190 | # Symbols for columns Cj 191 | cols = [r'$C_{' + str(j) + '}$' for j in range(1, matrix.shape[1] + 1)] 192 | 193 | 194 | # Part 1 - study with single weighting method 195 | 196 | # Determine criteria weights with chosen weighting method 197 | weights = mcda_weights.entropy_weighting(matrix) 198 | 199 | # Create the VIKOR method object 200 | # vikor = VIKOR(normalization_method=norms.minmax_normalization) 201 | vikor = VIKOR(normalization_method=norms.minmax_normalization) 202 | 203 | # Calculate alternatives preference function values with VIKOR method 204 | pref = vikor(matrix, weights, types) 205 | 206 | # when there is only one (single) preference vector 207 | rank = rank_preferences(pref, reverse = False) 208 | 209 | print(rank) 210 | 211 | 212 | # Part 2 - study with several weighting methods 213 | # Create a list with weighting methods that you want to explore 214 | weighting_methods_set = [ 215 | mcda_weights.entropy_weighting, 216 | # mcda_weights.std_weighting, 217 | mcda_weights.critic_weighting, 218 | mcda_weights.gini_weighting, 219 | mcda_weights.merec_weighting, 220 | mcda_weights.stat_var_weighting, 221 | # mcda_weights.cilos_weighting, 222 | mcda_weights.idocriw_weighting, 223 | mcda_weights.angle_weighting, 224 | mcda_weights.coeff_var_weighting 225 | ] 226 | 227 | 228 | #df_weights = pd.DataFrame(weights.reshape(1, -1), index = ['Weights'], columns = cols) 229 | # Create dataframes for weights, preference function values and rankings determined using different weighting methods 230 | df_weights = pd.DataFrame(index = cols) 231 | df_preferences = pd.DataFrame(index = list_alt_names) 232 | df_rankings = pd.DataFrame(index = list_alt_names) 233 | 234 | # Create the VIKOR method object 235 | vikor = VIKOR(normalization_method=norms.minmax_normalization) 236 | 237 | for weight_type in weighting_methods_set: 238 | 239 | if weight_type.__name__ in ["cilos_weighting", "idocriw_weighting", "angle_weighting", "merec_weighting"]: 240 | weights = weight_type(matrix, types) 241 | else: 242 | weights = weight_type(matrix) 243 | 244 | df_weights[weight_type.__name__[:-10].upper().replace('_', ' ')] = weights 245 | pref = vikor(matrix, weights, types) 246 | 247 | rank = rank_preferences(pref, reverse = False) 248 | 249 | df_preferences[weight_type.__name__[:-10].upper().replace('_', ' ')] = pref 250 | df_rankings[weight_type.__name__[:-10].upper().replace('_', ' ')] = rank 251 | 252 | 253 | 254 | # plot criteria weights distribution using box chart 255 | plot_boxplot(df_weights.T) 256 | 257 | # plot stacked column chart of criteria weights 258 | plot_barplot(df_weights, 'Weighting methods', 'Weight value', 'Criteria') 259 | 260 | # plot column chart of alternatives rankings 261 | plot_barplot(df_rankings, 'Alternatives', 'Rank', 'Weighting methods') 262 | 263 | # Plot heatmaps of rankings correlation coefficient 264 | # Create dataframe with rankings correlation values 265 | results = copy.deepcopy(df_rankings) 266 | method_types = list(results.columns) 267 | dict_new_heatmap_rw = Create_dictionary() 268 | 269 | for el in method_types: 270 | dict_new_heatmap_rw.add(el, []) 271 | 272 | for i, j in [(i, j) for i in method_types[::-1] for j in method_types]: 273 | dict_new_heatmap_rw[j].append(corrs.weighted_spearman(results[i], results[j])) 274 | 275 | df_new_heatmap_rw = pd.DataFrame(dict_new_heatmap_rw, index = method_types[::-1]) 276 | df_new_heatmap_rw.columns = method_types 277 | 278 | # Plot heatmap with rankings correlation 279 | draw_heatmap(df_new_heatmap_rw, r'$r_w$') 280 | 281 | 282 | 283 | # SMAA method 284 | cols_ai = [str(el) for el in range(1, matrix.shape[0] + 1)] 285 | 286 | # criteria number 287 | n = matrix.shape[1] 288 | # SMAA iterations number (number of weight vectors for SMAA) 289 | iterations = 10000 290 | 291 | start = time.time() 292 | # create the VIKOR_SMAA object 293 | vikor_smaa = VIKOR_SMAA() 294 | # generate matrix with weight vectors for SMAA 295 | weight_vectors = vikor_smaa._generate_weights(n, iterations) 296 | 297 | # run the VIKOR_SMAA method 298 | rank_acceptability_index, central_weight_vector, rank_scores = vikor_smaa(matrix, weight_vectors, types) 299 | 300 | end = time.time() - start 301 | print('Run time: ', end) 302 | 303 | acc_in_df = pd.DataFrame(rank_acceptability_index, index = list_alt_names, columns = cols_ai) 304 | 305 | 306 | matplotlib.rcdefaults() 307 | plot_barplot(acc_in_df, 'Alternative', 'Rank acceptability index', 'Rank') 308 | 309 | draw_heatmap_smaa(acc_in_df, 'Rank acceptability indexes') 310 | 311 | central_weights_df = pd.DataFrame(central_weight_vector, index = list_alt_names, columns = cols) 312 | 313 | 314 | rank_scores_df = pd.DataFrame(rank_scores, index = list_alt_names, columns = ['Rank']) 315 | 316 | 317 | 318 | 319 | if __name__ == '__main__': 320 | main() -------------------------------------------------------------------------------- /examples/main_pyrepo_mcda_update2.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import matplotlib.pyplot as plt 4 | import seaborn as sns 5 | import copy 6 | 7 | from pyrepo_mcda.additions import rank_preferences 8 | from pyrepo_mcda.mcda_methods import VMCM, TOPSIS, EDAS, VIKOR, PROMETHEE_II, PROSA_C, COCOSO 9 | from pyrepo_mcda import normalizations as norms 10 | from pyrepo_mcda import correlations as corrs 11 | 12 | 13 | # heat maps with correlations 14 | def draw_heatmap(df_new_heatmap, title): 15 | """ 16 | Visualization method to display heatmap with correlations of compared rankings generated using different methods 17 | 18 | Parameters 19 | ---------- 20 | data : DataFrame 21 | DataFrame with correlation values between compared rankings 22 | 23 | Examples 24 | --------- 25 | >>> draw_heatmap(df_new_heatmap) 26 | """ 27 | 28 | plt.figure(figsize = (11, 5)) 29 | sns.set(font_scale = 1.2) 30 | heatmap = sns.heatmap(df_new_heatmap, annot=True, fmt=".4f", cmap="YlGnBu", 31 | linewidth=0.5, linecolor='w') 32 | plt.yticks(va="center") 33 | plt.yticks(va="center") 34 | plt.xlabel('MCDA methods') 35 | plt.ylabel('MCDA methods') 36 | plt.title('Correlation coefficient: ' + title) 37 | plt.tight_layout() 38 | plt.savefig('./results_update2/heatmap.pdf') 39 | plt.savefig('./results_update2/heatmap.eps') 40 | plt.show() 41 | 42 | 43 | # Create dictionary class 44 | class Create_dictionary(dict): 45 | 46 | # __init__ function 47 | def __init__(self): 48 | self = dict() 49 | 50 | # Function to add key:value 51 | def add(self, key, value): 52 | self[key] = value 53 | 54 | 55 | 56 | def main(): 57 | # Load data including decision matrix and criteria types 58 | data = pd.read_csv('dataset_waste.csv', index_col = 'Alternative') 59 | df_data = data.iloc[:len(data) - 1, :] 60 | 61 | matrix = df_data.to_numpy() 62 | 63 | # Initialize the VMCM method object 64 | vmcm = VMCM() 65 | 66 | # Print the criteria to be eliminated 67 | vmcm._elimination(matrix) 68 | 69 | # Provide criteria types 70 | types = data.iloc[len(data) - 1, :].to_numpy() 71 | 72 | # Determine criteria weights 73 | weights = vmcm._weighting(matrix) 74 | 75 | df_vmcm = pd.DataFrame(index=df_data.index) 76 | 77 | normalized_matrix = vmcm._normalization(matrix) 78 | print(normalized_matrix) 79 | 80 | # Determine pattern and anti-pattern 81 | pattern, antipattern = vmcm._pattern_determination(matrix, types) 82 | 83 | # Calculate value of the synthetic measure for each object 84 | pref = vmcm(matrix, weights, types, pattern, antipattern) 85 | 86 | # Classify evaluated objects according to synthetic measure values 87 | classes = vmcm._classification(pref) 88 | 89 | # Rank evaluated objects according to synthetic measure values 90 | rank = rank_preferences(pref, reverse = True) 91 | 92 | df_vmcm['Synthetic measure'] = pref 93 | df_vmcm['Class'] = classes 94 | df_vmcm['Rank'] = rank 95 | 96 | df_vmcm.to_csv('./results_update2/vmcm_results.csv') 97 | 98 | # COMAPRATIVE ANALYSIS 99 | df_comparative = pd.DataFrame(index=df_data.index) 100 | df_comparative['VMCM'] = rank 101 | 102 | topsis = TOPSIS(normalization_method=norms.minmax_normalization) 103 | pref = topsis(matrix, weights, types) 104 | rank = rank_preferences(pref, reverse = True) 105 | df_comparative['TOPSIS'] = rank 106 | 107 | vikor = VIKOR() 108 | pref = vikor(matrix, weights, types) 109 | rank = rank_preferences(pref, reverse = False) 110 | df_comparative['VIKOR'] = rank 111 | 112 | edas = EDAS() 113 | pref = edas(matrix, weights, types) 114 | rank = rank_preferences(pref, reverse = True) 115 | df_comparative['EDAS'] = rank 116 | 117 | promethee_II = PROMETHEE_II() 118 | preference_functions = [promethee_II._linear_function for pf in range(len(weights))] 119 | pref = promethee_II(matrix, weights, types, preference_functions) 120 | rank = rank_preferences(pref, reverse=True) 121 | df_comparative['PROMETHEE II'] = rank 122 | 123 | prosa_c = PROSA_C() 124 | pref = prosa_c(matrix, weights, types, preference_functions) 125 | rank = rank_preferences(pref, reverse=True) 126 | df_comparative['PROSA C'] = rank 127 | 128 | cocoso = COCOSO(normalization_method=norms.minmax_normalization) 129 | pref = cocoso(matrix, weights, types) 130 | rank = rank_preferences(pref, reverse=True) 131 | df_comparative['COCOSO'] = rank 132 | 133 | df_comparative.to_csv('./results_update2/df_comparative.csv') 134 | 135 | 136 | # Rankings correlations 137 | results = copy.deepcopy(df_comparative) 138 | method_types = list(results.columns) 139 | dict_new_heatmap_rw = Create_dictionary() 140 | 141 | for el in method_types: 142 | dict_new_heatmap_rw.add(el, []) 143 | 144 | # heatmaps for correlations coefficients 145 | for i, j in [(i, j) for i in method_types[::-1] for j in method_types]: 146 | dict_new_heatmap_rw[j].append(corrs.weighted_spearman(results[i], results[j])) 147 | 148 | df_new_heatmap_rw = pd.DataFrame(dict_new_heatmap_rw, index = method_types[::-1]) 149 | df_new_heatmap_rw.columns = method_types 150 | 151 | # correlation matrix with rw coefficient 152 | draw_heatmap(df_new_heatmap_rw, r'$r_w$') 153 | 154 | 155 | if __name__ == '__main__': 156 | main() -------------------------------------------------------------------------------- /examples/results/bar_chart_MCDA methods1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/energyinpython/pyrepo-mcda/b3ac297f12116f90af42f39aa16dc48d73063473/examples/results/bar_chart_MCDA methods1.png -------------------------------------------------------------------------------- /examples/results/bar_chart_MCDA methods2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/energyinpython/pyrepo-mcda/b3ac297f12116f90af42f39aa16dc48d73063473/examples/results/bar_chart_MCDA methods2.png -------------------------------------------------------------------------------- /examples/results/bar_chart_weights_Alternative.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/energyinpython/pyrepo-mcda/b3ac297f12116f90af42f39aa16dc48d73063473/examples/results/bar_chart_weights_Alternative.pdf -------------------------------------------------------------------------------- /examples/results/bar_chart_weights_Alternatives.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/energyinpython/pyrepo-mcda/b3ac297f12116f90af42f39aa16dc48d73063473/examples/results/bar_chart_weights_Alternatives.pdf -------------------------------------------------------------------------------- /examples/results/bar_chart_weights_Weighting methods.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/energyinpython/pyrepo-mcda/b3ac297f12116f90af42f39aa16dc48d73063473/examples/results/bar_chart_weights_Weighting methods.pdf -------------------------------------------------------------------------------- /examples/results/boxplot_weights.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/energyinpython/pyrepo-mcda/b3ac297f12116f90af42f39aa16dc48d73063473/examples/results/boxplot_weights.pdf -------------------------------------------------------------------------------- /examples/results/correlations_r_w1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/energyinpython/pyrepo-mcda/b3ac297f12116f90af42f39aa16dc48d73063473/examples/results/correlations_r_w1.png -------------------------------------------------------------------------------- /examples/results/correlations_r_w2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/energyinpython/pyrepo-mcda/b3ac297f12116f90af42f39aa16dc48d73063473/examples/results/correlations_r_w2.png -------------------------------------------------------------------------------- /examples/results/dataset1.csv: -------------------------------------------------------------------------------- 1 | Ai,$C_{1}$,$C_{2}$,$C_{3}$,$C_{4}$,$C_{5}$,$C_{6}$,$C_{7}$,$C_{8}$,$C_{9}$,$C_{10}$,$C_{11}$,$C_{12}$ 2 | $A_{1}$,16347.0,9.0,73.8,36.7,1.5,3730.0,2.0,1.0,38.8,4.0,1720524.0,40012.0 3 | $A_{2}$,14219.0,8.5,55.0,36.0,2.0,3240.0,1.0,1.0,33.1,2.0,1496512.0,34803.0 4 | $A_{3}$,8160.0,9.0,64.8,28.5,2.0,1860.0,2.0,2.0,45.8,4.0,858830.0,19973.0 5 | $A_{4}$,8160.0,8.5,62.5,29.5,1.5,1860.0,1.0,3.0,27.3,3.0,858830.0,19973.0 6 | -------------------------------------------------------------------------------- /examples/results/dataset2.csv: -------------------------------------------------------------------------------- 1 | Ai,$C_{1}$,$C_{2}$,$C_{3}$,$C_{4}$,$C_{5}$,$C_{6}$,$C_{7}$,$C_{8}$,$C_{9}$,$C_{10}$ 2 | $A_{1}$,106.78,6.75,2.0,220.0,6.0,1.0,52.0,455.5,8.9,36.8 3 | $A_{2}$,86.37,7.12,3.0,400.0,10.0,0.0,20.0,336.5,7.2,29.8 4 | $A_{3}$,104.85,6.95,60.0,220.0,7.0,1.0,60.0,416.0,8.7,36.2 5 | $A_{4}$,46.6,6.04,1.0,220.0,3.0,0.0,50.0,277.0,3.9,16.0 6 | $A_{5}$,69.18,7.05,33.16,220.0,8.0,0.0,35.49,364.79,5.39,33.71 7 | $A_{6}$,66.48,6.06,26.32,220.0,6.53,0.0,34.82,304.02,4.67,27.07 8 | $A_{7}$,74.48,6.61,48.25,400.0,4.76,1.0,44.19,349.45,4.93,28.89 9 | $A_{8}$,73.67,6.06,19.54,400.0,3.19,0.0,46.41,354.65,8.01,21.09 10 | $A_{9}$,100.58,6.37,39.27,220.0,8.43,1.0,22.07,449.42,7.89,17.62 11 | $A_{10}$,94.81,6.13,50.58,220.0,4.18,1.0,21.14,450.88,5.12,17.3 12 | $A_{11}$,48.93,7.12,21.48,220.0,5.47,1.0,55.72,454.71,8.39,19.16 13 | $A_{12}$,74.75,6.58,7.08,400.0,9.9,1.0,26.01,455.17,4.78,18.44 14 | -------------------------------------------------------------------------------- /examples/results/heatmap_smaa.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/energyinpython/pyrepo-mcda/b3ac297f12116f90af42f39aa16dc48d73063473/examples/results/heatmap_smaa.pdf -------------------------------------------------------------------------------- /examples/results/heatmap_weights.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/energyinpython/pyrepo-mcda/b3ac297f12116f90af42f39aa16dc48d73063473/examples/results/heatmap_weights.pdf -------------------------------------------------------------------------------- /examples/results/results.csv: -------------------------------------------------------------------------------- 1 | Ai,TOPSIS,CODAS,VIKOR,SPOTIS,EDAS,MABAC,MMOORA,WASPAS,Compromise 2 | $A_{1}$,1,1,1,1,1,1,1,1,1 3 | $A_{2}$,4,2,5,3,2,3,2,2,3 4 | $A_{3}$,2,3,2,2,3,2,3,3,2 5 | $A_{4}$,6,5,6,6,6,6,5,5,6 6 | $A_{5}$,5,6,4,5,5,5,4,6,5 7 | $A_{6}$,3,4,3,4,4,4,3,4,4 8 | -------------------------------------------------------------------------------- /examples/results/results_1.csv: -------------------------------------------------------------------------------- 1 | Ai,PROMETHEE II,PROSA C,ARAS,COPRAS,CRADIS,MARCOS,SAW,VIKOR 2 | $A_{1}$,2,3,3,2,3,3,3,4 3 | $A_{2}$,1,1,1,1,1,1,1,1 4 | $A_{3}$,4,4,4,4,4,4,4,3 5 | $A_{4}$,3,2,2,3,2,2,2,2 6 | -------------------------------------------------------------------------------- /examples/results/results_2.csv: -------------------------------------------------------------------------------- 1 | Ai,PROMETHEE II,PROSA C,TOPSIS,VIKOR,SPOTIS,EDAS,MABAC,CRADIS 2 | $A_{1}$,6,6,6,6,6,6,6,6 3 | $A_{2}$,2,2,2,2,2,2,2,2 4 | $A_{3}$,10,12,12,11,11,12,11,11 5 | $A_{4}$,1,1,1,1,1,1,1,1 6 | $A_{5}$,5,5,5,5,5,5,5,5 7 | $A_{6}$,4,4,4,4,4,4,4,4 8 | $A_{7}$,9,9,10,9,9,9,9,9 9 | $A_{8}$,3,3,3,3,3,3,3,3 10 | $A_{9}$,12,10,9,10,10,10,10,10 11 | $A_{10}$,11,11,11,12,12,11,12,12 12 | $A_{11}$,8,8,8,8,8,8,8,8 13 | $A_{12}$,7,7,7,7,7,7,7,7 14 | -------------------------------------------------------------------------------- /examples/results/robustness_C1.csv: -------------------------------------------------------------------------------- 1 | ,Rank,Performance,Alternative 2 | 0,6,14.0,$A_{1}$ 3 | 1,6,15.0,$A_{1}$ 4 | 2,5,16.0,$A_{1}$ 5 | 3,5,17.0,$A_{1}$ 6 | 4,4,18.0,$A_{1}$ 7 | 5,4,19.0,$A_{1}$ 8 | 6,4,20.0,$A_{1}$ 9 | 7,4,21.0,$A_{1}$ 10 | 8,4,22.0,$A_{1}$ 11 | 9,4,23.0,$A_{1}$ 12 | 10,3,24.0,$A_{1}$ 13 | 11,3,25.0,$A_{1}$ 14 | 12,3,26.0,$A_{1}$ 15 | 13,3,27.0,$A_{1}$ 16 | 14,2,28.0,$A_{1}$ 17 | 15,2,29.0,$A_{1}$ 18 | 16,2,30.0,$A_{1}$ 19 | 17,2,31.0,$A_{1}$ 20 | 18,2,32.0,$A_{1}$ 21 | 19,1,33.0,$A_{1}$ 22 | 20,1,34.0,$A_{1}$ 23 | 21,1,35.0,$A_{1}$ 24 | 22,1,36.0,$A_{1}$ 25 | 23,1,37.0,$A_{1}$ 26 | 24,1,38.0,$A_{1}$ 27 | 25,1,39.0,$A_{1}$ 28 | 26,1,40.0,$A_{1}$ 29 | 27,1,41.0,$A_{1}$ 30 | 28,1,42.0,$A_{1}$ 31 | 29,1,43.0,$A_{1}$ 32 | 30,1,44.0,$A_{1}$ 33 | 31,1,45.0,$A_{1}$ 34 | 32,6,14.0,$A_{2}$ 35 | 33,6,15.0,$A_{2}$ 36 | 34,5,16.0,$A_{2}$ 37 | 35,5,17.0,$A_{2}$ 38 | 36,5,18.0,$A_{2}$ 39 | 37,5,19.0,$A_{2}$ 40 | 38,5,20.0,$A_{2}$ 41 | 39,4,21.0,$A_{2}$ 42 | 40,4,22.0,$A_{2}$ 43 | 41,4,23.0,$A_{2}$ 44 | 42,4,24.0,$A_{2}$ 45 | 43,4,25.0,$A_{2}$ 46 | 44,4,26.0,$A_{2}$ 47 | 45,4,27.0,$A_{2}$ 48 | 46,4,28.0,$A_{2}$ 49 | 47,4,29.0,$A_{2}$ 50 | 48,4,30.0,$A_{2}$ 51 | 49,4,31.0,$A_{2}$ 52 | 50,4,32.0,$A_{2}$ 53 | 51,4,33.0,$A_{2}$ 54 | 52,4,34.0,$A_{2}$ 55 | 53,3,35.0,$A_{2}$ 56 | 54,3,36.0,$A_{2}$ 57 | 55,3,37.0,$A_{2}$ 58 | 56,3,38.0,$A_{2}$ 59 | 57,3,39.0,$A_{2}$ 60 | 58,3,40.0,$A_{2}$ 61 | 59,3,41.0,$A_{2}$ 62 | 60,2,42.0,$A_{2}$ 63 | 61,2,43.0,$A_{2}$ 64 | 62,2,44.0,$A_{2}$ 65 | 63,2,45.0,$A_{2}$ 66 | 64,3,14.0,$A_{3}$ 67 | 65,3,15.0,$A_{3}$ 68 | 66,3,16.0,$A_{3}$ 69 | 67,2,17.0,$A_{3}$ 70 | 68,2,18.0,$A_{3}$ 71 | 69,2,19.0,$A_{3}$ 72 | 70,2,20.0,$A_{3}$ 73 | 71,2,21.0,$A_{3}$ 74 | 72,2,22.0,$A_{3}$ 75 | 73,2,23.0,$A_{3}$ 76 | 74,2,24.0,$A_{3}$ 77 | 75,2,25.0,$A_{3}$ 78 | 76,2,26.0,$A_{3}$ 79 | 77,2,27.0,$A_{3}$ 80 | 78,2,28.0,$A_{3}$ 81 | 79,2,29.0,$A_{3}$ 82 | 80,1,30.0,$A_{3}$ 83 | 81,1,31.0,$A_{3}$ 84 | 82,1,32.0,$A_{3}$ 85 | 83,1,33.0,$A_{3}$ 86 | 84,1,34.0,$A_{3}$ 87 | 85,1,35.0,$A_{3}$ 88 | 86,1,36.0,$A_{3}$ 89 | 87,1,37.0,$A_{3}$ 90 | 88,1,38.0,$A_{3}$ 91 | 89,1,39.0,$A_{3}$ 92 | 90,1,40.0,$A_{3}$ 93 | 91,1,41.0,$A_{3}$ 94 | 92,1,42.0,$A_{3}$ 95 | 93,1,43.0,$A_{3}$ 96 | 94,1,44.0,$A_{3}$ 97 | 95,1,45.0,$A_{3}$ 98 | 96,6,14.0,$A_{4}$ 99 | 97,6,15.0,$A_{4}$ 100 | 98,6,16.0,$A_{4}$ 101 | 99,6,17.0,$A_{4}$ 102 | 100,5,18.0,$A_{4}$ 103 | 101,5,19.0,$A_{4}$ 104 | 102,5,20.0,$A_{4}$ 105 | 103,5,21.0,$A_{4}$ 106 | 104,4,22.0,$A_{4}$ 107 | 105,4,23.0,$A_{4}$ 108 | 106,4,24.0,$A_{4}$ 109 | 107,4,25.0,$A_{4}$ 110 | 108,4,26.0,$A_{4}$ 111 | 109,4,27.0,$A_{4}$ 112 | 110,4,28.0,$A_{4}$ 113 | 111,3,29.0,$A_{4}$ 114 | 112,3,30.0,$A_{4}$ 115 | 113,3,31.0,$A_{4}$ 116 | 114,3,32.0,$A_{4}$ 117 | 115,2,33.0,$A_{4}$ 118 | 116,2,34.0,$A_{4}$ 119 | 117,2,35.0,$A_{4}$ 120 | 118,2,36.0,$A_{4}$ 121 | 119,2,37.0,$A_{4}$ 122 | 120,1,38.0,$A_{4}$ 123 | 121,1,39.0,$A_{4}$ 124 | 122,1,40.0,$A_{4}$ 125 | 123,1,41.0,$A_{4}$ 126 | 124,1,42.0,$A_{4}$ 127 | 125,1,43.0,$A_{4}$ 128 | 126,1,44.0,$A_{4}$ 129 | 127,1,45.0,$A_{4}$ 130 | 128,5,14.0,$A_{5}$ 131 | 129,5,15.0,$A_{5}$ 132 | 130,5,16.0,$A_{5}$ 133 | 131,5,17.0,$A_{5}$ 134 | 132,5,18.0,$A_{5}$ 135 | 133,5,19.0,$A_{5}$ 136 | 134,4,20.0,$A_{5}$ 137 | 135,4,21.0,$A_{5}$ 138 | 136,4,22.0,$A_{5}$ 139 | 137,4,23.0,$A_{5}$ 140 | 138,4,24.0,$A_{5}$ 141 | 139,4,25.0,$A_{5}$ 142 | 140,4,26.0,$A_{5}$ 143 | 141,3,27.0,$A_{5}$ 144 | 142,3,28.0,$A_{5}$ 145 | 143,3,29.0,$A_{5}$ 146 | 144,3,30.0,$A_{5}$ 147 | 145,2,31.0,$A_{5}$ 148 | 146,2,32.0,$A_{5}$ 149 | 147,2,33.0,$A_{5}$ 150 | 148,2,34.0,$A_{5}$ 151 | 149,2,35.0,$A_{5}$ 152 | 150,1,36.0,$A_{5}$ 153 | 151,1,37.0,$A_{5}$ 154 | 152,1,38.0,$A_{5}$ 155 | 153,1,39.0,$A_{5}$ 156 | 154,1,40.0,$A_{5}$ 157 | 155,1,41.0,$A_{5}$ 158 | 156,1,42.0,$A_{5}$ 159 | 157,1,43.0,$A_{5}$ 160 | 158,1,44.0,$A_{5}$ 161 | 159,1,45.0,$A_{5}$ 162 | 160,4,14.0,$A_{6}$ 163 | 161,4,15.0,$A_{6}$ 164 | 162,4,16.0,$A_{6}$ 165 | 163,3,17.0,$A_{6}$ 166 | 164,3,18.0,$A_{6}$ 167 | 165,3,19.0,$A_{6}$ 168 | 166,3,20.0,$A_{6}$ 169 | 167,3,21.0,$A_{6}$ 170 | 168,3,22.0,$A_{6}$ 171 | 169,3,23.0,$A_{6}$ 172 | 170,3,24.0,$A_{6}$ 173 | 171,3,25.0,$A_{6}$ 174 | 172,3,26.0,$A_{6}$ 175 | 173,3,27.0,$A_{6}$ 176 | 174,3,28.0,$A_{6}$ 177 | 175,3,29.0,$A_{6}$ 178 | 176,3,30.0,$A_{6}$ 179 | 177,3,31.0,$A_{6}$ 180 | 178,3,32.0,$A_{6}$ 181 | 179,3,33.0,$A_{6}$ 182 | 180,3,34.0,$A_{6}$ 183 | 181,3,35.0,$A_{6}$ 184 | 182,2,36.0,$A_{6}$ 185 | 183,2,37.0,$A_{6}$ 186 | 184,2,38.0,$A_{6}$ 187 | 185,2,39.0,$A_{6}$ 188 | 186,2,40.0,$A_{6}$ 189 | 187,2,41.0,$A_{6}$ 190 | 188,2,42.0,$A_{6}$ 191 | 189,2,43.0,$A_{6}$ 192 | 190,2,44.0,$A_{6}$ 193 | 191,2,45.0,$A_{6}$ 194 | -------------------------------------------------------------------------------- /examples/results/robustness_C2.csv: -------------------------------------------------------------------------------- 1 | ,Rank,Performance,Alternative 2 | 0,2,3800.0,$A_{1}$ 3 | 1,2,3790.0,$A_{1}$ 4 | 2,2,3780.0,$A_{1}$ 5 | 3,2,3770.0,$A_{1}$ 6 | 4,2,3760.0,$A_{1}$ 7 | 5,2,3750.0,$A_{1}$ 8 | 6,2,3740.0,$A_{1}$ 9 | 7,1,3730.0,$A_{1}$ 10 | 8,1,3720.0,$A_{1}$ 11 | 9,1,3710.0,$A_{1}$ 12 | 10,1,3700.0,$A_{1}$ 13 | 11,1,3690.0,$A_{1}$ 14 | 12,1,3680.0,$A_{1}$ 15 | 13,1,3670.0,$A_{1}$ 16 | 14,1,3660.0,$A_{1}$ 17 | 15,1,3650.0,$A_{1}$ 18 | 16,1,3640.0,$A_{1}$ 19 | 17,1,3630.0,$A_{1}$ 20 | 18,1,3620.0,$A_{1}$ 21 | 19,1,3610.0,$A_{1}$ 22 | 20,1,3600.0,$A_{1}$ 23 | 21,1,3590.0,$A_{1}$ 24 | 22,1,3580.0,$A_{1}$ 25 | 23,1,3570.0,$A_{1}$ 26 | 24,1,3560.0,$A_{1}$ 27 | 25,1,3550.0,$A_{1}$ 28 | 26,1,3540.0,$A_{1}$ 29 | 27,1,3530.0,$A_{1}$ 30 | 28,1,3520.0,$A_{1}$ 31 | 29,1,3510.0,$A_{1}$ 32 | 30,1,3500.0,$A_{1}$ 33 | 31,1,3490.0,$A_{1}$ 34 | 32,1,3480.0,$A_{1}$ 35 | 33,1,3470.0,$A_{1}$ 36 | 34,1,3460.0,$A_{1}$ 37 | 35,1,3450.0,$A_{1}$ 38 | 36,1,3440.0,$A_{1}$ 39 | 37,1,3430.0,$A_{1}$ 40 | 38,1,3420.0,$A_{1}$ 41 | 39,1,3410.0,$A_{1}$ 42 | 40,1,3400.0,$A_{1}$ 43 | 41,1,3390.0,$A_{1}$ 44 | 42,1,3380.0,$A_{1}$ 45 | 43,1,3370.0,$A_{1}$ 46 | 44,1,3360.0,$A_{1}$ 47 | 45,1,3350.0,$A_{1}$ 48 | 46,1,3340.0,$A_{1}$ 49 | 47,1,3330.0,$A_{1}$ 50 | 48,1,3320.0,$A_{1}$ 51 | 49,1,3310.0,$A_{1}$ 52 | 50,1,3300.0,$A_{1}$ 53 | 51,1,3290.0,$A_{1}$ 54 | 52,1,3280.0,$A_{1}$ 55 | 53,1,3270.0,$A_{1}$ 56 | 54,1,3260.0,$A_{1}$ 57 | 55,1,3250.0,$A_{1}$ 58 | 56,1,3240.0,$A_{1}$ 59 | 57,1,3230.0,$A_{1}$ 60 | 58,1,3220.0,$A_{1}$ 61 | 59,1,3210.0,$A_{1}$ 62 | 60,1,3200.0,$A_{1}$ 63 | 61,1,3190.0,$A_{1}$ 64 | 62,1,3180.0,$A_{1}$ 65 | 63,1,3170.0,$A_{1}$ 66 | 64,1,3160.0,$A_{1}$ 67 | 65,1,3150.0,$A_{1}$ 68 | 66,1,3140.0,$A_{1}$ 69 | 67,1,3130.0,$A_{1}$ 70 | 68,1,3120.0,$A_{1}$ 71 | 69,1,3110.0,$A_{1}$ 72 | 70,1,3100.0,$A_{1}$ 73 | 71,1,3090.0,$A_{1}$ 74 | 72,1,3080.0,$A_{1}$ 75 | 73,1,3070.0,$A_{1}$ 76 | 74,1,3060.0,$A_{1}$ 77 | 75,1,3050.0,$A_{1}$ 78 | 76,1,3040.0,$A_{1}$ 79 | 77,1,3030.0,$A_{1}$ 80 | 78,1,3020.0,$A_{1}$ 81 | 79,1,3010.0,$A_{1}$ 82 | 80,1,3000.0,$A_{1}$ 83 | 81,4,3800.0,$A_{2}$ 84 | 82,4,3790.0,$A_{2}$ 85 | 83,4,3780.0,$A_{2}$ 86 | 84,4,3770.0,$A_{2}$ 87 | 85,4,3760.0,$A_{2}$ 88 | 86,4,3750.0,$A_{2}$ 89 | 87,4,3740.0,$A_{2}$ 90 | 88,4,3730.0,$A_{2}$ 91 | 89,4,3720.0,$A_{2}$ 92 | 90,4,3710.0,$A_{2}$ 93 | 91,4,3700.0,$A_{2}$ 94 | 92,4,3690.0,$A_{2}$ 95 | 93,4,3680.0,$A_{2}$ 96 | 94,4,3670.0,$A_{2}$ 97 | 95,4,3660.0,$A_{2}$ 98 | 96,4,3650.0,$A_{2}$ 99 | 97,4,3640.0,$A_{2}$ 100 | 98,4,3630.0,$A_{2}$ 101 | 99,4,3620.0,$A_{2}$ 102 | 100,4,3610.0,$A_{2}$ 103 | 101,4,3600.0,$A_{2}$ 104 | 102,4,3590.0,$A_{2}$ 105 | 103,4,3580.0,$A_{2}$ 106 | 104,4,3570.0,$A_{2}$ 107 | 105,4,3560.0,$A_{2}$ 108 | 106,4,3550.0,$A_{2}$ 109 | 107,4,3540.0,$A_{2}$ 110 | 108,4,3530.0,$A_{2}$ 111 | 109,4,3520.0,$A_{2}$ 112 | 110,4,3510.0,$A_{2}$ 113 | 111,4,3500.0,$A_{2}$ 114 | 112,4,3490.0,$A_{2}$ 115 | 113,4,3480.0,$A_{2}$ 116 | 114,4,3470.0,$A_{2}$ 117 | 115,4,3460.0,$A_{2}$ 118 | 116,4,3450.0,$A_{2}$ 119 | 117,4,3440.0,$A_{2}$ 120 | 118,3,3430.0,$A_{2}$ 121 | 119,3,3420.0,$A_{2}$ 122 | 120,3,3410.0,$A_{2}$ 123 | 121,3,3400.0,$A_{2}$ 124 | 122,3,3390.0,$A_{2}$ 125 | 123,2,3380.0,$A_{2}$ 126 | 124,2,3370.0,$A_{2}$ 127 | 125,1,3360.0,$A_{2}$ 128 | 126,1,3350.0,$A_{2}$ 129 | 127,1,3340.0,$A_{2}$ 130 | 128,1,3330.0,$A_{2}$ 131 | 129,1,3320.0,$A_{2}$ 132 | 130,1,3310.0,$A_{2}$ 133 | 131,1,3300.0,$A_{2}$ 134 | 132,1,3290.0,$A_{2}$ 135 | 133,1,3280.0,$A_{2}$ 136 | 134,1,3270.0,$A_{2}$ 137 | 135,1,3260.0,$A_{2}$ 138 | 136,1,3250.0,$A_{2}$ 139 | 137,1,3240.0,$A_{2}$ 140 | 138,1,3230.0,$A_{2}$ 141 | 139,1,3220.0,$A_{2}$ 142 | 140,1,3210.0,$A_{2}$ 143 | 141,1,3200.0,$A_{2}$ 144 | 142,1,3190.0,$A_{2}$ 145 | 143,1,3180.0,$A_{2}$ 146 | 144,1,3170.0,$A_{2}$ 147 | 145,1,3160.0,$A_{2}$ 148 | 146,1,3150.0,$A_{2}$ 149 | 147,1,3140.0,$A_{2}$ 150 | 148,1,3130.0,$A_{2}$ 151 | 149,1,3120.0,$A_{2}$ 152 | 150,1,3110.0,$A_{2}$ 153 | 151,1,3100.0,$A_{2}$ 154 | 152,1,3090.0,$A_{2}$ 155 | 153,1,3080.0,$A_{2}$ 156 | 154,1,3070.0,$A_{2}$ 157 | 155,1,3060.0,$A_{2}$ 158 | 156,1,3050.0,$A_{2}$ 159 | 157,1,3040.0,$A_{2}$ 160 | 158,1,3030.0,$A_{2}$ 161 | 159,1,3020.0,$A_{2}$ 162 | 160,1,3010.0,$A_{2}$ 163 | 161,1,3000.0,$A_{2}$ 164 | 162,6,3800.0,$A_{3}$ 165 | 163,6,3790.0,$A_{3}$ 166 | 164,6,3780.0,$A_{3}$ 167 | 165,6,3770.0,$A_{3}$ 168 | 166,6,3760.0,$A_{3}$ 169 | 167,6,3750.0,$A_{3}$ 170 | 168,6,3740.0,$A_{3}$ 171 | 169,6,3730.0,$A_{3}$ 172 | 170,6,3720.0,$A_{3}$ 173 | 171,6,3710.0,$A_{3}$ 174 | 172,6,3700.0,$A_{3}$ 175 | 173,6,3690.0,$A_{3}$ 176 | 174,6,3680.0,$A_{3}$ 177 | 175,6,3670.0,$A_{3}$ 178 | 176,6,3660.0,$A_{3}$ 179 | 177,6,3650.0,$A_{3}$ 180 | 178,6,3640.0,$A_{3}$ 181 | 179,6,3630.0,$A_{3}$ 182 | 180,6,3620.0,$A_{3}$ 183 | 181,6,3610.0,$A_{3}$ 184 | 182,6,3600.0,$A_{3}$ 185 | 183,6,3590.0,$A_{3}$ 186 | 184,6,3580.0,$A_{3}$ 187 | 185,6,3570.0,$A_{3}$ 188 | 186,6,3560.0,$A_{3}$ 189 | 187,6,3550.0,$A_{3}$ 190 | 188,5,3540.0,$A_{3}$ 191 | 189,5,3530.0,$A_{3}$ 192 | 190,5,3520.0,$A_{3}$ 193 | 191,5,3510.0,$A_{3}$ 194 | 192,5,3500.0,$A_{3}$ 195 | 193,5,3490.0,$A_{3}$ 196 | 194,4,3480.0,$A_{3}$ 197 | 195,4,3470.0,$A_{3}$ 198 | 196,4,3460.0,$A_{3}$ 199 | 197,4,3450.0,$A_{3}$ 200 | 198,4,3440.0,$A_{3}$ 201 | 199,4,3430.0,$A_{3}$ 202 | 200,4,3420.0,$A_{3}$ 203 | 201,3,3410.0,$A_{3}$ 204 | 202,3,3400.0,$A_{3}$ 205 | 203,3,3390.0,$A_{3}$ 206 | 204,3,3380.0,$A_{3}$ 207 | 205,3,3370.0,$A_{3}$ 208 | 206,3,3360.0,$A_{3}$ 209 | 207,3,3350.0,$A_{3}$ 210 | 208,3,3340.0,$A_{3}$ 211 | 209,3,3330.0,$A_{3}$ 212 | 210,3,3320.0,$A_{3}$ 213 | 211,3,3310.0,$A_{3}$ 214 | 212,3,3300.0,$A_{3}$ 215 | 213,3,3290.0,$A_{3}$ 216 | 214,3,3280.0,$A_{3}$ 217 | 215,3,3270.0,$A_{3}$ 218 | 216,3,3260.0,$A_{3}$ 219 | 217,3,3250.0,$A_{3}$ 220 | 218,3,3240.0,$A_{3}$ 221 | 219,2,3230.0,$A_{3}$ 222 | 220,2,3220.0,$A_{3}$ 223 | 221,2,3210.0,$A_{3}$ 224 | 222,2,3200.0,$A_{3}$ 225 | 223,2,3190.0,$A_{3}$ 226 | 224,2,3180.0,$A_{3}$ 227 | 225,2,3170.0,$A_{3}$ 228 | 226,2,3160.0,$A_{3}$ 229 | 227,2,3150.0,$A_{3}$ 230 | 228,2,3140.0,$A_{3}$ 231 | 229,2,3130.0,$A_{3}$ 232 | 230,2,3120.0,$A_{3}$ 233 | 231,2,3110.0,$A_{3}$ 234 | 232,2,3100.0,$A_{3}$ 235 | 233,2,3090.0,$A_{3}$ 236 | 234,2,3080.0,$A_{3}$ 237 | 235,2,3070.0,$A_{3}$ 238 | 236,2,3060.0,$A_{3}$ 239 | 237,2,3050.0,$A_{3}$ 240 | 238,2,3040.0,$A_{3}$ 241 | 239,2,3030.0,$A_{3}$ 242 | 240,2,3020.0,$A_{3}$ 243 | 241,2,3010.0,$A_{3}$ 244 | 242,2,3000.0,$A_{3}$ 245 | 243,6,3800.0,$A_{4}$ 246 | 244,6,3790.0,$A_{4}$ 247 | 245,6,3780.0,$A_{4}$ 248 | 246,6,3770.0,$A_{4}$ 249 | 247,6,3760.0,$A_{4}$ 250 | 248,6,3750.0,$A_{4}$ 251 | 249,6,3740.0,$A_{4}$ 252 | 250,6,3730.0,$A_{4}$ 253 | 251,6,3720.0,$A_{4}$ 254 | 252,6,3710.0,$A_{4}$ 255 | 253,6,3700.0,$A_{4}$ 256 | 254,6,3690.0,$A_{4}$ 257 | 255,6,3680.0,$A_{4}$ 258 | 256,6,3670.0,$A_{4}$ 259 | 257,6,3660.0,$A_{4}$ 260 | 258,6,3650.0,$A_{4}$ 261 | 259,6,3640.0,$A_{4}$ 262 | 260,6,3630.0,$A_{4}$ 263 | 261,6,3620.0,$A_{4}$ 264 | 262,6,3610.0,$A_{4}$ 265 | 263,6,3600.0,$A_{4}$ 266 | 264,6,3590.0,$A_{4}$ 267 | 265,6,3580.0,$A_{4}$ 268 | 266,6,3570.0,$A_{4}$ 269 | 267,6,3560.0,$A_{4}$ 270 | 268,6,3550.0,$A_{4}$ 271 | 269,6,3540.0,$A_{4}$ 272 | 270,6,3530.0,$A_{4}$ 273 | 271,6,3520.0,$A_{4}$ 274 | 272,6,3510.0,$A_{4}$ 275 | 273,6,3500.0,$A_{4}$ 276 | 274,6,3490.0,$A_{4}$ 277 | 275,6,3480.0,$A_{4}$ 278 | 276,6,3470.0,$A_{4}$ 279 | 277,6,3460.0,$A_{4}$ 280 | 278,6,3450.0,$A_{4}$ 281 | 279,6,3440.0,$A_{4}$ 282 | 280,6,3430.0,$A_{4}$ 283 | 281,6,3420.0,$A_{4}$ 284 | 282,6,3410.0,$A_{4}$ 285 | 283,6,3400.0,$A_{4}$ 286 | 284,6,3390.0,$A_{4}$ 287 | 285,6,3380.0,$A_{4}$ 288 | 286,6,3370.0,$A_{4}$ 289 | 287,6,3360.0,$A_{4}$ 290 | 288,6,3350.0,$A_{4}$ 291 | 289,5,3340.0,$A_{4}$ 292 | 290,5,3330.0,$A_{4}$ 293 | 291,5,3320.0,$A_{4}$ 294 | 292,5,3310.0,$A_{4}$ 295 | 293,5,3300.0,$A_{4}$ 296 | 294,5,3290.0,$A_{4}$ 297 | 295,5,3280.0,$A_{4}$ 298 | 296,4,3270.0,$A_{4}$ 299 | 297,4,3260.0,$A_{4}$ 300 | 298,4,3250.0,$A_{4}$ 301 | 299,4,3240.0,$A_{4}$ 302 | 300,4,3230.0,$A_{4}$ 303 | 301,4,3220.0,$A_{4}$ 304 | 302,4,3210.0,$A_{4}$ 305 | 303,4,3200.0,$A_{4}$ 306 | 304,4,3190.0,$A_{4}$ 307 | 305,4,3180.0,$A_{4}$ 308 | 306,4,3170.0,$A_{4}$ 309 | 307,4,3160.0,$A_{4}$ 310 | 308,4,3150.0,$A_{4}$ 311 | 309,4,3140.0,$A_{4}$ 312 | 310,4,3130.0,$A_{4}$ 313 | 311,4,3120.0,$A_{4}$ 314 | 312,4,3110.0,$A_{4}$ 315 | 313,4,3100.0,$A_{4}$ 316 | 314,4,3090.0,$A_{4}$ 317 | 315,3,3080.0,$A_{4}$ 318 | 316,3,3070.0,$A_{4}$ 319 | 317,3,3060.0,$A_{4}$ 320 | 318,3,3050.0,$A_{4}$ 321 | 319,3,3040.0,$A_{4}$ 322 | 320,3,3030.0,$A_{4}$ 323 | 321,3,3020.0,$A_{4}$ 324 | 322,3,3010.0,$A_{4}$ 325 | 323,3,3000.0,$A_{4}$ 326 | 324,6,3800.0,$A_{5}$ 327 | 325,6,3790.0,$A_{5}$ 328 | 326,6,3780.0,$A_{5}$ 329 | 327,6,3770.0,$A_{5}$ 330 | 328,6,3760.0,$A_{5}$ 331 | 329,6,3750.0,$A_{5}$ 332 | 330,6,3740.0,$A_{5}$ 333 | 331,6,3730.0,$A_{5}$ 334 | 332,6,3720.0,$A_{5}$ 335 | 333,6,3710.0,$A_{5}$ 336 | 334,6,3700.0,$A_{5}$ 337 | 335,6,3690.0,$A_{5}$ 338 | 336,6,3680.0,$A_{5}$ 339 | 337,6,3670.0,$A_{5}$ 340 | 338,6,3660.0,$A_{5}$ 341 | 339,6,3650.0,$A_{5}$ 342 | 340,6,3640.0,$A_{5}$ 343 | 341,6,3630.0,$A_{5}$ 344 | 342,6,3620.0,$A_{5}$ 345 | 343,6,3610.0,$A_{5}$ 346 | 344,6,3600.0,$A_{5}$ 347 | 345,6,3590.0,$A_{5}$ 348 | 346,6,3580.0,$A_{5}$ 349 | 347,6,3570.0,$A_{5}$ 350 | 348,6,3560.0,$A_{5}$ 351 | 349,6,3550.0,$A_{5}$ 352 | 350,6,3540.0,$A_{5}$ 353 | 351,6,3530.0,$A_{5}$ 354 | 352,6,3520.0,$A_{5}$ 355 | 353,6,3510.0,$A_{5}$ 356 | 354,6,3500.0,$A_{5}$ 357 | 355,6,3490.0,$A_{5}$ 358 | 356,6,3480.0,$A_{5}$ 359 | 357,6,3470.0,$A_{5}$ 360 | 358,6,3460.0,$A_{5}$ 361 | 359,6,3450.0,$A_{5}$ 362 | 360,6,3440.0,$A_{5}$ 363 | 361,6,3430.0,$A_{5}$ 364 | 362,6,3420.0,$A_{5}$ 365 | 363,6,3410.0,$A_{5}$ 366 | 364,6,3400.0,$A_{5}$ 367 | 365,6,3390.0,$A_{5}$ 368 | 366,6,3380.0,$A_{5}$ 369 | 367,6,3370.0,$A_{5}$ 370 | 368,6,3360.0,$A_{5}$ 371 | 369,5,3350.0,$A_{5}$ 372 | 370,5,3340.0,$A_{5}$ 373 | 371,5,3330.0,$A_{5}$ 374 | 372,5,3320.0,$A_{5}$ 375 | 373,5,3310.0,$A_{5}$ 376 | 374,5,3300.0,$A_{5}$ 377 | 375,5,3290.0,$A_{5}$ 378 | 376,5,3280.0,$A_{5}$ 379 | 377,5,3270.0,$A_{5}$ 380 | 378,5,3260.0,$A_{5}$ 381 | 379,5,3250.0,$A_{5}$ 382 | 380,5,3240.0,$A_{5}$ 383 | 381,5,3230.0,$A_{5}$ 384 | 382,4,3220.0,$A_{5}$ 385 | 383,4,3210.0,$A_{5}$ 386 | 384,4,3200.0,$A_{5}$ 387 | 385,4,3190.0,$A_{5}$ 388 | 386,4,3180.0,$A_{5}$ 389 | 387,4,3170.0,$A_{5}$ 390 | 388,4,3160.0,$A_{5}$ 391 | 389,4,3150.0,$A_{5}$ 392 | 390,4,3140.0,$A_{5}$ 393 | 391,4,3130.0,$A_{5}$ 394 | 392,4,3120.0,$A_{5}$ 395 | 393,4,3110.0,$A_{5}$ 396 | 394,4,3100.0,$A_{5}$ 397 | 395,4,3090.0,$A_{5}$ 398 | 396,4,3080.0,$A_{5}$ 399 | 397,4,3070.0,$A_{5}$ 400 | 398,4,3060.0,$A_{5}$ 401 | 399,4,3050.0,$A_{5}$ 402 | 400,3,3040.0,$A_{5}$ 403 | 401,3,3030.0,$A_{5}$ 404 | 402,3,3020.0,$A_{5}$ 405 | 403,3,3010.0,$A_{5}$ 406 | 404,3,3000.0,$A_{5}$ 407 | 405,6,3800.0,$A_{6}$ 408 | 406,6,3790.0,$A_{6}$ 409 | 407,6,3780.0,$A_{6}$ 410 | 408,6,3770.0,$A_{6}$ 411 | 409,6,3760.0,$A_{6}$ 412 | 410,6,3750.0,$A_{6}$ 413 | 411,6,3740.0,$A_{6}$ 414 | 412,6,3730.0,$A_{6}$ 415 | 413,6,3720.0,$A_{6}$ 416 | 414,6,3710.0,$A_{6}$ 417 | 415,6,3700.0,$A_{6}$ 418 | 416,6,3690.0,$A_{6}$ 419 | 417,6,3680.0,$A_{6}$ 420 | 418,6,3670.0,$A_{6}$ 421 | 419,6,3660.0,$A_{6}$ 422 | 420,6,3650.0,$A_{6}$ 423 | 421,6,3640.0,$A_{6}$ 424 | 422,6,3630.0,$A_{6}$ 425 | 423,6,3620.0,$A_{6}$ 426 | 424,6,3610.0,$A_{6}$ 427 | 425,6,3600.0,$A_{6}$ 428 | 426,6,3590.0,$A_{6}$ 429 | 427,6,3580.0,$A_{6}$ 430 | 428,6,3570.0,$A_{6}$ 431 | 429,6,3560.0,$A_{6}$ 432 | 430,6,3550.0,$A_{6}$ 433 | 431,6,3540.0,$A_{6}$ 434 | 432,6,3530.0,$A_{6}$ 435 | 433,6,3520.0,$A_{6}$ 436 | 434,6,3510.0,$A_{6}$ 437 | 435,6,3500.0,$A_{6}$ 438 | 436,6,3490.0,$A_{6}$ 439 | 437,6,3480.0,$A_{6}$ 440 | 438,6,3470.0,$A_{6}$ 441 | 439,6,3460.0,$A_{6}$ 442 | 440,6,3450.0,$A_{6}$ 443 | 441,6,3440.0,$A_{6}$ 444 | 442,6,3430.0,$A_{6}$ 445 | 443,6,3420.0,$A_{6}$ 446 | 444,6,3410.0,$A_{6}$ 447 | 445,6,3400.0,$A_{6}$ 448 | 446,6,3390.0,$A_{6}$ 449 | 447,6,3380.0,$A_{6}$ 450 | 448,6,3370.0,$A_{6}$ 451 | 449,6,3360.0,$A_{6}$ 452 | 450,6,3350.0,$A_{6}$ 453 | 451,6,3340.0,$A_{6}$ 454 | 452,6,3330.0,$A_{6}$ 455 | 453,5,3320.0,$A_{6}$ 456 | 454,5,3310.0,$A_{6}$ 457 | 455,5,3300.0,$A_{6}$ 458 | 456,5,3290.0,$A_{6}$ 459 | 457,5,3280.0,$A_{6}$ 460 | 458,5,3270.0,$A_{6}$ 461 | 459,3,3260.0,$A_{6}$ 462 | 460,3,3250.0,$A_{6}$ 463 | 461,3,3240.0,$A_{6}$ 464 | 462,3,3230.0,$A_{6}$ 465 | 463,3,3220.0,$A_{6}$ 466 | 464,3,3210.0,$A_{6}$ 467 | 465,3,3200.0,$A_{6}$ 468 | 466,3,3190.0,$A_{6}$ 469 | 467,3,3180.0,$A_{6}$ 470 | 468,3,3170.0,$A_{6}$ 471 | 469,3,3160.0,$A_{6}$ 472 | 470,3,3150.0,$A_{6}$ 473 | 471,3,3140.0,$A_{6}$ 474 | 472,3,3130.0,$A_{6}$ 475 | 473,3,3120.0,$A_{6}$ 476 | 474,3,3110.0,$A_{6}$ 477 | 475,3,3100.0,$A_{6}$ 478 | 476,3,3090.0,$A_{6}$ 479 | 477,3,3080.0,$A_{6}$ 480 | 478,3,3070.0,$A_{6}$ 481 | 479,3,3060.0,$A_{6}$ 482 | 480,3,3050.0,$A_{6}$ 483 | 481,3,3040.0,$A_{6}$ 484 | 482,3,3030.0,$A_{6}$ 485 | 483,3,3020.0,$A_{6}$ 486 | 484,3,3010.0,$A_{6}$ 487 | 485,3,3000.0,$A_{6}$ 488 | -------------------------------------------------------------------------------- /examples/results/robustness_C3.csv: -------------------------------------------------------------------------------- 1 | ,Rank,Performance,Alternative 2 | 0,2,30.0,$A_{1}$ 3 | 1,2,35.0,$A_{1}$ 4 | 2,1,40.0,$A_{1}$ 5 | 3,1,45.0,$A_{1}$ 6 | 4,1,50.0,$A_{1}$ 7 | 5,1,55.0,$A_{1}$ 8 | 6,1,60.0,$A_{1}$ 9 | 7,6,30.0,$A_{2}$ 10 | 8,6,35.0,$A_{2}$ 11 | 9,6,40.0,$A_{2}$ 12 | 10,6,45.0,$A_{2}$ 13 | 11,5,50.0,$A_{2}$ 14 | 12,4,55.0,$A_{2}$ 15 | 13,4,60.0,$A_{2}$ 16 | 14,2,30.0,$A_{3}$ 17 | 15,2,35.0,$A_{3}$ 18 | 16,2,40.0,$A_{3}$ 19 | 17,1,45.0,$A_{3}$ 20 | 18,1,50.0,$A_{3}$ 21 | 19,1,55.0,$A_{3}$ 22 | 20,1,60.0,$A_{3}$ 23 | 21,6,30.0,$A_{4}$ 24 | 22,6,35.0,$A_{4}$ 25 | 23,6,40.0,$A_{4}$ 26 | 24,6,45.0,$A_{4}$ 27 | 25,6,50.0,$A_{4}$ 28 | 26,5,55.0,$A_{4}$ 29 | 27,4,60.0,$A_{4}$ 30 | 28,6,30.0,$A_{5}$ 31 | 29,6,35.0,$A_{5}$ 32 | 30,5,40.0,$A_{5}$ 33 | 31,5,45.0,$A_{5}$ 34 | 32,4,50.0,$A_{5}$ 35 | 33,4,55.0,$A_{5}$ 36 | 34,3,60.0,$A_{5}$ 37 | 35,3,30.0,$A_{6}$ 38 | 36,3,35.0,$A_{6}$ 39 | 37,2,40.0,$A_{6}$ 40 | 38,2,45.0,$A_{6}$ 41 | 39,1,50.0,$A_{6}$ 42 | 40,1,55.0,$A_{6}$ 43 | 41,1,60.0,$A_{6}$ 44 | -------------------------------------------------------------------------------- /examples/results/robustness_C4.csv: -------------------------------------------------------------------------------- 1 | ,Rank,Performance,Alternative 2 | 0,2,0.6,$A_{1}$ 3 | 1,2,0.7,$A_{1}$ 4 | 2,1,0.7999999999999999,$A_{1}$ 5 | 3,1,0.8999999999999999,$A_{1}$ 6 | 4,5,0.6,$A_{2}$ 7 | 5,4,0.7,$A_{2}$ 8 | 6,4,0.7999999999999999,$A_{2}$ 9 | 7,4,0.8999999999999999,$A_{2}$ 10 | 8,3,0.6,$A_{3}$ 11 | 9,3,0.7,$A_{3}$ 12 | 10,2,0.7999999999999999,$A_{3}$ 13 | 11,2,0.8999999999999999,$A_{3}$ 14 | 12,6,0.6,$A_{4}$ 15 | 13,6,0.7,$A_{4}$ 16 | 14,5,0.7999999999999999,$A_{4}$ 17 | 15,4,0.8999999999999999,$A_{4}$ 18 | 16,6,0.6,$A_{5}$ 19 | 17,6,0.7,$A_{5}$ 20 | 18,5,0.7999999999999999,$A_{5}$ 21 | 19,4,0.8999999999999999,$A_{5}$ 22 | 20,3,0.6,$A_{6}$ 23 | 21,3,0.7,$A_{6}$ 24 | 22,3,0.7999999999999999,$A_{6}$ 25 | 23,2,0.8999999999999999,$A_{6}$ 26 | -------------------------------------------------------------------------------- /examples/results_smaa/ai.csv: -------------------------------------------------------------------------------- 1 | ,1,2,3,4,5,6,7,8,9,10,11,12 2 | $A_{1}$,0.2361,0.2458,0.1879,0.1354,0.0507,0.0546,0.0227,0.048,0.0151,0.0037,0.0,0.0 3 | $A_{2}$,0.2208,0.3555,0.2194,0.1165,0.0455,0.0345,0.0078,0.0,0.0,0.0,0.0,0.0 4 | $A_{3}$,0.0001,0.0111,0.0229,0.0725,0.287,0.1485,0.1467,0.1366,0.1656,0.009,0.0,0.0 5 | $A_{4}$,0.1136,0.067,0.0717,0.1356,0.1719,0.2304,0.0778,0.0375,0.0305,0.0322,0.0318,0.0 6 | $A_{5}$,0.0003,0.0123,0.0129,0.0217,0.078,0.0999,0.2542,0.1427,0.1256,0.2322,0.0202,0.0 7 | $A_{6}$,0.0,0.0007,0.007,0.0511,0.0251,0.0369,0.1353,0.1146,0.1594,0.1655,0.1277,0.1767 8 | $A_{7}$,0.0,0.0,0.0011,0.0012,0.0062,0.0298,0.0306,0.0327,0.0822,0.0739,0.0924,0.6499 9 | $A_{8}$,0.0,0.0011,0.0025,0.005,0.0626,0.0392,0.0569,0.1442,0.0743,0.121,0.4544,0.0388 10 | $A_{9}$,0.3802,0.1025,0.2888,0.0389,0.0271,0.0282,0.0239,0.0177,0.068,0.0247,0.0,0.0 11 | $A_{10}$,0.0106,0.0425,0.0703,0.0684,0.086,0.169,0.0987,0.0715,0.1419,0.1403,0.0911,0.0097 12 | $A_{11}$,0.0,0.1083,0.0779,0.2967,0.0668,0.049,0.0606,0.0944,0.0243,0.0794,0.101,0.0416 13 | $A_{12}$,0.0383,0.0532,0.0376,0.057,0.0931,0.08,0.0848,0.1601,0.1131,0.1181,0.0814,0.0833 14 | -------------------------------------------------------------------------------- /examples/results_smaa/cw.csv: -------------------------------------------------------------------------------- 1 | ,$C_{1}$,$C_{2}$,$C_{3}$,$C_{4}$,$C_{5}$,$C_{6}$,$C_{7}$,$C_{8}$,$C_{9}$,$C_{10}$,$C_{11}$ 2 | $A_{1}$,0.08004380077684044,0.06591290065619997,0.16690523134575608,0.1263208322870497,0.12220564170453381,0.07743816419225649,0.07159634971711881,0.08023123821845944,0.056638997126554375,0.05439461031598036,0.09831223365925053 3 | $A_{2}$,0.1171950304728913,0.08972372185770006,0.0762827700701533,0.12803851776176797,0.05626216285359029,0.08068131111542011,0.07895871876131728,0.08118136726672631,0.07143180459686643,0.11042425280293688,0.10982034244062992 4 | $A_{3}$,0.0033359510428977836,0.023275197319350762,0.030770669130253954,0.10272943211136948,0.28300132571335346,0.04313474571991287,0.0024381814305720222,0.03646390864760884,0.007505256038142727,0.31185894062445085,0.15548639222208732 5 | $A_{4}$,0.044720918667593416,0.08466951438572504,0.06550087435919902,0.05835025614454079,0.06624066554910514,0.08923652360357004,0.09051378932296979,0.0784414159566441,0.08013149913461591,0.2149065545826422,0.12728798829339447 6 | $A_{5}$,0.054053814032311576,0.2770077226936406,0.06841913437859624,0.020684732416445632,0.04081035012621299,0.03597376520149918,0.0486250032072983,0.02573723181840448,0.09608421034814922,0.25523254248319605,0.0773714932942458 7 | $A_{6}$,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 8 | $A_{7}$,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 9 | $A_{8}$,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 10 | $A_{9}$,0.09610021672020685,0.11636083161294683,0.06674970054201847,0.06192476176632081,0.10500608168399597,0.10590538943434706,0.10064414993426152,0.1034417691010669,0.13080347193928732,0.05441637652006149,0.0586472507454867 11 | $A_{10}$,0.052457243481313656,0.03217819332051471,0.039803319374100775,0.15040112623660848,0.03835325921681871,0.09973303038742808,0.11193161969551688,0.08192504921825605,0.0686719684985773,0.23064565552206626,0.09389953504879907 12 | $A_{11}$,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 13 | $A_{12}$,0.07569387761124985,0.03732527603542426,0.04425085507964977,0.04653410559597599,0.046289067330273,0.14360323065198,0.16146530366562217,0.1381306945906069,0.039331571122236834,0.15021842702358437,0.1171575912933969 14 | -------------------------------------------------------------------------------- /examples/results_smaa/fr.csv: -------------------------------------------------------------------------------- 1 | ,Rank 2 | $A_{1}$,3 3 | $A_{2}$,1 4 | $A_{3}$,6 5 | $A_{4}$,4 6 | $A_{5}$,9 7 | $A_{6}$,10 8 | $A_{7}$,12 9 | $A_{8}$,11 10 | $A_{9}$,2 11 | $A_{10}$,7 12 | $A_{11}$,5 13 | $A_{12}$,8 14 | -------------------------------------------------------------------------------- /examples/results_update2/df_comparative.csv: -------------------------------------------------------------------------------- 1 | Alternative,VMCM,TOPSIS,VIKOR,EDAS,PROMETHEE II,PROSA C,COCOSO 2 | A1 (AD),1,1,1,1,1,1,1 3 | A2 (LFGR),3,2,2,2,3,3,3 4 | A3 (INC),5,5,5,5,5,5,5 5 | A4 (GAS),4,4,4,4,4,4,4 6 | A5 (PYR),2,3,3,3,2,2,2 7 | -------------------------------------------------------------------------------- /examples/results_update2/heatmap.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/energyinpython/pyrepo-mcda/b3ac297f12116f90af42f39aa16dc48d73063473/examples/results_update2/heatmap.pdf -------------------------------------------------------------------------------- /examples/results_update2/vmcm_results.csv: -------------------------------------------------------------------------------- 1 | Alternative,Synthetic measure,Class,Rank 2 | A1 (AD),0.5831223102246021,1.0,1 3 | A2 (LFGR),0.4156988455559284,3.0,3 4 | A3 (INC),0.38703967845964976,3.0,5 5 | A4 (GAS),0.41406911302788457,3.0,4 6 | A5 (PYR),0.508045654288003,2.0,2 7 | -------------------------------------------------------------------------------- /examples/visualizations.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import seaborn as sns 3 | import numpy as np 4 | import pandas as pd 5 | 6 | 7 | # bar (column) chart 8 | def plot_barplot(df_plot, legend_title): 9 | """ 10 | Visualization method to display column chart of alternatives rankings obtained with 11 | different methods. 12 | 13 | Parameters 14 | ---------- 15 | df_plot : DataFrame 16 | DataFrame containing rankings of alternatives obtained with different methods. 17 | The particular rankings are included in subsequent columns of DataFrame. 18 | 19 | title : str 20 | Title of the legend (Name of group of explored methods, for example MCDA methods or Distance metrics). 21 | 22 | Examples 23 | ---------- 24 | >>> plot_barplot(df_plot, legend_title='MCDA methods') 25 | """ 26 | step = 1 27 | list_rank = np.arange(1, len(df_plot) + 1, step) 28 | 29 | ax = df_plot.plot(kind='bar', width = 0.8, stacked=False, edgecolor = 'black', figsize = (9,4)) 30 | ax.set_xlabel('Alternatives', fontsize = 12) 31 | ax.set_ylabel('Rank', fontsize = 12) 32 | ax.set_yticks(list_rank) 33 | 34 | ax.set_xticklabels(df_plot.index, rotation = 'horizontal') 35 | ax.tick_params(axis = 'both', labelsize = 12) 36 | y_ticks = ax.yaxis.get_major_ticks() 37 | ax.set_ylim(0, len(df_plot) + 1) 38 | 39 | plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc='lower left', 40 | ncol=4, mode="expand", borderaxespad=0., edgecolor = 'black', title = legend_title, fontsize = 12) 41 | 42 | ax.grid(True, linestyle = ':') 43 | ax.set_axisbelow(True) 44 | plt.tight_layout() 45 | legend_title = legend_title.replace("$", "") 46 | legend_title = legend_title.replace("{", "") 47 | legend_title = legend_title.replace("}", "") 48 | plt.savefig('./results/' + 'bar_chart_' + legend_title + '.eps') 49 | plt.show() 50 | 51 | 52 | # bar (column) chart for sensitivity analysis 53 | def plot_barplot_sensitivity(df_plot, method_name, criterion_name, filename = ""): 54 | """ 55 | Visualization method to display column chart of alternatives rankings obtained with 56 | modification of weight of given criterion. 57 | 58 | Parameters 59 | ---------- 60 | df_plot : DataFrame 61 | DataFrame containing rankings of alternatives obtained with different weights of 62 | selected criterion. The particular rankings are contained in subsequent columns of 63 | DataFrame. 64 | 65 | method_name : str 66 | Name of chosen MCDA method, i.e. `TOPSIS`, `VIKOR`, `CODAS`, `WASPAS`, `MULTIMOORA`, `MABAC`, `EDAS`, `SPOTIS` 67 | 68 | criterion_name : str 69 | Name of chosen criterion whose weight is modified 70 | 71 | filename : str 72 | Name of file to save this chart 73 | 74 | Examples 75 | ----------- 76 | >>> plot_barplot_sensitivity(df_plot, method_name, criterion_name, filename) 77 | """ 78 | step = 1 79 | list_rank = np.arange(1, len(df_plot) + 1, step) 80 | 81 | ax = df_plot.plot(kind='bar', width = 0.8, stacked=False, edgecolor = 'black', figsize = (9,4)) 82 | ax.set_xlabel('Alternatives', fontsize = 12) 83 | ax.set_ylabel('Rank', fontsize = 12) 84 | ax.set_yticks(list_rank) 85 | 86 | ax.set_xticklabels(df_plot.index, rotation = 'horizontal') 87 | ax.tick_params(axis='both', labelsize=12) 88 | y_ticks = ax.yaxis.get_major_ticks() 89 | ax.set_ylim(0, len(df_plot) + 1) 90 | ax.set_title(method_name + ', modification of ' + criterion_name + ' weights') 91 | 92 | plt.legend(bbox_to_anchor=(1.0, 0.82, 0.3, 0.2), loc='upper left', title = 'Weights change', edgecolor = 'black', fontsize = 12) 93 | 94 | ax.grid(True, linestyle = ':') 95 | ax.set_axisbelow(True) 96 | plt.tight_layout() 97 | criterion_name = criterion_name.replace("$", "") 98 | criterion_name = criterion_name.replace("{", "") 99 | criterion_name = criterion_name.replace("}", "") 100 | plt.savefig('./results/' + 'sensitivity_' + 'hist_' + method_name + '_' + criterion_name + '_' + filename + '.eps') 101 | plt.show() 102 | 103 | 104 | # plot line chart for sensitivity analysis 105 | def plot_lineplot_sensitivity(data_sens, method_name, criterion_name, x_title, filename = ""): 106 | """ 107 | Visualization method to display line chart of alternatives rankings obtained with 108 | modification of weight of given criterion. 109 | 110 | Parameters 111 | ---------- 112 | df_plot : DataFrame 113 | DataFrame containing rankings of alternatives obtained with different weight of 114 | selected criterion. The particular rankings are contained in subsequent columns of 115 | DataFrame. 116 | 117 | method_name : str 118 | Name of chosen MCDA method, i.e. `TOPSIS`, `VIKOR`, `CODAS`, `WASPAS`, `MULTIMOORA`, `MABAC`, `EDAS`, `SPOTIS` 119 | 120 | criterion_name : str 121 | Name of chosen criterion whose weight is modified 122 | 123 | x_title : str 124 | Title of x axis 125 | 126 | filename : str 127 | Name of file to save this chart 128 | 129 | Examples 130 | ---------- 131 | >>> plot_lineplot_sensitivity(df_plot, method_name, criterion_name, x_title, filename) 132 | """ 133 | plt.figure(figsize = (6, 3)) 134 | for j in range(data_sens.shape[0]): 135 | 136 | plt.plot(data_sens.iloc[j, :], linewidth = 2) 137 | ax = plt.gca() 138 | y_min, y_max = ax.get_ylim() 139 | x_min, x_max = ax.get_xlim() 140 | plt.annotate(data_sens.index[j], (x_max, data_sens.iloc[j, -1]), 141 | fontsize = 12, style='italic', 142 | horizontalalignment='left') 143 | 144 | plt.xlabel(x_title, fontsize = 12) 145 | plt.ylabel("Rank", fontsize = 12) 146 | plt.yticks(fontsize = 12) 147 | plt.xticks(fontsize = 12) 148 | plt.title(method_name + ', modification of ' + criterion_name + ' weight') 149 | plt.grid(True, linestyle = ':') 150 | plt.tight_layout() 151 | criterion_name = criterion_name.replace("$", "") 152 | criterion_name = criterion_name.replace("{", "") 153 | criterion_name = criterion_name.replace("}", "") 154 | plt.savefig('./results/' + 'sensitivity_' + 'lineplot_' + method_name + '_' + criterion_name + '_' + filename + '.eps') 155 | plt.show() 156 | 157 | 158 | # heat maps with correlations 159 | def draw_heatmap(df_new_heatmap, title): 160 | """ 161 | Visualization method to display heatmap with correlations of compared rankings generated using different methods 162 | 163 | Parameters 164 | ---------- 165 | data : DataFrame 166 | DataFrame with correlation values between compared rankings 167 | 168 | title : str 169 | title of chart containing name of used correlation coefficient 170 | 171 | Examples 172 | --------- 173 | >>> draw_heatmap(df_new_heatmap, title) 174 | """ 175 | plt.figure(figsize = (8, 5)) 176 | sns.set(font_scale = 1.2) 177 | heatmap = sns.heatmap(df_new_heatmap, annot=True, fmt=".2f", cmap="PuBu", 178 | linewidth=0.5, linecolor='w') 179 | plt.yticks(va="center") 180 | plt.xlabel('Methods') 181 | plt.title('Correlation: ' + title) 182 | plt.tight_layout() 183 | title = title.replace("$", "") 184 | plt.savefig('./results/' + 'correlations_' + title + '.eps') 185 | plt.show() 186 | 187 | 188 | # radar chart 189 | def plot_radar(data, title, j): 190 | """ 191 | Visualization method to display rankings of alternatives obtained with different methods 192 | on the radar chart. 193 | 194 | Parameters 195 | ----------- 196 | data : DataFrame 197 | DataFrame containing containing rankings of alternatives obtained with different 198 | methods. The particular rankings are contained in subsequent columns of DataFrame. 199 | 200 | title : str 201 | Chart title 202 | 203 | j : int 204 | Index of criterion chosen for weight modification in sensitivity analysis 205 | 206 | Examples 207 | ---------- 208 | >>> plot_radar(data, title, j) 209 | """ 210 | fig=plt.figure() 211 | ax = fig.add_subplot(111, polar = True) 212 | 213 | for col in list(data.columns): 214 | labels=np.array(list(data.index)) 215 | stats = data.loc[labels, col].values 216 | 217 | angles=np.linspace(0, 2*np.pi, len(labels), endpoint=False) 218 | # close the plot 219 | stats=np.concatenate((stats,[stats[0]])) 220 | angles=np.concatenate((angles,[angles[0]])) 221 | 222 | lista = list(data.index) 223 | lista.append(data.index[0]) 224 | labels=np.array(lista) 225 | 226 | ax.plot(angles, stats, '-o', linewidth=2) 227 | 228 | ax.set_thetagrids(angles * 180/np.pi, labels) 229 | ax.grid(True) 230 | ax.set_axisbelow(True) 231 | plt.legend(data.columns, bbox_to_anchor=(1.0, 0.95, 0.4, 0.2), loc='upper left') 232 | plt.title(title) 233 | plt.tight_layout() 234 | plt.savefig('./results/' + 'radar_chart_C' + str(j + 1) + '.eps') 235 | plt.show() 236 | 237 | # Examplary visualization method 238 | def plot_boxplot(data, title): 239 | """ 240 | Display boxplot showing distribution of preference values determined with different methods. 241 | 242 | Parameters 243 | ---------- 244 | data : dataframe 245 | dataframe with correlation values between compared rankings 246 | 247 | title : str 248 | Title of chart. 249 | 250 | Examples 251 | ---------- 252 | >>> plot_boxplot(data, title) 253 | """ 254 | 255 | df_melted = pd.melt(data) 256 | plt.figure(figsize = (7, 4)) 257 | ax = sns.boxplot(x = 'variable', y = 'value', data = df_melted, width = 0.4) 258 | ax.grid(True, linestyle = '--') 259 | ax.set_axisbelow(True) 260 | ax.set_xlabel('Alternatives', fontsize = 12) 261 | ax.set_ylabel('Preference distribution', fontsize = 12) 262 | plt.title(title) 263 | plt.tight_layout() 264 | plt.savefig('./results/' + 'TOPSIS_boxplot' + '.eps') 265 | plt.show() 266 | 267 | 268 | # plot box chart of results obtained in robustness analysis showing intervals of alternatives performance values for particular ranks 269 | def plot_boxplot_simulation(data, x, y, hue, xtitle, ytitle, title, filename): 270 | """ 271 | Visualization method to display box chart of results obtained in robustness analysis. It shows 272 | intervals of alternatives performance values for particular positions in ranking. 273 | 274 | Parameters 275 | ---------- 276 | data : DataFrame 277 | DataFrame containing results 278 | 279 | x : str 280 | Name of column in DataFrame with variable names in axis x on chart 281 | 282 | y : str 283 | Name of column in DataFrame with variable values in axis y on chart 284 | hue : str 285 | Name of hue, that determines how the data are plotted 286 | 287 | xtitle : str 288 | Name of axis x title 289 | 290 | ytitle : str 291 | Name of axis y title 292 | 293 | title : str 294 | Chart title 295 | 296 | filename : str 297 | Name of file in which chart will be saved 298 | 299 | Examples 300 | ---------- 301 | >>> plot_boxplot_simulation(data, x, y, hue , xtitle, ytitle, title, filename) 302 | 303 | """ 304 | plt.figure(figsize = (9,5)) 305 | ax = sns.boxplot(x = x, y = y, hue = hue, palette='husl', data = data) 306 | ax.set_xlabel(xtitle, fontsize = 12) 307 | ax.set_ylabel(ytitle, fontsize = 12) 308 | ax.grid(True, linestyle = ':') 309 | ax.set_axisbelow(True) 310 | plt.legend(bbox_to_anchor=(1.0, 0.82, 0.3, 0.2), loc='upper left', title = 'Rank', edgecolor = 'black', fontsize = 12) 311 | plt.title(title) 312 | plt.tight_layout() 313 | plt.savefig('./results/' + filename + '.eps') 314 | plt.show() -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=42"] 3 | build-backend = "setuptools.build_meta" -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib==3.7.1 2 | numpy==1.24.2 3 | pandas==1.5.3 4 | scipy==1.14.0 5 | seaborn==0.13.2 6 | setuptools==70.3.0 7 | tabulate==0.9.0 8 | -------------------------------------------------------------------------------- /requirements_python_vermin.txt: -------------------------------------------------------------------------------- 1 | Detecting python files.. 2 | Analyzing 41 files using 8 processes.. 3 | ~2, ~3 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\docs\conf.py 4 | 2.3, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\examples\main_crispyn.py 5 | 2.3, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\examples\main_pyrepo_mcda.py 6 | 2.3, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\examples\main_pyrepo_mcda_update.py 7 | 2.3, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\examples\main_visualizations.py 8 | 2.3, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\examples\visualizations.py 9 | !2, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\setup.py 10 | ~2, ~3 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\__init__.py 11 | 2.4, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\additions.py 12 | 2.6, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\compromise_rankings.py 13 | 2.3, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\correlations.py 14 | 2.6, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\distance_metrics.py 15 | ~2, ~3 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\mcda_methods\__init__.py 16 | 2.4, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\mcda_methods\ahp.py 17 | 2.4, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\mcda_methods\aras.py 18 | 2.4, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\mcda_methods\cocoso.py 19 | 2.6, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\mcda_methods\codas.py 20 | 2.4, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\mcda_methods\copras.py 21 | 2.4, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\mcda_methods\cradis.py 22 | ~2, ~3 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\mcda_methods\edas.py 23 | ~2, ~3 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\mcda_methods\mabac.py 24 | 2.4, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\mcda_methods\marcos.py 25 | !2, 3.4 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\mcda_methods\mcda_method.py 26 | 2.4, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\mcda_methods\multimoora.py 27 | 2.6, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\mcda_methods\promethee.py 28 | 2.6, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\mcda_methods\prosa_c.py 29 | 2.4, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\mcda_methods\saw.py 30 | 2.4, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\mcda_methods\spotis.py 31 | 2.4, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\mcda_methods\topsis.py 32 | 2.4, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\mcda_methods\vikor.py 33 | 2.4, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\mcda_methods\vikor_smaa.py 34 | !2, 3.6 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\mcda_methods\vmcm.py 35 | 2.4, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\mcda_methods\waspas.py 36 | ~2, ~3 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\normalizations.py 37 | 2.6, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\sensitivity_analysis_weights_percentages.py 38 | 2.6, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\sensitivity_analysis_weights_values.py 39 | 2.6, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\src\pyrepo_mcda\weighting_methods.py 40 | 2.1, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\tests\test_correlations.py 41 | 2.3, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\tests\test_mcda_methods_1.py 42 | 2.1, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\tests\test_mcda_methods_2.py 43 | 2.1, 3.0 D:\HUAWEI\PYREPO_MCDA_VMCM_UPDATE_2023\pyrepo-mcda\tests\test_weighting_methods.py 44 | 45 | Tips: 46 | - Since '# novm' or '# novermin' weren't used, a speedup can be achieved using: --no-parse-comments 47 | (disable using: --no-tips) 48 | 49 | Minimum required versions: 3.6 50 | Incompatible versions: 2 51 | -------------------------------------------------------------------------------- /requirements_src.txt: -------------------------------------------------------------------------------- 1 | numpy==1.24.2 2 | pandas==1.5.3 3 | scipy==1.11.3 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r", encoding="utf-8") as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | name="pyrepo-mcda", 8 | version="0.1.8", 9 | author="Aleksandra Bączkiewicz", 10 | author_email="aleksandra.baczkiewicz@phd.usz.edu.pl", 11 | description="Python library for Multi-Criteria Decision Analysis.", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/energyinpython/pyrepo-mcda", 15 | package_dir={"": "src"}, 16 | packages=setuptools.find_packages(where="src"), 17 | classifiers=[ 18 | "Programming Language :: Python :: 3", 19 | "License :: OSI Approved :: MIT License", 20 | "Operating System :: OS Independent", 21 | ], 22 | python_requires=">=3.6", 23 | install_requires=['numpy', 'pandas', 'scipy'] 24 | ) -------------------------------------------------------------------------------- /src/pyrepo_mcda/__init__.py: -------------------------------------------------------------------------------- 1 | from . import mcda_methods 2 | from . import additions 3 | from . import compromise_rankings 4 | from . import correlations 5 | from . import distance_metrics 6 | from . import normalizations 7 | from . import sensitivity_analysis_weights_percentages 8 | from . import sensitivity_analysis_weights_values 9 | from . import weighting_methods -------------------------------------------------------------------------------- /src/pyrepo_mcda/additions.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def rank_preferences(pref, reverse = True): 5 | """ 6 | Rank alternatives according to MCDA preference function values. If more than one alternative 7 | have the same preference function value, they will be given the same rank value (tie). 8 | 9 | Parameters 10 | ------------ 11 | pref : ndarray 12 | Vector with MCDA preference function values for alternatives 13 | reverse : bool 14 | The boolean variable is True for MCDA methods that rank alternatives in descending 15 | order (for example, TOPSIS, CODAS) and False for MCDA methods that rank alternatives in ascending 16 | order (for example, VIKOR, SPOTIS) 17 | 18 | Returns 19 | --------- 20 | ndarray 21 | Vector with alternatives ranking. Alternative with 1 value is the best and has the first position in the ranking. 22 | 23 | Examples 24 | ---------- 25 | >>> rank = rank_preferences(pref, reverse = True) 26 | """ 27 | 28 | # Create an array ndarray for the ranking values of alternatives 29 | rank = np.zeros(len(pref)) 30 | # Generate sorted vector with MCDA preference function values 31 | # sorting order is determined by variable `reverse` and depends on MCDA method 32 | sorted_pref = sorted(pref, reverse = reverse) 33 | # position of the best alternative in ranking is denoted by 1, so assign 1 to `pos` 34 | pos = 1 35 | for i in range(len(sorted_pref) - 1): 36 | # find index in vector with preference values `pref` equal to preference value in sorted vector `sorted_pref` 37 | ind = np.where(sorted_pref[i] == pref)[0] 38 | # assign rank denoted by `pos` to index `ind` in rank vector 39 | rank[ind] = pos 40 | # if the next preference value in sorted vector is higher than actual, increase `pos` which denotes rank 41 | # if the next preference value is equal to actual, `pos` will be unchanged 42 | if sorted_pref[i] != sorted_pref[i + 1]: 43 | pos += 1 44 | # find index with the last preference value 45 | ind = np.where(sorted_pref[i + 1] == pref)[0] 46 | # assign the last place `pos` to alternative with the last preference value 47 | rank[ind] = pos 48 | return rank.astype(int) -------------------------------------------------------------------------------- /src/pyrepo_mcda/compromise_rankings.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import numpy as np 3 | 4 | from .additions import rank_preferences 5 | from .normalizations import multimoora_normalization 6 | 7 | 8 | # Copeland Method for compromise ranking 9 | def copeland(matrix): 10 | """Calculate the compromise ranking considering several rankings obtained using different 11 | methods using the Copeland compromise ranking methodology. 12 | 13 | Parameters 14 | ----------- 15 | matrix : ndarray 16 | Two-dimensional matrix containing different rankings in columns. 17 | 18 | Returns 19 | -------- 20 | ndarray 21 | Vector including compromise ranking. 22 | 23 | Examples 24 | ---------- 25 | >>> rank = copeland(matrix) 26 | """ 27 | 28 | grade_matrix = matrix.shape[0] - matrix 29 | wins_score = np.sum(grade_matrix, axis = 1) 30 | lossess_score = np.sum(wins_score) - wins_score 31 | rank = rank_preferences(wins_score - lossess_score, reverse = True) 32 | return rank 33 | 34 | 35 | # Dominance Directed Graph for compromise ranking 36 | def dominance_directed_graph(matrix): 37 | """Calculate the compromise ranking considering several rankings obtained using different 38 | methods using Dominance Directed Graph methodology 39 | 40 | Parameters 41 | ----------- 42 | matrix : ndarray 43 | Two-dimensional matrix containing different rankings in columns. 44 | 45 | Returns 46 | -------- 47 | ndarray 48 | Vector including compromise ranking. 49 | 50 | Examples 51 | ---------- 52 | >>> rank = dominance_directed_graph(matrix) 53 | """ 54 | 55 | m, n = matrix.shape 56 | A = np.zeros((m, m)) 57 | for j, i in itertools.product(range(n), range(m)): 58 | ind_better = np.where(matrix[i, j] < matrix[:, j])[0] 59 | A[i, j] += len(ind_better) 60 | AS = np.sum(A, axis = 1) 61 | ASR = rank_preferences(AS, reverse = True) 62 | return ASR 63 | 64 | 65 | # Rank Position Method for compromise ranking 66 | def rank_position_method(matrix): 67 | """Calculate the compromise ranking considering several rankings obtained using different 68 | methods using Rank Position Method 69 | 70 | Parameters 71 | ----------- 72 | matrix : ndarray 73 | Two-dimensional matrix containing different rankings in columns. 74 | 75 | Returns 76 | -------- 77 | ndarray 78 | Vector including compromise ranking. 79 | 80 | Examples 81 | --------- 82 | >>> rank = rank_position_method(matrix) 83 | """ 84 | 85 | A = 1 / (np.sum((1 / matrix), axis = 1)) 86 | RPM = rank_preferences(A, reverse = False) 87 | return RPM 88 | 89 | 90 | # Improved Borda Rule method for compromise for MULTIMOORA 91 | def improved_borda_rule(prefs, ranks): 92 | """Calculate the compromise ranking considering several rankings obtained using different 93 | methods using Improved Borda rule methodology 94 | 95 | Parameters 96 | ----------- 97 | prefs : ndarray 98 | Two-dimensional matrix containing preferences calculated by different methods in columns. 99 | 100 | ranks : ndarray 101 | Two-dimensional matrix containing rankings determined by different methods in columns. 102 | 103 | Returns 104 | -------- 105 | ndarray 106 | Vector including compromise ranking. 107 | 108 | Examples 109 | ---------- 110 | >>> rank = improved_borda_rule(prefs, ranks) 111 | """ 112 | 113 | m, n = ranks.shape 114 | nprefs = multimoora_normalization(prefs, np.ones(n)) 115 | A = (nprefs[:, 0] * ((m - ranks[:, 0] + 1) / (m * (m + 1) / 2))) - \ 116 | (nprefs[:, 1] * (ranks[:, 1] / (m * (m + 1) / 2))) + \ 117 | (nprefs[:, 2] * ((m - ranks[:, 2] + 1) / (m * (m + 1) / 2))) 118 | IMB = rank_preferences(A, reverse = True) 119 | return IMB -------------------------------------------------------------------------------- /src/pyrepo_mcda/correlations.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | # spearman coefficient rs 5 | def spearman(R, Q): 6 | """ 7 | Calculate Spearman rank correlation coefficient between two vectors 8 | 9 | Parameters 10 | ----------- 11 | R : ndarray 12 | First vector containing values 13 | Q : ndarray 14 | Second vector containing values 15 | 16 | Returns 17 | -------- 18 | float 19 | Value of correlation coefficient between two vectors 20 | 21 | Examples 22 | ---------- 23 | >>> rS = spearman(R, Q) 24 | """ 25 | 26 | N = len(R) 27 | denominator = N*(N**2-1) 28 | numerator = 6*sum((R-Q)**2) 29 | rS = 1-(numerator/denominator) 30 | return rS 31 | 32 | 33 | # weighted spearman coefficient rw 34 | def weighted_spearman(R, Q): 35 | """ 36 | Calculate Weighted Spearman rank correlation coefficient between two vectors 37 | 38 | Parameters 39 | ----------- 40 | R : ndarray 41 | First vector containing values 42 | Q : ndarray 43 | Second vector containing values 44 | 45 | Returns 46 | -------- 47 | float 48 | Value of correlation coefficient between two vectors 49 | 50 | Examples 51 | --------- 52 | >>> rW = weighted_spearman(R, Q) 53 | """ 54 | 55 | N = len(R) 56 | denominator = N**4 + N**3 - N**2 - N 57 | numerator = 6 * sum((R - Q)**2 * ((N - R + 1) + (N - Q + 1))) 58 | rW = 1 - (numerator / denominator) 59 | return rW 60 | 61 | 62 | # pearson coefficient 63 | def pearson_coeff(R, Q): 64 | """ 65 | Calculate Pearson correlation coefficient between two vectors 66 | 67 | Parameters 68 | ----------- 69 | R : ndarray 70 | First vector containing values 71 | Q : ndarray 72 | Second vector containing values 73 | 74 | Returns 75 | -------- 76 | float 77 | Value of correlation coefficient between two vectors 78 | 79 | Examples 80 | ---------- 81 | >>> corr = pearson_coeff(R, Q) 82 | """ 83 | 84 | numerator = np.sum((R - np.mean(R)) * (Q - np.mean(Q))) 85 | denominator = np.sqrt(np.sum((R - np.mean(R))**2) * np.sum((Q - np.mean(Q))**2)) 86 | corr = numerator / denominator 87 | return corr 88 | 89 | 90 | # rank similarity coefficient WS 91 | def WS_coeff(R, Q): 92 | """ 93 | Calculate Rank smilarity coefficient between two vectors 94 | 95 | Parameters 96 | ----------- 97 | R : ndarray 98 | First vector containing values 99 | Q : ndarray 100 | Second vector containing values 101 | 102 | Returns 103 | -------- 104 | float 105 | Value of similarity coefficient between two vectors 106 | 107 | Examples 108 | ---------- 109 | >>> ws = WS_coeff(R, Q) 110 | """ 111 | 112 | N = len(R) 113 | numerator = 2**(-np.float64(R)) * np.abs(R - Q) 114 | denominator = np.max((np.abs(R - 1), np.abs(R - N)), axis = 0) 115 | ws = 1 - np.sum(numerator / denominator) 116 | return ws -------------------------------------------------------------------------------- /src/pyrepo_mcda/mcda_methods/__init__.py: -------------------------------------------------------------------------------- 1 | from . import mcda_method 2 | from .ahp import AHP 3 | from .aras import ARAS 4 | from .cocoso import COCOSO 5 | from .codas import CODAS 6 | from .copras import COPRAS 7 | from .cradis import CRADIS 8 | from .edas import EDAS 9 | from .mabac import MABAC 10 | from .marcos import MARCOS 11 | from .multimoora import MULTIMOORA 12 | from .multimoora import MULTIMOORA_RS 13 | from .promethee import PROMETHEE_II 14 | from .prosa_c import PROSA_C 15 | from .saw import SAW 16 | from .spotis import SPOTIS 17 | from .topsis import TOPSIS 18 | from .vikor import VIKOR 19 | from .vmcm import VMCM 20 | from .waspas import WASPAS 21 | from .vikor_smaa import VIKOR_SMAA 22 | from .pvm import PVM -------------------------------------------------------------------------------- /src/pyrepo_mcda/mcda_methods/ahp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from scipy.sparse.linalg import eigs 4 | 5 | from .mcda_method import MCDA_method 6 | from ..normalizations import minmax_normalization 7 | 8 | 9 | 10 | class AHP(MCDA_method): 11 | def __init__(self, normalization_method = minmax_normalization): 12 | """ 13 | Create the AHP method object 14 | 15 | Parameters 16 | ----------- 17 | normalization_method : function 18 | If you use the AHP method to evaluate a matrix containing the numerical 19 | values of the performance alternatives and you have a vector of criteria 20 | weights containing their numerical values, then you are not using the 21 | `classic_ahp` method but a method called `ahp`. 22 | You have to choose a method to normalize the given decision matrix. 23 | The default normalization technique is `minmax_normalization` 24 | """ 25 | self.normalization_method = normalization_method 26 | 27 | 28 | def __call__(self, matrix, weights, types): 29 | """ 30 | Score alternatives provided in decision matrix `matrix` using criteria `weights` and criteria `types`. 31 | 32 | Parameters 33 | ------------ 34 | matrix : ndarray 35 | Decision matrix with numerical performance values of alternatives. Decision matrix 36 | includes m alternatives in rows and n criteria in columns. 37 | weights: ndarray 38 | Vector with criteria weights given in numerical values. The sum of weights 39 | must be equal to 1. 40 | types: ndarray 41 | Criteria types. Profit criteria are represented by 1 and cost by -1. 42 | 43 | Returns 44 | --------- 45 | ndrarray 46 | Preference values of each alternative. The best alternative has the highest preference value. 47 | 48 | Examples 49 | ---------- 50 | >>> ahp = AHP() 51 | >>> pref = ahp(matrix, weights, types) 52 | >>> rank = rank_preferences(pref, reverse = True) 53 | """ 54 | AHP._verify_input_data(matrix, weights, types) 55 | return AHP._ahp(self, matrix, weights, types, self.normalization_method) 56 | 57 | 58 | def _check_consistency(self, X): 59 | """ 60 | Consistency Check on the Pairwise Comparison Matrix of the Criteria or alternatives 61 | 62 | Parameters 63 | ----------- 64 | X : ndarray 65 | matrix of pairwise comparisons 66 | 67 | Examples 68 | ---------- 69 | >>> PCcriteria = np.array([[1, 1, 5, 3], [1, 1, 5, 3], [1/5, 1/5, 1, 1/3], [1/3, 1/3, 3, 1]]) 70 | >>> ahp = AHP() 71 | >>> ahp._check_consistency(PCcriteria) 72 | """ 73 | n = X.shape[1] 74 | RI = [0, 0, 0.58, 0.90, 1.12, 1.24, 1.32, 1.41, 1.45, 1.49] 75 | lambdamax = np.amax(np.linalg.eigvals(X).real) 76 | CI = (lambdamax - n) / (n - 1) 77 | CR = CI / RI[n - 1] 78 | print("Inconsistency index: ", CR) 79 | if CR > 0.1: 80 | print("The pairwise comparison matrix is inconsistent") 81 | 82 | 83 | def _calculate_eigenvector(self, X): 84 | """ 85 | Compute the Priority Vector of Criteria (weights) or alternatives using Eigenvector method 86 | 87 | Parameters 88 | ----------- 89 | X : ndarray 90 | matrix of pairwise comparisons 91 | 92 | Returns 93 | --------- 94 | ndarray 95 | Eigenvector 96 | 97 | Examples 98 | ---------- 99 | >>> PCM1 = np.array([[1, 5, 1, 1, 1/3, 3], 100 | [1/5, 1, 1/3, 1/5, 1/7, 1], 101 | [1, 3, 1, 1/3, 1/5, 1], 102 | [1, 5, 3, 1, 1/3, 3], 103 | [3, 7, 5, 3, 1, 7], 104 | [1/3, 1, 1, 1/3, 1/7, 1]]) 105 | >>> ahp = AHP() 106 | >>> S = ahp._calculate_eigenvector(PCM1) 107 | """ 108 | val, vec = eigs(X, k=1) 109 | eig_vec = np.real(vec) 110 | S = eig_vec / np.sum(eig_vec) 111 | S = S.ravel() 112 | return S 113 | 114 | 115 | def _normalized_column_sum(self, X): 116 | """ 117 | Compute the Priority Vector of Criteria (weights) or alternatives using The normalized column sum method 118 | 119 | Parameters 120 | ----------- 121 | X : ndarray 122 | matrix of pairwise comparisons 123 | 124 | Returns 125 | --------- 126 | ndarray 127 | Vector with weights calculated with The normalized column sum method 128 | 129 | Examples 130 | ---------- 131 | >>> PCM1 = np.array([[1, 5, 1, 1, 1/3, 3], 132 | [1/5, 1, 1/3, 1/5, 1/7, 1], 133 | [1, 3, 1, 1/3, 1/5, 1], 134 | [1, 5, 3, 1, 1/3, 3], 135 | [3, 7, 5, 3, 1, 7], 136 | [1/3, 1, 1, 1/3, 1/7, 1]]) 137 | >>> ahp = AHP() 138 | >>> S = ahp._normalized_column_sum(PCM1) 139 | """ 140 | return np.sum(X, axis = 1) / np.sum(X) 141 | 142 | 143 | def _geometric_mean(self, X): 144 | """ 145 | Compute the Priority Vector of Criteria (weights) or alternatives using The geometric mean method 146 | 147 | Parameters 148 | ----------- 149 | X : ndarray 150 | matrix of pairwise comparisons 151 | 152 | Returns 153 | --------- 154 | ndarray 155 | Vector with weights calculated with The geometric mean method 156 | 157 | Examples 158 | ---------- 159 | >>> PCM1 = np.array([[1, 5, 1, 1, 1/3, 3], 160 | [1/5, 1, 1/3, 1/5, 1/7, 1], 161 | [1, 3, 1, 1/3, 1/5, 1], 162 | [1, 5, 3, 1, 1/3, 3], 163 | [3, 7, 5, 3, 1, 7], 164 | [1/3, 1, 1, 1/3, 1/7, 1]]) 165 | >>> ahp = AHP() 166 | >>> S = ahp._geometric_mean(PCM1) 167 | """ 168 | n = X.shape[1] 169 | numerator = (np.prod(X, axis = 1))**(1 / n) 170 | denominator = np.sum(numerator) 171 | return numerator / denominator 172 | 173 | 174 | def _classic_ahp(self, alt_matrices, weights, calculate_priority_vector_method = None): 175 | """ 176 | Calculate the global alternative priorities. 177 | This is a method for classic AHP where you provide matrices with values of pairwise 178 | comparisons of alternatives and weights in the form of a priority vector. 179 | 180 | Parameters 181 | ------------ 182 | alt_matrices : list 183 | list with matrices including values of pairwise comparisons of alternatives 184 | weights : ndarray 185 | priority vector of criteria (weights) 186 | calculate_priority_vector_method : function 187 | Method for calculation of the priority vector. It can be chosen from three 188 | available methods: _calculate_eigenvector, _normalized_column_sum and 189 | _geometric_mean 190 | if the user does not provide calculate_priority_vector_method, it is automatically 191 | set as the default _calculate_eigenvector 192 | 193 | Returns 194 | --------- 195 | ndarray 196 | vector with the global alternative priorities 197 | 198 | Examples 199 | ----------- 200 | >>> PCcriteria = np.array([[1, 1, 5, 3], [1, 1, 5, 3], 201 | [1/5, 1/5, 1, 1/3], [1/3, 1/3, 3, 1]]) 202 | >>> PCM1 = np.array([[1, 5, 1, 1, 1/3, 3], 203 | [1/5, 1, 1/3, 1/5, 1/7, 1], 204 | [1, 3, 1, 1/3, 1/5, 1], 205 | [1, 5, 3, 1, 1/3, 3], 206 | [3, 7, 5, 3, 1, 7], 207 | [1/3, 1, 1, 1/3, 1/7, 1]]) 208 | >>> PCM2 = np.array([[1, 7, 3, 1/3, 1/3, 1/3], 209 | [1/7, 1, 1/3, 1/7, 1/9, 1/7], 210 | [1/3, 3, 1, 1/5, 1/5, 1/5], 211 | [3, 7, 5, 1, 1, 1], 212 | [3, 9, 5, 1, 1, 1], 213 | [3, 7, 5, 1, 1, 1]]) 214 | >>> PCM3 = np.array([[1, 1/9, 1/7, 1/9, 1, 1/5], 215 | [9, 1, 1, 1, 5, 3], 216 | [7, 1, 1, 1, 5, 1], 217 | [9, 1, 1, 1, 7, 3], 218 | [1, 1/5, 1/5, 1/7, 1, 1/3], 219 | [5, 1/3, 1, 1/3, 3, 1]]) 220 | >>> PCM4 = np.array([[1, 1/5, 1/5, 1/3, 1/7, 1/5], 221 | [5, 1, 1, 3, 1/3, 1], 222 | [5, 1, 1, 1, 1/3, 1], 223 | [3, 1/3, 1, 1, 1/7, 1], 224 | [7, 3, 3, 7, 1, 5], 225 | [5, 1, 1, 1, 1/5, 1]]) 226 | 227 | >>> ahp = AHP() 228 | >>> ahp._check_consistency(PCcriteria) 229 | >>> weights = ahp._calculate_eigenvector(PCcriteria) 230 | >>> alt_matrices = [] 231 | >>> alt_matrices.append(PCM1) 232 | >>> alt_matrices.append(PCM2) 233 | >>> alt_matrices.append(PCM3) 234 | >>> alt_matrices.append(PCM4) 235 | 236 | >>> calculate_priority_vector_method = ahp._calculate_eigenvector 237 | >>> pref = ahp._classic_ahp(alt_matrices, weights, calculate_priority_vector_method) 238 | >>> rank = rank_preferences(pref, reverse = True) 239 | """ 240 | 241 | # eigenvector method is default method to calculate priority vector 242 | if calculate_priority_vector_method is None: 243 | calculate_priority_vector_method = self._calculate_eigenvector 244 | # Check consistency of all pairwise comparison matrices for alternatives 245 | for alt in alt_matrices: 246 | self._check_consistency(alt) 247 | 248 | m = alt_matrices[0].shape[0] 249 | n = len(weights) 250 | 251 | # Calculate priority vector withe selected method 252 | S = np.zeros((m, n)) 253 | for el, alt in enumerate(alt_matrices): 254 | S[:, el] = calculate_priority_vector_method(alt) 255 | 256 | # Calculate the global alternative priorities 257 | # Calculate the weighted matrix 258 | Sw = S * weights 259 | # Aggregate the Local Priorities and Rank the Alternatives 260 | S_final = np.sum(Sw, axis = 1) / np.sum(Sw) 261 | 262 | return S_final 263 | 264 | 265 | @staticmethod 266 | def _ahp(self, matrix, weights, types, normalization_method): 267 | """ 268 | Score alternatives provided in decision matrix `matrix` using criteria `weights` and criteria `types`. 269 | 270 | Parameters 271 | ------------ 272 | matrix : ndarray 273 | Decision matrix with numerical performance values of alternatives. The decision matrix 274 | includes m alternatives in rows and n criteria in columns. 275 | weights: ndarray 276 | Vector with criteria weights given in numerical values. The sum of weights 277 | must be equal to 1. 278 | types: ndarray 279 | Criteria types. Profit criteria are represented by 1 and cost by -1. 280 | 281 | Returns 282 | --------- 283 | ndrarray 284 | Preference values of each alternative. The best alternative has the highest preference value. 285 | 286 | Examples 287 | ---------- 288 | >>> ahp = AHP() 289 | >>> pref = ahp(matrix, weights, types) 290 | >>> rank = rank_preferences(pref, reverse = True) 291 | """ 292 | nmatrix = normalization_method(matrix, types) 293 | weighted_matrix = nmatrix * weights 294 | pref = np.sum(weighted_matrix, axis = 1) 295 | return pref -------------------------------------------------------------------------------- /src/pyrepo_mcda/mcda_methods/aras.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from .mcda_method import MCDA_method 4 | from ..normalizations import sum_normalization 5 | 6 | 7 | 8 | class ARAS(MCDA_method): 9 | def __init__(self, normalization_method = sum_normalization): 10 | """ 11 | Create the ARAS method object 12 | """ 13 | self.normalization_method = normalization_method 14 | 15 | def __call__(self, matrix, weights, types): 16 | """ 17 | Score alternatives provided in decision matrix `matrix` using criteria `weights` and criteria `types`. 18 | 19 | Parameters 20 | ----------- 21 | matrix : ndarray 22 | Decision matrix with m alternatives in rows and n criteria in columns. 23 | weights: ndarray 24 | Criteria weights. Sum of weights must be equal to 1. 25 | types: ndarray 26 | Criteria types. Profit criteria are represented by 1 and cost by -1. 27 | 28 | Returns 29 | -------- 30 | ndrarray 31 | Preference values of each alternative. The best alternative has the highest preference value. 32 | 33 | Examples 34 | ---------- 35 | >>> aras = ARAS() 36 | >>> pref = aras(matrix, weights, types) 37 | >>> rank = rank_preferences(pref, reverse = True) 38 | """ 39 | 40 | ARAS._verify_input_data(matrix, weights, types) 41 | return ARAS._aras(matrix, weights, types, self.normalization_method) 42 | 43 | @staticmethod 44 | def _aras(matrix, weights, types, normalization_method): 45 | # Create optimal alternative 46 | A0 = np.zeros(matrix.shape[1]) 47 | A0[types == 1] = np.max(matrix[:, types == 1], axis = 0) 48 | A0[types == -1] = np.min(matrix[:, types == -1], axis = 0) 49 | matrix = np.vstack((A0, matrix)) 50 | # Normalize matrix using the sum normalization method 51 | norm_matrix = normalization_method(matrix, types) 52 | # Calculate the weighted normalized decision matrix 53 | d = norm_matrix * weights 54 | # Calculate the optimality function for each alternative 55 | S = np.sum(d, axis = 1) 56 | # Determine the degree of utility 57 | U = S / S[0] 58 | return U[1:] -------------------------------------------------------------------------------- /src/pyrepo_mcda/mcda_methods/cocoso.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from ..normalizations import minmax_normalization 4 | from .mcda_method import MCDA_method 5 | 6 | 7 | class COCOSO(MCDA_method): 8 | def __init__(self, normalization_method = minmax_normalization, lambda_param = 0.5): 9 | """ 10 | Create the COCOSO method object and select value of lambda parameter called `lambda_param`. 11 | By default, the lambda parameter is equal to 0.5 12 | 13 | Parameters 14 | ----------- 15 | lambda_param : parameter chosen by decision makers, usually is equal to 0.5. It determines 16 | flexibility and stability of the proposed CoCoSo 17 | """ 18 | 19 | self.normalization_method = normalization_method 20 | self.lambda_param = lambda_param 21 | 22 | 23 | def __call__(self, matrix, weights, types): 24 | """ 25 | Score alternatives provided in decision matrix `matrix` with m alternatives in rows and 26 | n criteria in columns using criteria `weights` and criteria `types`. 27 | 28 | Parameters 29 | ---------- 30 | matrix : ndarray 31 | Decision matrix with m alternatives in rows and n criteria in columns. 32 | weights: ndarray 33 | Vector with criteria weights. Sum of weights must be equal to 1. 34 | types: ndarray 35 | Vector with criteria types. Profit criteria are represented by 1 and cost by -1. 36 | 37 | Returns 38 | ------- 39 | ndrarray 40 | Vector with preference values of each alternative. The best alternative has the highest preference value. 41 | 42 | Examples 43 | --------- 44 | >>> cocoso = COCOSO(lambda_param = lambda_param) 45 | >>> pref = cocoso(matrix, weights, types) 46 | >>> rank = rank_preferences(pref, reverse = True) 47 | """ 48 | 49 | COCOSO._verify_input_data(matrix, weights, types) 50 | return COCOSO._cocoso(matrix, weights, types, self.normalization_method, self.lambda_param) 51 | 52 | 53 | @staticmethod 54 | def _cocoso(matrix, weights, types, normalization_method = minmax_normalization, lambda_param = 0.5): 55 | # Normalize matrix using chosen normalization. minmax_normalization is default 56 | norm_matrix = normalization_method(matrix, types) 57 | S = np.sum(weights * norm_matrix, axis = 1) 58 | P = np.sum(norm_matrix**weights, axis = 1) 59 | 60 | kia = (P + S) / np.sum(P + S) 61 | kib = S / np.min(S) + P / np.min(P) 62 | kic = (lambda_param * S + (1 - lambda_param) * P) / (lambda_param * np.max(S) + (1 - lambda_param) * np.max(P)) 63 | K = (kia * kib * kic)**(1/3) + 1/3*(kia + kib + kic) 64 | return K -------------------------------------------------------------------------------- /src/pyrepo_mcda/mcda_methods/codas.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import numpy as np 3 | 4 | from ..normalizations import linear_normalization 5 | from ..distance_metrics import euclidean 6 | from .mcda_method import MCDA_method 7 | 8 | 9 | class CODAS(MCDA_method): 10 | def __init__(self, normalization_method = linear_normalization, distance_metric = euclidean, tau = 0.02): 11 | """ 12 | Create the CODAS method object and select normalization method `normalization_method`, default 13 | normalization method for CODAS is `linear_normalization`, distance metric 14 | `distance_metric` selected from `distance_metrics`, which is `euclidean` by default and tau parameter `tau`, 15 | which is set on 0.02 by default. 16 | 17 | Parameters 18 | ----------- 19 | normalization_method : function 20 | method for decision matrix normalization chosen from `normalizations` 21 | 22 | distance_metric : functions 23 | method for calculating the distance between two vectors 24 | 25 | tau : float 26 | the threshold parameter between 0.01 to 0.05. If the difference between 27 | Euclidean `euclidean` or other selected distances of two alternatives is less than tau, these two alternatives 28 | are also compared by the Taxicab distance 29 | """ 30 | self.normalization_method = normalization_method 31 | self.distance_metric = distance_metric 32 | self.tau = tau 33 | 34 | 35 | def __call__(self, matrix, weights, types): 36 | """ 37 | Score alternatives provided in decision matrix `matrix` with m alternatives and n criteria 38 | using criteria `weights` and criteria `types`. 39 | 40 | Parameters 41 | ----------- 42 | matrix : ndarray 43 | Decision matrix with m alternatives in rows and n criteria in columns. 44 | weights: ndarray 45 | Vector with criteria weights. Sum of weights must be equal to 1. 46 | types: ndarray 47 | Vector with criteria types. Profit criteria are represented by 1 and cost by -1. 48 | 49 | Returns 50 | -------- 51 | ndrarray 52 | Vector with preference values of each alternative. The best alternative has the highest preference value. 53 | 54 | Examples 55 | ---------- 56 | >>> codas = CODAS(normalization_method = linear_normalization, distance_metric = euclidean, tau = 0.02) 57 | >>> pref = codas(matrix, weights, types) 58 | >>> rank = rank_preferences(pref, reverse = True) 59 | """ 60 | CODAS._verify_input_data(matrix, weights, types) 61 | return CODAS._codas(self, matrix, weights, types, self.normalization_method, self.distance_metric) 62 | 63 | 64 | # psi 0.01 - 0.05 recommended range of tau (threshold parameter) value 65 | def _psi(self, x): 66 | return 1 if np.abs(x) >= self.tau else 0 67 | 68 | 69 | @staticmethod 70 | def _codas(self, matrix, weights, types, normalization_method, distance_metric): 71 | # Normalize matrix using linear normalization 72 | norm_matrix = normalization_method(matrix, types) 73 | 74 | # Multiply all rows of normalized matrix by weights 75 | weighted_matrix = norm_matrix * weights 76 | m, n = weighted_matrix.shape 77 | 78 | # Calculate NIS vector (anti-ideal solution) 79 | nis = np.min(weighted_matrix, axis=0) 80 | 81 | # Calculate chosen distance (for example Euclidean) and Taxicab distance from anti-ideal solution 82 | E = np.array([distance_metric(x, nis) for x in weighted_matrix]) 83 | 84 | # Calculate Taxicab (Manhattan) distance 85 | T = np.sum(np.abs(weighted_matrix - nis), axis=1) 86 | 87 | # Construct the relative assessment matrix H 88 | h = np.zeros((m, m)) 89 | for i, j in itertools.product(range(m), range(m)): 90 | h[i, j] = (E[i] - E[j]) + (self._psi(E[i] - E[j]) * (T[i] - T[j])) 91 | 92 | H = np.sum(h, axis=1) 93 | return H -------------------------------------------------------------------------------- /src/pyrepo_mcda/mcda_methods/copras.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from ..normalizations import sum_normalization 3 | from .mcda_method import MCDA_method 4 | 5 | 6 | class COPRAS(MCDA_method): 7 | def __init__(self, normalization_method = sum_normalization): 8 | """ 9 | Create the COPRAS method object and select normalization method `normalization_method` 10 | 11 | Parameters 12 | ----------- 13 | normalization_method : function 14 | method for decision matrix normalization chosen from `normalizations` 15 | """ 16 | self.normalization_method = normalization_method 17 | 18 | def __call__(self, matrix, weights, types): 19 | """ 20 | Score alternatives provided in decision matrix `matrix` using criteria `weights` and criteria `types`. 21 | 22 | Parameters 23 | ----------- 24 | matrix : ndarray 25 | Decision matrix with m alternatives in rows and n criteria in columns. 26 | weights: ndarray 27 | Criteria weights. Sum of weights must be equal to 1. 28 | types: ndarray 29 | Criteria types. Profit criteria are represented by 1 and cost by -1. 30 | 31 | Returns 32 | -------- 33 | ndrarray 34 | Preference values of each alternative. The best alternative has the highest preference value. 35 | 36 | Examples 37 | ---------- 38 | >>> copras = COPRAS(normalization_method = sum_normalization) 39 | >>> pref = copras(matrix, weights, types) 40 | >>> rank = rank_preferences(pref, reverse = True) 41 | """ 42 | 43 | COPRAS._verify_input_data(matrix, weights, types) 44 | return COPRAS._copras(matrix, weights, types, self.normalization_method) 45 | 46 | @staticmethod 47 | def _copras(matrix, weights, types, normalization_method): 48 | # Normalize matrix as for profit criteria using chosen normalization method. 49 | # norm_matrix = matrix/np.sum(matrix, axis = 0) 50 | norm_matrix = normalization_method(matrix, np.ones(len(weights))) 51 | # Multiply all values in the normalized matrix by weights. 52 | d = norm_matrix * weights 53 | # Calculate the sums of weighted normalized outcomes for profit criteria. 54 | Sp = np.sum(d[:, types == 1], axis = 1) 55 | # Calculate the sums of weighted normalized outcomes for cost criteria. 56 | Sm = np.sum(d[:, types == -1], axis = 1) 57 | # Calculate the relative priority Q of evaluated options. 58 | Q = Sp + ((np.sum(Sm))/(Sm * np.sum(1 / Sm))) 59 | # Calculate the quantitive utility value for each of the evaluated options. 60 | U = Q / np.max(Q) 61 | return U 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/pyrepo_mcda/mcda_methods/cradis.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from .mcda_method import MCDA_method 4 | from ..normalizations import linear_normalization 5 | 6 | 7 | class CRADIS(MCDA_method): 8 | def __init__(self, normalization_method = linear_normalization): 9 | """ 10 | Create the CRADIS method object 11 | """ 12 | self.normalization_method = normalization_method 13 | 14 | 15 | def __call__(self, matrix, weights, types): 16 | """ 17 | Score alternatives provided in decision matrix `matrix` using criteria `weights` and criteria `types`. 18 | 19 | Parameters 20 | ----------- 21 | matrix : ndarray 22 | Decision matrix with m alternatives in rows and n criteria in columns. 23 | weights: ndarray 24 | Criteria weights. Sum of weights must be equal to 1. 25 | types: ndarray 26 | Criteria types. Profit criteria are represented by 1 and cost by -1. 27 | 28 | Returns 29 | -------- 30 | ndrarray 31 | Preference values of each alternative. The best alternative has the highest preference value. 32 | 33 | Examples 34 | ---------- 35 | >>> cradis = CRADIS() 36 | >>> pref = cradis(matrix, weights, types) 37 | >>> rank = rank_preferences(pref, reverse = True) 38 | """ 39 | CRADIS._verify_input_data(matrix, weights, types) 40 | return CRADIS._cradis(matrix, weights, types, self.normalization_method) 41 | 42 | 43 | @staticmethod 44 | def _cradis(matrix, weights, types, normalization_method): 45 | 46 | # Normalize the decision matrix 47 | norm_matrix = normalization_method(matrix, types) 48 | # Create the weighted normalized decision matrix 49 | v = norm_matrix * weights 50 | 51 | # Determine the ideal and anti-ideal solution 52 | 53 | # calculation of the ideal solution is done by finding the largest value in 54 | # weighted normalized matrix v(max) 55 | # calculation of the anti-ideal solution is done by finding the smallest value in 56 | # weighted normalized matrix v(min) 57 | 58 | # Calculation of deviations from ideal and anti-ideal solutions 59 | # Calculating the grades of the deviation of individual alternatives from ideal and 60 | # anti-ideal solutions 61 | Sp = np.sum(np.max(v) - v, axis = 1) 62 | Sm = np.sum(v - np.min(v), axis = 1) 63 | 64 | # Calculation of the utility function for each alternative in relation to the deviations 65 | # from the optimal alternatives 66 | 67 | # Sop is the optimal alternative that has the smallest distance from the ideal solution 68 | # Som is the optimal alternative that has the greatest distance from the anti-ideal solution 69 | 70 | Sop = np.sum(np.max(v) - np.max(v, axis = 0)) 71 | Som = np.sum(np.max(v, axis = 0) - np.min(v)) 72 | 73 | Kp = Sop / Sp 74 | Km = Sm / Som 75 | Q = (Kp + Km) / 2 76 | 77 | # The best alternative is the one that has the highest value 78 | return Q -------------------------------------------------------------------------------- /src/pyrepo_mcda/mcda_methods/edas.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from .mcda_method import MCDA_method 3 | 4 | 5 | class EDAS(MCDA_method): 6 | def __init__(self): 7 | """Create object of the EDAS method""" 8 | pass 9 | 10 | 11 | def __call__(self, matrix, weights, types): 12 | """ 13 | Score alternatives provided in decision matrix `matrix` using criteria `weights` and criteria `types`. 14 | 15 | Parameters 16 | ----------- 17 | matrix : ndarray 18 | Decision matrix with m alternatives in rows and n criteria in columns. 19 | weights: ndarray 20 | Vector with criteria weights. Sum of weights must be equal to 1. 21 | types: ndarray 22 | Vevtor with criteria types. Profit criteria are represented by 1 and cost by -1. 23 | 24 | Returns 25 | -------- 26 | ndrarray 27 | Vector with preference values of each alternative. The best alternative has the highest preference value. 28 | 29 | Examples 30 | --------- 31 | >>> edas = EDAS() 32 | >>> pref = edas(matrix, weights, types) 33 | >>> rank = rank_preferences(pref, reverse = True) 34 | """ 35 | EDAS._verify_input_data(matrix, weights, types) 36 | return EDAS._edas(matrix, weights, types) 37 | 38 | 39 | def _edas(matrix, weights, types): 40 | m, n = matrix.shape 41 | #AV = np.mean(matrix, axis = 0) 42 | 43 | # Calculate the average solution for each criterion 44 | AV = np.sum(matrix, axis = 0) / m 45 | 46 | # Calculate the Positive Distance (PDA) and Negative Distance (NDA) from average solution 47 | PDA = np.zeros(matrix.shape) 48 | NDA = np.zeros(matrix.shape) 49 | 50 | for j in range(0, n): 51 | if types[j] == 1: 52 | PDA[:, j] = (matrix[:, j] - AV[j]) / AV[j] 53 | NDA[:, j] = (AV[j] - matrix[:, j]) / AV[j] 54 | else: 55 | PDA[:, j] = (AV[j] - matrix[:, j]) / AV[j] 56 | NDA[:, j] = (matrix[:, j] - AV[j]) / AV[j] 57 | 58 | PDA[PDA < 0] = 0 59 | NDA[NDA < 0] = 0 60 | 61 | # Calculate the weighted sum of PDA and NDA for all alternatives 62 | SP = np.sum(weights * PDA, axis = 1) 63 | SN = np.sum(weights * NDA, axis = 1) 64 | 65 | # Normalize obtained values 66 | NSP = SP / np.max(SP) 67 | NSN = 1 - (SN / np.max(SN)) 68 | 69 | # Calculate the appraisal score (AS) for each alternative 70 | AS = (NSP + NSN) / 2 71 | return AS 72 | 73 | -------------------------------------------------------------------------------- /src/pyrepo_mcda/mcda_methods/mabac.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from ..normalizations import minmax_normalization 3 | from .mcda_method import MCDA_method 4 | 5 | 6 | class MABAC(MCDA_method): 7 | def __init__(self, normalization_method = minmax_normalization): 8 | """ 9 | Create the MABAC method object and select normalization method `normalization_method` from 10 | `normalizations`. The default normalization method for MABAC method is `minmax_normalization` 11 | 12 | Parameters 13 | ----------- 14 | normalization_method : function 15 | method for decision matrix normalization chosen from `normalizations` 16 | """ 17 | self.normalization_method = normalization_method 18 | 19 | 20 | def __call__(self, matrix, weights, types): 21 | """ 22 | Score alternatives provided in decision matrix `matrix` using criteria `weights` and criteria `types`. 23 | 24 | Parameters 25 | ----------- 26 | matrix : ndarray 27 | Decision matrix with m alternatives in rows and n criteria in columns. 28 | weights: ndarray 29 | Vector with criteria weights. Sum of weights must be equal to 1. 30 | types: ndarray 31 | Vector with criteria types. Profit criteria are represented by 1 and cost by -1. 32 | 33 | Returns 34 | -------- 35 | ndrarray 36 | Vector with preference values of each alternative. The best alternative has the highest preference value. 37 | 38 | Examples 39 | --------- 40 | >>> mabac = MABAC(normalization_method = minmax_normalization) 41 | >>> pref = mabac(matrix, weights, types) 42 | >>> rank = rank_preferences(pref, reverse = True) 43 | """ 44 | MABAC._verify_input_data(matrix, weights, types) 45 | return MABAC._mabac(matrix, weights, types, self.normalization_method) 46 | 47 | 48 | def _mabac(matrix, weights, types, normalization_method): 49 | m, n = matrix.shape 50 | # Normalize the decision matrix 51 | norm_matrix = normalization_method(matrix, types) 52 | # Calculate elements from the weighted matrix 53 | V = weights * (norm_matrix + 1) 54 | # Determine the border approximation area matrix 55 | G = np.product(V, axis = 0) ** (1/m) 56 | # Calculate distance of alternatives from the border approximation area for the matrix elements 57 | Q = V - G 58 | # Calculate the sum of distance of alternatives from the border approximation areas 59 | # By calculating the sum of elements from Q matrix by rows we obtain the final values of 60 | # the criterion functions of the alternatives 61 | S = np.sum(Q, axis = 1) 62 | return S 63 | 64 | -------------------------------------------------------------------------------- /src/pyrepo_mcda/mcda_methods/marcos.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from .mcda_method import MCDA_method 4 | 5 | 6 | class MARCOS(MCDA_method): 7 | def __init__(self): 8 | """ 9 | Create the MARCOS method object 10 | """ 11 | pass 12 | 13 | def __call__(self, matrix, weights, types): 14 | """ 15 | Score alternatives provided in decision matrix `matrix` using criteria `weights` and criteria `types`. 16 | 17 | Parameters 18 | ----------- 19 | matrix : ndarray 20 | Decision matrix with m alternatives in rows and n criteria in columns. 21 | weights: ndarray 22 | Criteria weights. Sum of weights must be equal to 1. 23 | types: ndarray 24 | Criteria types. Profit criteria are represented by 1 and cost by -1. 25 | 26 | Returns 27 | -------- 28 | ndrarray 29 | Preference values of each alternative. The best alternative has the highest preference value. 30 | 31 | Examples 32 | ---------- 33 | >>> marcos = MARCOS() 34 | >>> pref = marcos(matrix, weights, types) 35 | >>> rank = rank_preferences(pref, reverse = True) 36 | """ 37 | 38 | MARCOS._verify_input_data(matrix, weights, types) 39 | return MARCOS._marcos(matrix, weights, types) 40 | 41 | 42 | @staticmethod 43 | def _marcos(matrix, weights, types): 44 | # Step 1 - define the decision matrix with m alternatives and n criteria 45 | # a decision matrix is provided as an argument of the MARCOS method 46 | # Step 2 47 | # Create the ideal solution AI 48 | AI = np.zeros(matrix.shape[1]) 49 | # for profit criteria 50 | AI[types == 1] = np.max(matrix[:, types == 1], axis = 0) 51 | # for cost criteria 52 | AI[types == -1] = np.min(matrix[:, types == -1], axis = 0) 53 | 54 | # Create the antiideal solution AAI 55 | AAI = np.zeros(matrix.shape[1]) 56 | # for profit criteria 57 | AAI[types == 1] = np.min(matrix[:, types == 1], axis = 0) 58 | # for cost criteria 59 | AAI[types == -1] = np.max(matrix[:, types == -1], axis = 0) 60 | 61 | 62 | e_matrix = np.vstack((AAI, matrix)) 63 | e_matrix = np.vstack((e_matrix, AI)) 64 | 65 | # Step 3 normalization of extended matrix 66 | norm_matrix = np.zeros(e_matrix.shape) 67 | # for cost criteria 68 | norm_matrix[:, types == -1] = AI[types == -1] / e_matrix[:, types == -1] 69 | 70 | # for profit criteria 71 | norm_matrix[:, types == 1] = e_matrix[:, types == 1] / AI[types == 1] 72 | 73 | # Step 4 determination of the weighted matrix 74 | v = norm_matrix * weights 75 | 76 | # Step 5 Calculation of the utility degree of alternatives Ki 77 | Si = np.sum(v, axis = 1) 78 | Ki_minus = Si / np.sum(v[0, :]) 79 | Ki_plus = Si / np.sum(v[-1, :]) 80 | 81 | # Step 6 Determination of the utility function of alternatives f(Ki). 82 | f_Ki_minus = Ki_plus / (Ki_plus + Ki_minus) 83 | f_Ki_plus = Ki_minus / (Ki_plus + Ki_minus) 84 | f_Ki = (Ki_plus + Ki_minus) / (1 + ((1 - f_Ki_plus) / f_Ki_plus) + ((1 - f_Ki_minus) / f_Ki_minus)) 85 | 86 | # Step 7 Ranking the alternatives. 87 | # Ranking of the alternatives is based on the final values of utility functions. 88 | # It is desirable that an alternative has the highest possible value of 89 | # the utility function 90 | 91 | # return utility function values for each alternative 92 | return f_Ki[1:-1] -------------------------------------------------------------------------------- /src/pyrepo_mcda/mcda_methods/mcda_method.py: -------------------------------------------------------------------------------- 1 | from abc import ABC 2 | import numpy as np 3 | 4 | class MCDA_method(ABC): 5 | 6 | def __call__(self, matrix, weights, types): 7 | """ 8 | Score alternatives from decision matrix `matrix` using criteria weights `weights` and 9 | criteria types `types` 10 | 11 | Parameters 12 | ---------- 13 | matrix : ndarray 14 | decision matrix with performance values for m alternatives in rows and n criteria 15 | in columns 16 | weights : ndarray 17 | matrix with criteria weights vectors with number of columns equal to 18 | number of columns n of `matrix` 19 | types : ndarray 20 | vector with criteria types containing values of 1 for profit criteria and -1 for 21 | cost criteria with size equal to number of columns n of `matrix` 22 | """ 23 | pass 24 | 25 | @staticmethod 26 | def _verify_input_data(matrix, weights, types): 27 | m, n = matrix.shape 28 | # if weights are vector with one dimension 29 | if len(weights.shape) == 1: 30 | if len(weights) != n: 31 | raise ValueError('The size of the weight vector must be the same as the number of criteria') 32 | # if weights are two-dimensional matrix containing many weight vectors in rows 33 | elif len(weights.shape) == 2: 34 | if weights.shape[1] != n: 35 | raise ValueError('The number of columns of matrix with weight vectors must be the same as the number of criteria') 36 | if len(types) != n: 37 | raise ValueError('The size of the types vector must be the same as the number of criteria') 38 | check_types = np.all((types == 1) | (types == -1)) 39 | if check_types == False: 40 | raise ValueError('Criteria types can only have a value of 1 for profits and -1 for costs') -------------------------------------------------------------------------------- /src/pyrepo_mcda/mcda_methods/multimoora.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from ..normalizations import multimoora_normalization 4 | from ..additions import rank_preferences 5 | from ..compromise_rankings import dominance_directed_graph 6 | from .mcda_method import MCDA_method 7 | 8 | 9 | class MULTIMOORA_RS(MCDA_method): 10 | def __init__(self): 11 | """Create object of the MULTIMOORA Ratio System (RS) method. This method is an integral part of the 12 | MULTIMOORA method. This method is the same as the MOORA method, and it can be used 13 | independently as a separate MCDA method""" 14 | pass 15 | 16 | 17 | def __call__(self, matrix, weights, types): 18 | """ 19 | Score alternatives provided in decision matrix `matrix` using vector with criteria weights 20 | `weights` and vector with criteria types `types`. 21 | 22 | Parameters 23 | ---------- 24 | matrix : ndarray 25 | Decision matrix with m alternatives in rows and n criteria in columns. 26 | weights: ndarray 27 | Criteria weights. Sum of weights must be equal to 1. 28 | types: ndarray 29 | Criteria types. Profit criteria are represented by 1 and cost by -1. 30 | 31 | Returns 32 | ------- 33 | ndrarray 34 | Preference values of each alternative. The best alternative has the highest preference value. 35 | 36 | Examples 37 | --------- 38 | >>> multimoora_rs = MULTIMOORA_RS() 39 | >>> pref = multimoora_rs(matrix, weights, types) 40 | >>> rank = rank_preferences(pref, reverse = True) 41 | """ 42 | MULTIMOORA_RS._verify_input_data(matrix, weights, types) 43 | return MULTIMOORA_RS._multimoora_rs(matrix, weights, types) 44 | 45 | 46 | @staticmethod 47 | def _multimoora_rs(matrix, weights, types): 48 | # Normalize decision matrix using normalization method `multimoora_normalization` dedicated to the MULTIMOORA method 49 | norm_matrix = multimoora_normalization(matrix) 50 | # Calculate the overall performance index of alternatives as 51 | # difference between sums of weighted normalized performance ratings 52 | # of the profit and cost criteria 53 | Q_sum_profit = np.sum(weights[types == 1] * norm_matrix[:, types == 1], axis = 1) 54 | Q_sum_cost = np.sum(weights[types == -1] * norm_matrix[:, types == -1], axis = 1) 55 | Q = Q_sum_profit - Q_sum_cost 56 | return Q 57 | 58 | 59 | class MULTIMOORA_RP(MCDA_method): 60 | def __init__(self): 61 | """Create object of the MULTIMOORA Reference Point (RP) method. This method is an integral part of the MULTIMOORA 62 | method""" 63 | pass 64 | 65 | 66 | def __call__(self, matrix, weights, types): 67 | """ 68 | Score alternatives provided in decision matrix `matrix` using vector with criteria weights 69 | `weights` and vector with criteria types `types`. 70 | 71 | Parameters 72 | ---------- 73 | matrix : ndarray 74 | Decision matrix with m alternatives in rows and n criteria in columns. 75 | weights: ndarray 76 | Criteria weights. Sum of weights must be equal to 1. 77 | types: ndarray 78 | Criteria types. Profit criteria are represented by 1 and cost by -1. 79 | 80 | Returns 81 | ------- 82 | ndrarray 83 | Preference values of each alternative. The best alternative has the lowest preference value. 84 | 85 | Examples 86 | --------- 87 | >>> multimoora_rp = MULTIMOORA_RP() 88 | >>> pref = multimoora_rp(matrix, weights, types) 89 | >>> rank = rank_preferences(pref, reverse = False) 90 | """ 91 | MULTIMOORA_RP._verify_input_data(matrix, weights, types) 92 | return MULTIMOORA_RP._multimoora_rp(matrix, weights, types) 93 | 94 | 95 | @staticmethod 96 | def _multimoora_rp(matrix, weights, types): 97 | # Normalize decision matrix using normalization method `multimoora_normalization` dedicated to the MULTIMOORA method 98 | norm_matrix = multimoora_normalization(matrix) 99 | 100 | RR = np.zeros(norm_matrix.shape[1]) 101 | 102 | maximums = np.amax(norm_matrix, axis = 0) 103 | minimums = np.amin(norm_matrix, axis = 0) 104 | RR[types == 1] = maximums[types == 1] 105 | RR[types == -1] = minimums[types == -1] 106 | weighted_matrix = weights * np.abs(RR - norm_matrix) 107 | A = np.max(weighted_matrix, axis = 1) 108 | return A 109 | 110 | 111 | class MULTIMOORA_FMF(MCDA_method): 112 | def __init__(self): 113 | """Create object of the MULTIMOORA Full Multiplicative Form (FMF) method. This method is an integral part of the MULTIMOORA 114 | method""" 115 | pass 116 | 117 | 118 | def __call__(self, matrix, weights, types): 119 | """ 120 | Score alternatives provided in decision matrix `matrix` using vector with criteria weights 121 | `weights` and vector with criteria types `types`. 122 | 123 | Parameters 124 | ---------- 125 | matrix : ndarray 126 | Decision matrix with m alternatives in rows and n criteria in columns. 127 | weights: ndarray 128 | Criteria weights. Sum of weights must be equal to 1. 129 | types: ndarray 130 | Criteria types. Profit criteria are represented by 1 and cost by -1. 131 | 132 | Returns 133 | ------- 134 | ndrarray 135 | Preference values of each alternative. The best alternative has the highest preference value. 136 | 137 | Examples 138 | --------- 139 | >>> multimoora_fmf = MULTIMOORA_FMF() 140 | >>> pref = multimoora_fmf(matrix, weights, types) 141 | >>> rank = rank_preferences(pref, reverse = True) 142 | """ 143 | MULTIMOORA_FMF._verify_input_data(matrix, weights, types) 144 | return MULTIMOORA_FMF._multimoora_fmf(matrix, weights, types) 145 | 146 | 147 | @staticmethod 148 | def _multimoora_fmf(matrix, weights, types): 149 | # Normalize decision matrix using normalization method `multimoora_normalization` dedicated to the MULTIMOORA method 150 | norm_matrix = multimoora_normalization(matrix) 151 | A = np.prod(weights[types == 1] * norm_matrix[:, types == 1], axis = 1) 152 | B = np.prod(weights[types == -1] * norm_matrix[:, types == -1], axis = 1) 153 | U = A / B 154 | return U 155 | 156 | 157 | class MULTIMOORA(MCDA_method): 158 | def __init__(self, compromise_rank_method = dominance_directed_graph): 159 | """Create object of the MULTIMOORA method. 160 | 161 | Parameters: 162 | ----------- 163 | compromise_rank_method : function 164 | method determining compromise ranking based on RS, RP and FMF MULTIMOORA rankings. 165 | The compromise rank method is selected from `compromise_rankings`. 166 | It can be `borda_copeland_compromise_ranking`, `dominance_directed_graph`, 167 | `rank_position_method` and `improved_borda_rule` which is dedicated to the MULTIMOORA method 168 | 169 | """ 170 | self.compromise_rank_method = compromise_rank_method 171 | 172 | 173 | def __call__(self, matrix, weights, types): 174 | """ 175 | Score alternatives provided in decision matrix `matrix` using vector with criteria weights 176 | `weights` and vector with criteria types `types`. 177 | 178 | Parameters 179 | ---------- 180 | matrix : ndarray 181 | Decision matrix with m alternatives in rows and n criteria in columns. 182 | weights: ndarray 183 | Criteria weights. Sum of weights must be equal to 1. 184 | types: ndarray 185 | Criteria types. Profit criteria are represented by 1 and cost by -1. 186 | 187 | Returns 188 | -------- 189 | ndrarray 190 | Preference values of each alternative. The best alternative has the highest preference value. 191 | 192 | Examples 193 | ---------- 194 | >>> multimoora = MULTIMOORA() 195 | >>> rank = multimoora(matrix, weights, types) 196 | """ 197 | MULTIMOORA._verify_input_data(matrix, weights, types) 198 | return MULTIMOORA._multimoora(matrix, weights, types, self.compromise_rank_method) 199 | 200 | 201 | def _multimoora(matrix, weights, types, compromise_rank_method): 202 | 203 | # Calculate preference values and ranking of alternatives by MULTIMOORA RS 204 | multimoora_rs = MULTIMOORA_RS() 205 | pref_rs = multimoora_rs(matrix, weights, types) 206 | 207 | # in MULTIMOORA RS alternatives are sorted in descending order like in TOPSIS 208 | rank_rs = rank_preferences(pref_rs, reverse = True) 209 | 210 | # Calculate preference values and ranking of alternatives by MULTIMOORA RP 211 | multimoora_rp = MULTIMOORA_RP() 212 | pref_rp = multimoora_rp(matrix, weights, types) 213 | pref = np.hstack((pref_rs.reshape(-1,1), pref_rp.reshape(-1,1))) 214 | 215 | # in MULTIMOORA RP alternatives are sorted in ascending order like in VIKOR 216 | rank_rp = rank_preferences(pref_rp, reverse = False) 217 | rank = np.hstack((rank_rs.reshape(-1,1), rank_rp.reshape(-1,1))) 218 | 219 | # Calculate preference values and ranking of alternatives by MULTIMOORA FMF 220 | multimoora_fmf = MULTIMOORA_FMF() 221 | pref_fmf = multimoora_fmf(matrix, weights, types) 222 | pref = np.hstack((pref, pref_fmf.reshape(-1,1))) 223 | 224 | # in MULTIMOORA FMF alternatives are sorted in descending order like in TOPSIS 225 | rank_fmf = rank_preferences(pref_fmf, reverse = True) 226 | rank = np.hstack((rank, rank_fmf.reshape(-1,1))) 227 | 228 | if compromise_rank_method.__name__ == 'improved_borda_rule': 229 | mmoora_rank = compromise_rank_method(pref, rank) 230 | else: 231 | mmoora_rank = compromise_rank_method(rank) 232 | 233 | return mmoora_rank -------------------------------------------------------------------------------- /src/pyrepo_mcda/mcda_methods/promethee.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import numpy as np 3 | from .mcda_method import MCDA_method 4 | 5 | 6 | class PROMETHEE_II(MCDA_method): 7 | def __init__(self): 8 | """ 9 | Create the PROMETHEE II method object 10 | """ 11 | pass 12 | 13 | 14 | def __call__(self, matrix, weights, types, preference_functions = None, p = None, q = None): 15 | PROMETHEE_II._verify_input_data(matrix, weights, types) 16 | 17 | if preference_functions is None: 18 | preference_functions = [self._usual_function for pf in range(len(weights))] 19 | 20 | if p is None: 21 | u = np.sqrt(np.sum(np.square(np.mean(matrix, axis = 0) - matrix), axis = 0) / matrix.shape[0]) 22 | p = 2 * u 23 | 24 | if q is None: 25 | u = np.sqrt(np.sum(np.square(np.mean(matrix, axis = 0) - matrix), axis = 0) / matrix.shape[0]) 26 | q = 0.5 * u 27 | 28 | if len(preference_functions) != np.shape(matrix)[1]: 29 | raise ValueError('The list of preference functions must be equal in length to the number of criteria') 30 | if len(p) != np.shape(matrix)[1]: 31 | raise ValueError('The length of the vector p must be equal to the number of criteria') 32 | if len(q) != np.shape(matrix)[1]: 33 | raise ValueError('The length of the vector q must be equal to the number of criteria') 34 | 35 | return PROMETHEE_II._promethee_II(self, matrix, weights, types, preference_functions, p, q) 36 | 37 | 38 | # Preference function type 1 (Usual criterion) requires no parameters 39 | # alternatives are indifferent only if they are equal to each other 40 | # otherwise there is a strong preference for one of them 41 | def _usual_function(self, d, p, q): 42 | if d <= 0: 43 | return 0 44 | else: 45 | return 1 46 | 47 | # Preference function type 2 (U-shape criterion) requires indifference threshold (q) 48 | def _ushape_function(self, d, p, q): 49 | if d <= q: 50 | return 0 51 | else: 52 | return 1 53 | 54 | # Preference function type 3 (V-shape criterion) requires threshold of absolute preference (p) 55 | def _vshape_function(self, d, p, q): 56 | if d <= 0: 57 | return 0 58 | elif 0 <= d <= p: 59 | return d / p 60 | elif d > p: 61 | return 1 62 | 63 | # preference function type 4 (Level criterion) requires both preference and indifference thresholds (p and q) 64 | def _level_function(self, d, p, q): 65 | if d <= q: 66 | return 0 67 | elif q < d <= p: 68 | return 0.5 69 | elif d > p: 70 | return 1 71 | 72 | # Preference function type 5 (V-shape with indifference criterion also known as linear) 73 | # requires both preference and indifference thresholds (p and q) 74 | def _linear_function(self, d, p, q): 75 | if d <= q: 76 | return 0 77 | elif q < d <= p: 78 | return (d - q) / (p - q) 79 | elif d > p: 80 | return 1 81 | 82 | # preference function type 6 (Gaussian criterion) 83 | # requires to fix parameter s which is an intermediate value between q and p 84 | def _gaussian_function(self, d, p, q): 85 | if d <= 0: 86 | return 0 87 | elif d > 0: 88 | s = (p + q) / 2 89 | return 1 - np.exp(-((d ** 2) / (2 * s ** 2))) 90 | 91 | 92 | @staticmethod 93 | def _promethee_II(self, matrix, weights, types, preference_functions, p, q): 94 | """ 95 | Score alternatives provided in the decision matrix `matrix` using criteria `weights` and criteria `types`. 96 | 97 | Parameters 98 | ----------- 99 | matrix : ndarray 100 | Decision matrix with m alternatives in rows and n criteria in columns. 101 | weights: ndarray 102 | Criteria weights. The sum of weights must be equal to 1. 103 | types: ndarray 104 | Criteria types. Profit criteria are represented by 1 and cost by -1. 105 | preference_functions : list 106 | List with methods containing preference functions for calculating the 107 | preference degree for each criterion. 108 | p : ndarray 109 | Vector with values representing the threshold of absolute preference. 110 | q : ndarray 111 | Vector with values representing the threshold of indifference. 112 | 113 | Returns 114 | -------- 115 | ndrarray 116 | Preference values of each alternative. The best alternative has the highest preference value. 117 | 118 | Examples 119 | ---------- 120 | >>> promethee_II = PROMETHEE_II() 121 | >>> preference_functions = [promethee_II._linear_function for pf in range(len(weights))] 122 | >>> u = np.sqrt(np.sum(np.square(np.mean(matrix, axis = 0) - matrix), axis = 0) / matrix.shape[0]) 123 | >>> p = 2 * u 124 | >>> q = 0.5 * u 125 | >>> pref = promethee_II(matrix, weights, types, preference_functions, p = p, q = q) 126 | >>> rank = rank_preferences(pref, reverse = True) 127 | """ 128 | 129 | m, n = matrix.shape 130 | 131 | H = np.zeros((m, m)) 132 | 133 | # A preference index of two options, i-th and k-th, is calculated 134 | for i, k, j in itertools.product(range(m), range(m), range(n)): 135 | H[i, k] += preference_functions[j](types[j] * (matrix[i, j] - matrix[k, j]), p[j], q[j]) * weights[j] 136 | 137 | # Output phi_plus and input phi_minus dominance flows are determined 138 | phi_plus = np.sum(H, axis = 1) / (m - 1) 139 | phi_min = np.sum(H, axis = 0) / (m - 1) 140 | 141 | # The net dominance flow phi_net is calculated 142 | phi_net = phi_plus - phi_min 143 | return phi_net -------------------------------------------------------------------------------- /src/pyrepo_mcda/mcda_methods/prosa_c.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import numpy as np 3 | 4 | from .promethee import PROMETHEE_II 5 | 6 | 7 | 8 | # PROSA Examining Sustainability at the Criteria Level (PROSA-C) 9 | class PROSA_C(PROMETHEE_II): 10 | def __init__(self): 11 | """ 12 | Create the PROSA method object 13 | """ 14 | pass 15 | 16 | 17 | def __call__(self, matrix, weights, types, preference_functions = None, p = None, q = None, s = None): 18 | PROSA_C._verify_input_data(matrix, weights, types) 19 | 20 | if preference_functions is None: 21 | preference_functions = [self._usual_function for pf in range(len(weights))] 22 | 23 | if p is None: 24 | u = np.sqrt(np.sum(np.square(np.mean(matrix, axis = 0) - matrix), axis = 0) / matrix.shape[0]) 25 | p = 2 * u 26 | 27 | if q is None: 28 | u = np.sqrt(np.sum(np.square(np.mean(matrix, axis = 0) - matrix), axis = 0) / matrix.shape[0]) 29 | q = 0.5 * u 30 | 31 | if s is None: 32 | s = np.repeat(0.3, len(weights)) 33 | 34 | PROSA_C._verify_input_data(matrix, weights, types) 35 | # Verification of the correctness of given arguments 36 | if len(preference_functions) != np.shape(matrix)[1]: 37 | raise ValueError('The list of preference functions must be equal in length to the number of criteria') 38 | if len(p) != np.shape(matrix)[1]: 39 | raise ValueError('The length of the vector p must be equal to the number of criteria') 40 | if len(q) != np.shape(matrix)[1]: 41 | raise ValueError('The length of the vector q must be equal to the number of criteria') 42 | if len(s) != np.shape(matrix)[1]: 43 | raise ValueError('The length of the vector s must be equal to the number of criteria') 44 | 45 | return PROSA_C._prosa_c(self, matrix, weights, types, preference_functions, p, q, s) 46 | 47 | 48 | @staticmethod 49 | def _prosa_c(self, matrix, weights, types, preference_functions, p, q, s): 50 | """ 51 | Score alternatives provided in the decision matrix `matrix` using criteria `weights` and criteria `types`. 52 | 53 | Parameters 54 | ----------- 55 | matrix : ndarray 56 | Decision matrix with m alternatives in rows and n criteria in columns. 57 | weights: ndarray 58 | Criteria weights. The sum of weights must be equal to 1. 59 | types: ndarray 60 | Criteria types. Profit criteria are represented by 1 and cost by -1. 61 | preference_functions : list 62 | List with methods containing preference functions for calculating the 63 | preference degree for each criterion. 64 | p : ndarray 65 | Vector with values representing the threshold of absolute preference. 66 | q : ndarray 67 | Vector with values representing the threshold of indifference. 68 | s : ndarray 69 | Vector with values of the coefficient sj for the criteria 70 | 71 | Returns 72 | -------- 73 | ndrarray 74 | Preference values of each alternative. The best alternative has the highest preference value. 75 | 76 | Examples 77 | ---------- 78 | >>> prosa_c = PROSA_C() 79 | >>> preference_functions = [prosa_c._linear_function for pf in range(len(weights))] 80 | >>> u = np.sqrt(np.sum(np.square(np.mean(matrix, axis = 0) - matrix), axis = 0) / matrix.shape[0]) 81 | >>> p = 2 * u 82 | >>> q = 0.5 * u 83 | >>> s = np.repeat(0.3, len(weights)) 84 | >>> pref = promethee_II(matrix, weights, types, preference_functions, p = p, q = q, s = s) 85 | >>> rank = rank_preferences(pref, reverse = True) 86 | """ 87 | 88 | m, n = matrix.shape 89 | phi = np.zeros((m, n)) 90 | 91 | # Determination of deviations based on pair-wise comparisons, 92 | # Application of the preference function, 93 | # Calculation of a single criterion net outranking flow 94 | # preference functions are inherited from the PROMETHEE II class 95 | for j, i, k in itertools.product(range(n), range(m), range(m)): 96 | phi[i, j] += preference_functions[j](types[j] * (matrix[i, j] - matrix[k, j]), p[j], q[j]) -\ 97 | preference_functions[j](types[j] * (matrix[k, j] - matrix[i, j]), p[j], q[j]) 98 | 99 | 100 | phi = phi / (m - 1) 101 | # Calculation of a global (overall) net outranking flow, 102 | # phi_net is the weighted sum of net flow for each criterion 103 | phi_net = np.sum(phi * weights, axis = 1) 104 | 105 | # Calculate the value of a mean absolute deviation in a weighted form, 106 | # where the sustainability (compensation) coefficient was taken into consideration, 107 | # where s denotes the sustainability (compensation) coefficient for a criterion j. 108 | WMAD = np.sum(np.abs(phi_net.reshape(-1, 1) - phi) * weights * s, axis = 1) 109 | 110 | # The final evaluation of alternatives (PSV_net) (PROSA net Sustainable Value), is calculated 111 | PSV_net = phi_net - WMAD 112 | 113 | return PSV_net -------------------------------------------------------------------------------- /src/pyrepo_mcda/mcda_methods/pvm.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | class PVM(): 4 | def __init__(self): 5 | """ 6 | Create the PVM method object 7 | """ 8 | pass 9 | 10 | def __call__(self, matrix, weights, types, psi = None, phi = None): 11 | """ 12 | Score alternatives provided in decision matrix `matrix` using criteria `weights` and criteria `types`. 13 | 14 | Parameters 15 | ----------- 16 | matrix : ndarray 17 | Decision matrix with m alternatives in rows and n criteria in columns. 18 | weights: ndarray 19 | Attribute weights to the criteria. Sum of weights must be equal to 1. 20 | types: list 21 | Define the criteria character: 22 | Motivating criteria are represented by `m` 23 | Demotivating criteria are represented by `dm` 24 | Desirable criteria are represented by `d` 25 | Non-desirable criteria are represented by `nd` 26 | psi: ndarray 27 | Preference vector with motivating or desirable values. Providing of psi is optional. 28 | phi: ndarray 29 | Preference vector with demotivating or non-desirable values. Providing of psi is optional. 30 | 31 | Returns 32 | -------- 33 | ndrarray 34 | Preference values of each alternative. The best alternative has the highest preference value. 35 | 36 | Examples 37 | ---------- 38 | >>> pvm = PVM() 39 | >>> pref = pvm(matrix, weights, types) 40 | >>> rank = rank_preferences(pref, reverse = True) 41 | """ 42 | 43 | return PVM._pvm(matrix, weights, types, psi, phi) 44 | 45 | @staticmethod 46 | def _pvm(matrix, weights, types, psi, phi): 47 | 48 | # Determining the preference vector 49 | if (psi is None) or (phi is None): 50 | psi = np.zeros(matrix.shape[1]) 51 | phi = np.zeros(matrix.shape[1]) 52 | 53 | for i in range(len(types)): 54 | if types[i] == 'm': 55 | psi[i] = np.quantile(matrix[:,i], 0.75) 56 | phi[i] = np.quantile(matrix[:,i], 0.25) 57 | elif types[i] == 'dm': 58 | psi[i] = np.quantile(matrix[:,i], 0.25) 59 | phi[i] = np.quantile(matrix[:,i], 0.75) 60 | elif types[i] == 'd': 61 | psi[i] = np.max(matrix[:, i]) 62 | phi[i] = np.min(matrix[:, i]) 63 | elif types[i] == 'nd': 64 | psi[i] = np.min(matrix[:, i]) 65 | phi[i] = np.max(matrix[:, i]) 66 | else: 67 | raise ValueError('Only `m`, `dm`, `d` and `nd` criteria types are accepted.') 68 | 69 | # Normalization of decision matrix 70 | nmatrix = matrix / np.sqrt(np.sum(np.square(matrix), axis = 0)) 71 | 72 | # Normalization of preference vector 73 | psi_prim = psi / np.sqrt(np.sum(np.square(matrix), axis = 0)) 74 | phi_prim = phi / np.sqrt(np.sum(np.square(matrix), axis = 0)) 75 | 76 | T_prim = psi_prim - phi_prim 77 | 78 | T_prim_prim = T_prim / np.sqrt(np.sum(np.square(T_prim))) 79 | 80 | # Determination of importance factor `mi` 81 | mi_v = np.zeros(matrix.shape[0]) 82 | mi_d = np.zeros(matrix.shape[0]) 83 | mi_nd = np.zeros(matrix.shape[0]) 84 | l_mi_v, l_mi_d, l_mi_nd = 0, 0, 0 85 | 86 | for i in range(len(types)): 87 | if types[i] == 'm' or types[i] == 'dm': 88 | mi_v += (nmatrix[:,i] - phi_prim[i]) * T_prim_prim[i] * weights[i] 89 | l_mi_v += 1 90 | elif types[i] == 'd': 91 | mi_d += weights[i]**2 * (nmatrix[:,i] - psi_prim[i])**2 92 | l_mi_d += 1 93 | elif types[i] == 'nd': 94 | mi_nd += weights[i]**2 * (nmatrix[:,i] - psi_prim[i])**2 95 | l_mi_nd += 1 96 | 97 | mi_d = np.sqrt(mi_d) 98 | mi_nd = np.sqrt(mi_nd) 99 | 100 | mi = (mi_v * l_mi_v - mi_d * l_mi_d + mi_nd * l_mi_nd) / (l_mi_v + l_mi_d + l_mi_nd) 101 | 102 | return mi -------------------------------------------------------------------------------- /src/pyrepo_mcda/mcda_methods/saw.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from .mcda_method import MCDA_method 4 | from ..normalizations import linear_normalization 5 | 6 | 7 | 8 | class SAW(MCDA_method): 9 | def __init__(self, normalization_method = linear_normalization): 10 | """ 11 | Create the SAW method object and select normalization method `normalization_method`. 12 | 13 | Parameters 14 | ----------- 15 | normalization_method : function 16 | method for decision matrix normalization chosen from `normalizations` 17 | """ 18 | 19 | self.normalization_method = normalization_method 20 | 21 | def __call__(self, matrix, weights, types): 22 | """ 23 | Score alternatives provided in decision matrix `matrix` with m alternatives in rows and 24 | n criteria in columns using criteria `weights` and criteria `types`. 25 | 26 | Parameters 27 | ---------- 28 | matrix : ndarray 29 | Decision matrix with m alternatives in rows and n criteria in columns. 30 | weights: ndarray 31 | Vector with criteria weights. Sum of weights must be equal to 1. 32 | types: ndarray 33 | Vector with criteria types. Profit criteria are represented by 1 and cost by -1. 34 | 35 | Returns 36 | -------- 37 | ndrarray 38 | Vector with preference values of each alternative. The best alternative has the highest preference value. 39 | 40 | Examples 41 | --------- 42 | >>> saw = SAW(normalization_method = minmax_normalization) 43 | >>> pref = saw(matrix, weights, types) 44 | >>> rank = rank_preferences(pref, reverse = True) 45 | """ 46 | 47 | SAW._verify_input_data(matrix, weights, types) 48 | return SAW._saw(matrix, weights, types, self.normalization_method) 49 | 50 | 51 | @staticmethod 52 | def _saw(matrix, weights, types, normalization_method): 53 | # Normalize matrix using chosen normalization (for example linear normalization) 54 | norm_matrix = normalization_method(matrix, types) 55 | 56 | # Multiply all rows of normalized matrix by weights 57 | weighted_matrix = norm_matrix * weights 58 | 59 | # Aggregate and return scores 60 | return np.sum(weighted_matrix, axis = 1) -------------------------------------------------------------------------------- /src/pyrepo_mcda/mcda_methods/spotis.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from .mcda_method import MCDA_method 3 | 4 | class SPOTIS(MCDA_method): 5 | def __init__(self): 6 | """Create SPOTIS method object. 7 | """ 8 | pass 9 | 10 | 11 | def __call__(self, matrix, weights, types, bounds): 12 | """Score alternatives provided in decision matrix `matrix` using criteria `weights` and criteria `types`. 13 | 14 | Parameters 15 | ----------- 16 | matrix : ndarray 17 | Decision matrix with m alternatives in rows and n criteria in columns. 18 | weights: ndarray 19 | Vector with criteria weights. Sum of weights must be equal to 1. 20 | types: ndarray 21 | Vector with criteria types. Profit criteria are represented by 1 and cost by -1. 22 | bounds: ndarray 23 | Bounds is ndarray with 2 rows and number of columns equal to criteria number. 24 | Bounds contain minimum values in the first row and maximum values in the second row 25 | for each criterion. Minimum and maximum values for the same criterion cannot be 26 | the same. 27 | 28 | Returns 29 | -------- 30 | ndrarray 31 | Vector with preference values of each alternative. The best alternative has the lowest preference value. 32 | 33 | Examples 34 | ---------- 35 | >>> bounds_min = np.amin(matrix, axis = 0) 36 | >>> bounds_max = np.amax(matrix, axis = 0) 37 | >>> bounds = np.vstack((bounds_min, bounds_max)) 38 | >>> spotis = SPOTIS() 39 | >>> pref = spotis(matrix, weights, types, bounds) 40 | >>> rank = rank_preferences(pref, reverse = False) 41 | """ 42 | SPOTIS._verify_input_data(matrix, weights, types) 43 | return SPOTIS._spotis(matrix, weights, types, bounds) 44 | 45 | 46 | @staticmethod 47 | def _spotis(matrix, weights, types, bounds): 48 | # Determine Ideal Solution Point (ISP) 49 | isp = np.zeros(matrix.shape[1]) 50 | isp[types == 1] = bounds[1, types == 1] 51 | isp[types == -1] = bounds[0, types == -1] 52 | 53 | # Calculate normalized distances 54 | norm_matrix = np.abs(matrix - isp) / np.abs(bounds[1, :] - bounds[0, :]) 55 | # Calculate the normalized weighted average distance 56 | D = np.sum(weights * norm_matrix, axis = 1) 57 | return D 58 | -------------------------------------------------------------------------------- /src/pyrepo_mcda/mcda_methods/topsis.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from ..normalizations import minmax_normalization 4 | from ..distance_metrics import euclidean 5 | from .mcda_method import MCDA_method 6 | 7 | class TOPSIS(MCDA_method): 8 | def __init__(self, normalization_method = minmax_normalization, distance_metric = euclidean): 9 | """ 10 | Create the TOPSIS method object and select normalization method `normalization_method` and 11 | distance metric `distance metric`. 12 | 13 | Parameters 14 | ----------- 15 | normalization_method : function 16 | method for decision matrix normalization chosen from `normalizations` 17 | 18 | distance_metric : functions 19 | method for calculating the distance between two vectors 20 | """ 21 | self.normalization_method = normalization_method 22 | self.distance_metric = distance_metric 23 | 24 | 25 | def __call__(self, matrix, weights, types): 26 | """ 27 | Score alternatives provided in decision matrix `matrix` with m alternatives in rows and 28 | n criteria in columns using criteria `weights` and criteria `types`. 29 | 30 | Parameters 31 | ---------- 32 | matrix : ndarray 33 | Decision matrix with m alternatives in rows and n criteria in columns. 34 | weights: ndarray 35 | Vector with criteria weights. Sum of weights must be equal to 1. 36 | types: ndarray 37 | Vector with criteria types. Profit criteria are represented by 1 and cost by -1. 38 | 39 | Returns 40 | ------- 41 | ndrarray 42 | Vector with preference values of each alternative. The best alternative has the highest preference value. 43 | 44 | Examples 45 | --------- 46 | >>> topsis = TOPSIS(normalization_method = minmax_normalization, distance_metric = euclidean) 47 | >>> pref = topsis(matrix, weights, types) 48 | >>> rank = rank_preferences(pref, reverse = True) 49 | """ 50 | TOPSIS._verify_input_data(matrix, weights, types) 51 | return TOPSIS._topsis(matrix, weights, types, self.normalization_method, self.distance_metric) 52 | 53 | 54 | @staticmethod 55 | def _topsis(matrix, weights, types, normalization_method, distance_metric): 56 | # Normalize matrix using chosen normalization (for example linear normalization) 57 | norm_matrix = normalization_method(matrix, types) 58 | 59 | # Multiply all rows of normalized matrix by weights 60 | weighted_matrix = norm_matrix * weights 61 | 62 | # Calculate vectors of PIS (ideal solution) and NIS (anti-ideal solution) 63 | pis = np.max(weighted_matrix, axis=0) 64 | nis = np.min(weighted_matrix, axis=0) 65 | 66 | # Calculate chosen distance of every alternative from PIS and NIS using chosen distance metric `distance_metric` from `distance_metrics` 67 | Dp = np.array([distance_metric(x, pis) for x in weighted_matrix]) 68 | Dm = np.array([distance_metric(x, nis) for x in weighted_matrix]) 69 | 70 | C = Dm / (Dm + Dp) 71 | return C -------------------------------------------------------------------------------- /src/pyrepo_mcda/mcda_methods/vikor.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from .mcda_method import MCDA_method 4 | 5 | 6 | class VIKOR(MCDA_method): 7 | def __init__(self, normalization_method = None, v = 0.5): 8 | """Create the VIKOR method object. 9 | 10 | Parameters 11 | ----------- 12 | normalization_method : function 13 | VIKOR does not use normalization by default, thus `normalization_method` is set to None by default. 14 | However, you can choose method for normalization of decision matrix chosen `normalization_method` from `normalizations`. 15 | It is used in a way `normalization_method(X, types)` where `X` is a decision matrix 16 | and `types` is a vector with criteria types where 1 means profit and -1 means cost. 17 | 18 | v : float 19 | parameter that is weight of strategy of the majority of criteria (the maximum group utility) 20 | """ 21 | self.v = v 22 | self.normalization_method = normalization_method 23 | 24 | 25 | def __call__(self, matrix, weights, types): 26 | """ 27 | Score alternatives provided in decision matrix `matrix` using criteria `weights` and criteria `types`. 28 | 29 | Parameters 30 | ----------- 31 | matrix : ndarray 32 | Decision matrix with m alternatives in rows and n criteria in columns. 33 | weights: ndarray 34 | Vector with criteria weights. Sum of weights must be equal to 1. 35 | types: ndarray 36 | Vector with criteria types. Profit criteria are represented by 1 and cost by -1. 37 | 38 | Returns 39 | -------- 40 | ndrarray 41 | Vector with preference values of each alternative. The best alternative has the lowest preference value. 42 | 43 | Examples 44 | --------- 45 | >>> vikor = VIKOR(normalization_method = minmax_normalization) 46 | >>> pref = vikor(matrix, weights, types) 47 | >>> rank = rank_preferences(pref, reverse = False) 48 | """ 49 | VIKOR._verify_input_data(matrix, weights, types) 50 | return VIKOR._vikor(matrix, weights, types, self.normalization_method, self.v) 51 | 52 | 53 | @staticmethod 54 | def _vikor(matrix, weights, types, normalization_method, v): 55 | # Without special normalization method 56 | if normalization_method == None: 57 | 58 | # Determine the best `fstar` and the worst `fmin` values of all criterion function 59 | maximums_matrix = np.amax(matrix, axis = 0) 60 | minimums_matrix = np.amin(matrix, axis = 0) 61 | 62 | fstar = np.zeros(matrix.shape[1]) 63 | fmin = np.zeros(matrix.shape[1]) 64 | 65 | # for profit criteria (`types` == 1) and for cost criteria (`types` == -1) 66 | fstar[types == 1] = maximums_matrix[types == 1] 67 | fstar[types == -1] = minimums_matrix[types == -1] 68 | fmin[types == 1] = minimums_matrix[types == 1] 69 | fmin[types == -1] = maximums_matrix[types == -1] 70 | 71 | # Calculate the weighted matrix 72 | weighted_matrix = weights * ((fstar - matrix) / (fstar - fmin)) 73 | else: 74 | # With special normalization method 75 | norm_matrix = normalization_method(matrix, types) 76 | fstar = np.amax(norm_matrix, axis = 0) 77 | fmin = np.amin(norm_matrix, axis = 0) 78 | 79 | # Calculate the weighted matrix 80 | weighted_matrix = weights * ((fstar - norm_matrix) / (fstar - fmin)) 81 | 82 | # Calculate the `S` and `R` values 83 | S = np.sum(weighted_matrix, axis = 1) 84 | R = np.amax(weighted_matrix, axis = 1) 85 | # Calculate the Q values 86 | Sstar = np.min(S) 87 | Smin = np.max(S) 88 | if (Sstar == Smin): 89 | Sstar = 0. 90 | Smin = 1. 91 | 92 | Rstar = np.min(R) 93 | Rmin = np.max(R) 94 | if (Rstar == Rmin): 95 | Rstar = 0. 96 | Rmin = 1. 97 | 98 | Q = v * (S - Sstar) / (Smin - Sstar) + (1 - v) * (R - Rstar) / (Rmin - Rstar) 99 | return Q 100 | -------------------------------------------------------------------------------- /src/pyrepo_mcda/mcda_methods/vikor_smaa.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from .vikor import VIKOR 4 | from ..additions import rank_preferences 5 | 6 | 7 | class VIKOR_SMAA(): 8 | def __init__(self, normalization_method = None, v = 0.5): 9 | """Create the VIKOR method object. 10 | 11 | Parameters 12 | ----------- 13 | 14 | normalization_method : function 15 | VIKOR does not use normalization by default, thus `normalization_method` is set to None by default. 16 | However, you can choose method for normalization of decision matrix chosen `normalization_method` from `normalizations`. 17 | It is used in a way `normalization_method(X, types)` where `X` is a decision matrix 18 | and `types` is a vector with criteria types where 1 means profit and -1 means cost. 19 | v : float 20 | parameter that is the weight of strategy of the majority of criteria (the maximum group utility) 21 | """ 22 | self.v = v 23 | self.normalization_method = normalization_method 24 | 25 | def __call__(self, matrix, weights, types): 26 | """ 27 | Score alternatives provided in decision matrix `matrix` using criteria `weights` and criteria `types`. 28 | 29 | Parameters 30 | ----------- 31 | matrix : ndarray 32 | Decision matrix with m alternatives in rows and n criteria in columns. 33 | weights : ndarray 34 | Matrix with i vectors in rows of n weights in columns. i means number of 35 | iterations of SMAA 36 | types : ndarray 37 | Vector with criteria types. Profit criteria are represented by 1 and cost by -1. 38 | 39 | Returns 40 | -------- 41 | ndrarray, ndarray, ndarray 42 | Matrix with acceptability indexes values for each alternative in rows in relation to each rank in columns, 43 | Matrix with central weight vectors for each alternative in rows 44 | Matrix with final ranking of alternatives 45 | 46 | Examples 47 | --------- 48 | >>> vikor_smaa = VIKOR_SMAA(normalization_method = minmax_normalization) 49 | >>> rank_acceptability_index, central_weight_vector, rank_scores = vikor_smaa(matrix, weights, types) 50 | """ 51 | 52 | return VIKOR_SMAA._vikor_smaa(self, matrix, weights, types, self.normalization_method, self.v) 53 | 54 | 55 | # function to generate multiple weight vectors 56 | # returns matrix with n weights in columns and number of vectors in rows equal to iterations number 57 | def _generate_weights(self, n, iterations): 58 | """ 59 | Function to generate multiple weight vectors 60 | 61 | Parameters 62 | ----------- 63 | n : int 64 | Number of criteria 65 | iterations : int 66 | Number of weight vector to generate 67 | 68 | Returns 69 | ---------- 70 | ndarray 71 | Matrix containing in rows vectors with weights for n criteria 72 | 73 | """ 74 | weight_vectors = np.zeros((iterations, n)) 75 | # n weight generation - when no preference information available 76 | # generate n - 1 uniform distributed weights within the range [0, 1] 77 | for i in range(iterations): 78 | w = np.random.uniform(0, 1, n) 79 | 80 | # sort weights into ascending order (q[1], ..., q[n-1]) 81 | ind = np.argsort(w) 82 | w = w[ind] 83 | 84 | # insert 0 as the first q[0] and 1 as the last (q[n]) numbers 85 | w = np.insert(w, 0, 0) 86 | w = np.insert(w, len(w), 1) 87 | 88 | # the weights are obtained as intervals between consecutive numbers (w[j] = q[j] - q[j-1]) 89 | weights = [w[i] - w[i - 1] for i in range(1, n + 1)] 90 | weights = np.array(weights) 91 | 92 | # scale the generated weights so that their sum is 1 93 | new_weights = weights / np.sum(weights) 94 | weight_vectors[i, :] = new_weights 95 | return weight_vectors 96 | 97 | 98 | @staticmethod 99 | def _vikor_smaa(self, matrix, weights, types, normalization_method, v): 100 | m, n = matrix.shape 101 | 102 | # Central weight vector for each alternative 103 | central_weight_vector = np.zeros((m, n)) 104 | 105 | # Rank acceptability index of each place for each alternative 106 | rank_acceptability_index = np.zeros((m, m)) 107 | 108 | # Ranks 109 | rank_score = np.zeros(m) 110 | 111 | vikor = VIKOR() 112 | 113 | # Calculate rankings based on preference values 114 | pref = np.zeros((matrix.shape[0], weights.shape[0])) 115 | rank = np.zeros((matrix.shape[0], weights.shape[0])) 116 | for i in range(weights.shape[0]): 117 | # Calculate alternatives preference function values with VIKOR method 118 | pref[:, i] = vikor(matrix, weights[i, :], types) 119 | rank[:, i] = rank_preferences(pref[:, i], reverse = False) 120 | 121 | # add value for the rank acceptability index for each alternative considering rank and rank score 122 | # iteration by each alternative 123 | rr = rank[:, i] 124 | for k, r in enumerate(rr): 125 | rank_acceptability_index[k, int(r - 1)] += 1 126 | # rank score 127 | # calculate how many alternatives have worst preference values than k-th alternative 128 | # Note: in VIKOR better alternatives have lower preference values 129 | better_ranks = rr[rr > rr[k]] 130 | # add to k-th index value 1 for each alternative that is worse than k-th alternative 131 | rank_score[k] += len(better_ranks) 132 | 133 | # add central weights for the best scored alternative 134 | ind_min = np.argmin(rr) 135 | central_weight_vector[ind_min, :] += weights[i, :] 136 | 137 | # 138 | # end of loop for i iterations 139 | # Calculate the rank acceptability index 140 | rank_acceptability_index = rank_acceptability_index / pref.shape[1] 141 | 142 | # Calculate central the weights vectors 143 | central_weight_vector = central_weight_vector / pref.shape[1] 144 | for i in range(m): 145 | if np.sum(central_weight_vector[i, :]): 146 | central_weight_vector[i, :] = central_weight_vector[i, :] / np.sum(central_weight_vector[i, :]) 147 | 148 | # Calculate rank scores 149 | rank_score = rank_score / pref.shape[1] 150 | rank_scores = rank_preferences(rank_score, reverse = True) 151 | 152 | return rank_acceptability_index, central_weight_vector, rank_scores -------------------------------------------------------------------------------- /src/pyrepo_mcda/mcda_methods/vmcm.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from .mcda_method import MCDA_method 3 | 4 | class VMCM(MCDA_method): 5 | def __init__(self): 6 | """ 7 | Create the VMCM method object. 8 | """ 9 | 10 | pass 11 | 12 | 13 | def _elimination(self, matrix): 14 | """ 15 | Calculate significance coefficient values for each criterion. 16 | Criteria with significance coefficient values between 0 and 0.1 are recommended to be eliminated from the considered criteria set. 17 | 18 | Parameters 19 | -------------- 20 | matrix : ndarray 21 | Decision matrix with m alternatives in rows and n criteria in columns. 22 | 23 | Examples 24 | -------------- 25 | >>> vmcm = VMCM() 26 | >>> vmcm._elimination(matrix) 27 | """ 28 | 29 | # Elimination of variables 30 | v = np.std(matrix, axis = 0, ddof = 1) / np.mean(matrix, axis = 0) 31 | to_eliminate = [] 32 | print('Elimination of variables stage (significance coefficient of features):') 33 | for el, vj in enumerate(v): 34 | print(f'C{el + 1} = {vj:.4f}') 35 | if 0 <= vj <= 0.1: 36 | to_eliminate.append('C' + str(el + 1)) 37 | print('Criteria to eliminate:') 38 | if not(to_eliminate): 39 | print('None') 40 | for te in to_eliminate: 41 | print(te) 42 | 43 | 44 | 45 | def _weighting(self, matrix): 46 | """ 47 | Calculate criteria weights 48 | 49 | Parameters 50 | ------------- 51 | matrix : ndarray 52 | Decision matrix with m alternatives in rows and n criteria in columns. 53 | 54 | Returns 55 | ------------- 56 | ndarray 57 | Vector with criteria weights 58 | 59 | Examples 60 | ------------ 61 | >>> vmcm = VMCM() 62 | >>> weights = vmcm._weighting(matrix) 63 | """ 64 | 65 | v = np.std(matrix, axis = 0, ddof = 1) / np.mean(matrix, axis = 0) 66 | return v / np.sum(v) 67 | 68 | 69 | def _normalization(self, matrix): 70 | """ 71 | Calculates normalized matrix 72 | 73 | Parameters 74 | -------------- 75 | matrix : ndarray 76 | Decision matrix with m alternatives in rows and n criteria in columns. 77 | 78 | Returns 79 | --------------- 80 | ndarray 81 | Normalized matrix 82 | 83 | Examples 84 | --------------- 85 | >>> vmcm = VMCM() 86 | >>> norm_matrix = vmcm._normalization(matrix) 87 | """ 88 | 89 | norm_matrix = (matrix - np.mean(matrix, axis = 0)) / np.std(matrix, axis = 0, ddof = 1) 90 | 91 | return norm_matrix 92 | 93 | 94 | def _pattern_determination(self, matrix, types): 95 | """ 96 | Automatic determination of pattern and anti-pattern 97 | 98 | Parameters 99 | -------------- 100 | matrix : ndarray 101 | Decision matrix with m alternatives in rows and n criteria in columns. 102 | 103 | Returns 104 | -------------- 105 | ndarray, ndarray 106 | Two vectors including values respectively of pattern and anti-pattern 107 | 108 | Examples 109 | -------------- 110 | >>> vmcm = VMCM() 111 | >>> pattern, antipattern = vmcm._pattern_determination(matrix, types) 112 | """ 113 | 114 | # Normalization of variables 115 | norm_matrix = self._normalization(matrix) 116 | 117 | # Determination of pattern and anti-pattern 118 | q1 = np.quantile(norm_matrix, 0.25, axis = 0) 119 | q3 = np.quantile(norm_matrix, 0.75, axis = 0) 120 | 121 | pattern = np.zeros(matrix.shape[1]) 122 | anti_pattern = np.zeros(matrix.shape[1]) 123 | 124 | pattern[types == 1] = q3[types == 1] 125 | pattern[types == -1] = q1[types == -1] 126 | 127 | anti_pattern[types == 1] = q1[types == 1] 128 | anti_pattern[types == -1] = q3[types == -1] 129 | 130 | return pattern, anti_pattern 131 | 132 | 133 | def _classification(self, m): 134 | """ 135 | Assign evaluated objects to classes 136 | 137 | Parameters 138 | ------------- 139 | m : ndarray 140 | Vector with values of synthetic measure 141 | 142 | Returns 143 | ------------- 144 | ndarray 145 | Vector including classes assigned to evaluated objects 146 | 147 | Examples 148 | -------------- 149 | >>> vmcm = VMCM() 150 | >>> pref = vmcm(matrix, weights, types) 151 | >>> classes = vmcm._classification(pref) 152 | """ 153 | 154 | # Classification of objects 155 | m_mean = np.mean(m) 156 | m_std = np.std(m, ddof = 1) 157 | 158 | cl = np.zeros(len(m)) 159 | 160 | cl[m >= m_mean + m_std] = 1 161 | cl[np.logical_and(m >= m_mean, m < m_mean + m_std)] = 2 162 | cl[np.logical_and(m >= m_mean - m_std, m < m_mean)] = 3 163 | cl[m < m_mean - m_std] = 4 164 | 165 | return cl 166 | 167 | 168 | def __call__(self, matrix, weights, types, pattern, anti_pattern): 169 | """ 170 | Score alternatives provided in decision matrix `matrix` with m alternatives in rows and 171 | n criteria in columns using criteria `weights` and criteria `types`. 172 | 173 | Parameters 174 | ---------- 175 | matrix : ndarray 176 | Decision matrix with m alternatives in rows and n criteria in columns. 177 | weights: ndarray 178 | Vector with criteria weights. Sum of weights must be equal to 1. 179 | types : ndarray 180 | Vector with criteria types. Profit criteria are represented by 1 and cost by -1. 181 | pattern : ndarray 182 | Vector with values of pattern 183 | anti_pattern : ndarray 184 | Vector with values of anti-pattern 185 | 186 | Returns 187 | ------- 188 | ndrarray 189 | Vector with preference values of each alternative. The best alternative has the highest preference value. 190 | 191 | Examples 192 | --------- 193 | >>> vmcm = VMCM() 194 | >>> pattern, antipattern = vmcm._pattern_determination(matrix, types) 195 | >>> pref = vmcm(matrix, weights, types, pattern, antipattern) 196 | >>> rank = rank_preferences(pref, reverse = True) 197 | """ 198 | 199 | VMCM._verify_input_data(matrix, weights, types) 200 | return VMCM._vmcm(self, matrix, weights, types, pattern, anti_pattern) 201 | 202 | 203 | @staticmethod 204 | def _vmcm(self, matrix, weights, types, pattern, anti_pattern): 205 | # Normalization of variables 206 | norm_matrix = self._normalization(matrix) 207 | 208 | # Weighting of normalized matrix 209 | weighted_matrix = norm_matrix * weights 210 | 211 | # Construction of the synthetic measure 212 | m = np.sum((weighted_matrix - anti_pattern) * (pattern - anti_pattern), axis = 1) / np.sum((pattern - anti_pattern)**2) 213 | 214 | return m -------------------------------------------------------------------------------- /src/pyrepo_mcda/mcda_methods/waspas.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from ..normalizations import linear_normalization 3 | from .mcda_method import MCDA_method 4 | 5 | 6 | class WASPAS(MCDA_method): 7 | def __init__(self, normalization_method = linear_normalization, lambda_param = 0.5): 8 | """ 9 | Create the WASPAS method object and select normalization method `normalization_method`, default 10 | normalization method for WASPAS is `linear_normalization` and lambda parameter `lambda_param`, 11 | which is set on 0.5 by default. 12 | 13 | Parameters 14 | ----------- 15 | normalization_method : function 16 | method for decision matrix normalization chosen from `normalizations` 17 | 18 | lambda_param : float 19 | lambda parameter is between 0 and 1 20 | """ 21 | 22 | self.normalization_method = normalization_method 23 | self.lambda_param = lambda_param 24 | 25 | 26 | def __call__(self, matrix, weights, types): 27 | """ 28 | Score alternatives provided in decision matrix `matrix` with m alternatives and n criteria 29 | using criteria `weights` and criteria `types`. 30 | 31 | Parameters 32 | ----------- 33 | matrix : ndarray 34 | Decision matrix with m alternatives in rows and n criteria in columns. 35 | weights: ndarray 36 | Vector with criteria weights. Sum of weights must be equal to 1. 37 | types: ndarray 38 | Vector with criteria types. Profit criteria are represented by 1 and cost by -1. 39 | 40 | Returns 41 | -------- 42 | ndrarray 43 | Vector with preference values of each alternative. The best alternative has the highest preference value. 44 | 45 | Examples 46 | ---------- 47 | >>> waspas = WASPAS(normalization_method = linear_normalization, lambda_param = 0.5) 48 | >>> pref = waspas(matrix, weights, types) 49 | >>> rank = rank_preferences(pref, reverse = True) 50 | """ 51 | WASPAS._verify_input_data(matrix, weights, types) 52 | return WASPAS._waspas(matrix, weights, types, self.normalization_method, self.lambda_param) 53 | 54 | 55 | @staticmethod 56 | def _waspas(matrix, weights, types, normalization_method, lambda_param): 57 | # Normalize decision matrix 58 | norm_matrix = normalization_method(matrix, types) 59 | # Calculate the total relative importance of alternatives based on WSM 60 | Q1 = np.sum((norm_matrix * weights), axis = 1) 61 | # Calculate the total relative importance of alternatives based on WPM 62 | Q2 = np.prod((norm_matrix ** weights), axis = 1) 63 | # Determine the total relative importance of alternatives 64 | # If lambda is equal to 0, WASPAS method is transformed to WPM 65 | # If lambda is equal to 1, WASPAS becomes WSM 66 | Q = lambda_param * Q1 + (1 - lambda_param) * Q2 67 | return Q 68 | -------------------------------------------------------------------------------- /src/pyrepo_mcda/normalizations.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | # linear normalization 5 | def linear_normalization(matrix, types): 6 | """ 7 | Normalize decision matrix using linear normalization method. 8 | 9 | Parameters 10 | ----------- 11 | matrix : ndarray 12 | Decision matrix with m alternatives in rows and n criteria in columns 13 | types : ndarray 14 | Criteria types. Profit criteria are represented by 1 and cost by -1. 15 | 16 | Returns 17 | -------- 18 | ndarray 19 | Normalized decision matrix 20 | 21 | Examples 22 | ---------- 23 | >>> nmatrix = linear_normalization(matrix, types) 24 | """ 25 | x_norm = np.zeros(np.shape(matrix)) 26 | x_norm[:, types == 1] = matrix[:, types == 1] / (np.amax(matrix[:, types == 1], axis = 0)) 27 | x_norm[:, types == -1] = np.amin(matrix[:, types == -1], axis = 0) / matrix[:, types == -1] 28 | return x_norm 29 | 30 | 31 | # min-max normalization 32 | def minmax_normalization(matrix, types): 33 | """ 34 | Normalize decision matrix using minimum-maximum normalization method. 35 | 36 | Parameters 37 | ----------- 38 | matrix : ndarray 39 | Decision matrix with m alternatives in rows and n criteria in columns 40 | types : ndarray 41 | Criteria types. Profit criteria are represented by 1 and cost by -1. 42 | 43 | Returns 44 | -------- 45 | ndarray 46 | Normalized decision matrix 47 | 48 | Examples 49 | ---------- 50 | >>> nmatrix = minmax_normalization(matrix, types) 51 | """ 52 | x_norm = np.zeros((matrix.shape[0], matrix.shape[1])) 53 | x_norm[:, types == 1] = (matrix[:, types == 1] - np.amin(matrix[:, types == 1], axis = 0) 54 | ) / (np.amax(matrix[:, types == 1], axis = 0) - np.amin(matrix[:, types == 1], axis = 0)) 55 | 56 | x_norm[:, types == -1] = (np.amax(matrix[:, types == -1], axis = 0) - matrix[:, types == -1] 57 | ) / (np.amax(matrix[:, types == -1], axis = 0) - np.amin(matrix[:, types == -1], axis = 0)) 58 | 59 | return x_norm 60 | 61 | 62 | # max normalization 63 | def max_normalization(matrix, types): 64 | """ 65 | Normalize decision matrix using maximum normalization method. 66 | 67 | Parameters 68 | ----------- 69 | matrix : ndarray 70 | Decision matrix with m alternatives in rows and n criteria in columns 71 | types : ndarray 72 | Criteria types. Profit criteria are represented by 1 and cost by -1. 73 | 74 | Returns 75 | -------- 76 | ndarray 77 | Normalized decision matrix 78 | 79 | Examples 80 | ---------- 81 | >>> nmatrix = max_normalization(matrix, types) 82 | """ 83 | maximes = np.amax(matrix, axis = 0) 84 | matrix = matrix / maximes 85 | matrix[:, types == -1] = 1 - matrix[:, types == -1] 86 | return matrix 87 | 88 | 89 | # sum normalization 90 | def sum_normalization(matrix, types): 91 | """ 92 | Normalize decision matrix using sum normalization method. 93 | 94 | Parameters 95 | ----------- 96 | matrix : ndarray 97 | Decision matrix with m alternatives in rows and n criteria in columns 98 | types : ndarray 99 | Criteria types. Profit criteria are represented by 1 and cost by -1. 100 | 101 | Returns 102 | -------- 103 | ndarray 104 | Normalized decision matrix 105 | 106 | Examples 107 | ---------- 108 | >>> nmatrix = sum_normalization(matrix, types) 109 | """ 110 | x_norm = np.zeros((matrix.shape[0], matrix.shape[1])) 111 | x_norm[:, types == 1] = matrix[:, types == 1] / np.sum(matrix[:, types == 1], axis = 0) 112 | x_norm[:, types == -1] = (1 / matrix[:, types == -1]) / np.sum((1 / matrix[:, types == -1]), axis = 0) 113 | 114 | return x_norm 115 | 116 | 117 | # vector normalization 118 | def vector_normalization(matrix, types): 119 | """ 120 | Normalize decision matrix using vector normalization method. 121 | 122 | Parameters 123 | ----------- 124 | matrix : ndarray 125 | Decision matrix with m alternatives in rows and n criteria in columns 126 | types : ndarray 127 | Criteria types. Profit criteria are represented by 1 and cost by -1. 128 | 129 | Returns 130 | -------- 131 | ndarray 132 | Normalized decision matrix 133 | 134 | Examples 135 | ----------- 136 | >>> nmatrix = vector_normalization(matrix, types) 137 | """ 138 | x_norm = np.zeros((matrix.shape[0], matrix.shape[1])) 139 | x_norm[:, types == 1] = matrix[:, types == 1] / (np.sum(matrix[:, types == 1] ** 2, axis = 0))**(0.5) 140 | x_norm[:, types == -1] = 1 - (matrix[:, types == -1] / (np.sum(matrix[:, types == -1] ** 2, axis = 0))**(0.5)) 141 | 142 | return x_norm 143 | 144 | 145 | # multimoora normalization 146 | def multimoora_normalization(matrix): 147 | """ 148 | Normalize decision matrix using vector normalization method as for profit criteria. 149 | 150 | Parameters 151 | ------------ 152 | matrix : ndarray 153 | Decision matrix with m alternatives in rows and n criteria in columns 154 | 155 | Examples 156 | ----------- 157 | >>> nmatrix = multimoora_normalization(matrix) 158 | """ 159 | x_norm = matrix / ((np.sum(matrix ** 2, axis = 0))**(0.5)) 160 | return x_norm 161 | -------------------------------------------------------------------------------- /src/pyrepo_mcda/sensitivity_analysis_weights_percentages.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import numpy as np 3 | import pandas as pd 4 | 5 | from .additions import rank_preferences 6 | 7 | 8 | class Sensitivity_analysis_weights_percentages(): 9 | def __init__(self): 10 | """ 11 | Create object of `Sensitivity_analysis_weights_percentages` method. 12 | """ 13 | pass 14 | 15 | 16 | def __call__(self, matrix, weights, types, percentages, method, j, dir_list): 17 | """ 18 | Method for sensitivity analysis. This method determines rankings of alternatives using chosen 19 | MCDA method name `mcda_name` for different modifications of criterion `j` weight. 20 | 21 | Parameters 22 | ----------- 23 | matrix : ndarray 24 | Decision matrix with alternatives performances data. This matrix includes 25 | values of alternatives performances in rows considering criteria in columns 26 | 27 | weights : ndarray 28 | Vector with criteria weights. All weights in this vector must sum to 1. 29 | 30 | types : ndarray 31 | Vector with criteria types. Types can be equal to 1 for profit criteria and -1 32 | for cost criteria. 33 | 34 | percentages : ndarray 35 | Vector with percentage values of given criteria weight modification in range from 0 to 1. 36 | 37 | method : class 38 | Initialized object of class of chosen MCDA method 39 | 40 | j : int 41 | Index of column in decision matrix `matrix` that indicates for which criterion 42 | the weight is modified. 43 | 44 | dir_list : list 45 | list with directions (signs of value) of criterion weight modification. 1 denotes increasing, 46 | and -1 denotes decreasing weight value. You can provide [-1, 1] for increasing and 47 | decreasing, [-1] for only decreasing, or [1] for only increasing chosen criterion weight. 48 | 49 | Returns 50 | -------- 51 | data_sens : DataFrame 52 | dataframe with rankings calculated for subsequent modifications of criterion j weight 53 | 54 | Examples 55 | ---------- 56 | >>> sensitivity_analysis = Sensitivity_analysis_weights_percentages() 57 | >>> df_sens = sensitivity_analysis(matrix, weights, types, percentages, method, j, [-1, 1]) 58 | """ 59 | list_alt_names = [r'$A_{' + str(i) + '}$' for i in range(1, matrix.shape[0] + 1)] 60 | return Sensitivity_analysis_weights_percentages._sensitivity_analysis_weights_percentages(self, matrix, weights, types, percentages, method, list_alt_names, j, dir_list) 61 | 62 | 63 | def _change_weights(self, j, weights, change_val): 64 | """ 65 | Method for criteria weights modification in sensitivity analysis procedure. 66 | 67 | Parameters 68 | ----------- 69 | j : int 70 | Index of column in decision matrix `matrix` that indicates for which criterion 71 | the weight is modified. 72 | 73 | weights : ndarray 74 | Vector of criteria weights 75 | 76 | change_val : float 77 | Percentage value of criterion weight modification in range from 0 to 1 78 | 79 | Returns 80 | -------- 81 | weights_copy : ndarray 82 | Vector with criteria weights after modification their values for sensitivity analysis 83 | 84 | """ 85 | 86 | weights_copy = copy.deepcopy(weights) 87 | # Calculate value of selected criterion j weight modification 88 | change = weights_copy[j] * change_val 89 | # Calculate new value of selected criterion j weights 90 | new_weight = weights_copy[j] + change 91 | # Calculate new values of other criteria weights considering modification of j weights 92 | weights_copy = weights_copy - (change / (len(weights) - 1)) 93 | # Assign new weight to criterion j 94 | weights_copy[j] = new_weight 95 | return weights_copy 96 | 97 | 98 | @staticmethod 99 | def _sensitivity_analysis_weights_percentages(self, matrix, weights, types, percentages, method, list_alt_names, j, dir_list): 100 | # Create a dataframe for sensitivity analysis results (rankings) 101 | data_sens = pd.DataFrame() 102 | # Assisgn indexes (alternatives symbols) to dataframe `data_sens` 103 | data_sens['Ai'] = list_alt_names 104 | # Iterate by two directions of weight modification: -1 for weight decreasing 105 | # and 1 for weight increasing 106 | for dir in dir_list: 107 | # Sorting percentages in appropriate order for visualization results 108 | if dir == -1: 109 | direct_percentages = copy.deepcopy(percentages[::-1]) 110 | else: 111 | direct_percentages = copy.deepcopy(percentages) 112 | # Iterate by values of weight change in vector `direct_percentages` 113 | for change_val in direct_percentages: 114 | # Change weights using method named `_change_weights` from class `Sensitivity_analysis_weights_percentages` 115 | weights_copy = self._change_weights(j, weights, dir * change_val) 116 | 117 | # Calculate alternatives ranking using selected MCDA method, `matrix`, vector of new weights 118 | # `weights_copy` and criteria types `types` 119 | if method.__class__.__name__ == 'TOPSIS': 120 | pref = method(matrix, weights_copy, types) 121 | rank = rank_preferences(pref, reverse = True) 122 | 123 | elif method.__class__.__name__ == 'CODAS': 124 | pref = method(matrix, weights_copy, types) 125 | rank = rank_preferences(pref, reverse = True) 126 | 127 | elif method.__class__.__name__ == 'VIKOR': 128 | pref = method(matrix, weights_copy, types) 129 | rank = rank_preferences(pref, reverse = False) 130 | 131 | elif method.__class__.__name__ == 'SPOTIS': 132 | bounds_min = np.amin(matrix, axis = 0) 133 | bounds_max = np.amax(matrix, axis = 0) 134 | bounds = np.vstack((bounds_min, bounds_max)) 135 | pref = method(matrix, weights_copy, types, bounds) 136 | rank = rank_preferences(pref, reverse = False) 137 | 138 | elif method.__class__.__name__ == 'EDAS': 139 | pref = method(matrix, weights_copy, types) 140 | rank = rank_preferences(pref, reverse = True) 141 | 142 | elif method.__class__.__name__ == 'MABAC': 143 | pref = method(matrix, weights_copy, types) 144 | rank = rank_preferences(pref, reverse = True) 145 | 146 | elif method.__class__.__name__ == 'MULTIMOORA': 147 | rank = method(matrix, weights_copy, types) 148 | 149 | elif method.__class__.__name__ == 'WASPAS': 150 | pref = method(matrix, weights_copy, types) 151 | rank = rank_preferences(pref, reverse = True) 152 | 153 | elif method.__class__.__name__ == 'ARAS': 154 | pref = method(matrix, weights_copy, types) 155 | rank = rank_preferences(pref, reverse = True) 156 | 157 | elif method.__class__.__name__ == 'COPRAS': 158 | pref = method(matrix, weights_copy, types) 159 | rank = rank_preferences(pref, reverse = True) 160 | 161 | elif method.__class__.__name__ == 'CRADIS': 162 | pref = method(matrix, weights_copy, types) 163 | rank = rank_preferences(pref, reverse = True) 164 | 165 | elif method.__class__.__name__ == 'MARCOS': 166 | pref = method(matrix, weights_copy, types) 167 | rank = rank_preferences(pref, reverse = True) 168 | 169 | elif method.__class__.__name__ == 'CRADIS': 170 | pref = method(matrix, weights_copy, types) 171 | rank = rank_preferences(pref, reverse = True) 172 | 173 | elif method.__class__.__name__ == 'SAW': 174 | pref = method(matrix, weights_copy, types) 175 | rank = rank_preferences(pref, reverse = True) 176 | 177 | elif method.__class__.__name__ == 'PROMETHEE_II': 178 | pref = method(matrix, weights_copy, types) 179 | rank = rank_preferences(pref, reverse = True) 180 | 181 | elif method.__class__.__name__ == 'PROSA_C': 182 | pref = method(matrix, weights_copy, types) 183 | rank = rank_preferences(pref, reverse = True) 184 | 185 | else: 186 | print('There is no available sensitivity analysis for this method') 187 | return 188 | 189 | # Assign calculated ranking to column with value of j criterion weight change in `data_sens` dataframe 190 | data_sens['{:.0f}'.format(dir * change_val * 100) + '%'] = rank 191 | 192 | # Drop column with `-0%` name for visualization result 193 | if '-0%' in list(data_sens.columns): 194 | data_sens = data_sens.drop(['-0%'], axis = 1) 195 | # Set index with alternatives symbols in dataframe with sensitivity analysis results (rankings) 196 | data_sens = data_sens.set_index('Ai') 197 | return data_sens -------------------------------------------------------------------------------- /src/pyrepo_mcda/sensitivity_analysis_weights_values.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | 4 | from .additions import rank_preferences 5 | 6 | 7 | class Sensitivity_analysis_weights_values(): 8 | def __init__(self): 9 | """ 10 | Create object of `Sensitivity_analysis_weights_values` method. 11 | """ 12 | pass 13 | 14 | 15 | def __call__(self, matrix, weight_values, types, method, j): 16 | """ 17 | Method for sensitivity analysis. This method determines rankings of alternatives using chosen 18 | MCDA method name `mcda_name` for the value of criterion `j` weight set as chosen `weight_value`. 19 | 20 | Parameters 21 | ----------- 22 | matrix : ndarray 23 | Decision matrix with performance values of alternatives. This matrix includes 24 | data on alternatives in rows considering criteria in columns. 25 | 26 | weight_values : ndarray 27 | Vector with values to be set as the weight of chosen criterion in the sensitivity analysis procedure in range from 0 to 1. 28 | 29 | types : ndarray 30 | Vector with criteria types. Types must be equal to 1 for profit criteria and -1 31 | for cost criteria. 32 | 33 | method : class 34 | Initialized object of class of chosen MCDA method 35 | 36 | j : int 37 | Index of the column in decision matrix `matrix` that indicates for which criterion 38 | the weight is set with chosen value. 39 | 40 | Returns 41 | -------- 42 | data_sens : DataFrame 43 | dataframe with rankings calculated for subsequent changes of criterion `j` weight. 44 | Particular rankings for different weight values of criterion `j` are included in 45 | subsequent columns of the dataframe. 46 | 47 | Examples 48 | ---------- 49 | >>> sensitivity_analysis = Sensitivity_analysis_weights_values() 50 | >>> df_sens = sensitivity_analysis(matrix, weight_values, types, method, j) 51 | """ 52 | list_alt_names = [r'$A_{' + str(i) + '}$' for i in range(1, matrix.shape[0] + 1)] 53 | return Sensitivity_analysis_weights_values._sensitivity_analysis_weights_values(self, matrix, weight_values, types, method, list_alt_names, j) 54 | 55 | 56 | def _change_weights(self, matrix, weight_value, j): 57 | """ 58 | Method for criteria weights changing in sensitivity analysis procedure. 59 | 60 | Parameters 61 | ----------- 62 | matrix : ndarray 63 | Decision matrix with performance values of alternatives. This matrix includes 64 | data on alternatives in rows considering criteria in columns 65 | 66 | weight_value : float 67 | Value in range from 0 to 1 to be set as the weight of chosen criterion with index `j`. 68 | 69 | j : int 70 | Index of the column in decision matrix `matrix` indicating for which criterion 71 | the weight value is changed. 72 | 73 | Returns 74 | -------- 75 | new_weights : ndarray 76 | Vector with criteria weights after changing their values for sensitivity analysis 77 | """ 78 | 79 | new_weights = np.ones(matrix.shape[1]) * ((1 - weight_value) / (matrix.shape[1] - 1)) 80 | new_weights[j] = weight_value 81 | return new_weights 82 | 83 | 84 | @staticmethod 85 | def _sensitivity_analysis_weights_values(self, matrix, weight_values, types, method, list_alt_names, j): 86 | # Create a dataframe for sensitivity analysis results (rankings) 87 | data_sens = pd.DataFrame() 88 | # Assisgn indexes (alternatives symbols) to dataframe `data_sens` 89 | data_sens['Ai'] = list_alt_names 90 | 91 | # Iterate by values of weight change in vector `weight_values` 92 | for val in weight_values: 93 | # Change weights using method named `_change_weights` from class `Sensitivity_analysis_weights_values` 94 | new_weights = self._change_weights(matrix, val, j) 95 | 96 | # Calculate alternatives ranking using selected MCDA method, `matrix`, vector of new weights 97 | # `new_weights` and criteria types `types` 98 | if method.__class__.__name__ == 'TOPSIS': 99 | pref = method(matrix, new_weights, types) 100 | rank = rank_preferences(pref, reverse = True) 101 | 102 | elif method.__class__.__name__ == 'CODAS': 103 | pref = method(matrix, new_weights, types) 104 | rank = rank_preferences(pref, reverse = True) 105 | 106 | elif method.__class__.__name__ == 'VIKOR': 107 | pref = method(matrix, new_weights, types) 108 | rank = rank_preferences(pref, reverse = False) 109 | 110 | elif method.__class__.__name__ == 'SPOTIS': 111 | bounds_min = np.amin(matrix, axis = 0) 112 | bounds_max = np.amax(matrix, axis = 0) 113 | bounds = np.vstack((bounds_min, bounds_max)) 114 | pref = method(matrix, new_weights, types, bounds) 115 | rank = rank_preferences(pref, reverse = False) 116 | 117 | elif method.__class__.__name__ == 'EDAS': 118 | pref = method(matrix, new_weights, types) 119 | rank = rank_preferences(pref, reverse = True) 120 | 121 | elif method.__class__.__name__ == 'MABAC': 122 | pref = method(matrix, new_weights, types) 123 | rank = rank_preferences(pref, reverse = True) 124 | 125 | elif method.__class__.__name__ == 'MULTIMOORA': 126 | rank = method(matrix, new_weights, types) 127 | 128 | elif method.__class__.__name__ == 'WASPAS': 129 | pref = method(matrix, new_weights, types) 130 | rank = rank_preferences(pref, reverse = True) 131 | 132 | elif method.__class__.__name__ == 'ARAS': 133 | pref = method(matrix, new_weights, types) 134 | rank = rank_preferences(pref, reverse = True) 135 | 136 | elif method.__class__.__name__ == 'COPRAS': 137 | pref = method(matrix, new_weights, types) 138 | rank = rank_preferences(pref, reverse = True) 139 | 140 | elif method.__class__.__name__ == 'CRADIS': 141 | pref = method(matrix, new_weights, types) 142 | rank = rank_preferences(pref, reverse = True) 143 | 144 | elif method.__class__.__name__ == 'MARCOS': 145 | pref = method(matrix, new_weights, types) 146 | rank = rank_preferences(pref, reverse = True) 147 | 148 | elif method.__class__.__name__ == 'CRADIS': 149 | pref = method(matrix, new_weights, types) 150 | rank = rank_preferences(pref, reverse = True) 151 | 152 | elif method.__class__.__name__ == 'SAW': 153 | pref = method(matrix, new_weights, types) 154 | rank = rank_preferences(pref, reverse = True) 155 | 156 | elif method.__class__.__name__ == 'PROMETHEE_II': 157 | pref = method(matrix, new_weights, types) 158 | rank = rank_preferences(pref, reverse = True) 159 | 160 | elif method.__class__.__name__ == 'PROSA_C': 161 | pref = method(matrix, new_weights, types) 162 | rank = rank_preferences(pref, reverse = True) 163 | 164 | else: 165 | print('There is no available sensitivity analysis for this method') 166 | return 167 | 168 | # Assign calculated ranking to column with value of j criterion weight change in `data_sens` dataframe 169 | data_sens['{:.2f}'.format(val)] = rank 170 | 171 | # Set index with alternatives symbols in dataframe with sensitivity analysis results (rankings) 172 | data_sens = data_sens.set_index('Ai') 173 | return data_sens -------------------------------------------------------------------------------- /tests/test_correlations.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | from scipy.stats import pearsonr 4 | from pyrepo_mcda import correlations as corrs 5 | 6 | 7 | # Test for Spearman rank correlation coefficient 8 | class Test_Spearman(unittest.TestCase): 9 | 10 | def test_spearman(self): 11 | """Test based on paper Sałabun, W., & Urbaniak, K. (2020, June). A new coefficient of rankings similarity 12 | in decision-making problems. In International Conference on Computational Science 13 | (pp. 632-645). Springer, Cham.""" 14 | 15 | R = np.array([1, 2, 3, 4, 5]) 16 | Q = np.array([1, 3, 2, 4, 5]) 17 | test_result = corrs.spearman(R, Q) 18 | real_result = 0.9 19 | self.assertEqual(test_result, real_result) 20 | 21 | 22 | # Test for Weighted Spearman rank correlation coefficient 23 | class Test_Weighted_Spearman(unittest.TestCase): 24 | 25 | def test_weighted_spearman(self): 26 | """Test based on paper Sałabun, W., & Urbaniak, K. (2020, June). A new coefficient of rankings similarity 27 | in decision-making problems. In International Conference on Computational Science 28 | (pp. 632-645). Springer, Cham.""" 29 | 30 | R = np.array([1, 2, 3, 4, 5]) 31 | Q = np.array([1, 3, 2, 4, 5]) 32 | test_result = corrs.weighted_spearman(R, Q) 33 | real_result = 0.8833 34 | self.assertEqual(np.round(test_result, 4), real_result) 35 | 36 | 37 | # Test for Similarity rank coefficient WS 38 | class Test_WS(unittest.TestCase): 39 | 40 | def test_ws(self): 41 | """Test based on paper Sałabun, W., & Urbaniak, K. (2020, June). A new coefficient of rankings similarity 42 | in decision-making problems. In International Conference on Computational Science 43 | (pp. 632-645). Springer, Cham.""" 44 | 45 | R = np.array([1, 2, 3, 4, 5]) 46 | Q = np.array([1, 3, 2, 4, 5]) 47 | test_result = corrs.WS_coeff(R, Q) 48 | real_result = 0.8542 49 | self.assertEqual(np.round(test_result, 4), real_result) 50 | 51 | 52 | # Test for Pearson correlation coefficient 53 | class Test_Pearson(unittest.TestCase): 54 | 55 | def test_pearson(self): 56 | """Test based on paper Sałabun, W., & Urbaniak, K. (2020, June). A new coefficient of rankings similarity 57 | in decision-making problems. In International Conference on Computational Science 58 | (pp. 632-645). Springer, Cham.""" 59 | 60 | R = np.array([1, 2, 3, 4, 5]) 61 | Q = np.array([1, 3, 2, 4, 5]) 62 | test_result = corrs.pearson_coeff(R, Q) 63 | real_result, _ = pearsonr(R, Q) 64 | self.assertEqual(test_result, real_result) 65 | 66 | 67 | def main(): 68 | test_spearman_coeff = Test_Spearman() 69 | test_spearman_coeff.test_spearman() 70 | 71 | test_weighted_spearman_coeff = Test_Weighted_Spearman() 72 | test_weighted_spearman_coeff.test_weighted_spearman() 73 | 74 | test_pearson_coeff = Test_Pearson() 75 | test_pearson_coeff.test_pearson() 76 | 77 | test_ws = Test_WS() 78 | test_ws.test_ws() 79 | 80 | 81 | if __name__ == '__main__': 82 | main() --------------------------------------------------------------------------------