概述
TOPSIS(逼近理想解排序法)是一种多属性决策方法,通过计算各方案与 “理想解”“负理想解” 的距离,排序选最优。
操作步骤
- 输入原始决策矩阵(方案 × 指标);
- 标准化处理(消除量纲);
- 确定指标权重(如 AHP、熵权法);
- 计算加权标准化矩阵;
- 找理想解(各指标最大值)、负理想解(各指标最小值);
- 算各方案到两解的欧式距离;
- 求贴近度并排序。
输入原始矩阵
消除量纲,进行标准化处理
通过某些方法,如层次分析法等得到权重向量
标准化矩阵与权重向量相乘得到结果矩阵
按照题意 从结果矩阵中构造出理想解和负理想解
然后计算每个方案与最优目标、最劣目标的欧氏距离,进行排序
就能对方案的好坏进行得到结论了
包含了极大型和极小型指标的处理
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt# -------------------------- 1. 读取数据 + 自动筛选数值列(核心修改)--------------------------
file_path = r'F:\01\study\2.熵权TOPSIS\TOPSIS.xlsx'
data = pd.read_excel(file_path) # 读取所有列(含“方案名称”等非数值列)# 自动筛选数值列:仅保留int/float类型的列(排除字符串列如“方案名称”)
# select_dtypes(include=[np.number]) → 筛选所有数值类型列
numeric_data = data.select_dtypes(include=[np.number]).copy()
# 提取数值列的列名(用于后续极小型指标匹配、权重输出)
numeric_columns = numeric_data.columns.tolist()# 检查数值列是否存在(防止Excel表无数值列导致报错)
if len(numeric_columns) == 0:raise ValueError("Excel表中未检测到数值类型列,请检查数据格式!")# -------------------------- 2. 数据标准化(逻辑不变,仅用自动识别的数值列)--------------------------
def standardize_data(df, min_columns):df_std = df.copy()for column in df_std.columns:max_val = df_std[column].max()min_val = df_std[column].min()# 处理极端情况:某列所有值相同(避免除以0)if max_val == min_val:df_std[column] = 0.0else:if column in min_columns: # 极小型指标反向标准化df_std[column] = (max_val - df_std[column]) / (max_val - min_val)else: # 极大型指标正向标准化df_std[column] = (df_std[column] - min_val) / (max_val - min_val)return df_std# -------------------------- 3. 计算熵权(逻辑不变)--------------------------
def calculate_entropy_weights(df):# 概率矩阵(避免0值导致log报错)prob_matrix = df.div(df.sum(axis=0) + np.finfo(float).eps, axis=1)# 计算熵值entropy = -np.sum(prob_matrix * np.log(prob_matrix + np.finfo(float).eps), axis=0) / np.log(len(df))# 计算权重(确保权重和为1)weights = (1 - entropy) / (1 - entropy).sum()return weights# -------------------------- 4. TOPSIS算法(逻辑不变)--------------------------
def topsis(df, weights):# 加权标准化weighted_normalized_df = df.mul(weights, axis=1)# 确定理想解(正理想解:最大值;负理想解:最小值)ideal_best = weighted_normalized_df.max(axis=0)ideal_worst = weighted_normalized_df.min(axis=0)# 计算欧氏距离distance_to_best = np.sqrt(((weighted_normalized_df - ideal_best) ** 2).sum(axis=1))distance_to_worst = np.sqrt(((weighted_normalized_df - ideal_worst) ** 2).sum(axis=1))# 计算相对贴近度(避免分母为0)relative_closeness = distance_to_worst / (distance_to_best + distance_to_worst + np.finfo(float).eps)return relative_closeness# -------------------------- 5. 用户输入与主流程(适配自动识别的数值列)--------------------------
# 1. 向用户展示自动识别的数值列及对应索引(便于用户正确输入极小型指标)
print("自动识别的数值指标列(索引从1开始):")
for idx, col in enumerate(numeric_columns, 1):print(f"{idx}: {col}")# 2. 获取用户输入的极小型指标索引
min_columns_input = input("\n请输入极小型指标的列索引(用空格分隔,如2 3 5): ").split()
try:min_columns_index = [int(i) for i in min_columns_input]# 验证索引是否在有效范围内(防止用户输入超出数值列数量的索引)for idx in min_columns_index:if idx < 1 or idx > len(numeric_columns):raise ValueError(f"索引{idx}无效!有效索引范围为1-{len(numeric_columns)}")
except ValueError as e:print(f"输入错误:{e}")exit() # 输入错误时退出程序,避免后续报错# 3. 根据索引获取极小型指标的列名(索引1对应numeric_columns[0])
min_columns = [numeric_columns[i-1] for i in min_columns_index]# -------------------------- 6. 执行核心计算 + 结果整合--------------------------
standardized_data = standardize_data(numeric_data, min_columns) # 标准化数值列
weights = calculate_entropy_weights(standardized_data) # 计算熵权
topsis_scores = topsis(standardized_data, weights) # 计算TOPSIS得分# 将得分合并到原始数据(保留“方案名称”等非数值列)
data['TOPSIS_Score'] = topsis_scores
# 按得分降序排序(便于查看最优方案)
data_sorted = data.sort_values(by='TOPSIS_Score', ascending=False).reset_index(drop=True)# -------------------------- 7. 结果保存与可视化(适配自动识别的列)--------------------------
# 保存结果到Excel
output_path = 'processed_data_with_topsis.xlsx'
data_sorted.to_excel(output_path, index=False)# 可视化(用“方案名称”作为x轴,需先确认“方案名称”列存在)
if '方案名称' in data.columns:x_labels = data_sorted['方案名称']
else:# 若无“方案名称”列,用“样本1、样本2...”作为标识x_labels = [f"样本{i+1}" for i in range(len(data_sorted))]# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题plt.figure(figsize=(12, 8))
bars = plt.bar(x_labels, data_sorted['TOPSIS_Score'], color='skyblue')
# 在柱状图上添加具体得分
for bar in bars:height = bar.get_height()plt.text(bar.get_x() + bar.get_width()/2., height + 0.01,f'{height:.4f}', ha='center', va='bottom', fontsize=10)plt.xlabel('方案/样本', fontsize=12)
plt.ylabel('TOPSIS得分(越高越优)', fontsize=12)
plt.title('各方案TOPSIS得分对比', fontsize=14, fontweight='bold')
plt.ylim(0, 1.1) # 调整y轴范围,避免得分显示不全
plt.grid(axis='y', alpha=0.3)
plt.tight_layout() # 自动调整布局,防止标签截断
plt.savefig('topsis_scores.png', format='png', dpi=300)
plt.show()# -------------------------- 8. 输出结果信息--------------------------
print(f"\n✅ 数据已保存至: {output_path}")
print(f"✅ 可视化图表已保存至: topsis_scores.png")print("\n📊 各指标熵权:")
weights_series = pd.Series(weights, index=numeric_columns)
for col, weight in weights_series.items():print(f"{col}: {weight:.6f}")print("\n🏆 各方案TOPSIS得分(降序):")
for idx, row in data_sorted.iterrows():# 优先用“方案名称”标识,无则用样本索引if '方案名称' in data_sorted.columns:scheme_name = row['方案名称']else:scheme_name = f"样本{idx+1}"print(f"{scheme_name}: {row['TOPSIS_Score']:.6f}")
方案名称 | 产品质量(%) | 交货周期(天) | 价格(元) | 售后服务评分(10 分) | 订单响应速度(小时) |
供应商 A | 95 | 5 | 200 | 8.5 | 2 |
供应商 B | 92 | 7 | 180 | 7.8 | 3 |
供应商 C | 98 | 4 | 220 | 9.2 | 1.5 |
供应商 D | 90 | 6 | 190 | 8 | 2.5 |
F:\01\study\.venv\Scripts\python.exe F:\01\study\2.熵权TOPSIS\2.熵权TOPSIS模型.py
自动识别的数值指标列(索引从1开始):
1: 产品质量(%)
2: 交货周期(天)
3: 价格(元)
4: 售后服务评分(10 分)
5: 订单响应速度(小时)请输入极小型指标的列索引(用空格分隔,如2 3 5): 2 3 5
✅ 数据已保存至: processed_data_with_topsis.xlsx
✅ 可视化图表已保存至: topsis_scores.png📊 各指标熵权:
产品质量(%): 0.207976
交货周期(天): 0.187343
价格(元): 0.162630
售后服务评分(10 分): 0.254707
订单响应速度(小时): 0.187343🏆 各方案TOPSIS得分(降序):
供应商 C: 0.721959
供应商 A: 0.581576
供应商 D: 0.305729
供应商 B: 0.299548
加入了对中间型指标和区间型指标的处理
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt# -------------------------- 1. 读取数据并自动筛选数值列 --------------------------
file_path = r'F:\01\study\2.熵权TOPSIS\TOPSIS.xlsx' # 替换为你的文件路径
data = pd.read_excel(file_path)# 自动筛选数值列(排除字符串列如“方案名称”)
numeric_data = data.select_dtypes(include=[np.number]).copy()
numeric_columns = numeric_data.columns.tolist()if not numeric_columns:raise ValueError("未检测到数值列,请检查数据格式!")# -------------------------- 2. 扩展标准化函数(支持4类指标) --------------------------
def standardize_data(df, max_cols, min_cols, mid_cols, interval_cols):"""标准化数据:根据指标类型应用不同公式参数:- df: 数值型数据框- max_cols: 极大型指标(列名列表)- min_cols: 极小型指标(列名列表)- mid_cols: 中间型指标(字典,{列名: 最优值})- interval_cols: 区间型指标(字典,{列名: (下限a, 上限b)})返回:标准化后的DataFrame(值越大越优)"""df_std = df.copy()for column in df_std.columns:x = df_std[column]x_max = x.max()x_min = x.min()# 1. 极大型指标if column in max_cols:if x_max == x_min:df_std[column] = 0.0else:df_std[column] = (x - x_min) / (x_max - x_min)# 2. 极小型指标elif column in min_cols:if x_max == x_min:df_std[column] = 0.0else:df_std[column] = (x_max - x) / (x_max - x_min)# 3. 中间型指标elif column in mid_cols:x_best = mid_cols[column] # 最优值# 计算最大偏差(用于分母,避免为0)max_deviation = max(x_max - x_best, x_best - x_min)if max_deviation == 0:df_std[column] = 1.0 # 所有值都等于最优值else:df_std[column] = 1 - np.abs(x - x_best) / max_deviation# 4. 区间型指标elif column in interval_cols:a, b = interval_cols[column] # 最优区间[a, b]if a > b:raise ValueError(f"区间型指标{column}的下限a必须≤上限b!")# 计算最大偏离范围(用于分母)max_range = max(a - x_min, x_max - b)if max_range == 0:df_std[column] = 1.0 # 所有值都在区间内else:# 分情况计算标准化值df_std[column] = np.where(x < a, 1 - (a - x) / max_range,np.where(x > b, 1 - (x - b) / max_range, 1.0))# 未指定类型的指标(默认按极大型处理,或报错)else:raise ValueError(f"指标{column}未指定类型(极大型/极小型/中间型/区间型)!")return df_std# -------------------------- 3. 熵权计算与TOPSIS核心逻辑(不变) --------------------------
def calculate_entropy_weights(df):prob_matrix = df.div(df.sum(axis=0) + np.finfo(float).eps, axis=1)entropy = -np.sum(prob_matrix * np.log(prob_matrix + np.finfo(float).eps), axis=0) / np.log(len(df))weights = (1 - entropy) / (1 - entropy).sum()return weightsdef topsis(df, weights):weighted_normalized_df = df.mul(weights, axis=1)ideal_best = weighted_normalized_df.max(axis=0)ideal_worst = weighted_normalized_df.min(axis=0)distance_to_best = np.sqrt(((weighted_normalized_df - ideal_best) ** 2).sum(axis=1))distance_to_worst = np.sqrt(((weighted_normalized_df - ideal_worst) ** 2).sum(axis=1))return distance_to_worst / (distance_to_best + distance_to_worst + np.finfo(float).eps)# -------------------------- 4. 用户输入:指定指标类型及参数 --------------------------
# 显示自动识别的数值列及索引
print("自动识别的数值指标列(索引从1开始):")
for idx, col in enumerate(numeric_columns, 1):print(f"{idx}: {col}")# 1. 极大型指标
max_input = input("\n请输入极大型指标的列索引(用空格分隔,如1 4): ").split()
max_index = [int(i) - 1 for i in max_input] # 转换为0-based索引
max_columns = [numeric_columns[i] for i in max_index]# 2. 极小型指标
min_input = input("请输入极小型指标的列索引(用空格分隔,如2 3): ").split()
min_index = [int(i) - 1 for i in min_input]
min_columns = [numeric_columns[i] for i in min_index]# 3. 中间型指标(需输入索引和最优值)
mid_columns = {}
mid_input = input("请输入中间型指标的列索引(用空格分隔,如5): ").split()
if mid_input:mid_index = [int(i) - 1 for i in mid_input]for idx in mid_index:col = numeric_columns[idx]x_best = float(input(f"请输入中间型指标「{col}」的最优值: "))mid_columns[col] = x_best# 4. 区间型指标(需输入索引和区间[a, b])
interval_columns = {}
interval_input = input("请输入区间型指标的列索引(用空格分隔,如6): ").split()
if interval_input:interval_index = [int(i) - 1 for i in interval_input]for idx in interval_index:col = numeric_columns[idx]a, b = map(float, input(f"请输入区间型指标「{col}」的最优区间[a, b](用空格分隔,如36 37): ").split())interval_columns[col] = (a, b)# -------------------------- 5. 验证指标类型无重复 --------------------------
all_specified = set(max_columns) | set(min_columns) | set(mid_columns.keys()) | set(interval_columns.keys())
if len(all_specified) != len(max_columns) + len(min_columns) + len(mid_columns) + len(interval_columns):raise ValueError("存在指标被重复指定类型,请检查输入!")if set(all_specified) != set(numeric_columns):missing = set(numeric_columns) - all_specifiedraise ValueError(f"以下指标未指定类型:{missing}")# -------------------------- 6. 执行计算与结果输出 --------------------------
# 标准化数据
standardized_data = standardize_data(numeric_data,max_cols=max_columns,min_cols=min_columns,mid_cols=mid_columns,interval_cols=interval_columns
)# 计算熵权与TOPSIS得分
weights = calculate_entropy_weights(standardized_data)
topsis_scores = topsis(standardized_data, weights)# 合并结果并排序
data['TOPSIS_Score'] = topsis_scores
data_sorted = data.sort_values(by='TOPSIS_Score', ascending=False).reset_index(drop=True)# -------------------------- 7. 结果保存与可视化 --------------------------
# 保存结果
output_path = 'topsis_results_with_all_types.xlsx'
data_sorted.to_excel(output_path, index=False)# 可视化
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
x_labels = data_sorted['方案名称'] if '方案名称' in data.columns else [f'样本{i + 1}' for i in range(len(data))]plt.figure(figsize=(12, 8))
bars = plt.bar(x_labels, data_sorted['TOPSIS_Score'], color='lightgreen')
for bar in bars:height = bar.get_height()plt.text(bar.get_x() + bar.get_width() / 2, height + 0.01,f'{height:.4f}', ha='center', va='bottom')plt.xlabel('方案')
plt.ylabel('TOPSIS得分(越高越优)')
plt.title('各方案TOPSIS得分对比(含中间型/区间型指标)')
plt.ylim(0, 1.1)
plt.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.savefig('topsis_scores_all_types.png', dpi=300)
plt.show()# -------------------------- 8. 打印结果 --------------------------
print(f"\n结果已保存至:{output_path}")
print(f"可视化图表已保存至:topsis_scores_all_types.png")print("\n各指标熵权:")
for col, w in weights.items():print(f"{col}: {w:.6f}")print("\n各方案TOPSIS得分(降序):")
for i, row in data_sorted.iterrows():name = row['方案名称'] if '方案名称' in row else f'样本{i + 1}'print(f"{name}: {row['TOPSIS_Score']:.6f}")
方案名称 | 产品合格率(%) | 售后服务评分(10 分制) | 交货周期(天) | 产品单价(元 / 件) | 生产能耗(kWh / 件) | 产品尺寸偏差(mm) | 仓储温度(℃) | 原材料纯度(%) |
供应商 A | 98.2 | 9.1 | 5 | 185 | 5.2 | 0.12 | 22.5 | 99.2 |
供应商 B | 96.5 | 8.5 | 7 | 170 | 4.8 | 0.08 | 19.3 | 98.3 |
供应商 C | 99 | 9.5 | 4 | 192 | 5 | 0.11 | 23.8 | 99 |
供应商 D | 97.8 | 8.8 | 6 | 178 | 5.5 | 0.15 | 26.1 | 99.6 |
供应商 E | 95.3 | 8.2 | 8 | 165 | 4.5 | 0.09 | 18.7 | 98.8 |
供应商 F | 98.5 | 9.3 | 5 | 180 | 5.1 | 0.1 | 24.2 | 99.4 |
F:\01\study\.venv\Scripts\python.exe F:\01\study\2.熵权TOPSIS\2.熵权TOPSIS模型.py
自动识别的数值指标列(索引从1开始):
1: 产品合格率(%)
2: 售后服务评分(10 分制)
3: 交货周期(天)
4: 产品单价(元 / 件)
5: 生产能耗(kWh / 件)
6: 产品尺寸偏差(mm)
7: 仓储温度(℃)
8: 原材料纯度(%)请输入极大型指标的列索引(用空格分隔,如1 4): 1 2
请输入极小型指标的列索引(用空格分隔,如2 3): 3 4
请输入中间型指标的列索引(用空格分隔,如5): 5 6
请输入中间型指标「生产能耗(kWh / 件)」的最优值: 5
请输入中间型指标「产品尺寸偏差(mm)」的最优值: 0.1
请输入区间型指标的列索引(用空格分隔,如6): 7 8
请输入区间型指标「仓储温度(℃)」的最优区间[a, b](用空格分隔,如36 37): 35 37
请输入区间型指标「原材料纯度(%)」的最优区间[a, b](用空格分隔,如36 37): 40 60结果已保存至:topsis_results_with_all_types.xlsx
可视化图表已保存至:topsis_scores_all_types.png各指标熵权:
产品合格率(%): 0.099616
售后服务评分(10 分制): 0.117754
交货周期(天): 0.111792
产品单价(元 / 件): 0.117166
生产能耗(kWh / 件): 0.179717
产品尺寸偏差(mm): 0.084347
仓储温度(℃): 0.144930
原材料纯度(%): 0.144679各方案TOPSIS得分(降序):
供应商 F: 0.739305
供应商 C: 0.695332
供应商 A: 0.589764
供应商 B: 0.480903
供应商 D: 0.377151
供应商 E: 0.333267