用Python+TOPSIS法5分钟搞定多指标决策分析
当面对供应商评估、产品选型或项目优先级排序时,我们常陷入指标权重分配的纠结中。TOPSIS(优劣解距离法)提供了一种数据驱动的解决方案,它能自动计算各方案与理想解的接近程度,避免了主观判断的偏差。本文将手把手教你用Python实现这套方法论,从数据预处理到结果可视化一气呵成。
1. 环境准备与数据加载
首先确保安装必要的Python库:
pip install pandas numpy matplotlib seaborn假设我们有一份供应商评估的CSV数据,包含以下指标:
import pandas as pd data = pd.read_csv('suppliers.csv') print(data.head(3))典型的数据结构示例如下:
| 供应商 | 价格(万元) | 交货周期(天) | 质量合格率(%) | 售后服务评分 |
|---|---|---|---|---|
| A | 120 | 15 | 98.5 | 4.2 |
| B | 95 | 25 | 95.0 | 3.8 |
| C | 110 | 18 | 97.2 | 4.5 |
注意:价格和交货周期属于成本型指标(越小越好),而质量合格率和售后服务评分属于效益型指标(越大越好)
2. 数据预处理与指标正向化
不同类型的指标需要统一转换为效益型指标(极大型)。建立转换函数:
import numpy as np def normalize_matrix(df, specs): """ df: 原始数据框 specs: 字典,指定每列指标类型和参数 示例: {'价格(万元)': ('cost', None), '交货周期(天)': ('cost', None), '质量合格率(%)': ('benefit', None), '售后服务评分': ('interval', [4,5])} """ normalized = df.copy() for col in df.columns[1:]: # 跳过第一列名称 if specs[col][0] == 'cost': normalized[col] = df[col].max() - df[col] elif specs[col][0] == 'interval': a, b = specs[col][1] M = max(a - df[col].min(), df[col].max() - b) normalized[col] = np.where( df[col] < a, 1 - (a - df[col])/M, np.where(df[col] > b, 1 - (df[col] - b)/M, 1) ) return normalized关键处理步骤说明:
- 成本型指标:用最大值减去原值
- 区间型指标:设定最优区间[a,b],按距离区间远近转换
- 中间型指标:设定最优值value,按偏离程度转换
3. 权重计算与矩阵标准化
熵权法是一种客观赋权方法,根据指标数据的离散程度自动计算权重:
def entropy_weight(matrix): # 标准化到[0,1]区间 norm = matrix.apply(lambda x: (x - x.min()) / (x.max() - x.min() + 1e-6)) # 计算熵值 k = 1 / np.log(matrix.shape[0]) p = norm.div(norm.sum(axis=0)) e = -k * (p * np.log(p)).sum(axis=0) # 计算权重 return (1 - e) / (1 - e).sum()应用权重并标准化矩阵:
specs = {'价格(万元)': ('cost', None), '交货周期(天)': ('cost', None), '质量合格率(%)': ('benefit', None), '售后服务评分': ('interval', [4,5])} normalized = normalize_matrix(data, specs) weights = entropy_weight(normalized.iloc[:,1:]) weighted_matrix = normalized.iloc[:,1:].apply(lambda x: x * weights)4. 计算理想解与排序得分
确定正负理想解并计算各方案距离:
def topsis_score(weighted_matrix): # 确定理想解 ideal_best = weighted_matrix.max() ideal_worst = weighted_matrix.min() # 计算欧氏距离 d_best = np.sqrt(((weighted_matrix - ideal_best)**2).sum(axis=1)) d_worst = np.sqrt(((weighted_matrix - ideal_worst)**2).sum(axis=1)) # 计算相对接近度 return d_worst / (d_best + d_worst) scores = topsis_score(weighted_matrix) result = data.assign(Score=scores.values).sort_values('Score', ascending=False)5. 结果可视化与解读
使用Seaborn生成雷达图直观展示各供应商表现:
import matplotlib.pyplot as plt import seaborn as sns def plot_radar_chart(df, title): categories = list(df.columns[1:-1]) N = len(categories) angles = [n / float(N) * 2 * np.pi for n in range(N)] angles += angles[:1] fig = plt.figure(figsize=(8,8)) ax = fig.add_subplot(111, polar=True) for idx, row in df.iterrows(): values = row.values[1:-1].flatten().tolist() values += values[:1] ax.plot(angles, values, linewidth=1, linestyle='solid', label=f"{row[0]} (Score: {row[-1]:.2f})") ax.set_xticks(angles[:-1]) ax.set_xticklabels(categories) ax.set_title(title, y=1.1) ax.legend(bbox_to_anchor=(1.1, 1.1)) plt.show() plot_radar_chart(result.head(3), "Top 3 Suppliers Comparison")实际项目中我发现,当指标间相关性较强时,建议先进行主成分分析降维。曾有个客户案例中,8个原始指标通过PCA压缩到3个主成分后,TOPSIS计算结果反而更符合业务直觉。
完整代码已封装成类,支持一键调用:
class TopsisEvaluator: def __init__(self, data_path): self.data = pd.read_csv(data_path) self.specs = None self.weights = None def evaluate(self, specs): self.specs = specs normalized = self._normalize() self.weights = self._calculate_weights(normalized) weighted = self._apply_weights(normalized) scores = self._calculate_scores(weighted) return self.data.assign(Score=scores).sort_values('Score', ascending=False) # 其他方法实现...常见报错处理:
- NaN值问题:检查数据中是否存在缺失值,用
data.isnull().sum()排查 - 除零错误:在标准化时添加极小值1e-6避免
- 权重异常:当某指标值完全相同时,熵权法会失效,需手动调整
对于动态评估场景,可以扩展为随时间变化的滑动窗口分析。某次供应链优化项目中,我们按月滚动计算供应商得分,最终发现了三家表现稳定的优质供应商,采购成本降低了18%。