scikit-mcda
===========

It is a python library made to provide multi-criteria decision aid for developers and operacional researchers.

Scikit-mcda provides an easy way to apply several popular decision-making methods. It can be used as part of your development or for analytical experiments using notebooks like Jupyter, colab or kaggle. The package is available on the Pypi allowing installation by *pip install scikit-mcda* command.

Some methods available:

*DMUU*

- laplace, hurwicz, maximax, maximin, minimax-regret ...

*MCDA*

- Weighted Sum Model (WSM), Weighted Product Model (WPM) , Weighted Aggregated Sum Product Assessment (WASPAS), Technique for Order Preference by Similarity to an Ideal Solution (TOPSIS), Preference Ranking Organization METHod for Enrichment of Evaluations (PROMETHEE II), ELimination Et Choix Traduisant la REalité (ELECTRE I and II), Višekriterijumska Optimizacija I Kompromisno Rješenje (VIKOR) ...

*Definition of criteria weights*

- Manually, Entropy, Ranking Methods, Analytic Hierarchy Process (AHP) ... 

*Nomalization*

- Linear MinMax, Linear Max, Linear Sum, Vector, Enhanced Accuracy and Logarithmic.

Scikit-mcda is free to use for personal, commercial and academic projects, always respecting the terms of the Apache 2.0 License. Do not forget to refer to this Library when it is used in your experiments, lectures, presentations, classes and research papers. The reference must follow this citation format.

    (HORTA, 2021)

    HORTA, Antonio (2021). Scikit-mcda: The Python library for multi-criteria decision aid. 
    Version 0.21. [opensource], 17 jan. 2021. Available in: https://gitlab.com/cybercrafter/scikit-mcda. 
    Acessed in: 17 jan. 2021.

It's a project made by Cybercrafter® <ajhorta@cybercrafter.com.br>

Module for Multi-Criteria Decision Aid (MCDA)
---------------------------------------------

**MCDA**: Basis Class Module for Multi-Criteria Decision-Aid

Attributes:
  - df_original 
  - weights
  - signals
  - df_normalized
  - df_weighted
  - df_decision

**MCDA basis methods**:

- dataframe(alt_data, alt_labels=[], state_labels=[])
- set_signals([MIN, MIN, MAX])

**MCDA weights determination methods**:

- set_weights_manually([])
- set_weights_by_entropy(normalization_method_for_entropy=LinearSum\_)
- set_weights_by_ranking_A() 
- set_weights_by_ranking_B() 
- set_weights_by_ranking_B_POW(default=0)
- set_weights_by_ranking_C()
- set_weights_by_AHP(saaty_preference_matrix)

*Ranking methods A, B, B_POW and C need criteria ordered by importance C1> c2> C3 ...*

**Properties**

- pretty_original(tablefmt='psql')
- pretty_normalized(tablefmt='psql')
- pretty_weighted(tablefmt='psql')
- pretty_decision(tablefmt='psql')

tablefmt: "psql" or "latex" or "html" 

**Classes for Decision-Making**:

- Class WSM: decide(normalization_method=None)
- Class WPM: decide(normalization_method=None)
- Class WASPAS: decide(lambda=0.5, normalization_method=None)
- Class TOPSIS: decide(normalization_method=TopsisOriginal\_)
- Class PROMETHEE_II: decide(normalization_method=LinearMinMax\_)
- Class ELECTRE_I: decide(c=1, d=0, normalization_method=None)
- Class ELECTRE_II: decide(c=1, d=0, normalization_method=None)
- Class VIKOR: decide()

Normalization constants: 
  LinearMinMax\_, LinearMax\_, LinearSum\_, Vector\_, EnhancedAccuracy\_ and Logarithmic\_

    # Use help(<class_name>) to list all attributes and methods of classes 
    # e.g.

    help(ELECTRE_I)

Quick Start for MCDA
--------------------

