TOPSIS法的原理及其 Python 实现

TOPSIS法的原理

TOPSIS (Technique for Order Preference by Similarity to an Ideal Solution )法是C.L.Hwang和K.Yoon于1981年首次提出,TOPSIS法根据有限个评价对象与理想化目标的接近程度进行排序的方法,是在现有的对象中进行相对优劣的评价。TOPSIS法是一种逼近于理想解的排序法,该方法只要求各效用函数具有单调递增(或递减)性就行。TOPSIS法是多目标决策分析中一种常用的有效方法,又称为优劣解距离法。

基本思路就是通过假定正负理想解,测算各个样本与正负理想解的距离,得到其与理想方案的相对贴进度(即距离正理想解越近的同时距离负理想解越远),进行各个评价对象的右列排序。

TOPSIS法的思路

数据的处理

这部分我们可以结合《熵权法原理及其 Python 实现》 来进行处理,计算得到权重 w 以及标准化 p,然后我们可以获得我们的 Z 矩阵如下:

$$ Z = (z_{ij})_{n \times m} = (p_{ij}*w_j) $$

确定正负理想解

正理想解指各指标都达到样本中最好的值,负理想解指各指标都为样本中最差的值。

$$ Z^{+} = (\max\{z_{11}, z_{21}, ... , z_{n1}\}, \max\{z_{12}, z_{22},...,z_{n2}\}, ..., \max\{z_{1m}, z_{2m}, ..., z_{mn}\}) = (Z^{+}_1, Z^{+}_2, ..., Z^{+}_m) $$

$$ Z^{-} = (\min\{z_{11}, z_{21}, ... , z_{n1}\}, \min\{z_{12}, z_{22},...,z_{n2}\}, ..., \min\{z_{1m}, z_{2m}, ..., z_{mn}\}) = (Z^{-}_1, Z^{-}_2, ..., Z^{-}_m) $$

计算正负理想解距离

$$ D^{+}_i = \sqrt{\displaystyle\sum^m_{j = 1}(z_{ij} - z^{+}_j)^2} $$

$$ D^{-}_i = \sqrt{\displaystyle\sum^m_{j = 1}(z_{ij} - z^{-}_j)^2} $$

计算贴合程度

$$ C_i = \frac{D^{-}_i}{D^{+}_i + D^{-}_i} $$

其中 Ci 取值范围在 [0, 1] ,从上述式子可观察出,若 Ci 越靠近 1 ,即表明样本评分越好

Python 实现

#!/usr/bin/env python
# coding=utf-8

import numpy as np

def negativeIndex(data):
    n, m = data.shape
    x = np.ones([n, m])
    for j in range(m):
        max_xj, min_xj = max(data.T[j]), min(data.T[j])
        for i in range(n):
            x[i, j] = (data[i, j] - min_xj) / (max_xj - min_xj)

    return x

def ln(x):
    return np.log(x)

def calcP(data):
    n, m = data.shape
    p = np.ones([n, m])

    for j in range(m):
        for i in range(n):
            p[i, j] = (data[i, j] / np.sum(data.T[j])).astype(np.float64)

    return p

def calcEntropy(data):
    # data = calcP(data)

    # print("Calculated P:")
    # print(data)

    n, m = data.shape
    k = 1.0 / ln(n)
    E = np.ones(m)

    for j in range(m):
        sum = 0
        for i in range(n):
            sum += (data[i, j] * np.nan_to_num(ln(data[i, j])))

        print("Sum = ", sum)
        E[j] = -k * sum

    return E

def calcWeight(data):
    data = calcEntropy(data)

    return (1 - data) / (1 - data).sum()

def calcWeight2(data):
    E = np.nansum(-data * np.log(data) / np.log(len(data)), axis=0)
    print(np.nansum(data * np.log(data), axis=0))

    return (1 - E) / (1 - E).sum()

def topsis(data, weight=None):
    n, m = data.shape
    weight = calcWeight(data)

    Zs = np.ones(data.shape)
    Z_Plus, Z_Minus = np.ones(m), np.ones(m)
    for j in range(m):
        for i in range(n):
            Zs[i, j] = weight[j] * data[i, j]

    for j in range(m):
        Z_Plus[j] = max(Zs.T[j])
        Z_Minus[j] = min(Zs.T[j])

    result = np.ones(n)
    for i in range(n):
        S_Plus, S_Minus = 0, 0
        for j in range(m):
            S_Plus += (data[i, j] - Z_Plus[j]) ** 2
            S_Minus += (data[i, j] - Z_Minus[j]) ** 2

        result[i] = (np.sqrt(S_Minus)) / (np.sqrt(S_Plus) - np.sqrt(S_Minus))

    return result, [Z_Plus, Z_Minus], weight

if __name__ == "__main__":
    data = np.loadtxt("./data/entropy.txt", delimiter=",")
    nIndex = negativeIndex(data)

    [result, sol, weight] = topsis(nIndex)

    print("\n\nResults:")
    print(result)

    print("\n\nSolutions:")
    print(sol)

    print("\n\nWeights:")
    print(weight)