用Python手把手教你实现TOPSIS算法:从数据预处理到结果排序的完整流程 用Python手把手教你实现TOPSIS算法从数据预处理到结果排序的完整流程当我们需要在多个候选方案中做出选择时TOPSISTechnique for Order Preference by Similarity to Ideal Solution算法提供了一种科学而直观的决策方法。想象一下你正在评估几家供应商或者需要为团队选择最佳的项目方案每个选项都有多个维度的评价指标——这正是TOPSIS大显身手的场景。与那些停留在理论层面的教程不同本文将带你用Python从零开始实现完整的TOPSIS流程。我们会使用Pandas处理真实业务数据用Numpy进行高效向量化计算最终封装成可复用的Python函数。特别地我会分享在实际项目中遇到的坑——比如如何处理缺失值、为什么某些归一化方法会导致结果失真以及如何用向量化运算提升10倍性能。1. 理解TOPSIS从业务场景到数学原理TOPSIS的核心思想非常直观找出距离理想解最近且距离负理想解最远的方案。这里的理想解指的是每个指标都达到最佳值的虚拟方案而负理想解则是每个指标都是最差值的虚拟方案。典型应用场景包括供应商评估价格、交货期、质量评分投资项目选择收益率、风险等级、流动性人才选拔技能评分、工作经验、面试表现让我们用一个具体的例子来说明。假设我们需要评估5个软件开发团队考虑三个指标代码质量评分越高越好平均交付时间越短越好每行代码成本越低越好import pandas as pd teams pd.DataFrame({ 团队: [A, B, C, D, E], 代码质量: [85, 70, 90, 60, 80], 交付时间: [10, 15, 8, 20, 12], 代码成本: [0.5, 0.3, 0.6, 0.2, 0.4] })2. 数据预处理标准化与权重分配数据预处理是TOPSIS中最容易出错的环节。不同的指标往往有不同的量纲单位和变化范围直接计算会导致量纲大的指标主导结果。我们需要先将所有指标规范化到统一尺度。2.1 数据标准化方法对比方法公式适用场景优点缺点向量归一化$b_{ij} a_{ij}/\sqrt{\sum a_{ij}^2}$大多数TOPSIS应用保持相对关系结果不在[0,1]区间线性比例变换$b_{ij} a_{ij}/\max(a_j)$效益型指标直观易理解对异常值敏感极差变换$(a_{ij}-\min(a_j))/(\max(a_j)-\min(a_j))$需要严格[0,1]范围结果标准化丢失原始比例关系def normalize(df, criteria_types): 向量归一化标准化 :param df: 原始数据DataFrame :param criteria_types: 字典指定每个指标是效益型(1)还是成本型(-1) :return: 标准化后的DataFrame normalized df.copy() for col in criteria_types: if criteria_types[col] 1: # 效益型 normalized[col] df[col] / np.sqrt((df[col]**2).sum()) else: # 成本型 normalized[col] df[col] / np.sqrt((df[col]**2).sum()) normalized[col] 1 - normalized[col] # 成本型取反 return normalized2.2 权重分配的艺术权重反映了各指标的重要性差异。确定权重的方法包括主观赋权法专家打分、AHP层次分析法客观赋权法熵权法、CRITIC法组合赋权主客观结合提示权重分配是TOPSIS中最具主观性的环节建议通过敏感性分析验证权重变化的鲁棒性。3. 核心计算寻找理想解与距离度量标准化和加权后我们进入TOPSIS的核心计算阶段。这一部分将展示如何用Numpy高效实现。3.1 确定正负理想解def get_ideal_solutions(normalized_df, criteria_types): 获取正理想解和负理想解 :param normalized_df: 标准化后的DataFrame :param criteria_types: 指标类型字典 :return: 正理想解, 负理想解 positive_ideal [] negative_ideal [] for col in criteria_types: if criteria_types[col] 1: # 效益型 positive_ideal.append(normalized_df[col].max()) negative_ideal.append(normalized_df[col].min()) else: # 成本型 positive_ideal.append(normalized_df[col].min()) negative_ideal.append(normalized_df[col].max()) return np.array(positive_ideal), np.array(negative_ideal)3.2 欧氏距离计算优化传统实现可能使用循环计算每个方案到理想解的距离但在Python中我们可以利用Numpy的广播机制实现向量化运算性能提升显著def calculate_distances(normalized_df, positive_ideal, negative_ideal): 向量化计算距离 :param normalized_df: 标准化后的DataFrame :param positive_ideal: 正理想解 :param negative_ideal: 负理想解 :return: 到正理想解的距离, 到负理想解的距离 values normalized_df.drop(columns[团队]).values pos_dist np.sqrt(((values - positive_ideal)**2).sum(axis1)) neg_dist np.sqrt(((values - negative_ideal)**2).sum(axis1)) return pos_dist, neg_dist4. 结果分析与可视化计算出各方案到理想解的距离后我们可以计算相对贴近度并排序def calculate_ranking(pos_dist, neg_dist): 计算相对贴近度并排序 :param pos_dist: 到正理想解的距离数组 :param neg_dist: 到负理想解的距离数组 :return: 排序后的索引(从优到劣) closeness neg_dist / (pos_dist neg_dist) return np.argsort(-closeness) # 降序排列结果可视化建议雷达图展示各方案指标对比二维散点图正理想距离 vs 负理想距离排序柱状图import matplotlib.pyplot as plt def plot_results(df, closeness, ranking): plt.figure(figsize(10, 5)) plt.barh(df[团队][ranking], closeness[ranking], colorskyblue) plt.xlabel(相对贴近度) plt.title(TOPSIS评估结果排序) plt.gca().invert_yaxis() # 最佳方案显示在顶部 plt.show()5. 工程实践封装完整TOPSIS类将上述步骤封装成可复用的Python类方便在不同项目中调用class TOPSIS: def __init__(self, data, criteria_types, weightsNone): 初始化TOPSIS评估器 :param data: 包含方案名称和指标值的DataFrame :param criteria_types: 字典指定每个指标是效益型(1)还是成本型(-1) :param weights: 各指标权重默认为等权重 self.original_data data self.criteria_types criteria_types self.weights weights if weights else np.ones(len(criteria_types))/len(criteria_types) self.normalized_data None self.positive_ideal None self.negative_ideal None self.pos_distances None self.neg_distances None self.closeness None self.ranking None def normalize(self): 数据标准化 self.normalized_data self.original_data.copy() for i, col in enumerate(self.criteria_types): norm np.sqrt((self.original_data[col]**2).sum()) if self.criteria_types[col] 1: # 效益型 self.normalized_data[col] self.original_data[col] / norm else: # 成本型 self.normalized_data[col] (1 - self.original_data[col]/norm) def calculate_ideal_solutions(self): 计算理想解 values self.normalized_data.drop(columns[团队]).values self.positive_ideal [] self.negative_ideal [] for i, col in enumerate(self.criteria_types): if self.criteria_types[col] 1: # 效益型 self.positive_ideal.append(values[:, i].max()) self.negative_ideal.append(values[:, i].min()) else: # 成本型 self.positive_ideal.append(values[:, i].min()) self.negative_ideal.append(values[:, i].max()) self.positive_ideal np.array(self.positive_ideal) * self.weights self.negative_ideal np.array(self.negative_ideal) * self.weights def evaluate(self): 执行完整评估流程 self.normalize() self.calculate_ideal_solutions() # 加权标准化矩阵 weighted_matrix self.normalized_data.drop(columns[团队]).values * self.weights # 计算距离 self.pos_distances np.sqrt(((weighted_matrix - self.positive_ideal)**2).sum(axis1)) self.neg_distances np.sqrt(((weighted_matrix - self.negative_ideal)**2).sum(axis1)) # 计算相对贴近度 self.closeness self.neg_distances / (self.pos_distances self.neg_distances) self.ranking np.argsort(-self.closeness) return { closeness: self.closeness, ranking: self.ranking, normalized_data: self.normalized_data }6. 实战案例供应商选择系统让我们用一个完整的案例演示如何使用封装的TOPSIS类解决实际问题。假设我们需要从6家供应商中选择最佳合作伙伴评估指标包括产品单价成本型交货准时率效益型质量合格率效益型售后服务评分效益型# 准备数据 suppliers pd.DataFrame({ 供应商: [S1, S2, S3, S4, S5, S6], 单价: [85, 70, 90, 60, 80, 75], 准时率: [0.95, 0.85, 0.92, 0.88, 0.90, 0.82], 合格率: [0.98, 0.97, 0.99, 0.96, 0.95, 0.94], 服务评分: [4.2, 3.8, 4.5, 3.9, 4.1, 3.7] }) # 定义指标类型和权重 criteria { 单价: -1, # 成本型 准时率: 1, 合格率: 1, 服务评分: 1 } weights [0.3, 0.25, 0.25, 0.2] # 总和不一定要等于1会自动归一化 # 执行TOPSIS评估 topsis TOPSIS(suppliers, criteria, weights) results topsis.evaluate() # 输出结果 result_df suppliers.copy() result_df[相对贴近度] results[closeness] result_df[排名] results[ranking] 1 # 转为1-based print(result_df.sort_values(排名))常见问题解决方案缺失值处理删除包含缺失值的方案样本量足够时用列均值或中位数填充使用KNN等算法预测缺失值异常值处理Winsorize处理缩尾用IQR方法识别并处理异常值考虑使用更鲁棒的距离度量如曼哈顿距离敏感性分析对权重进行±10%的扰动观察排名变化使用蒙特卡洛模拟评估不同权重组合下的结果稳定性注意当两个方案的相对贴近度非常接近时如差值0.01在实际应用中可视为同等优秀不必强行区分。