ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python实现TOPSIS多指标决策分析:从原理到实战应用

Python实现TOPSIS多指标决策分析:从原理到实战应用 1. 项目缘起从“拍脑袋”决策到量化评估在项目评审、产品选型、人才评估这些日常工作中我们经常会遇到一个头疼的问题面对一堆各有优劣的选项到底该选哪个比如要从三个供应商里选一个A价格最低但交货慢B质量最好但价格最高C各方面都还行但没什么亮点。这时候很多团队的做法是开会讨论最后往往变成“我觉得A好”、“我支持B”的“拍脑袋”式决策缺乏一个客观、统一的衡量标尺。TOPSIS分析法就是为了解决这个痛点而生的。它的全称是“逼近理想解排序法”听起来有点学术但核心思想非常直观在一堆选项里最好的那个应该离“理想中最好的情况”最近同时离“理想中最差的情况”最远。这个“理想中最好的情况”就是所有选项在每个评价指标上能达到的最优值集合反之“理想中最差的情况”就是所有最差值的集合。TOPSIS通过计算每个选项与这两个“理想点”的距离来给所有选项排个名次距离“最好”越近、距离“最差”越远的综合得分就越高。以前这类多指标决策分析往往依赖SPSS、MATLAB等专业软件或者Excel手动计算过程繁琐且容易出错。现在用Python来实现TOPSIS事情就变得简单多了。利用NumPy、Pandas这些库我们可以轻松处理数据矩阵、进行向量运算把复杂的数学过程封装成几行清晰的代码。这不仅让分析过程可重复、可审计更重要的是它把决策从“感觉”层面拉回到了“数据”层面为团队提供了一个透明、一致的决策依据。接下来我就结合一个实际的供应商选择案例手把手带你用Python走通TOPSIS的全流程并分享几个我踩过坑才总结出来的关键细节。2. TOPSIS的核心原理与数学拆解要真正用好一个工具不能只停留在调用API的层面理解其背后的数学逻辑至关重要。这能帮助我们在数据异常、结果存疑时有能力进行排查和调整。TOPSIS的整个过程可以分解为几个清晰的数学步骤。2.1 构建决策矩阵与指标同趋化首先我们把待评价的m个方案比如3个供应商和n个评价指标比如价格、交货期、质量合格率整理成一个m行×n列的矩阵这就是决策矩阵。假设我们有3个供应商S1, S2, S3评估4个指标价格万元成本型越低越好、交货期天成本型越短越好、质量合格率%效益型越高越好、售后服务评分1-10分效益型越高越好。原始数据可能如下供应商价格万元交货期天质量合格率%售后服务评分S112015958S210020989S315010927这里第一个关键点就出现了指标的同趋化。我们的指标有“成本型”越小越好和“效益型”越大越好两种。TOPSIS计算距离要求所有指标方向一致通常都转化为“效益型”越大越好。所以对于成本型指标价格、交货期我们需要进行转化。最常见的方法是取倒数或做差值但取倒数在数值为0时会出问题。更稳健的方法是使用“向量归一化”过程中的一种变形或者先进行“极差变换”。我推荐使用极差变换因为它能保持数据分布特性且不受零值影响。对于成本型指标极差变换公式为X (max - X) / (max - min)。经过变换后所有指标都变成了效益型且数值范围在[0, 1]之间。这是后续计算的基础。2.2 权重确定从主观赋权到客观熵权指标权重是TOPSIS的灵魂权重分配不同结果可能大相径庭。确定权重主要有两大类方法主观赋权法如层次分析法AHP、德尔菲法。依赖专家经验适合指标含义明确、但数据难以直接反映重要性的场景。比如“售后服务评分”其重要性可能来自战略考量而非历史数据。客观赋权法如熵权法。完全由数据本身驱动通过计算指标的信息熵来确定权重。某个指标的数据差异越大即熵越小说明该指标在区分各方案时提供的信息量越多其权重就应该越大。在实际项目中我通常采用主客观结合法。先用AHP确定一个基础权重框架体现业务逻辑再用熵权法对数据进行计算得到一个客观权重最后对两者进行加权综合如各占50%。这样既能尊重业务经验又能响应数据事实避免了纯主观的武断和纯客观的“数据偏见”。后文在代码实现部分我会演示如何集成熵权法来计算客观权重。2.3 距离计算与贴近度排序数据同趋化并赋予权重后我们得到一个加权的规范化决策矩阵。接着找出每个指标在所有方案中的最大值和最小值分别构成“正理想解”Z和“负理想解”Z-。然后计算每个方案到Z和Z-的欧氏距离D和D-。最后计算每个方案的相对贴近度CC D- / (D D-)。这个C值就是我们的最终得分范围在0到1之间。C值越大说明该方案越接近正理想解同时越远离负理想解综合表现就越好。所有方案按C值从大到小排序就得到了优劣顺序。理解了这个流程我们就能明白TOPSIS的结果严重依赖于原始数据的准确性、指标同趋化的方法以及权重的设定。任何一个环节处理不当都可能让结果“失之毫厘谬以千里”。3. Python实战一步步实现TOPSIS评价理论说得再多不如一行代码。我们直接进入实战环节用Python把上面的供应商选择案例完整实现一遍。我会使用numpy和pandas这两个核心库它们几乎是数据科学领域的标配。3.1 环境准备与数据加载首先确保你的Python环境已经安装了必要的库。如果还没有在终端或命令提示符中执行以下命令pip install numpy pandas接下来我们开始编写代码。第一步导入库并加载数据。import numpy as np import pandas as pd # 定义原始数据 data { 供应商: [S1, S2, S3], 价格万元: [120, 100, 150], 交货期天: [15, 20, 10], 质量合格率%: [95, 98, 92], 售后服务评分: [8, 9, 7] } # 创建DataFrame df_original pd.DataFrame(data) print(原始数据) print(df_original) print(\n) # 提取数值矩阵忽略‘供应商’列 matrix df_original.iloc[:, 1:].values print(决策矩阵数值部分) print(matrix)3.2 数据预处理同趋化与归一化这里我们采用“极差变换法”进行同趋化并同时完成归一化将数据缩放到[0,1]区间。注意我们需要指明每一列是成本型还是效益型。def normalize_matrix(matrix, indicators_type): 对决策矩阵进行同趋化和归一化极差变换法。 matrix: 原始决策矩阵二维numpy数组。 indicators_type: 列表每个元素为1效益型或-1成本型。 返回规范化后的矩阵。 norm_matrix np.zeros_like(matrix, dtypefloat) n_cols matrix.shape[1] for i in range(n_cols): col matrix[:, i] max_val, min_val col.max(), col.min() # 避免除零错误 if max_val min_val: norm_matrix[:, i] 1 else: if indicators_type[i] 1: # 效益型 norm_matrix[:, i] (col - min_val) / (max_val - min_val) else: # 成本型 norm_matrix[:, i] (max_val - col) / (max_val - min_val) return norm_matrix # 定义指标类型价格成本型-交货期成本型-质量合格率效益型售后服务评分效益型 indicators_type [-1, -1, 1, 1] norm_matrix normalize_matrix(matrix, indicators_type) print(同趋化 归一化后的矩阵) print(norm_matrix)运行后你会发现原来价格150最差现在变成了0价格100最好变成了1。交货期、合格率等也做了相应转换所有数值都在0到1之间且都是越大越好。3.3 集成熵权法计算客观权重为了更客观我们接着用熵权法计算权重。熵权法的步骤是1)计算每个指标下各方案的比重2)计算该指标的信息熵3)计算信息效用值1-熵4)将效用值归一化得到权重。def calculate_entropy_weight(norm_matrix): 基于归一化后的矩阵计算熵权。 norm_matrix: 同趋化且归一化后的决策矩阵。 返回权重向量numpy数组。 m, n norm_matrix.shape # 计算比重矩阵 p norm_matrix / norm_matrix.sum(axis0, keepdimsTrue) # 避免log(0)错误将0替换为一个极小值 p np.where(p 0, 1e-10, p) # 计算信息熵 k 1 / np.log(m) e -k * (p * np.log(p)).sum(axis0) # 计算信息效用值 d 1 - e # 计算权重 w d / d.sum() return w # 计算熵权 entropy_weights calculate_entropy_weight(norm_matrix) print(熵权法计算的权重) for i, (col_name, weight) in enumerate(zip(df_original.columns[1:], entropy_weights)): print(f{col_name}: {weight:.4f})在我的这次运行中得到的权重可能类似于[0.25, 0.30, 0.28, 0.17]。这反映了数据本身的区分度。你可以将这个客观权重与业务方讨论确定的主观权重进行综合。3.4 计算加权矩阵与理想解假设我们这里直接使用熵权作为最终权重计算加权规范化矩阵。# 使用熵权作为权重 weights entropy_weights print(f\n最终使用的权重向量{weights}) # 计算加权规范化矩阵 weighted_norm_matrix norm_matrix * weights print(\n加权规范化矩阵) print(weighted_norm_matrix) # 确定正理想解Z和负理想解Z- # 由于所有指标都已转为效益型正理想解就是每列最大值负理想解就是每列最小值。 z_positive weighted_norm_matrix.max(axis0) z_negative weighted_norm_matrix.min(axis0) print(f\n正理想解 Z{z_positive}) print(f负理想解 Z-{z_negative})3.5 计算距离与相对贴近度最后计算每个方案到Z和Z-的欧氏距离并得出最终的贴近度C值。# 计算每个方案到Z和Z-的欧氏距离 # axis1表示对每一行进行计算 d_positive np.sqrt(((weighted_norm_matrix - z_positive) ** 2).sum(axis1)) d_negative np.sqrt(((weighted_norm_matrix - z_negative) ** 2).sum(axis1)) print(\n各方案到正理想解的距离 D, d_positive) print(各方案到负理想解的距离 D-, d_negative) # 计算相对贴近度 C c d_negative / (d_positive d_negative) print(\n各方案相对贴近度 C, c) # 将结果添加到原始DataFrame中 df_result df_original.copy() df_result[D] d_positive df_result[D-] d_negative df_result[C] c df_result[排名] df_result[C].rank(ascendingFalse, methodmin).astype(int) print(\n最终评价结果) print(df_result.sort_values(by排名))运行全部代码你会得到一个包含距离、贴近度和排名的完整结果表。在我的案例数据下结果很可能是S2排名第一价格和售后服务有优势质量最好S1次之S3最后。这个量化的结果就可以作为决策会议上有力的数据支撑了。4. 关键细节、常见陷阱与优化策略代码跑通只是第一步要让TOPSIS在实际项目中真正发挥作用还需要注意以下几个我踩过坑的细节。4.1 指标类型与同趋化方法的选择“成本型转效益型”不是只有极差变换一种方法。除了之前提到的倒数法还有(max - X)这种简单差值法。每种方法都有其适用场景和缺陷极差变换最常用能保留数据相对关系且结果固定在[0,1]推荐优先使用。倒数法X 1/X。适用于绝对数值型指标且所有值必须为正数。若原始值接近0会导致变换后值极大扭曲权重分配。简单差值法X max - X。计算简单但变换后的数据范围取决于原始数据的极差可能与其他效益型指标量纲不统一影响后续加权。注意务必在分析报告中对采用的同趋化方法进行说明这是保证结果可复现、可审计的重要一环。4.2 权重敏感性与结果稳健性检验TOPSIS的结果对权重非常敏感。一个重要的实践是进行敏感性分析。具体做法是轻微调整某个关键指标的权重比如±10%观察排名顺序是否发生变化。如果微调权重就导致排名翻转说明这个决策本身在数据上就不够稳健需要谨慎对待或者回头重新审视指标体系的合理性。我们可以写一个简单的循环来模拟这个过程def sensitivity_analysis(base_weights, norm_matrix, change_index, change_range0.1, steps5): 对指定指标的权重进行敏感性分析。 base_weights: 基础权重向量。 change_index: 要调整的权重索引。 change_range: 调整范围如0.1表示±10%。 steps: 调整步数。 original_c calculate_topsis(norm_matrix, base_weights)[1] # 假设calculate_topsis是封装好的计算函数返回C值 original_rank pd.Series(original_c).rank(ascendingFalse).values print(f对指标 {df_original.columns[1:][change_index]} 进行权重敏感性分析 (±{change_range*100:.0f}%)) print(- * 50) for delta in np.linspace(-change_range, change_range, steps*21): new_weights base_weights.copy() # 调整指定指标的权重 new_weights[change_index] base_weights[change_index] * (1 delta) # 重新归一化使权重总和为1 new_weights new_weights / new_weights.sum() new_c calculate_topsis(norm_matrix, new_weights)[1] new_rank pd.Series(new_c).rank(ascendingFalse).values rank_change any(original_rank ! new_rank) print(f权重调整 {delta:.2%}: 新权重{new_weights[change_index]:.4f}, 排名变化: {rank_change}) if rank_change: print(f 原始排名: {original_rank.astype(int)}) print(f 新排名: {new_rank.astype(int)})4.3 数据标准化归一化的误区我们前面做的“极差变换”已经包含了归一化。但有时人们会混淆“向量归一化”即每列元素除以该列的模长和“极差归一化”。TOPSIS的原始论文使用的是向量归一化。这两种方法的主要区别是向量归一化Zij Xij / sqrt(∑(Xij^2))。这种方法会改变数据之间的比例关系更侧重于消除量纲。极差归一化Zij (Xij - min) / (max - min)。这种方法保留数据在[0,1]内的相对分布。在大多数实际应用中极差归一化因其直观和稳定而更受欢迎。但如果你需要严格遵循经典TOPSIS公式或者与某些学术研究对标则应使用向量归一化。关键是要在整个分析过程中保持一致并在报告中明确说明。4.4 处理缺失值与异常值现实数据很少是完美的。如果某个方案的某个指标数据缺失怎么办粗暴地删除整个方案或填0都会引入偏差。常见的处理策略有均值/中位数填充用该指标在其他方案上的均值或中位数填充。适用于数据随机缺失的情况。基于模型预测填充如果指标间存在相关性可以用回归等模型预测缺失值。视为最差值对于效益型指标将缺失值赋为该指标的最小值或一个极小的数对于成本型指标则赋为最大值。这是一种保守策略会降低该方案在该指标上的得分。异常值比如价格数据中混入了一个错误的小数点会极大影响最大值、最小值和归一化结果。在计算前必须进行数据清洗使用箱线图或3σ原则识别并处理异常值。5. 从脚本到工具封装、可视化与报告生成当我们需要频繁使用TOPSIS进行类似的分析时将上述代码封装成一个可复用的类或函数模块是很有必要的。这不仅能提高效率也减少了每次复制粘贴可能带来的错误。5.1 封装成Python类下面是一个简单的TOPSIS分析器的类封装示例class TopsisAnalyzer: def __init__(self, data_df, benefit_columnsNone, cost_columnsNone, weightsNone): 初始化分析器。 data_df: 包含方案名和指标列的DataFrame。 benefit_columns: 效益型指标列名列表。 cost_columns: 成本型指标列名列表。 weights: 权重列表或数组若为None则使用熵权法计算。 self.df data_df.copy() self.方案名 data_df.iloc[:, 0].values if data_df.columns[0] 方案 else data_df.iloc[:, 0].values # 假设第一列是方案名 self.matrix data_df.iloc[:, 1:].values self.column_names data_df.columns[1:].tolist() # 构建指标类型列表 self.indicator_types [] for col in self.column_names: if benefit_columns and col in benefit_columns: self.indicator_types.append(1) # 效益型 elif cost_columns and col in cost_columns: self.indicator_types.append(-1) # 成本型 else: raise ValueError(f列 {col} 未在 benefit_columns 或 cost_columns 中指定类型。) self.weights weights def run(self): 执行完整的TOPSIS分析流程。 # 1. 数据预处理 self.norm_matrix self._normalize() # 2. 确定权重 if self.weights is None: self.weights self._calculate_entropy_weight(self.norm_matrix) # 3. 计算加权矩阵和理想解 self.weighted_matrix self.norm_matrix * self.weights self.z_pos self.weighted_matrix.max(axis0) self.z_neg self.weighted_matrix.min(axis0) # 4. 计算距离和贴近度 self.d_pos np.sqrt(((self.weighted_matrix - self.z_pos) ** 2).sum(axis1)) self.d_neg np.sqrt(((self.weighted_matrix - self.z_neg) ** 2).sum(axis1)) self.c self.d_neg / (self.d_pos self.d_neg) # 5. 整理结果 self.result_df pd.DataFrame({ 方案: self.方案名, D: self.d_pos, D-: self.d_neg, 综合得分C: self.c, 排名: pd.Series(self.c).rank(ascendingFalse, methodmin).astype(int) }).sort_values(by排名) return self.result_df def _normalize(self): # ... (同之前的normalize_matrix函数略) pass def _calculate_entropy_weight(self, norm_matrix): # ... (同之前的calculate_entropy_weight函数略) pass def plot_scores(self): 可视化综合得分。 import matplotlib.pyplot as plt plt.figure(figsize(10, 6)) plt.barh(self.result_df[方案][::-1], self.result_df[综合得分C][::-1], colorskyblue) plt.xlabel(综合得分 (C)) plt.title(TOPSIS综合评价结果) plt.xlim(0, 1) for i, v in enumerate(self.result_df[综合得分C][::-1]): plt.text(v 0.01, i, f{v:.3f}, vacenter) plt.tight_layout() plt.show()使用这个类分析过程变得非常简洁# 使用示例 analyzer TopsisAnalyzer( data_dfdf_original.rename(columns{供应商:方案}), benefit_columns[质量合格率%, 售后服务评分], cost_columns[价格万元, 交货期天] ) result analyzer.run() print(result) analyzer.plot_scores()5.2 结果可视化与报告解读“一张图胜过千言万语”。除了用plot_scores生成柱状图我们还可以用雷达图来直观展示每个方案在各个指标上的表现使用加权前的规范化数据这有助于理解为什么某个方案得分高或低。def plot_radar_chart(analyzer): import matplotlib.pyplot as plt import numpy as np labels np.array(analyzer.column_names) norms analyzer.norm_matrix # 使用规范化后的数据 n_vars len(labels) angles np.linspace(0, 2 * np.pi, n_vars, endpointFalse).tolist() angles angles[:1] # 闭合图形 fig, ax plt.subplots(figsize(8,8), subplot_kwdict(projectionpolar)) for idx, scheme_name in enumerate(analyzer.方案名): values norms[idx].tolist() values values[:1] ax.plot(angles, values, o-, linewidth2, labelf{scheme_name} (C{analyzer.c[idx]:.3f})) ax.fill(angles, values, alpha0.1) ax.set_xticks(angles[:-1]) ax.set_xticklabels(labels) ax.set_ylim(0, 1) ax.set_title(各方案指标表现雷达图 (规范化后), size15, y1.1) ax.legend(locupper right, bbox_to_anchor(1.3, 1.0)) plt.tight_layout() plt.show() plot_radar_chart(analyzer)在向业务方汇报时不能只扔出一个排名。报告应至少包含评价目标与指标体系说明为什么选这些指标数据来源与预处理方法原始数据、同趋化方法、缺失值处理。权重确定过程是主观赋权、客观熵权还是综合权重理由是什么TOPSIS计算结果包括得分、排名以及关键的距离数据D, D-。可视化图表综合得分柱状图、指标雷达图。敏感性分析结论权重微调是否会导致排名变化结果是否稳健最终建议与局限性基于分析结果给出建议并坦诚说明方法的局限性如对权重敏感、依赖数据质量等。通过这样一套从数据到代码再到分析和报告的完整流程Python实现的TOPSIS就不再是一个黑箱模型而是一个透明、可信、可辅助决策的强力工具。它把决策会议上的争论转化为了对指标、权重和数据的理性讨论这才是其最大的价值所在。
返回列表