Tutorial of how to use scikit-criteria AHP extension module

Considerations

  • This tutorial asumes that you know the AHP method
  • The full example is here (Spanish only)

Citation

If you use scikit-criteria or the AHP extension in a scientific publication or thesis, we would appreciate citations to the following paper:

Cabral, Juan B., Nadia Ayelen Luczywo, and José Luis Zanazzi 2016 Scikit-Criteria: Colección de Métodos de Análisis Multi-Criterio Integrado Al Stack Científico de Python. In XLV Jornadas Argentinas de Informática E Investigación Operativa (45JAIIO)-XIV Simposio Argentino de Investigación Operativa (SIO) (Buenos Aires, 2016) Pp. 59–66. http://45jaiio.sadio.org.ar/sites/default/files/Sio-23.pdf.

Bibtex entry:

@inproceedings{scikit-criteria,
    author={
        Juan B Cabral and Nadia Ayelen Luczywo and Jos\'{e} Luis Zanazzi},
    title={
        Scikit-Criteria: Colecci\'{o}n de m\'{e}todos de an\'{a}lisis
        multi-criterio integrado al stack cient\'{i}fico de {P}ython},
    booktitle = {
        XLV Jornadas Argentinas de Inform{\'a}tica
        e Investigaci{\'o}n Operativa (45JAIIO)-
        XIV Simposio Argentino de Investigaci\'{o}n Operativa (SIO)
        (Buenos Aires, 2016)},
    year={2016},
    pages = {59--66},
    url={http://45jaiio.sadio.org.ar/sites/default/files/Sio-23.pdf}
}

Instalation

  1. Installing Scikit-Criteria: http://scikit-criteria.org/en/latest/install.html
  2. Download the ahp.py module.

Why AHP is not part of Scikit-Criteria

The main problem is how the data are feeded to AHP. All the methods included in Scikit-Criteria uses the clasical

$$ SkC_{madm}(mtx, criteria, weights) $$

Where

  • $SkC_{madm}$ is a Scikit-Criteria multi-attribute-decision-making method
  • $mtx$ is the alternative 2D array-like matrix, where where every column is a criteria, and every row is an alternative.
  • $criteria$ 1D array-like whit the same number of elements than columns has the alternative mattrix (mtx) where every component represent the optimal sense of every criteria.
  • $weights$ weights 1D array like.

All this 3 components can be modeled as the single scikit-criteria DATA object:


In [3]:
from skcriteria import Data, MIN, MAX

In [5]:
mtx = [
    [1, 2, 3],  # alternative 1
    [4, 5, 6],  # alternative 2
]
mtx


Out[5]:
[[1, 2, 3], [4, 5, 6]]

In [6]:
# let's says the first two alternatives are
# for maximization and the last one for minimization
criteria = [MAX, MAX, MIN]
criteria


Out[6]:
[1, 1, -1]

In [7]:
# et’s asume we know in our case, that the importance of 
# the autonomy is the 50%, the confort only a 5% and 
# the price is 45%
weights=[.5, .05, .45]
weights


Out[7]:
[0.5, 0.05, 0.45]

In [9]:
data = Data(mtx, criteria, weights)
data


Out[9]:
ALT./CRIT. C0 (max) W.0.5 C1 (max) W.0.05 C2 (min) W.0.45
A0 1 2 3
A1 4 5 6

In other hand AHP uses as an in put 2 totally different

$$ AHP(CvC, AvA) $$

Where:

  • $CVC$: A triangular matrix of criteria vs criteria with values from Satty Scale
  • $AvA$: A collection of $n$ triangular matrices of alternative vs alternative with values from Satty Scale

AHP Turorial

1. Creating triangular matrices

first we need to import the ahp module


In [24]:
import ahp

The function ahp.t (from triangular) accept the lower half of the mattrix and return a complete mattrix with the reciprocal values


In [25]:
mtx = ahp.t(
          [[1],
           [1., 1],
           [1/3.0, 1/6.0, 1]])
mtx


Out[25]:
array([[1.        , 1.        , 3.        ],
       [1.        , 1.        , 6.        ],
       [0.33333333, 0.16666667, 1.        ]])

2. Validating the data (optional)

You can validate if some mattrix has the correct values for AHP with the function

ahp.validate_ahp_matrix(n, mtx)

Where:

  • n: is the number of rows and columns (remember all mattrix in AHP has the same rows and columns).
  • mtx: The mattrix to validate.

Example 1 - Correct Mattrix


In [26]:
# this validate the data
ahp.validate_ahp_matrix(3, mtx)

Example 2 invalid Satty Values in the cell (0, 1)


In [28]:
invalid_mtx = mtx.copy()
invalid_mtx[0, 1] = 89
invalid_mtx


Out[28]:
array([[ 1.        , 89.        ,  3.        ],
       [ 1.        ,  1.        ,  6.        ],
       [ 0.33333333,  0.16666667,  1.        ]])

In [29]:
ahp.validate_ahp_matrix(3, invalid_mtx)


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-29-70f781c61428> in <module>()
----> 1 ahp.validate_ahp_matrix(3, invalid_mtx)

~/proyectos/skcriteria/src/ahp.py in validate_ahp_matrix(rows_and_columns, mtx, mtxtype)
    165         raise ValueError(msg)
    166 
--> 167     validate_values(mtx)
    168 
    169     triu, tril = np.triu(mtx), np.tril(mtx)

~/proyectos/skcriteria/src/ahp.py in validate_values(values)
    131     if not np.all((values > SAATY_MIN) & (values < SAATY_MAX)):
    132         msg = "All values must >= {} and <= {}"
--> 133         raise ValueError(msg.format(SAATY_MIN+1, SAATY_MAX-1))
    134 
    135 

ValueError: All values must >= 1 and <= 9

Example 3: Matrix with un-recriprocal values


In [32]:
invalid_mtx = mtx.copy()
invalid_mtx[0, 1] = 0.5
invalid_mtx


Out[32]:
array([[1.        , 0.5       , 3.        ],
       [1.        , 1.        , 6.        ],
       [0.33333333, 0.16666667, 1.        ]])

In [33]:
ahp.validate_ahp_matrix(3, invalid_mtx)


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-33-70f781c61428> in <module>()
----> 1 ahp.validate_ahp_matrix(3, invalid_mtx)

~/proyectos/skcriteria/src/ahp.py in validate_ahp_matrix(rows_and_columns, mtx, mtxtype)
    178 
    179     if not np.allclose(triu, trilu):
--> 180         raise ValueError("The matix is not symmetric with reciprocal values")
    181 
    182 

ValueError: The matix is not symmetric with reciprocal values

3. Running AHP

First lets create a criteria vs criteria mattrix


In [35]:
crit_vs_crit = ahp.t([
    [1.], 
    [1./3., 1.], 
    [1./3., 1./2., 1.]
])
crit_vs_crit


Out[35]:
array([[1.        , 3.        , 3.        ],
       [0.33333333, 1.        , 2.        ],
       [0.33333333, 0.5       , 1.        ]])

And lets asume we have 3 alternatives, and because we have 3 criteria: 3 alternatives vs alternatives mattrix must be created


In [36]:
alt_vs_alt_by_crit = [
    ahp.t([[1.], 
           [1./5., 1.], 
           [1./3., 3., 1.]]),
    ahp.t([
        [1.], 
        [9., 1.], 
        [3., 1./5., 1.]]),
    ahp.t([[1.], 
           [1/2., 1.], 
           [5., 7., 1.]]),
]

alt_vs_alt_by_crit


Out[36]:
[array([[1.        , 5.        , 3.        ],
        [0.2       , 1.        , 0.33333333],
        [0.33333333, 3.        , 1.        ]]),
 array([[1.        , 0.11111111, 0.33333333],
        [9.        , 1.        , 5.        ],
        [3.        , 0.2       , 1.        ]]),
 array([[1.        , 2.        , 0.2       ],
        [0.5       , 1.        , 0.14285714],
        [5.        , 7.        , 1.        ]])]

Now run th ahp.ahp() function. This function return 6 values

  1. The rank of the alternatives (rank).
  2. The points of every alternative (points).
  3. The criteria consistence index (crit_ci)
  4. The alternative vs alternative by criteria consistency index (avabc_ci)
  5. The criteria consistence ratio (crit_cr). ranked, points, crit_ci, avabc_ci, crit_cr, avabc_cr
  6. The alternative vs alternative by criteria consistency ratio (avabc_cr)

In [49]:
result = ahp.ahp(crit_vs_crit, alt_vs_alt_by_crit)
rank, points, crit_ci, avabc_ci, crit_cr, avabc_cr = result

4. Analysing the results

The rank vector:


In [50]:
rank


Out[50]:
array([1, 3, 2])

So ouer best altetnative is the first one, and the worst is the second one

The final points of every alternative is:


In [51]:
points


Out[51]:
array([0.41765182, 0.26598058, 0.3163676 ])