.. code-block:: python

    from scikitmcda.topsis import TOPSIS
    from scikitmcda.wsm import WSM
    from scikitmcda.wpm import WPM
    from scikitmcda.waspas import WASPAS
    from scikitmcda.promethee_ii import PROMETHEE_II
    from scikitmcda.electre_i import ELECTRE_I
    from scikitmcda.vikor import VIKOR
    from scikitmcda.constants import MAX, MIN, LinearMinMax_, LinearMax_, LinearSum_, Vector_, EnhancedAccuracy_, Logarithmic_ 

    # Example for TOPSIS

    topsis = TOPSIS()

    topsis.dataframe([[250, 16, 12, 5],
                      [200, 16,  8, 3],
                      [300, 32, 16, 4],
                      [275, 32,  8, 4],
                      [225, 16,  16, 2]],
                     ["Mobile 1", "Mobile 2", "Mobile 3", "Mobile 4", "Mobile 5"],
                     ["COST", "STORAGE", "CAMERA", "DESIGN"]
                     )
    print(topsis.pretty_original())

    +----+----------------+--------+-----------+----------+----------+
    |    | alternatives   |   COST |   STORAGE |   CAMERA |   DESIGN |
    |----+----------------+--------+-----------+----------+----------|
    |  0 | Mobile 1       |    250 |        16 |       12 |        5 |
    |  1 | Mobile 2       |    200 |        16 |        8 |        3 |
    |  2 | Mobile 3       |    300 |        32 |       16 |        4 |
    |  3 | Mobile 4       |    275 |        32 |        8 |        4 |
    |  4 | Mobile 5       |    225 |        16 |       16 |        2 |
    +----+----------------+--------+-----------+----------+----------+

    # topsis.set_weights_manually([0.5918, 0.2394, 0.1151, 0.0537])
    # topsis.set_weights_by_entropy()
    # topsis.set_weights_by_ranking_B_POW(0)
    
                                       # C1   C2     C3   C4 
    w_AHP = topsis.set_weights_by_AHP([[  1,    4,    5,   7],   # C1
                                       [1/4,    1,    3,   5],   # C2
                                       [1/5,  1/3,    1,   3],   # C3
                                       [1/7,  1/5,  1/3,   1]])  # C4
    print("AHP Returned:\n", w_AHP)
    topsis.set_signals([MIN, MAX, MAX, MAX])

    AHP Returned:
    {'consistency': True, 'lambda': 4.17992665646019, 'CIndex': 0.05997555215339675, 'CRatio': 0.06663950239266306}

    topsis.decide()

    print("WEIGHTS:\n", topsis.weights)
    
    WEIGHTS:
    [0.5809771356405764, 0.2429005339101441, 0.12011108977871769, 0.056011240670561804]

    print("NORMALIZED:\n", topsis.pretty_normalized())
    
    NORMALIZED:
    +----+----------------+----------+-----------+----------+----------+
    |    | alternatives   |     COST |   STORAGE |   CAMERA |   DESIGN |
    |----+----------------+----------+-----------+----------+----------|
    |  0 | Mobile 1       | 0.442807 |  0.301511 | 0.428571 | 0.597614 |
    |  1 | Mobile 2       | 0.354246 |  0.301511 | 0.285714 | 0.358569 |
    |  2 | Mobile 3       | 0.531369 |  0.603023 | 0.571429 | 0.478091 |
    |  3 | Mobile 4       | 0.487088 |  0.603023 | 0.285714 | 0.478091 |
    |  4 | Mobile 5       | 0.398527 |  0.301511 | 0.571429 | 0.239046 |
    +----+----------------+----------+-----------+----------+----------+

    print("WEIGHTED:\n", topsis.pretty_weighted())
    
    WEIGHTED:
    +----+----------------+----------+-----------+-----------+-----------+
    |    | alternatives   |     COST |   STORAGE |    CAMERA |    DESIGN |
    |----+----------------+----------+-----------+-----------+-----------|
    |  0 | Mobile 1       | 0.257261 | 0.0732373 | 0.0514762 | 0.0334731 |
    |  1 | Mobile 2       | 0.205809 | 0.0732373 | 0.0343175 | 0.0200839 |
    |  2 | Mobile 3       | 0.308713 | 0.146475  | 0.0686349 | 0.0267785 |
    |  3 | Mobile 4       | 0.282987 | 0.146475  | 0.0343175 | 0.0267785 |
    |  4 | Mobile 5       | 0.231535 | 0.0732373 | 0.0686349 | 0.0133892 |
    +----+----------------+----------+-----------+-----------+-----------+

    print("RANKING TOPSIS with", topsis.normalization_method , ":\n", topsis.pretty_decision())
    
    RANKING TOPSIS with Vector :
    +----+----------------+---------------------+--------+
    |    | alternatives   |   performance score |   rank |
    |----+----------------+---------------------+--------|
    |  0 | Mobile 2       |            0.55711  |      1 |
    |  1 | Mobile 5       |            0.513009 |      2 |
    |  2 | Mobile 4       |            0.481779 |      3 |
    |  3 | Mobile 3       |            0.44289  |      4 |
    |  4 | Mobile 1       |            0.388243 |      5 |
    +----+----------------+---------------------+--------+

    topsis.decide(EnhancedAccuracy_)
    print("RANKING TOPSIS with", topsis.normalization_method, ":\n", topsis.pretty_decision())

    RANKING TOPSIS with EnhancedAccuracy :
    +----+----------------+---------------------+--------+
    |    | alternatives   |   performance score |   rank |
    |----+----------------+---------------------+--------|
    |  0 | Mobile 2       |            0.70887  |      1 |
    |  1 | Mobile 5       |            0.638174 |      2 |
    |  2 | Mobile 1       |            0.457331 |      3 |
    |  3 | Mobile 4       |            0.358036 |      4 |
    |  4 | Mobile 3       |            0.29113  |      5 |
    +----+----------------+---------------------+--------+
    

Module for Decision-making Under Uncertainty (DMUU)
---------------------------------------------------

**DMUU**: Class Module for Decision-making Under Uncertainty

