news 2026/4/19 14:35:26

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

作者头像

张小明

前端开发工程师

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

用Python实现TOPSIS算法:从Excel表格到智能决策的跨越

当我们面对包含多个评价指标的数据集时,如何做出客观、量化的综合评估?传统Excel操作往往需要手动计算权重、标准化数据,不仅效率低下,还容易出错。这就是TOPSIS(优劣解距离法)大显身手的地方——它能够自动计算每个方案与理想解的相对接近程度,给出科学排序。而Python的NumPy和Pandas库,能让这个过程变得前所未有的简单。

1. TOPSIS算法核心原理

TOPSIS全称Technique for Order Preference by Similarity to Ideal Solution,是一种经典的多属性决策方法。它的核心思想很直观:最佳方案应该距离理想解最近,同时距离负理想解最远。就像选择租房位置时,我们既希望离工作地点近(正向理想),又希望远离嘈杂区域(负向理想)。

算法主要流程分为四个关键步骤:

  1. 原始矩阵正向化:将所有指标统一转换为极大型指标(越大越好)
  2. 数据标准化:消除不同指标量纲(单位)的影响
  3. 计算距离得分:测量每个方案与正/负理想解的距离
  4. 结果归一化:将得分转换为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, axis=0)) print("\n标准化矩阵Z:") print(Z)

标准化后,我们可以定义正理想解(Z+)和负理想解(Z-):

# 正理想解(各指标最大值) Z_plus = np.max(Z, axis=0) # 负理想解(各指标最小值) Z_minus = np.min(Z, axis=0) print(f"\n正理想解:{Z_plus}") print(f"负理想解:{Z_minus}")

接下来计算每个方案与这两个理想解的欧氏距离:

# 计算距离 D_plus = np.sqrt(np.sum((Z - Z_plus)**2, axis=1)) D_minus = np.sqrt(np.sum((Z - Z_minus)**2, axis=1)) 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(ascending=False) print("\n最终结果:") print(df.sort_values('排名'))

为了更直观地理解,我们可以将结果可视化:

import matplotlib.pyplot as plt plt.figure(figsize=(10, 5)) df.set_index('姓名')['得分'].sort_values().plot(kind='barh', color='skyblue') plt.title('TOPSIS综合评分结果') plt.xlabel('归一化得分') plt.grid(axis='x', linestyle='--', alpha=0.7) plt.show()

5. 工程化实践:封装可复用TOPSIS类

为了让代码更具复用性,我们可以将整个流程封装成一个类:

class TOPSIS: def __init__(self, data, benefit_columns=None): self.data = data.copy() self.benefit_columns = benefit_columns or [] def normalize(self, matrix): return matrix / np.sqrt(np.sum(matrix**2, axis=0)) def calculate_distances(self, Z): self.Z_plus = np.max(Z, axis=0) self.Z_minus = np.min(Z, axis=0) D_plus = np.sqrt(np.sum((Z - self.Z_plus)**2, axis=1)) D_minus = np.sqrt(np.sum((Z - self.Z_minus)**2, axis=1)) return D_plus, D_minus def evaluate(self, columns=None, weights=None): if columns is None: columns = self.data.select_dtypes(include=np.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_columns=None): benefit_columns = benefit_columns or self.benefit_columns scores = self.evaluate(benefit_columns) result = self.data.copy() result['TOPSIS得分'] = scores result['排名'] = result['TOPSIS得分'].rank(ascending=False) 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(weights=weights) df['加权得分'] = weighted_scores / weighted_scores.sum() df['加权排名'] = df['加权得分'].rank(ascending=False) 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(weights=w) rank = pd.Series(scores).rank(ascending=False) results.append({ '权重组合': str(w), '第一名': df.loc[rank.idxmin(), '姓名'], '最后一名': df.loc[rank.idxmax(), '姓名'] }) pd.DataFrame(results)

7. 与Excel方案的对比优势

传统Excel操作需要手动完成以下步骤:

  1. 编写公式转换指标类型
  2. 创建标准化计算列
  3. 设置距离计算区域
  4. 构建得分排名表

而Python方案的优势显而易见:

对比维度Excel方案Python方案
执行效率手动操作,耗时一键运行,毫秒级响应
可复用性每次需重新设置公式封装成类/函数,随时调用
数据规模受限于表格性能可处理百万级数据
权重调整需手动修改多个公式参数化调整,即时生效
可视化基础图表,手动更新动态交互,自动刷新
版本控制难以追踪变更代码管理,清晰记录

特别是在需要频繁更新数据或调整评价标准时,Python的自动化优势更加明显。例如,当新增员工数据时:

