score_weight.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. ##################################################################
  4. #
  5. # Copyright (c) 2023 CICV, Inc. All Rights Reserved
  6. #
  7. ##################################################################
  8. """
  9. @Authors: zhanghaiwen(zhanghaiwen@china-icv.cn), yangzihao(yangzihao@china-icv.cn)
  10. @Data: 2024/01/12
  11. @Last Modified: 2024/01/12
  12. @Summary: Weight cal
  13. """
  14. import sys
  15. sys.path.append('../common')
  16. sys.path.append('modules')
  17. sys.path.append('score')
  18. import numpy as np
  19. from common import get_interpolation
  20. from functools import reduce
  21. def cal_weight_from_80_old(score_list):
  22. # weight process
  23. s_list = [80.1 if x == 80 else x for x in score_list]
  24. weight_list = abs((np.array(s_list) - 80) / 100)
  25. # normalization
  26. weight_list = weight_list / sum(weight_list)
  27. return weight_list
  28. def cal_weight_from_80(score_list):
  29. # weight process
  30. s_list = [80.1 if x == 80 else x for x in score_list]
  31. weight_list = abs((np.array(s_list) - 80) / 100)
  32. # normalization
  33. weight_list = weight_list / sum(weight_list)
  34. # Round to 5 decimal places, but keep a copy of the original normalized values
  35. rounded_list = np.round(weight_list, 4).tolist()
  36. # Calculate the difference between the rounded sum and 1
  37. diff = 1 - sum(rounded_list)
  38. # If the difference is not zero, adjust the last element to make the sum exactly 1
  39. if diff != 0:
  40. rounded_list[-1] += diff
  41. rounded_list[-1] = round(rounded_list[-1], 4)
  42. return rounded_list
  43. def cal_score_with_priority(score_list, weight_list, priority_list):
  44. """
  45. """
  46. rho = 1
  47. flag = any(i < 80 for i in score_list)
  48. if flag:
  49. rho = 0.9
  50. for i in range(len(score_list)):
  51. if score_list[i] < 80 and priority_list[i] == 0:
  52. rho = 0.8
  53. # calculate score
  54. score_all = np.dot(weight_list, score_list) * rho
  55. return score_all
  56. def cal_score_from_80(score_list):
  57. """
  58. """
  59. # weight process
  60. weight_list = cal_weight_from_80(score_list)
  61. # calculate score
  62. score_all = np.dot(weight_list, score_list)
  63. # score_all = score_all * 0.8 if flag else score_all
  64. return round(score_all, 2)
  65. class ScoreModel(object):
  66. """
  67. 信息量越大,权重越大
  68. 对比强度和冲突性越大,信息量越大
  69. 标准差越大,对比强度越大
  70. 相关性越小,冲突性越大
  71. ————————————————————————
  72. 单列标准差大,列之间相关性小 -> 则权重大
  73. """
  74. def __init__(self, kind_list, optimal_value, multiple_list, arr):
  75. # n for cases
  76. # m for indicators
  77. self.n, self.m = arr.shape
  78. self.kind = kind_list
  79. self.optimal = optimal_value
  80. self.X = arr
  81. self.rho = 1 / 3 # 一般选0.5,最低分计算公式: rho/(1+rho)
  82. self.multiple = np.array(multiple_list)
  83. def calculate_score(self):
  84. """
  85. 灰色关联理论
  86. :return:
  87. """
  88. # m个指标,n个场景
  89. val_mean = []
  90. optimal_value = self.optimal
  91. for i in range(self.m):
  92. opt_val = optimal_value[i]
  93. val_mean_i = (sum(self.X[:, i]) + opt_val) / (self.n + 1) # Eq(15)
  94. val_mean.append(val_mean_i)
  95. self.X = self.X / np.array(val_mean) # 无量纲化
  96. optimal_value = np.array(optimal_value) / np.array(val_mean) # 最优值无量纲化
  97. abs_X = abs(optimal_value - self.X)
  98. minn = 0
  99. maxx = 2 * (self.multiple[0][1] - 1) / (self.multiple[0][1] + 1) # 五倍时参数为1.333333,三倍时参数为1
  100. eta = (minn + self.rho * maxx) / (abs_X + self.rho * maxx) # Eq(16)
  101. Eta = [x * 80 for x in list(np.mean(eta, axis=0))]
  102. return Eta
  103. def cal_score(self):
  104. """
  105. 数据处理前进行特判,先将无需打分的数据直接给分
  106. 例如,大于基准值五倍的值,直接给出100分、0分
  107. 单列均为同一个值时,符合预期值则100分,否则0分
  108. 先完成单用例特判,再考虑多用例特判
  109. """
  110. # 单用例版本
  111. # for j in range(self.n):
  112. # multiple = 5
  113. inteval_20_coefficient = 3
  114. flag_list = [-1] * self.m
  115. column_list = []
  116. for i in range(self.m):
  117. optimal = self.optimal[i]
  118. multiple = self.multiple[i]
  119. if self.kind[i] == 1: # 极大型
  120. if np.all(self.X[:, i] >= optimal * multiple[1]): # 补充线性插值
  121. flag_list[i] = 100
  122. elif np.all(self.X[:, i] >= optimal):
  123. flag_list[i] = float(get_interpolation(self.X[:, i], [optimal, 80], [optimal * multiple[1], 100]))
  124. elif self.X[:, i] <= optimal * multiple[0]:
  125. flag_list[i] = 0
  126. else:
  127. column_list.append(i)
  128. elif self.kind[i] == -1: # 极小型
  129. if np.all(self.X[:, i] <= optimal * multiple[0]):
  130. flag_list[i] = 100
  131. elif np.all(self.X[:, i] <= optimal):
  132. flag_list[i] = float(get_interpolation(self.X[:, i], [optimal, 80], [optimal * multiple[0], 100]))
  133. elif self.X[:, i] >= optimal * multiple[1]:
  134. flag_list[i] = 0
  135. else:
  136. column_list.append(i)
  137. elif self.kind[i] == 0: # 区间型
  138. if np.all(optimal * multiple[0] <= self.X[:, i] <= optimal):
  139. flag_list[i] = float(
  140. get_interpolation(optimal - self.X[:, i], [abs(optimal - optimal * multiple[0]), 80], [0, 100]))
  141. elif np.all(optimal <= self.X[:, i] <= optimal * multiple[1]):
  142. flag_list[i] = float(
  143. get_interpolation(self.X[:, i] - optimal, [abs(optimal * multiple[1] - optimal), 80], [0, 100]))
  144. elif np.all(self.X[:, i] < optimal * multiple[0]):
  145. dist = optimal * multiple[0] - self.X[:, i]
  146. interval_dist = (optimal - optimal * multiple[0]) / inteval_20_coefficient
  147. if dist < interval_dist:
  148. flag_list[i] = float(get_interpolation(dist, [interval_dist, 20], [0, 80]))
  149. else:
  150. flag_list[i] = 0
  151. elif np.all(optimal * multiple[1] < self.X[:, i]):
  152. dist = self.X[:, i] - optimal * multiple[1]
  153. interval_dist = (optimal * multiple[1] - optimal) / inteval_20_coefficient
  154. if dist < interval_dist:
  155. flag_list[i] = float(get_interpolation(dist, [interval_dist, 20], [0, 80]))
  156. else:
  157. flag_list[i] = 0
  158. else:
  159. column_list.append(i)
  160. arr_temp = self.X[:, column_list]
  161. kind_temp = [self.kind[i] for i in range(len(flag_list)) if flag_list[i] == -1]
  162. optimal_temp = [self.optimal[i] for i in range(len(flag_list)) if flag_list[i] == -1]
  163. multiple_temp = [self.multiple[i] for i in range(len(flag_list)) if flag_list[i] == -1]
  164. # n_temp = len(arr_temp)
  165. m_temp = len(arr_temp[0])
  166. critic_m = ScoreModel(kind_temp, optimal_temp, multiple_temp, arr_temp)
  167. if -1 not in flag_list: # 全为特殊值
  168. score = sum(flag_list) / len(flag_list)
  169. elif all(x == -1 for x in flag_list): # 无特殊值
  170. score_temp = critic_m.calculate_score()
  171. # score = sum(score_temp) / len(score_temp)
  172. flag_list = score_temp
  173. else: # 部分为特殊值
  174. score_temp = critic_m.calculate_score()
  175. # score_temp_mean = sum(score_temp) / len(score_temp)
  176. # w_temp = m_temp / self.m
  177. # score = 100 * (1 - w_temp) + score_temp_mean * w_temp
  178. index = 0
  179. for i, flag in enumerate(flag_list):
  180. if flag == -1:
  181. flag_list[i] = score_temp[index]
  182. index += 1
  183. score_temp = flag_list
  184. return score_temp
  185. class AHP:
  186. def __init__(self, matrix):
  187. self.A = np.array(matrix)
  188. self.n = len(matrix)
  189. def _get_consistency_ratio(self, w_max):
  190. RI = [0, 0, 0.0001, 0.52, 0.89, 1.12, 1.26, 1.36,
  191. 1.41, 1.46, 1.49, 1.52, 1.54, 1.56, 1.58, 1.59,
  192. 1.5943, 1.6064, 1.6133, 1.6207, 1.6292]
  193. CI = (w_max - self.n) / (self.n - 1)
  194. CR = CI / RI[self.n]
  195. return CR
  196. def get_weights(self, method='eigenvalue'):
  197. # Check consistency of pairwise comparison matrix
  198. w, v = np.linalg.eig(self.A)
  199. w_index = np.argmax(w)
  200. w_max = np.real(w[w_index])
  201. cr = self._get_consistency_ratio(w_max)
  202. if cr > 0.1:
  203. raise ValueError('The pairwise comparison matrix is inconsistent.')
  204. # Normalize matrix
  205. line_sum = [sum(m) for m in zip(*self.A)]
  206. D = np.zeros((self.n, self.n))
  207. for i in range(self.n):
  208. for j in range(self.n):
  209. D[i][j] = self.A[i][j] / line_sum[j]
  210. # Calculate weights with selected method
  211. if method == 'arithmetic':
  212. weights = np.zeros(self.n)
  213. for i in range(self.n):
  214. weights[i] = np.average(D[i])
  215. elif method == 'geometric':
  216. weights = np.zeros(self.n)
  217. for i in range(self.n):
  218. weights[i] = reduce(lambda x, y: x * y, self.A[i])
  219. weights[i] = pow(weights[i], 1 / self.n)
  220. weights = [e / np.sum(weights) for e in weights]
  221. elif method == 'eigenvalue':
  222. weights = np.zeros(self.n)
  223. v_index = np.argmax(v)
  224. v_max = np.real(v[:, v_index])
  225. weights = [e / np.sum(v_max) for e in v_max]
  226. return weights
  227. if __name__ == "__main__":
  228. kind_list = [-1]
  229. optimal_value = [6]
  230. multiple_list = [[0.5, 2]] # [3, 12]
  231. arr = [[1.999]]
  232. # arr = [[2.1]]
  233. # arr = [[2.999]]
  234. # arr = [[3]]
  235. # arr = [[4]]
  236. # arr = [[6]]
  237. # arr = [[11]]
  238. # arr = [[11.999]]
  239. # arr = [[12]]
  240. # arr = [[12.1]]
  241. cc = ScoreModel(kind_list, optimal_value, multiple_list, np.array(arr))
  242. res = cc.cal_score()
  243. print(res)