**Attributes:**
  
    df_original = DataFrame
    df_calc = DataFrame
    decision = {"alternative":,
                "index":,
                "value": ,
                "criteria": ,
                "result": ,
                "type_dm": "DMUU",
                "hurwicz_coeficient":}

**Criteria Methods**:

- maximax()
- maximin()
- laplace()
- minimax_regret()
- hurwicz(coef)

**Properties**

- pretty_original(tablefmt='psql')
- pretty_calc(tablefmt='psql')
- pretty_decision(tablefmt='psql')

tablefmt: "psql" or "latex" or "html" 

**Methods**:

- dataframe(alt_data, alt_labels=[], state_labels=[])
- decision_making(dmuu_criteria_list=[])

Quick Start for DMUU
--------------------

.. code-block:: python

    from scikitmcda.dmuu import DMUU

    # Defining labels for Alternatives and States")
    
    dmuu = DMUU()

    dmuu.dataframe([[5000, 2000, 100],
                    [50, 50, 500]],
                    ["ALT_A", "ALT_B"],
                    ["STATE A", "STATE B", "STATE C"]
                    )

    print(dmuu.pretty_original())
    +----+----------------+-----------+-----------+-----------+
    |    | alternatives   |   STATE A |   STATE B |   STATE C |
    |----+----------------+-----------+-----------+-----------|
    |  0 | ALT_A          |      5000 |      2000 |       100 |
    |  1 | ALT_B          |        50 |        50 |       500 |
    +----+----------------+-----------+-----------+-----------+
    
    # Specifying the criteria method
    
    dmuu.minimax_regret()

    print(dmuu.pretty_calc())
    +----+----------------+-----------+-----------+-----------+------------------+
    |    | alternatives   |   STATE A |   STATE B |   STATE C | minimax-regret   |
    |----+----------------+-----------+-----------+-----------+------------------|
    |  0 | ALT_A          |      5000 |      2000 |       100 | (400, 1)         |
    |  1 | ALT_B          |        50 |        50 |       500 | (4950, 0)        |
    +----+----------------+-----------+-----------+-----------+------------------+

    print(dmuu.pretty_decision())
    +---------------+---------+---------+----------------+-------------------------------+-----------+----------------------+
    | alternative   |   index |   value | criteria       | result                        | type_dm   | hurwicz_coeficient   |
    |---------------+---------+---------+----------------+-------------------------------+-----------+----------------------|
    | ALT_A         |       0 |     400 | minimax-regret | {'ALT_A': 400, 'ALT_B': 4950} | DMUU      |                      |
    +---------------+---------+---------+----------------+-------------------------------+-----------+----------------------+

    # Many crietria methods

    dmuu.decision_making([dmuu.maximax(), dmuu.maximin(), dmuu.hurwicz(0.8), dmuu.minimax_regret()])

    print(dmuu.pretty_calc())
    +----+----------------+-----------+-----------+-----------+------------------+-----------+-----------+------------------+
    |    | alternatives   |   STATE A |   STATE B |   STATE C | minimax-regret   | maximax   | maximin   | hurwicz          |
    |----+----------------+-----------+-----------+-----------+------------------+-----------+-----------+------------------|
    |  0 | ALT_A          |      5000 |      2000 |       100 | (400, 1)         | (5000, 1) | (100, 1)  | (4020.0, 1, 0.8) |
    |  1 | ALT_B          |        50 |        50 |       500 | (4950, 0)        | (500, 0)  | (50, 0)   | (410.0, 0, 0.8)  |
    +----+----------------+-----------+-----------+-----------+------------------+-----------+-----------+------------------+

    print(dmuu.pretty_decision())
    +---------------+---------+---------+----------------+-----------------------------------+-----------+----------------------+
    | alternative   |   index |   value | criteria       | result                            | type_dm   | hurwicz_coeficient   |
    |---------------+---------+---------+----------------+-----------------------------------+-----------+----------------------|
    | ALT_A         |       0 |    5000 | maximax        | {'ALT_A': 5000, 'ALT_B': 500}     | DMUU      |                      |
    | ALT_A         |       0 |     100 | maximin        | {'ALT_A': 100, 'ALT_B': 50}       | DMUU      |                      |
    | ALT_A         |       0 |    4020 | hurwicz        | {'ALT_A': 4020.0, 'ALT_B': 410.0} | DMUU      | 0.8                  |
    | ALT_A         |       0 |     400 | minimax-regret | {'ALT_A': 400, 'ALT_B': 4950}     | DMUU      |                      |
    +---------------+---------+---------+----------------+-----------------------------------+-----------+----------------------+

    dmuu.calc_clean()
    print(dmuu.pretty_calc())
    +----+----------------+-----------+-----------+-----------+
    |    | alternatives   |   STATE A |   STATE B |   STATE C |
    |----+----------------+-----------+-----------+-----------|
    |  0 | ALT_A          |      5000 |      2000 |       100 |
    |  1 | ALT_B          |        50 |        50 |       500 |
    +----+----------------+-----------+-----------+-----------+


