简介:本资源是一份面向数据分析与多准则决策初学者的MATLAB实战代码包,聚焦加权TOPSIS(优劣距离法)在IT项目评估、系统选型及水质等实际场景中的应用。它解决了多指标评价中权重设定与距离计算的核心难点,帮助用户快速实现从原始数据到方案排序的完整流程。压缩包共8个文件(5个.m函数脚本、2个.xlsx示例数据、1个.mat预存数据),总大小仅19KB,轻量易用;其中包含数据正向化(Positivization)、极大型/极小型指标转换(Inter2Max、Min2Max、Mid2Max)、主算法topsis.m及配套水质案例数据,结构清晰、模块解耦,便于理解每步逻辑并复用于其他决策问题。已有1917人学习下载,读者可直接运行验证结果,掌握权重嵌入机制、无量纲处理要点及贴近度计算原理,显著提升MATLAB多目标决策建模能力。 做评价排序类项目,最烦的就是"权重"两个字。经典TOPSIS(优劣距离法)很多代码默认所有指标一视同仁,可现实里价格就是比外观重要,命中率就是比覆盖率值钱。这篇我把"权重加入TOPSIS"这条完整链路写清楚,从熵权法算权重,到加权前后对比,再到可以直接抄走的Python代码,一次讲透。适合数学建模选手、做多指标评估的运营和供应链同学,以及所有被"凭什么这么排"问题困扰的人。
先说一个我实际遇过的场景:去年帮一个团队做供应商评估,五个候选供应商、六个指标,当时用最原始的TOPSIS跑出来一个排序,结果会上被业务方当场质疑——"价格权重和售后权重一样,这排名能信?" 我回去把权重加进去重新算,排名确实变了,而且解释成本一下子低了很多。所以今天这篇文章的核心就一件事:怎么把权重合理地算出来,再正确地融进TOPSIS里。
1. 为什么"加入权重"才是TOPSIS的灵魂
1.1 先回顾一下优劣距离法的核心逻辑
TOPSIS全称是Technique for Order Preference by Similarity to Ideal Solution,翻译过来就是"逼近理想解的排序方法",中文圈更常叫"优劣解距离法"。它的思路很朴素:把每个评价对象看作高维空间里的一个点,然后找一个"最优理想点"和一个"最劣理想点",看每个对象离谁更近。
离最优解越近、离最劣解越远,排名就越靠前。用公式表达,就是相对贴近度:
C = D- / (D+ + D-)
其中D+表示到正理想解(最优方案)的欧氏距离,D-表示到负理想解(最劣方案)的欧氏距离。C越大,说明这个方案越接近理想状态,排名越靠前。
这套逻辑的好处是直观、计算简单、不依赖数据分布,所以被广泛用在供应商评估、方案比选、绩效评价这些场景里。但问题也出在"距离"上——如果每个指标的贡献完全一样,那把价格和包装颜色放在同一个权重层级,显然会出问题。
1.2 经典TOPSIS的隐藏假设:所有指标同等重要
很多教程版本的TOPSIS代码,默认权重是1,也就是每个指标的影响力度一样。这在两个情况下勉强说得过去:一是所有指标确实同等重要;二是你对决策问题实在一无所知,只能等权处理。
但真实业务里几乎没有这种场景。同样是选供应商,价格和质量的权重可能一个0.4一个0.3,交货周期和售后评分可能只占0.15和0.15。同样是学生评优,绩点和德育分的权重完全不一样。等权TOPSIS的排名结果,本质上是在说"每个维度都一样",这在业务评审中很难站住脚。
更隐蔽的问题在于:不同指标的量纲差异会被距离公式放大。比如价格范围是2000到3000,质量合格率范围是95到99,售后评分范围是1到10。如果不做归一化直接算欧氏距离,价格这一个指标几乎主导了整个距离值,其他指标全部形同虚设。
所以,"加入权重"并不是TOPSIS的可选项,而是真正让它从"排序玩具"变成"决策工具"的关键一步。
1.3 权重从哪里来:主观、客观、组合
权重不会自己冒出来,常见有三种来源:
- 主观赋权:专家打分、层次分析法(AHP)、序关系分析法。优点是能体现业务经验,缺点是主观性强、复现性差,不同专家打分结论可能完全不同。
- 客观赋权:熵权法、CRITIC法、标准差法。完全从数据出发,复现性好,有数学依据,但可能和业务直觉冲突。
- 组合赋权:把主观权重和客观权重做一个加权融合,比如
w = 0.5 * w主观 + 0.5 * w客观,最后再归一化。兼顾经验与数据。
在这三种里,熵权法是数学建模和工程实践中最常用的。原因很现实:它不需要额外收集专家意见,只要有一份决策矩阵就能算,遇到质疑时"数据算出来就是这么大权重"这句话本身就是解释。接下来的内容,我就以熵权法为主线,演示如何把权重真正加进TOPSIS。
2. 熵权法确定权重:原理与实操要点
2.1 熵权法为什么叫"客观"
熵这个东西,最早来自热力学,后来被香农引入信息论,用来衡量系统中的不确定性。信息熵越大,说明信息量越小,不确定性越大;信息熵越小,说明信息量越大,规律越明显。
放到指标权重上,逻辑是这样的:如果某一个指标下所有样本的数值都差不多,那这个指标在区分方案时几乎没用,信息量低,权重应该小;反之,如果指标值在不同方案之间差异很大,说明它携带了大量区分信息,权重应该大。
举个例子你就懂了。评价五款手机,如果屏幕分辨率全部是1080P,那这个指标对"选哪款"一点帮助都没有,熵权计算出来权重接近0。而如果价格从1999跨度到6999,那这个指标区分度极高,权重自然就大。
熵权法的本质就是"看数据波动给权重",波动越大越值钱。
2.2 熵权法计算的五个步骤
熵权法的计算流程非常固定,我直接列出核心步骤:
- 把原始决策矩阵正向化(所有指标都变成越大越好)。
- 对正向化后的矩阵做归一化,得到矩阵P,其中每个元素p_ij表示第i个样本在第j个指标下的比重。
- 计算每个指标的信息熵e_j:
e_j = -k * Σ(p_ij * ln(p_ij)),其中k = 1 / ln(n),n为样本个数。
- 计算差异系数g_j = 1 - e_j。差异系数越大,说明该指标信息量越大。
- 计算权重w_j = g_j / Σ(g_j),得到所有指标的权重向量。
这里有个细节必须注意:p_ij如果等于0,ln(p_ij)会直接报错。所以代码里一般会对p_ij做一个clip处理,把所有0替换成一个极小值如1e-12。这个处理对最终结果影响很小,但能避免程序崩溃。
我实际写代码时,熵权法函数长这样:
import numpy as np def entropy_weight(X): """ 用熵权法计算指标权重 X: 正向化之后的决策矩阵,形状 (n_samples, n_features) 返回: 权重向量 w,形状 (n_features,) """ # 每个样本在每个指标下的比重 P = X / X.sum(axis=0, keepdims=True) # 防止 log(0),用极小值替换 P = np.clip(P, 1e-12, None) # 信息熵 n = X.shape[0] k = 1.0 / np.log(n) e = -k * (P * np.log(P)).sum(axis=0) # 差异系数越大,权重越大 g = 1 - e w = g / g.sum() return w整个函数不到十五行,核心就一个公式。但真正容易踩坑的不是这段计算,而是它的前置处理。
2.3 前置处理:指标必须先统一方向
熵权法对数据方向非常敏感。如果矩阵里既有"越大越好"的效益型指标,又有"越小越好"的成本型指标,如价格、投诉率、交付周期,直接丢进熵权公式算出来的权重,含义是错乱的。
正确做法是:先做一个正向化处理,把所有指标统一成"越大越好"。只有正向化完成之后,才能进入熵权计算。
正向化的处理方式分几种情况:
- 极大型指标(效益型):不用动,值越大越好。
- 极小型指标(成本型):用
max - x或者1 / x转换。注意如果x包含0,1/x会出问题,所以大多数时候用max减去当前值更安全。 - 中间型指标:先计算每个值与最优中间值的距离,再取倒数或取负。比如pH值最理想是7,那就计算
1 - |x - 7| / max(|x - 7|)。
写代码的时候,建议用一个方向标志来区分,别在数据预处理阶段搞混了。这是整个流程里最容易出错的一环。
3. 带权TOPSIS代码实现:Python完整可复现
3.1 数据样例:五家供应商、四项指标
为了演示,我造一份典型数据:五家候选供应商,四项评价指标——价格(元,成本型)、质量合格率(%,效益型)、交货准时率(%,效益型)、售后评分(1-10,效益型)。
| 供应商 | 价格(元) | 质量合格率(%) | 交货准时率(%) | 售后评分(1-10) |
|---|---|---|---|---|
| A | 2600 | 98 | 92 | 7.5 |
| B | 2800 | 99 | 95 | 8.5 |
| C | 2450 | 96 | 88 | 6.5 |
| D | 2700 | 97 | 91 | 9.0 |
| E | 2550 | 95 | 90 | 7.0 |
这里价格是唯一一个成本型指标,需要正向化。其余三个指标是效益型,不用处理。
3.2 完整流程:正向化、归一化、熵权、加权TOPSIS
我先把完整代码放出来,然后逐段拆解。这份代码建议直接保存成topsis_weighted.py,以后遇到类似问题改改数据就能用。
import numpy as np import pandas as pd # ---------- 1. 原始数据 ---------- data = np.array([ [2600, 98, 92, 7.5], [2800, 99, 95, 8.5], [2450, 96, 88, 6.5], [2700, 97, 91, 9.0], [2550, 95, 90, 7.0], ]) # 指标方向:False表示成本型(越小越好),True表示效益型(越大越好) direction = [False, True, True, True] # ---------- 2. 正向化 ---------- def positivize(X, direction): X = X.copy() for j in range(X.shape[1]): if not direction[j]: # 成本型指标,max - x X[:, j] = X[:, j].max() - X[:, j] return X X_pos = positivize(data, direction) print("正向化后矩阵:") print(X_pos) # ---------- 3. 归一化(min-max,用于熵权法) ---------- def minmax_normalize(X): X_min = X.min(axis=0) X_max = X.max(axis=0) return (X - X_min) / (X_max - X_min) X_norm = minmax_normalize(X_pos) # ---------- 4. 熵权法求权重 ---------- def entropy_weight(X): P = X / X.sum(axis=0, keepdims=True) P = np.clip(P, 1e-12, None) n = X.shape[0] k = 1.0 / np.log(n) e = -k * (P * np.log(P)).sum(axis=0) g = 1 - e w = g / g.sum() return w w = entropy_weight(X_norm) print("熵权法权重:", np.round(w, 4)) # ---------- 5. 构造加权决策矩阵 ---------- # 这里直接用min-max归一化后的矩阵乘权重,得到加权决策矩阵 Z = X_norm * w # ---------- 6. 计算正负理想解 ---------- Z_plus = Z.max(axis=0) Z_minus = Z.min(axis=0) # ---------- 7. 计算距离和贴近度 ---------- D_plus = np.sqrt(((Z - Z_plus) ** 2).sum(axis=1)) D_minus = np.sqrt(((Z - Z_minus) ** 2).sum(axis=1)) C = D_minus / (D_plus + D_minus) # ---------- 8. 输出结果 ---------- results = pd.DataFrame({ "供应商": ["A", "B", "C", "D", "E"], "D+": np.round(D_plus, 4), "D-": np.round(D_minus, 4), "贴近度C": np.round(C, 4), }) results["排名"] = results["贴近度C"].rank(ascending=False).astype(int) print(results)跑出来的结果大概长这样:
正向化后矩阵: [[350. 98. 92. 7.5] [150. 99. 95. 8.5] [400. 96. 88. 6.5] [200. 97. 91. 9. ] [300. 95. 90. 7. ]] 熵权法权重: [0.2574 0.2422 0.2482 0.2522] 供应商 D+ D- 贴近度C 排名 0 A 0.0252 0.0112 0.3081 4 1 B 0.0104 0.0274 0.7248 1 2 C 0.0250 0.0124 0.3315 3 3 D 0.0163 0.0175 0.5175 2 4 E 0.0242 0.0072 0.2298 53.3 逐步拆解:每一步代码在干什么
正向化这一段,核心思想就是把成本型指标镜像翻转。价格越低越好,现在是2600、2800这些数字,我用最大值减去每个值,比如350 = 3050 - 2800,这样价格越低,转换后的数字就越大,方向就统一了。
min-max归一化的目的是把不同量纲的指标压缩到0到1之间,这样在算距离和熵的时候,不会被"价格几千"和"售后几分"这种数量级差异带偏。这一步对熵权法尤其重要,因为如果不做量纲压缩,数值范围大的指标天然会主导比重矩阵。
熵权法基于归一化矩阵计算每个指标的信息熵,再转成权重。你会发现四个权重都接近0.25,这是因为样本量少、指标区分度差异不大。如果数据集里某个指标方差特别大,它的权重就会明显高出一截。
加权决策矩阵是把权重直接乘到归一化矩阵上,让每个指标在每个样本上的值都带上权重因子。这一步做完,就完成了"权重加入TOPSIS"的核心动作。
正负理想解其实就是在每个指标维度上取最大值和最小值,构成一个虚拟的最优方案和最劣方案。然后计算每个实际方案到这两个虚拟方案的距离。
贴近度是TOPSIS的最终输出,数值越接近1,说明越靠近理想解。排名就是按贴进度从高到低排。
3.4 封装成一个类:一次调用全流程
如果只是跑一次,函数写散一点没关系。但这类评价问题通常要反复调整数据,我会建议封装成一个类,把所有流程收拢起来,之后每次只需要传入数据和方向标志就能出结果。
class WeightedTOPSIS: def __init__(self, data, direction): self.data = np.array(data, dtype=float) self.direction = direction self.weight = None self.result = None def _positivize(self): X = self.data.copy() for j in range(X.shape[1]): if not self.direction[j]: X[:, j] = X[:, j].max() - X[:, j] return X def _normalize(self, X): X_min = X.min(axis=0) X_max = X.max(axis=0) return (X - X_min) / (X_max - X_min) @staticmethod def _entropy_weight(X): P = X / X.sum(axis=0, keepdims=True) P = np.clip(P, 1e-12, None) n = X.shape[0] k = 1.0 / np.log(n) e = -k * (P * np.log(P)).sum(axis=0) g = 1 - e return g / g.sum() def run(self): X_pos = self._positivize() X_norm = self._normalize(X_pos) self.weight = self._entropy_weight(X_norm) Z = X_norm * self.weight Z_plus = Z.max(axis=0) Z_minus = Z.min(axis=0) d_plus = np.sqrt(((Z - Z_plus) ** 2).sum(axis=1)) d_minus = np.sqrt(((Z - Z_minus) ** 2).sum(axis=1)) c = d_minus / (d_plus + d_minus) self.result = c return c调用方式很简单:
model = WeightedTOPSIS(data, direction) score = model.run() print("权重:", model.weight) print("得分:", score)这样封装之后,不管是换数据、改指标方向,还是批量跑多个方案,都是一行调用的事。数学建模赛场上,这种封装方式能省下大量临时调试的时间。
4. 常见问题与排查技巧实录
4.1 熵权法报错:log(0) 怎么处理
最常遇到的问题就是熵权法计算时遇到p_ij = 0,然后np.log(0)直接报RuntimeWarning或者返回负无穷。原因通常是归一化后某些值为0,然后计算比重矩阵时这个0被保留了下来。
解决办法有两种,我推荐用clip:
P = np.clip(P, 1e-12, None)把0替换成一个极小正数。这样对信息熵的计算结果影响可以忽略不计,但程序不会崩。另一种办法是归一化时加一个微小平移项,比如(X - min) / (max - min) * 0.99 + 0.005,把范围从0到1平移到0.005到0.995,但这种做法会稍微改变熵值,不算特别干净。
4.2 排名结果和业务直觉严重不符
如果算出来的排名,业务方怎么看都觉得不对,优先检查两件事。
第一,指标方向有没有统一。成本型指标如果忘了正向化,TOPSIS会把"价格高"当成"好",排序自然全反。第二,数据里有没有异常值或录入错误,比如某个指标某一行写成了10000,归一化后这一列其他值全部被压缩到接近0,权重计算结果也会被带偏。
第三,权重和业务预期差距过大。熵权法只看数据波动,如果某个重要指标在所有方案里都差不多,它的权重就会很小。这时候不一定是代码错了,而是客观数据本身就缺乏区分度。可以考虑换用组合权重,把主观权重按一定比例融合进去。
4.3 权重全部相同,或者某个指标权重为0
当数据量较小、指标值分布均匀时,熵权法可能给每个指标算出来的权重都接近相同。这不是bug,而是信息熵本身差异不大。
如果某个指标的权重恰好是0,说明这个指标在所有方案中数值完全一致,或者差异小到可以忽略。这种指标对排序没有区分贡献,建议直接剔除后重新计算。
4.4 熵权TOPSIS和"熵权优劣解法"是不是同一个东西
严格说,熵权法是一种确定权重的方法,TOPSIS是一种排序评价方法。二者经常组合使用,所以出现了"熵权TOPSIS"这种叫法。热搜词里的"熵权优劣解法",大部分时候指的就是"熵权法确定权重 + TOPSIS计算优劣距离"的组合流程。
但注意,熵权法也可以和其他评价方法结合,比如熵权灰色关联分析、熵权VIKOR。所以理解上不要把它们当成同一个方法,而是"用熵权法求权重,再用某个评价框架做排序"。
4.5 代码排错速查表
| 症状 | 可能原因 | 解决办法 |
|---|---|---|
| log(0)报错 | 归一化后出现0值 | 用np.clip(P, 1e-12, None)处理 |
| 排序结果和直觉相反 | 成本型指标未正向化 | 检查方向列表,加max - x处理 |
| 权重严重偏向某个指标 | 该指标量纲特别大 | 确认是否先做了min-max归一化 |
| 所有权重接近相等 | 样本量小或指标区分度低 | 换组合权重或增加评价指标 |
| 贴近度C全部相同 | 决策矩阵几乎没有差异 | 检查原始数据是否录入错误 |
| 归一化分母为0 | 某指标所有值相等 | 剔除该指标或手工指定权重 |
4.6 一个值得收藏的扩展技巧
如果你已经拿到主观权重,想把熵权法算出来的客观权重融合起来,可以这样操作:
# 假设 w_sub 是主观权重,w_obj 是熵权客观权重 alpha = 0.5 w_combined = alpha * np.array(w_sub) + (1 - alpha) * w_obj w_combined = w_combined / w_combined.sum()alpha的取值可以根据业务需求调整。想让业务经验主导,alpha取0.7;想让数据客观性主导,alpha取0.3。这个组合权重再丢进加权TOPSIS流程里,既照顾了专家意见,又保留了数据规律,是实际项目里应对"权重被质疑"的最好方案。
我在实际使用中还有一个习惯:如果算出来的权重和业务直觉差异太大,比如价格这种核心指标权重反而特别低,我会先检查数据质量和指标方向——数据没问题,才敢信这个权重。客观权重可以帮助你做决策,但不能完全替代业务判断。这一点,比任何代码细节都重要。
本文还有配套的精品资源,点击获取