别再只会Excel排序了!用Python手把手教你实现TOPSIS综合评价(附完整代码与数据)

张开发
2026/6/22 9:50:36 15 分钟阅读
别再只会Excel排序了!用Python手把手教你实现TOPSIS综合评价(附完整代码与数据)
用Python实现TOPSIS算法从Excel表格到智能决策的跨越当我们面对包含多个评价指标的数据集时如何做出客观、量化的综合评估传统Excel操作往往需要手动计算权重、标准化数据不仅效率低下还容易出错。这就是TOPSIS优劣解距离法大显身手的地方——它能够自动计算每个方案与理想解的相对接近程度给出科学排序。而Python的NumPy和Pandas库能让这个过程变得前所未有的简单。1. TOPSIS算法核心原理TOPSIS全称Technique for Order Preference by Similarity to Ideal Solution是一种经典的多属性决策方法。它的核心思想很直观最佳方案应该距离理想解最近同时距离负理想解最远。就像选择租房位置时我们既希望离工作地点近正向理想又希望远离嘈杂区域负向理想。算法主要流程分为四个关键步骤原始矩阵正向化将所有指标统一转换为极大型指标越大越好数据标准化消除不同指标量纲单位的影响计算距离得分测量每个方案与正/负理想解的距离结果归一化将得分转换为0-1之间的可比数值注意TOPSIS假设各指标权重相同。如需考虑权重差异可在距离计算环节引入权重系数这可以通过层次分析法或熵权法确定。2. 数据准备与正向化处理让我们从一个实际案例开始评估四位员工的综合表现考虑两个指标——工作成绩越高越好和客户投诉次数越低越好。import pandas as pd # 创建示例数据 data { 姓名: [小明, 小王, 小张, 小李], 成绩: [89, 60, 74, 99], # 极大型指标 投诉次数: [2, 3, 2, 0] # 极小型指标 } df pd.DataFrame(data) print(原始数据) print(df)2.1 指标类型识别与转换不同指标需要不同的正向化方法指标类型特点示例转换方法极大型数值越大越好销售额、满意度无需转换极小型数值越小越好成本、缺陷率max-x 或 1/x中间型接近某值最佳pH值、温度1-∣x-x_best∣/M区间型落在区间内最佳湿度、心率分段函数转换对于我们的数据需要将投诉次数这个极小型指标转换为极大型# 极小型转极大型 df[投诉正向化] df[投诉次数].max() - df[投诉次数] print(\n正向化处理后) print(df[[姓名, 成绩, 投诉正向化]])3. 数据标准化与距离计算标准化是为了消除不同指标量纲的影响使各指标具有可比性。最常用的方法是向量归一化import numpy as np # 提取需要标准化的列 cols_to_normalize [成绩, 投诉正向化] X df[cols_to_normalize].values # 标准化公式每个元素/√(该列各元素平方和) Z X / np.sqrt(np.sum(X**2, axis0)) print(\n标准化矩阵Z) print(Z)标准化后我们可以定义正理想解Z和负理想解Z-# 正理想解各指标最大值 Z_plus np.max(Z, axis0) # 负理想解各指标最小值 Z_minus np.min(Z, axis0) print(f\n正理想解{Z_plus}) print(f负理想解{Z_minus})接下来计算每个方案与这两个理想解的欧氏距离# 计算距离 D_plus np.sqrt(np.sum((Z - Z_plus)**2, axis1)) D_minus np.sqrt(np.sum((Z - Z_minus)**2, axis1)) print(\n各方案距离) print(f与正理想解距离{D_plus}) print(f与负理想解距离{D_minus})4. 综合得分计算与结果分析最后我们计算每个方案的相对接近度得分# 计算未归一化得分 S D_minus / (D_plus D_minus) # 归一化处理 S_normalized S / S.sum() df[得分] S_normalized df[排名] df[得分].rank(ascendingFalse) print(\n最终结果) print(df.sort_values(排名))为了更直观地理解我们可以将结果可视化import matplotlib.pyplot as plt plt.figure(figsize(10, 5)) df.set_index(姓名)[得分].sort_values().plot(kindbarh, colorskyblue) plt.title(TOPSIS综合评分结果) plt.xlabel(归一化得分) plt.grid(axisx, linestyle--, alpha0.7) plt.show()5. 工程化实践封装可复用TOPSIS类为了让代码更具复用性我们可以将整个流程封装成一个类class TOPSIS: def __init__(self, data, benefit_columnsNone): self.data data.copy() self.benefit_columns benefit_columns or [] def normalize(self, matrix): return matrix / np.sqrt(np.sum(matrix**2, axis0)) def calculate_distances(self, Z): self.Z_plus np.max(Z, axis0) self.Z_minus np.min(Z, axis0) D_plus np.sqrt(np.sum((Z - self.Z_plus)**2, axis1)) D_minus np.sqrt(np.sum((Z - self.Z_minus)**2, axis1)) return D_plus, D_minus def evaluate(self, columnsNone, weightsNone): if columns is None: columns self.data.select_dtypes(includenp.number).columns.tolist() X self.data[columns].values if weights is not None: X X * np.array(weights) Z self.normalize(X) D_plus, D_minus self.calculate_distances(Z) scores D_minus / (D_plus D_minus) return scores / scores.sum() def analyze(self, target_column, benefit_columnsNone): benefit_columns benefit_columns or self.benefit_columns scores self.evaluate(benefit_columns) result self.data.copy() result[TOPSIS得分] scores result[排名] result[TOPSIS得分].rank(ascendingFalse) return result.sort_values(排名)使用这个类处理我们的数据# 初始化TOPSIS处理器 topsis TOPSIS(df, benefit_columns[成绩, 投诉正向化]) result topsis.analyze(姓名) print(\n封装后的分析结果) print(result)6. 进阶应用权重调整与敏感性分析实际应用中不同指标的重要性往往不同。我们可以通过权重参数来反映这种差异# 假设成绩比投诉重要3倍 weights [0.75, 0.25] # 权重总和应为1 # 带权重的评估 weighted_scores topsis.evaluate(weightsweights) df[加权得分] weighted_scores / weighted_scores.sum() df[加权排名] df[加权得分].rank(ascendingFalse) print(\n加权后的结果) print(df.sort_values(加权排名))为了理解权重变化如何影响结果我们可以进行敏感性分析# 测试不同权重组合的影响 weight_combinations [ [0.5, 0.5], # 同等重要 [0.6, 0.4], # 成绩稍重要 [0.7, 0.3], # 成绩更重要 [0.8, 0.2] # 成绩非常重要 ] results [] for w in weight_combinations: scores topsis.evaluate(weightsw) rank pd.Series(scores).rank(ascendingFalse) results.append({ 权重组合: str(w), 第一名: df.loc[rank.idxmin(), 姓名], 最后一名: df.loc[rank.idxmax(), 姓名] }) pd.DataFrame(results)7. 与Excel方案的对比优势传统Excel操作需要手动完成以下步骤编写公式转换指标类型创建标准化计算列设置距离计算区域构建得分排名表而Python方案的优势显而易见对比维度Excel方案Python方案执行效率手动操作耗时一键运行毫秒级响应可复用性每次需重新设置公式封装成类/函数随时调用数据规模受限于表格性能可处理百万级数据权重调整需手动修改多个公式参数化调整即时生效可视化基础图表手动更新动态交互自动刷新版本控制难以追踪变更代码管理清晰记录特别是在需要频繁更新数据或调整评价标准时Python的自动化优势更加明显。例如当新增员工数据时# 新增员工数据 new_employee {姓名: 小周, 成绩: 85, 投诉次数: 1} df df.append(new_employee, ignore_indexTrue) # 自动重新计算 topsis TOPSIS(df, benefit_columns[成绩, 投诉正向化]) updated_result topsis.analyze(姓名) print(\n更新后的分析结果) print(updated_result)8. 常见问题与调试技巧在实际应用中可能会遇到以下典型问题NaN值处理数据中包含缺失值时需要先进行填充或删除df.fillna(df.mean(), inplaceTrue) # 用均值填充数值型缺失值指标方向判断错误确保正确识别每个指标的类型# 验证指标方向 print(成绩越大越好, df[成绩].max() Z_plus[0]) print(投诉越小越好, df[投诉次数].min() df[投诉次数].max() - Z_plus[1])权重设置不合理检查权重总和是否为1且均为正数assert sum(weights) 1, 权重总和必须为1 assert all(w 0 for w in weights), 权重不能为负数结果反直觉检查数据标准化和距离计算过程# 标准化检查每列平方和应为1 print(标准化验证, np.sum(Z**2, axis0))数据量纲差异过大确保标准化步骤正确执行# 标准化前各列幅度对比 print(标准化前数据范围, np.ptp(X, axis0))当结果出现异常时建议按照以下流程排查检查原始数据质量缺失值、异常值验证指标正向化是否正确确认标准化计算无误检查距离公式实现验证得分计算公式9. 扩展应用场景TOPSIS结合Python的强大生态可以应用于更多复杂场景场景一动态权重评估from sklearn.preprocessing import MinMaxScaler # 使用熵权法自动确定权重 def entropy_weight(X): # 数据归一化 X_norm MinMaxScaler().fit_transform(X) # 计算熵值 epsilon 1e-12 # 避免log(0) p X_norm / (np.sum(X_norm, axis0) epsilon) e -np.sum(p * np.log(p epsilon), axis0) / np.log(len(X)) # 计算权重 return (1 - e) / np.sum(1 - e) weights entropy_weight(df[[成绩, 投诉正向化]].values) print(\n熵权法计算的权重, weights)场景二大规模数据并行计算from multiprocessing import Pool def parallel_topsis(args): data_chunk, weights args topsis TOPSIS(data_chunk) return topsis.evaluate(weightsweights) # 分块处理大数据 data_chunks np.array_split(df, 4) # 分为4块 with Pool() as pool: results pool.map(parallel_topsis, [(chunk, weights) for chunk in data_chunks]) final_scores np.concatenate(results)场景三与机器学习管道集成from sklearn.pipeline import Pipeline from sklearn.base import BaseEstimator, TransformerMixin class TOPSISTransformer(BaseEstimator, TransformerMixin): def __init__(self, benefit_columnsNone): self.benefit_columns benefit_columns def fit(self, X, yNone): return self def transform(self, X): topsis TOPSIS(X, self.benefit_columns) scores topsis.evaluate() return scores.reshape(-1, 1) # 创建评估管道 pipeline Pipeline([ (topsis, TOPSISTransformer(benefit_columns[成绩, 投诉正向化])), # 可以添加其他处理步骤 ])TOPSIS算法在以下领域都有成功应用案例供应商评估与选择投资项目决策人才综合评价产品设计方案优选城市宜居性排名医疗方案选择10. 性能优化与最佳实践对于大型数据集我们可以采用以下优化策略向量化计算避免循环使用NumPy的广播机制# 非优化版本使用循环 D_plus_slow np.array([ np.sqrt(np.sum((Z[i] - Z_plus)**2)) for i in range(len(Z)) ]) # 优化版本向量化 D_plus_fast np.sqrt(np.sum((Z - Z_plus)**2, axis1))内存优化处理超大矩阵时使用分块计算def chunked_distance(Z, reference, chunk_size1000): distances [] for i in range(0, len(Z), chunk_size): chunk Z[i:ichunk_size] dist np.sqrt(np.sum((chunk - reference)**2, axis1)) distances.append(dist) return np.concatenate(distances)缓存中间结果避免重复计算from functools import lru_cache lru_cache(maxsizeNone) def cached_normalize(matrix_tuple): # 注意输入需要转为可哈希类型 matrix np.array(matrix_tuple) return matrix / np.sqrt(np.sum(matrix**2, axis0))使用更快的数学库# 使用NumExpr加速计算 import numexpr as ne def fast_distance(Z, reference): diff Z - reference return ne.evaluate(sqrt(sum(diff**2, axis1)))GPU加速对于超大规模数据import cupy as cp # 需要NVIDIA GPU def gpu_topsis(X): X_gpu cp.array(X) Z_gpu X_gpu / cp.sqrt(cp.sum(X_gpu**2, axis0)) Z_plus cp.max(Z_gpu, axis0) Z_minus cp.min(Z_gpu, axis0) D_plus cp.sqrt(cp.sum((Z_gpu - Z_plus)**2, axis1)) D_minus cp.sqrt(cp.sum((Z_gpu - Z_minus)**2, axis1)) return (D_minus / (D_plus D_minus)).get() # 转回CPU在实际项目中建议遵循以下最佳实践为TOPSIS类编写单元测试使用类型提示提高代码可读性添加详细的文档字符串实现日志记录功能提供示例数据集和演示代码支持多种输入格式CSV、Excel、数据库等

更多文章