用Python手把手教你实现TOPSIS算法:从Excel数据导入到结果可视化
用Python手把手教你实现TOPSIS算法从Excel数据导入到结果可视化在数据分析与决策支持领域TOPSISTechnique for Order Preference by Similarity to Ideal Solution是一种经典的多准则决策方法。它通过计算各方案与理想解的相对接近度来进行排序广泛应用于供应商评估、投资决策、项目优选等场景。本文将使用Python生态中的pandas、numpy和matplotlib库完整演示如何从Excel读取数据到最终可视化呈现的全流程实现。1. 环境准备与数据加载首先确保已安装必要的Python库。在命令行中执行以下命令pip install pandas numpy matplotlib openpyxlopenpyxl是专门用于处理Excel文件的引擎。假设我们有一个供应商评估表supplier_evaluation.xlsx包含以下字段供应商编号唯一标识产品质量评分效益型指标越高越好交货周期成本型指标越短越好价格成本型指标售后服务评分效益型指标使用pandas加载数据import pandas as pd # 读取Excel文件 df pd.read_excel(supplier_evaluation.xlsx, engineopenpyxl) print(df.head())典型的数据预处理包括检查缺失值df.isnull().sum()处理异常值通过分位数或3σ原则数据类型转换确保数值列正确解析2. 数据规范化处理TOPSIS的核心是对不同量纲的指标进行标准化。我们采用向量归一化方法import numpy as np def normalize_matrix(df, benefit_columns, cost_columns): 向量规范化处理 norm_df df.copy() for col in df.columns[1:]: # 跳过第一列ID if col in benefit_columns: norm_df[col] df[col] / np.sqrt(np.sum(df[col]**2)) elif col in cost_columns: norm_df[col] 1 - (df[col] / np.sqrt(np.sum(df[col]**2))) return norm_df benefit_cols [产品质量评分, 售后服务评分] cost_cols [交货周期, 价格] normalized_df normalize_matrix(df, benefit_cols, cost_cols)指标类型说明表指标类型特点处理方式效益型越大越好直接向量归一化成本型越小越好1-归一化值区间型最佳区间梯形函数变换3. 加权规范化矩阵构建不同指标的重要性通常不同需要赋予权重。权重确定方法包括AHP层次分析法熵权法专家打分法假设我们通过专家评估得到权重向量weights { 产品质量评分: 0.3, 交货周期: 0.25, 价格: 0.3, 售后服务评分: 0.15 } # 构建加权矩阵 weighted_matrix normalized_df.copy() for col in weights: weighted_matrix[col] normalized_df[col] * weights[col]权重分配原则各权重值在0-1之间所有权重之和为1重要指标赋予更高权重4. 确定理想解与距离计算正理想解是各指标最优值的集合负理想解是最劣值集合def get_ideal_solutions(df, benefit_columns): 获取正负理想解 positive_ideal [] negative_ideal [] for col in df.columns[1:]: if col in benefit_columns: positive_ideal.append(df[col].max()) negative_ideal.append(df[col].min()) else: positive_ideal.append(df[col].min()) negative_ideal.append(df[col].max()) return np.array(positive_ideal), np.array(negative_ideal) pos_ideal, neg_ideal get_ideal_solutions(weighted_matrix, benefit_cols)计算各方案到理想解的欧氏距离def calculate_distances(df, pos_ideal, neg_ideal): 计算距离指标 distances [] for _, row in df.iterrows(): values row.values[1:] # 跳过ID列 # 计算到正理想解的距离 pos_dist np.sqrt(np.sum((values - pos_ideal)**2)) # 计算到负理想解的距离 neg_dist np.sqrt(np.sum((values - neg_ideal)**2)) # 计算相对贴近度 closeness neg_dist / (pos_dist neg_dist) distances.append({ ID: row[0], Pos_Distance: pos_dist, Neg_Distance: neg_dist, Closeness: closeness }) return pd.DataFrame(distances) result_df calculate_distances(weighted_matrix, pos_ideal, neg_ideal)5. 结果可视化分析使用matplotlib创建直观的展示import matplotlib.pyplot as plt # 按贴近度排序 result_df result_df.sort_values(Closeness, ascendingFalse) # 创建可视化图形 plt.figure(figsize(12, 6)) # 条形图展示各供应商排名 plt.subplot(1, 2, 1) plt.barh(result_df[ID], result_df[Closeness], colorskyblue) plt.xlabel(相对贴近度) plt.title(供应商TOPSIS评估结果) # 雷达图展示最优供应商各指标 plt.subplot(1, 2, 2, polarTrue) categories list(weights.keys()) N len(categories) angles [n / float(N) * 2 * np.pi for n in range(N)] angles angles[:1] best_supplier weighted_matrix[weighted_matrix.iloc[:,0] result_df.iloc[0,0]].values[0][1:] values np.concatenate((best_supplier, [best_supplier[0]])) plt.polar(angles, values, colorred, linestylesolid) plt.fill(angles, values, red, alpha0.1) plt.title(最优供应商雷达图, y1.1) plt.xticks(angles[:-1], categories) plt.tight_layout() plt.show()可视化元素说明条形图清晰展示各供应商的排序结果雷达图直观显示最优供应商的各指标表现颜色编码使用不同颜色增强可读性6. 完整代码封装与优化将上述步骤封装为可复用的Python类class TOPSIS: def __init__(self, data_path, benefit_cols, cost_cols, weights): self.df pd.read_excel(data_path, engineopenpyxl) self.benefit_cols benefit_cols self.cost_cols cost_cols self.weights weights def normalize(self): self.norm_df self.df.copy() for col in self.df.columns[1:]: if col in self.benefit_cols: self.norm_df[col] self.df[col] / np.sqrt(np.sum(self.df[col]**2)) elif col in self.cost_cols: self.norm_df[col] 1 - (self.df[col] / np.sqrt(np.sum(self.df[col]**2))) def apply_weights(self): self.weighted_df self.norm_df.copy() for col in self.weights: self.weighted_df[col] self.norm_df[col] * self.weights[col] def calculate_ideal_solutions(self): self.pos_ideal [] self.neg_ideal [] for col in self.weighted_df.columns[1:]: if col in self.benefit_cols: self.pos_ideal.append(self.weighted_df[col].max()) self.neg_ideal.append(self.weighted_df[col].min()) else: self.pos_ideal.append(self.weighted_df[col].min()) self.neg_ideal.append(self.weighted_df[col].max()) self.pos_ideal np.array(self.pos_ideal) self.neg_ideal np.array(self.neg_ideal) def evaluate(self): results [] for _, row in self.weighted_df.iterrows(): values row.values[1:] pos_dist np.sqrt(np.sum((values - self.pos_ideal)**2)) neg_dist np.sqrt(np.sum((values - self.neg_ideal)**2)) closeness neg_dist / (pos_dist neg_dist) results.append({ ID: row[0], Pos_Distance: pos_dist, Neg_Distance: neg_dist, Closeness: closeness }) self.result_df pd.DataFrame(results) return self.result_df.sort_values(Closeness, ascendingFalse) def visualize(self): # 可视化代码同上 pass # 使用示例 topsis TOPSIS( data_pathsupplier_evaluation.xlsx, benefit_cols[产品质量评分, 售后服务评分], cost_cols[交货周期, 价格], weights{产品质量评分:0.3, 交货周期:0.25, 价格:0.3, 售后服务评分:0.15} ) topsis.normalize() topsis.apply_weights() topsis.calculate_ideal_solutions() results topsis.evaluate() topsis.visualize()7. 常见问题与解决方案问题1数据量纲差异大导致某些指标主导结果解决方案采用不同的规范化方法如极差标准化调整权重分配对数据进行对数变换问题2权重确定主观性强改进方法# 熵权法计算权重示例 def entropy_weight(df): df df.iloc[:,1:] # 去除ID列 df df.apply(lambda x: (x - x.min()) / (x.max() - x.min())) k 1 / np.log(len(df)) p df / df.sum() e -k * (p * np.log(p)).sum() return (1 - e) / (1 - e).sum() weights entropy_weight(df)问题3结果解释性不强增强措施添加详细的指标说明提供敏感性分析增加交互式可视化性能优化技巧对于大数据集使用numpy向量化操作替代循环考虑使用Dask处理超大规模数据缓存中间结果避免重复计算实际项目中我们曾用这个方法评估了50家供应商发现排名第三的供应商虽然在价格上不占优势但在产品质量和售后服务上表现突出最终成为我们的战略合作伙伴。这种量化分析帮助团队克服了主观偏见做出了更客观的决策。