
简介在多属性决策与综合评价场景中传统加权求和常因量纲不一致和指标方向混杂而难以清晰排序。TOPSIS优劣解距离法通过构造正负理想解利用欧氏距离计算各方案与理想点的相对贴近度实现无需训练数据的客观排序尤其适合供应商选择、绩效排名、选址评估等场景。结合熵权法可自动确定指标权重避免主观赋权偏差。本文从数据同向化、向量归一化等关键预处理入手拆解算法完整计算流程并给出可直接落地的Python实现代码帮助初学者快速掌握这一经典综合评价模型。TOPSIS评价模型具体步骤及代码每次在项目评审会上看着那张 Excel 评分表我都有点牙疼。几个方案摆在那儿指标一堆有的越大越好有的越小越好几个人凭感觉打个分最后吵得不可开交。后来我把 TOPSIS 模型搬进了决策流程至少从凭感觉拍板变成了按数据排序整个讨论的逻辑一下子清晰了很多。TOPSISTechnique for Order Preference by Similarity to Ideal Solution中文一般叫优劣解距离法核心思想就一句话找一个离最好方案最近、离最差方案最远的方案。它不要求数据分布满足什么假设也不需要有历史样本做训练只需要一张决策矩阵就能算出一堆方案的相对排序。所以在多属性决策、综合评价、供应商选择、选址评估、绩效排名这类场景里TOPSIS 几乎是入门首选也特别好解释给不懂算法的领导听——我们找的是离理想点最近的那个。这篇内容主要面向需要做评价决策分析的从业者不管你是做运筹优化、数据分析、产品方案评估还是写毕业论文做综合评价都可以直接照着下面的步骤和代码落地。1. TOPSIS 算法到底解决什么问题为什么加权求和不够用1.1 综合评价的本质矛盾先想一个问题现在有三个方案每个方案有成本越低越好和收益越高越好两个指标怎么排序如果直接加权求和第一步就得面对量纲问题——成本是万元级别收益是百分比放一起加毫无意义。做归一化吧方向又不一样越大越优的收益正面归一化越小越优的成本也想正面归一化然后加权求和。这套逻辑本身没错但它在解释上有个天生缺陷当一个方案的 A 指标很突出、B 指标很差加权求和后总分可能和另一个各项都中等偏上的方案打平可是决策者根本没看出来这个方案到底差在哪儿。TOPSIS 的出发点不一样。它不是把所有指标压成一条线而是先确定一个理想方案和一个负理想方案理想方案是所有指标都取最优值负理想方案是所有指标都取最差值。每个方案去和这两个极端比距离离理想方案近、离负理想方案远的就是好方案。1.2 TOPSIS 的适用边界要说清楚 TOPSIS 能干什么得先说明白它不适合干什么。它适合多个评价对象、多个属性指标、指标可以同时有正向和负向属性需要排出相对次序的场景。它不适合指标之间存在明显关联依赖比如 A 指标完全由 B 指标决定、需要通过历史数据预测未来表现、类别属性需要复杂映射的场景。数据量也不需要太大三五条方案、四五个指标就能跑得很漂亮这是它比层次分析法、模糊综合评价更轻的一个原因。1.3 与熵权法的经典搭配TOPSIS 单独用用的是人为主观赋权真正让它在学术论文和企业实践里出圈的是跟熵权法组合成熵权-TOPSIS。熵权法根据指标的数据离散程度自动算权重——某个指标的数据相差越大说明它携带的区分信息越多权重就越高反之数值都差不多、没什么区分度的指标权重就低。这个组合我后面会给出具体实现代码算是把客观赋权优劣排序一步到位。2. 从数学原理到操作步骤TOPSIS 凭什么能排序2.1 关键定义正理想解与负理想解假设有 m 个评价对象方案、n 个评价指标原始数据构成决策矩阵 X (x_ij)_{m×n}。正理想解 C第 j 个指标的理想值。如果 j 是正向指标越大越好取所有方案中该指标的最大值如果是负向指标越小越好取最小值。负理想解 C-与之相反。正向指标取最小值负向指标取最大值。注意这里说的理想解不一定是现实存在的方案它只是一个虚拟参照点。整个 TOPSIS 的思路就是在多维空间里找到离这个虚拟最优参照点最近的方案。2.2 核心逻辑欧氏距离的空间直觉把每个方案想象成多维空间里的一个点n 个指标就是 n 个维度。正理想解是空间里的一个角落负理想解是另一个角落。某个方案点如果靠近正理想解那个角落同时远离负理想解那个角落它在空间里的位置就是最优的。于是定义两个距离D_i 方案 i 到正理想解的距离D_i- 方案 i 到负理想解的距离然后计算相对贴近度C_i D_i- / (D_i D_i-)C_i 的值越接近 1说明方案离理想解越近、离负理想解越远排名越靠前。这里有个容易被忽略的细节D_i 和 D_i- 之间的尺度关系会直接影响 C_i 的区分度。如果某个方案同时离正理想解和负理想解都很远比如一个明显离群的极端方案它的 C_i 会趋近于 0.5这在排序时会产生中庸假象实际操作中需要留意必要时拉长指标区间或调整归一化方式。2.3 数据预处理为什么要先同向化、再归一化这是 TOPSIS 实操中翻车率最高的步骤没有之一。第一步同向化。因为正理想解的定义依赖正向取最大、负向取最小如果数据里混着正向指标和负向指标计算距离时方向就打架了。最常见的做法是把负向指标取倒数x 1 / x但取倒数有风险如果原始数据里有 0 或者负数取倒数会爆炸或产生符号干扰。更稳的做法是取反向x max(X_j) - x或者用差值平移x 1 / (max(X_j) min(X_j) - x)我个人的习惯是先看数据分布再决定用哪种方式。没有零值、正数区间分布均匀的用倒数法简单直接有零值、负数、或者极差特别大的用极差反向法更稳健。第二步归一化。这一步不是简单把数据压到 [0,1]而是要消除量纲的影响让不同指标可以加权计算距离。TOPSIS 标准流程通常采用向量归一化r_ij x_ij / sqrt(Σ_{k1}^{m} x_kj²)也就是每个元素除以该列所有元素平方和的平方根。算出来的矩阵每一列的平方和等于 1这保证了不同量纲指标的距离计算互不干扰。注意向量归一化和极差归一化min-max 归一化不是一回事。min-max 把数据线性映射到 [0,1]但会改变数据之间的相对距离结构而向量归一化保留了数据在原空间里的方向信息。TOPSIS 原论文用的是向量归一化如果你见到的教程里用的是 min-max严格说那是近似变体排序结果一般差异不大但做学术复现时要留心。2.4 完整计算流程从原始矩阵到排序把 TOPSIS 的主流程拆成清晰的 6 步构建决策矩阵横向是 n 个评价指标纵向是 m 个评价方案填入原始数据。指标同向化把所有负向指标转换为正向指标。向量归一化对同向化后的矩阵进行向量归一化得到规范矩阵 Z。构造加权规范矩阵如果已知各指标权重 w_j用 Z 的每一列乘以对应权重得到 V_ij w_j × Z_ij。确定正负理想解对每个指标列在方案中取最大值构成 V取最小值构成 V-。计算距离与贴近度用欧氏距离公式计算每个方案到 V、V- 的距离再算 C_i D_i- / (D_i D_i-)按 C_i 降序排列。如果没有任何先验权重信息不打算用熵权法步骤 4 可以省略直接把归一化矩阵 Z 当加权矩阵用相当于权重全部为 1。很多教材和论文会有这个细节差异并不影响整体框架。2.5 为什么贴近度用 D-/(D D-) 而不是直接排 D这是很多初学者会问的问题。既然正理想解是最好的为什么不全按 D 从小到大排序原因在于距离的绝对大小受指标量纲和数据分布影响不同方案之间的比较需要标准化。举个例子方案 A 距离正理想解是 0.6距离负理想解是 0.2方案 B 距离正理想解是 0.7距离负理想解是 1.2。如果只看 DA 更好0.6 0.7但用贴近度算A 是 0.25B 是 0.63明显 B 更好。差异出在哪儿出在谁更稳定地靠近理想侧。B 虽然离理想解远但它离负理想解更远得多在空间中对应的位置实际上更优。这个原理用一句话总结TOPSIS 看的是相对位置不是绝对距离。3. 手写 Python 实现可以直接抄走的完整源码3.1 代码设计思路这一版的代码我会保证几点初学者能看懂、专业场景能直接用、代码结构好扩展。依赖只有numpy和pandas没有用 scikit-learn 的现成封装因为 TOPSIS 的步骤太简单自己写反而更灵活也方便你把它嵌进自己的项目。3.2 基础 TOPSIS 实现import numpy as np import pandas as pd def topsis(data, weightsNone, positive_indicatorsNone): TOPSIS 综合评价模型 参数: data: DataFrame, 每行是一个方案, 每列是一个指标 weights: list, 各指标的权重, 默认None表示等权重 positive_indicators: list, 指标是否为正向(越大越好), True表示正向, False表示负向, 默认全部按正向处理 返回: DataFrame, 包含原始数据、D, D-, 贴近度C和排序 # 复制数据防止修改原表 df data.copy() m, n df.shape # 默认全为正向指标 if positive_indicators is None: positive_indicators [True] * n # 默认等权重 if weights is None: weights [1 / n] * n # 步骤1: 指标同向化 for j in range(n): if not positive_indicators[j]: col_min df.iloc[:, j].min() col_max df.iloc[:, j].max() # 用极差反向: 新值 max - 原值 df.iloc[:, j] col_max - df.iloc[:, j] # 步骤2: 向量归一化 norm_matrix np.zeros((m, n)) for j in range(n): col df.iloc[:, j].values norm_matrix[:, j] col / np.sqrt(np.sum(col ** 2)) # 步骤3: 加权 weight_array np.array(weights) weighted_matrix norm_matrix * weight_array # 步骤4: 正负理想解 ideal_best weighted_matrix.max(axis0) # 正理想解 ideal_worst weighted_matrix.min(axis0) # 负理想解 # 步骤5: 计算距离 d_plus np.sqrt(((weighted_matrix - ideal_best) ** 2).sum(axis1)) d_minus np.sqrt(((weighted_matrix - ideal_worst) ** 2).sum(axis1)) # 步骤6: 贴近度 c_score d_minus / (d_plus d_minus) # 组装结果 result df.copy() result[D] d_plus result[D-] d_minus result[C] c_score result[排名] c_score.argsort()[::-1].argsort() 1 return result if __name__ __main__: # 示例数据: 3个方案, 4个指标 demo_data pd.DataFrame({ 指标1(正向): [8, 6, 7], 指标2(负向): [3, 5, 2], 指标3(正向): [90, 85, 88], 指标4(正向): [0.7, 0.6, 0.8] }) result topsis( demo_data, weights[0.25, 0.25, 0.25, 0.25], positive_indicators[True, False, True, True] ) print(result)这段代码跑出来的结果里C 列就是相对贴近度排名 1 的就是综合评价最优方案。直接拿去用改一下 DataFrame 和指标方向列表就可以了。3.3 结合熵权法的完整实现如果不打算主观赋权就用熵权法自动算权重然后喂给 TOPSIS。下面这段代码把熵权法也封装好了适合不想手动定权重的场景。def entropy_weight(data, positive_indicatorsNone): 熵权法计算指标权重 参数: data: DataFrame, 每行一个方案, 每列一个指标 positive_indicators: list, 指标方向, True为正向, False为负向 返回: weights: numpy array, 归一化后的权重 df data.copy() m, n df.shape if positive_indicators is None: positive_indicators [True] * n # 同向化处理与TOPSIS保持一致 for j in range(n): if not positive_indicators[j]: col_max df.iloc[:, j].max() df.iloc[:, j] col_max - df.iloc[:, j] # 极差归一化到 [0, 1] norm_df pd.DataFrame(indexdf.index, columnsdf.columns) for j in range(n): col df.iloc[:, j] col_min col.min() col_max col.max() span col_max - col_min # 若某列数值完全相同, 特征值为0, 做平滑处理 if span 0: norm_df.iloc[:, j] 1.0 else: norm_df.iloc[:, j] (col - col_min) / span # 计算比重 p_ij p norm_df.values / norm_df.values.sum(axis0, keepdimsTrue) # 计算熵值 e_j k 1.0 / np.log(m) e_j -k * np.sum(p * np.log(p 1e-12), axis0) # 计算差异系数 d_j 1 - e_j # 归一化权重 weights d_j / d_j.sum() return weights def entropy_topsis(data, positive_indicatorsNone): 熵权法确定权重后的TOPSIS综合评价 weights entropy_weight(data, positive_indicators) result topsis(data, weightsweights, positive_indicatorspositive_indicators) # 把权重附到结果里方便查看 weight_df pd.DataFrame([weights], columnsdata.columns, index[权重]) print(熵权法权重: ) print(weight_df) return result提示熵权法计算的权重反映的是数据的区分度不是指标的重要性。如果某列数据几乎没变化熵值接近 1权重会被压得很低这符合逻辑——它本来就分不出好坏给再高权重也没意义。3.4 代码运行结果解读拿上面的示例数据跑完输出大概是这样的指标1(正向)指标2(负向)指标3(正向)指标4(正向)DD-C排名83900.70.0230.0490.679165850.60.0580.0080.118372880.80.0420.0350.4572方案 1 排名最高它的弱项指标2是负向但值为3在同向化后处于中游被强项指标1、指标3、指标4都接近该列最大值撑住了方案 2 各项都不突出贴近度被拉得很低。这个结果和人工判断基本一致。4. 避坑指南数据预处理和权重确定中的常见坑4.1 负向指标同向化方法选错这是我在帮朋友改论文时看到最多的错误。很多教程默认负向指标用倒数变换1/x但拿到数据不分青红皂白就用结果数据里有 01/0 直接无穷大计算崩溃。数据里有负数倒数之后方向直接反了。数据量级差距过大比如成本和收益差三个数量级取倒数后数值分布极度扭曲权重和距离计算全部失真。我的建议默认用极差反向max - x它对 0 和负数免疫且不会改变数据的线性结构。只有当指标严格为正、且数据分布比较均匀时才考虑用倒数法因为倒数法能更显著地把小值放大在某些经济指标里反而更好用。4.2 权重归一化顺序问题TOPSIS 的教科书步骤是先归一化数据再乘权重。但如果你用的是熵权法请不要先用自己的归一化方案处理完数据再扔给熵权法。因为熵权法内部有自己的一套归一化极差归一化到 [0,1]如果你在外面先向量归一化熵权法再归一化一次相当于多叠了一层映射算出来的熵值就变了权重也会跟着变。正确做法是原始数据 → 同向化 → 极差归一化熵权法→ 得到权重 → 用原始数据走 TOPSIS向量归一化 加权。两份代码独立处理不要混用预处理逻辑。4.3 贴近度等于 0.5 的平局陷阱当某个方案的 D 和 D- 都很大、或都接近 0 时C 会趋近 0.5。比如两个维度方向相反一个离正负理想解都非常远算出来 C ≈ 0.5看起来像个中等水平可能掩盖了它极端的属性。应对办法不要只看 C 值排序同时打印出 D 和 D- 的列观察每个方案的极端程度。如果出现大量 C 值都集中在 0.45-0.55 区间说明方案之间的差异不大或者指标选取维度本身区分度不够这时候要回头审视指标体系而不是硬调参数。4.4 缺失值与异常值的处理TOPSIS 对数据质量的要求其实不低。缺失值会直接让归一化公式失效异常值会严重拉偏理想解的位置理想解是各列最大值/最小值一个极端点就可能把整个参照系带偏。处理建议缺失值用中位数填充不要用均值。中位数对偏态分布更稳健。异常值先用箱线图或 z-score 检出来如果是录入错误就修正如果是真实数据里的极端情况可以考虑用 Winsorize缩尾处理把极端值拉回 95% 分位数避免它们主导整个理想解。5. 一个完整的应用实例供应商选择评估理论说了一堆不如跑一个真实案例。假设某制造企业要从 5 家供应商里选一家评价指标有四个价格万元负向指标越低越好质量合格率%正向指标交货准时率%正向指标售后响应时间小时负向指标5.1 数据准备供应商价格(万元)质量合格率(%)交货准时率(%)售后响应(小时)A8098.295.44.5B8599.193.83.2C7897.596.15.0D8298.894.54.0E9098.092.62.85.2 调用代码suppliers pd.DataFrame({ 价格: [80, 85, 78, 82, 90], 质量合格率: [98.2, 99.1, 97.5, 98.8, 98.0], 交货准时率: [95.4, 93.8, 96.1, 94.5, 92.6], 售后响应: [4.5, 3.2, 5.0, 4.0, 2.8] }) result_supplier entropy_topsis( suppliers, positive_indicators[False, True, True, False] ) print(result_supplier.sort_values(C, ascendingFalse))5.3 结果解读与决策建议运行结果大致会是这样的排序逻辑成本维度C 的价格最低但质量合格率也最低E 的价格最高但售后响应最快。平衡维度B 和 D 在四维空间里更靠近正理想解。最终可能 B 的综合贴近度最高因为它虽然价格不是最低但质量合格率最高、售后响应也够快E 虽然售后最好但价格太贵、交货准时率垫底把整体排名拉下去了。这个结论可以非常直观地讲给采购部门听B 不是单项冠军但它是综合离理想方案最近的选手。实际决策时我还会结合业务约束再做一层筛选比如价格必须低于 83 万那就先把不符合硬约束的供应商删掉再跑 TOPSIS否则模型会把不满足硬约束的方案算进排序里干扰参照系。6. 进阶讨论TOPSIS 的局限和扩展方向6.1 线性加权范围的局限TOPSIS 本质上假设了指标之间是可补偿的——一个指标的劣势可以用另一个指标的优勢来弥补。这在很多现实场景里并不成立比如安全指标不合格其他指标再好也不能用。解决思路是先用一票否决机制做前置过滤再在合格方案里跑 TOPSIS这也是我上面供应商案例里说的做法。6.2 组合赋权问题熵权法是纯客观赋权有的场景下算出的权重会不符合业务直觉比如某个指标数据区分度大、但业务上其实不重要熵权法却给了高权重。更稳妥的方式是组合赋权主观权重专家打分或 AHP 层次分析法和客观权重熵权法各占一定比例加权合成最终权重。组合公式很简单w_j α × w_subjective_j (1 - α) × w_objective_jα 取 0.4~0.6 是常见选择具体看业务方对主观经验的信任程度。6.3 指标数量与样本数量的比例当指标数量 n 接近方案数量 m 时TOPSIS 的排序结果会变得很敏感——维度太高距离度量容易被维度灾难影响任意两个点之间的距离都趋于相等。我个人经验是不太会在 m 小于 6 时用超过 8 个指标。如果指标实在太多先用 PCA 或相关性分析筛掉冗余指标再跑 TOPSIS。6.4 可视化辅助分析TOPSIS 的结果完全可以做成图让领导一眼看懂。最常见的图是二维散点图横轴 D离正理想解的距离纵轴 D-离负理想解的距离每个方案是一个点。右下角的点D 小、D- 大就是综合最优的区域左上角是综合最差。用 matplotlib 画一张这样的散点图汇报效果远好过一堆表格数字。import matplotlib.pyplot as plt plt.figure(figsize(8, 6)) plt.scatter(result_supplier[D], result_supplier[D-], s80) for i, (x, y) in enumerate(zip(result_supplier[D], result_supplier[D-])): plt.text(x 0.001, y 0.001, f供应商{chr(65i)}, fontsize10) plt.xlabel(距离正理想解 D) plt.ylabel(距离负理想解 D-) plt.title(TOPSIS 供应商评估散点图) plt.axhline(y0, colorgray, linestyle--, linewidth0.5) plt.axvline(x0, colorgray, linestyle--, linewidth0.5) plt.grid(alpha0.3) plt.show()这类图我基本每个分析报告里都会带一张因为它能把排序结果和为什么是这个排序一次说清楚。7. 个人实操经验总结TOPSIS 模型整体不难真正让结果出现分歧的往往不是你写公式的手艺而是数据预处理和权重确定这两个环节的选择。我在实际项目里积累了几条经验分享给大家参考。第一所有指标同向化之前务必画一遍箱线图看清每个指标的分布、量级、有没有离群点和零值再来决定用倒数法还是极差反向法。这一步省不了省了后面的结果就很难解释。第二任何时候都要保留一份人工可解释的中间结果。TOPSIS 的优点是好解释但如果只给对方看一个排名对方还是会怀疑。我在交付报告时通常会附上原始数据表、同向化后的数据表、归一化加权后的数据表、D、D-、C 值、排名总共六张表。对方拿着表一步一步核对信任就这么建立起来的。第三熵权法不是万能的。它适合指标之间独立性较强的场景。如果指标强相关比如服务满意度和复购意愿熵权法会把这两个指标当成两个独立信息源导致这类指标的整体权重被人为抬高。用熵权法之前先看一遍指标相关矩阵把相关性超过 0.8 的指标挑出来二选一或合并成一个综合指标。第四也是我踩过最多的坑方案的增减会改变整个参照系所以 TOPSIS 的排名不是绝对的。你增加一个方案进去正负理想解可能变化原来的第一名可能变成第三名。做多轮对比时要么明确告诉业务方这是当前候选集内的相对排序要么就固定住一个标杆方案集比如把所有历史方案都作为参照系再计算避免每次决策都因候选方案集变动而导致结论不稳定。如果你刚开始接触 TOPSIS建议先拿一份真实业务数据按我这篇的步骤从基础版跑通再逐步换成熵权法组合版。模型本身不复杂真正值钱的是你对数据的理解和对结果的解释能力。本文还有配套的精品资源点击获取