# 新增员工数据 new_employee = {'姓名': '小周', '成绩': 85, '投诉次数': 1} df = df.append(new_employee, ignore_index=True) # 自动重新计算 topsis = TOPSIS(df, benefit_columns=['成绩', '投诉正向化']) updated_result = topsis.analyze('姓名') print("\n更新后的分析结果:") print(updated_result)

8. 常见问题与调试技巧

在实际应用中,可能会遇到以下典型问题:

  1. NaN值处理:数据中包含缺失值时,需要先进行填充或删除

    df.fillna(df.mean(), inplace=True) # 用均值填充数值型缺失值
  2. 指标方向判断错误:确保正确识别每个指标的类型

    # 验证指标方向 print("成绩(越大越好):", df['成绩'].max() == Z_plus[0]) print("投诉(越小越好):", df['投诉次数'].min() == df['投诉次数'].max() - Z_plus[1])
  3. 权重设置不合理:检查权重总和是否为1,且均为正数

    assert sum(weights) == 1, "权重总和必须为1" assert all(w >= 0 for w in weights), "权重不能为负数"
  4. 结果反直觉:检查数据标准化和距离计算过程

    # 标准化检查:每列平方和应为1 print("标准化验证:", np.sum(Z**2, axis=0))
  5. 数据量纲差异过大:确保标准化步骤正确执行

    # 标准化前各列幅度对比 print("标准化前数据范围:", np.ptp(X, axis=0))

当结果出现异常时,建议按照以下流程排查:

  1. 检查原始数据质量(缺失值、异常值)
  2. 验证指标正向化是否正确
  3. 确认标准化计算无误
  4. 检查距离公式实现
  5. 验证得分计算公式

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, axis=0) + epsilon) e = -np.sum(p * np.log(p + epsilon), axis=0) / 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(weights=weights) # 分块处理大数据 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_columns=None): self.benefit_columns = benefit_columns def fit(self, X, y=None): 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. 性能优化与最佳实践

对于大型数据集,我们可以采用以下优化策略:

  1. 向量化计算:避免循环,使用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, axis=1))
  2. 内存优化:处理超大矩阵时使用分块计算

    def chunked_distance(Z, reference, chunk_size=1000): distances = [] for i in range(0, len(Z), chunk_size): chunk = Z[i:i+chunk_size] dist = np.sqrt(np.sum((chunk - reference)**2, axis=1)) distances.append(dist) return np.concatenate(distances)
  3. 缓存中间结果:避免重复计算

    from functools import lru_cache @lru_cache(maxsize=None) def cached_normalize(matrix_tuple): # 注意:输入需要转为可哈希类型 matrix = np.array(matrix_tuple) return matrix / np.sqrt(np.sum(matrix**2, axis=0))
  4. 使用更快的数学库

    # 使用NumExpr加速计算 import numexpr as ne def fast_distance(Z, reference): diff = Z - reference return ne.evaluate('sqrt(sum(diff**2, axis=1))')
  5. 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, axis=0)) Z_plus = cp.max(Z_gpu, axis=0) Z_minus = cp.min(Z_gpu, axis=0) D_plus = cp.sqrt(cp.sum((Z_gpu - Z_plus)**2, axis=1)) D_minus = cp.sqrt(cp.sum((Z_gpu - Z_minus)**2, axis=1)) return (D_minus / (D_plus + D_minus)).get() # 转回CPU

在实际项目中,建议遵循以下最佳实践:

  • 为TOPSIS类编写单元测试
  • 使用类型提示提高代码可读性
  • 添加详细的文档字符串
  • 实现日志记录功能
  • 提供示例数据集和演示代码
  • 支持多种输入格式(CSV、Excel、数据库等)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/19 14:31:52

2026届毕业生推荐的五大降AI率平台推荐

Ai论文网站排名(开题报告、文献综述、降aigc率、降重综合对比) TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 正处于人工智能辅助写作越来越普遍的当前状况下,怎样能够切实有效地减少文本所具…

作者头像 李华
网站建设 2026/4/19 14:31:48

Perl哈希怎么用?

Perl 哈希 哈希是 key/value 对的集合。 Perl中哈希变量以百分号 (%) 标记开始。 访问哈希元素格式:${key}。 以下是一个简单的哈希实例: 实例 #!/usr/bin/perl %data (google, google.com, , example.com, taobao, taobao.com); print "\$d…

作者头像 李华
网站建设 2026/4/19 14:30:28

K8s Pod生命周期全解析:从创建到优雅终止的完整流程与最佳实践

Kubernetes Pod生命周期深度剖析:从创建到优雅终止的全流程实践指南 引言:理解Pod生命周期的核心价值 在Kubernetes生态系统中,Pod作为最小调度单元,其生命周期管理直接关系到应用的稳定性和可靠性。想象这样一个场景:…

作者头像 李华