概述
????????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