单选址问题

比较了1)迭代法,2)重心法和3)one-median法的差异。总体而言从加权距离最小的角度看,迭代法选出来的结果最好。当网点和网点的单量呈现偏态分布时,one-median的结果更好,当网点和网点的单量呈现呈现随机分布时,重心法和one-median没有明显的差异。
参考链接:https://wiki.mbalib.com/zh-tw/%E9%87%8D%E5%BF%83%E6%B3%95

代码

  1. import numpy as np
  2. import pandas as pd
  3. import random
  4. import math as m
  5. import matplotlib
  6. import matplotlib.pyplot as plt
  7. import matplotlib.font_manager as fm
  8. plt.rcParams['font.sans-serif'] = [u'SimHei']
  9. """
  10. 单选址问题
  11. 比较了1)迭代法,2)重心法和3)one-median法的差异。总体而言从加权距离最小的角度看,迭代法选出来的结果最好。当网点和网点的单量呈现偏态分布时,one-median的结果更好,当网点和网点的单量呈现呈现随机分布时,重心法和one-median没有明显的差异。
  12. 参考链接:https://wiki.mbalib.com/zh-tw/%E9%87%8D%E5%BF%83%E6%B3%95
  13. """
  14. class Weight:
  15. def __init__(self):
  16. pass
  17. def generate_data_handle(self):
  18. # 生成数据
  19. x_list = [6, 4, 1, 9, 3]
  20. y_list = [7, 2, 6, 3, 10]
  21. w_list = [8000, 7000, 5000, 4000, 6000]
  22. cost_list = [1, 1, 1, 1, 1]
  23. data = pd.DataFrame({'x': x_list, 'y':y_list, 'w': w_list, 'cost': cost_list})
  24. data['w'] = data['w'] * data['cost']
  25. data['w^2'] = data['w'] * data['w']
  26. self.data = data
  27. def generate_data_random(self, batch_size=20, p_lb=0, p_ub=100):
  28. # 生成数据
  29. y_list = [random.randint(60, 110) for i in range(batch_size)]
  30. x_list = [random.randint(20, 50) for i in range(batch_size)]
  31. w_list = [random.randint(10, 100)*x_list[i] for i in range(batch_size)]
  32. cost_list = [1 for i in range(batch_size)]
  33. data = pd.DataFrame({'x': x_list, 'y': y_list, 'w': w_list, 'cost': cost_list})
  34. data['w'] = data['w'] * data['cost']
  35. data['w^2'] = data['w'] * data['w']
  36. self.data = data
  37. def generate_data_skewness(self, batch_size=20, p_lb=0, p_ub=100):
  38. # 生成数据
  39. y_list = [random.randint(60, 110) for i in range(2*batch_size)]
  40. y_list.extend([random.randint(80, 110) for i in range(2*batch_size)])
  41. x_list = [random.randint(20, 50) for i in range(2*batch_size)]
  42. x_list.extend([random.randint(40, 50) for i in range(2*batch_size)])
  43. w_list = [random.randint(10, 100)*x_list[i] for i in range(4*batch_size)]
  44. cost_list = [1 for i in range(4*batch_size)]
  45. data = pd.DataFrame({'x': x_list, 'y': y_list, 'w': w_list, 'cost': cost_list})
  46. data['w'] = data['w'] * data['cost']
  47. data['w^2'] = data['w'] * data['w']
  48. self.data = data
  49. def compute_node_weight(self, data, weight_col_name, d_j):
  50. # 普通重心法:加权均值:(x0, y0)
  51. WC = np.array(data[weight_col_name])
  52. WC_j = WC / d_j
  53. WCX = ((np.array(data['x']) * WC)/d_j).sum()
  54. WCY = ((np.array(data['y']) * WC)/d_j).sum()
  55. x = WCX / WC_j.sum()
  56. y = WCY / WC_j.sum()
  57. d_j = ((np.array(data['x']) - x) ** 2 + (np.array(data['y']) - y) ** 2) ** 0.5
  58. transportation_cost = (np.array(data['w']) * d_j).sum()
  59. return x, y, d_j, transportation_cost
  60. def run(self, iter_num=10):
  61. # one - median法: 加权平方均值
  62. x2, y2, d_j, transportation_cost2 = self.compute_node_weight(self.data, 'w^2', 1)
  63. print('加权平方重心选点大致位置:({},{}), 总运输费用:{}'.format(x2, y2, transportation_cost2))
  64. # 加权均值
  65. x0, y0, d_j, transportation_cost0 = self.compute_node_weight(self.data, 'w', 1)
  66. print('重心法选点大致位置:({},{}), 总运输费用:{}'.format(x0, y0, transportation_cost0))
  67. self.p0 = [x0, y0, transportation_cost0]
  68. self.p2 = [x2, y2, transportation_cost2]
  69. min_transportation_cost = transportation_cost0
  70. result_list = [[x0, y0, transportation_cost0, 0]]
  71. for i in range(iter_num):
  72. x, y, d_j, transportation_cost = self.compute_node_weight(self.data, 'w', d_j)
  73. result_list.append([x, y, transportation_cost, i+1])
  74. print('经{}次迭代后选址点位置:({},{}), 总运输费用:{}'.format(i + 1, x, y, transportation_cost))
  75. if min_transportation_cost - transportation_cost < 1:
  76. break
  77. min_transportation_cost = transportation_cost
  78. self.result = result_list
  79. def plot_figure(self):
  80. # 初始化函数用于绘制一块干净的画布,为后续绘图做准备
  81. result = self.result
  82. p0, p2 = self.p0, self.p2
  83. scatter_size = 200 # [200, 200, 200, 200, 200, 200]
  84. # 生成画布
  85. # plt.figure(figsize=(6, 6), dpi=80)
  86. plt.subplot(1, 2, 1)
  87. # 打开交互模式
  88. plt.ion()
  89. for step in range(len(result)):
  90. # 清除原有图像
  91. plt.cla()
  92. position = result[step]
  93. plt.scatter(self.data['x'], self.data['y'], scatter_size, c='pink', marker='*', alpha=0.7, label='配送点')
  94. plt.scatter(p0[0], p0[1], scatter_size, c='blue', marker='p', alpha=0.7, label='加权重心')
  95. plt.scatter(p2[0], p2[1], scatter_size, c='green', marker='p', alpha=0.7, label='加权平方重心')
  96. plt.scatter(position[0], position[1], scatter_size, c='red', marker='p', alpha=0.7, label='选址点')
  97. plt.xlabel('纬度', fontsize=11)
  98. plt.ylabel('经度', fontsize=11)
  99. plt.grid(True)
  100. # plt.text(1,3,'总费用{}'.format(T),fontsize=16)
  101. plt.title('重心法选址,第{}次结果示意图'.format(step + 1), fontsize=14)
  102. # plt.title('重心法选址结果示意图', fontsize=14)
  103. plt.legend(loc='lower left')
  104. plt.pause(0.2)
  105. # 关闭交互模式
  106. plt.ioff()
  107. # 图形显示
  108. # plt.show()
  109. def plot_iteration(self):
  110. result = self.result
  111. p0, p2 = self.p0, self.p2
  112. iter_num = len(result)
  113. # 生成画布
  114. # plt.figure(figsize=(6, 6), dpi=80)
  115. plt.subplot(1, 2, 2)
  116. plt.plot([0, iter_num-1], [p0[2], p0[2]], c='blue', label='加权重心')
  117. plt.plot([0, iter_num-1], [p2[2], p2[2]], c='green', label='加权平方重心')
  118. plt.plot([i for i in range(iter_num)], [p[2] for p in result], c='red', label='重心法')
  119. plt.xlabel('迭代次数', fontsize=11)
  120. plt.ylabel('总运输成本', fontsize=11)
  121. plt.grid(True)
  122. plt.title('重心法选址结果示意图', fontsize=14)
  123. plt.legend(loc='lower left')
  124. plt.show()
  125. def plot_figure_subplot(self):
  126. # 初始化函数用于绘制一块干净的画布,为后续绘图做准备
  127. result = self.result
  128. p0, p2 = self.p0, self.p2
  129. scatter_size = 200 # [200, 200, 200, 200, 200, 200]
  130. iter_num = len(result)
  131. # 生成画布
  132. plt.figure(figsize=(10, 6), dpi=80)
  133. plt.subplot(1, 2, 1)
  134. # 打开交互模式
  135. plt.ion()
  136. for step in range(len(result)):
  137. # 清除原有图像
  138. plt.cla()
  139. position = result[step]
  140. plt.scatter(self.data['x'], self.data['y'], scatter_size, c='pink', marker='*', alpha=0.7, label='配送点')
  141. plt.scatter(p0[0], p0[1], scatter_size, c='blue', marker='p', alpha=0.7, label='重心法')
  142. plt.scatter(p2[0], p2[1], scatter_size, c='green', marker='p', alpha=0.7, label='one-median')
  143. plt.scatter(position[0], position[1], scatter_size, c='red', marker='p', alpha=0.7, label='迭代法选址点')
  144. plt.xlabel('纬度', fontsize=11)
  145. plt.ylabel('经度', fontsize=11)
  146. plt.grid(True)
  147. # plt.text(1,3,'总费用{}'.format(T),fontsize=16)
  148. plt.title('重心法选址,第{}次结果示意图'.format(step + 1), fontsize=14)
  149. # plt.title('重心法选址结果示意图', fontsize=14)
  150. plt.legend(loc='lower left')
  151. plt.pause(0.2)
  152. # 关闭交互模式
  153. plt.ioff()
  154. plt.subplot(1, 2, 2)
  155. plt.plot([0, iter_num-1], [p0[2], p0[2]], c='blue', label='重心法')
  156. plt.plot([0, iter_num-1], [p2[2], p2[2]], c='green', label='one-median')
  157. plt.plot([i for i in range(iter_num)], [p[2] for p in result], c='red', label='迭代法选址点')
  158. plt.xlabel('迭代次数', fontsize=11)
  159. plt.ylabel('总运输成本', fontsize=11)
  160. plt.grid(True)
  161. plt.title('重心法选址结果示意图', fontsize=14)
  162. plt.legend(loc='lower left')
  163. plt.show()
  164. if __name__ == "__main__":
  165. weight_compare = Weight()
  166. # 生成小测试算例
  167. # weight_compare.generate_data_handle()
  168. # 生成随机数
  169. # weight_compare.generate_data_random(batch_size=30, p_lb=0, p_ub=100)
  170. # 生成偏态分布
  171. weight_compare.generate_data_skewness(batch_size=20)
  172. weight_compare.run(iter_num=50)
  173. # weight_compare.plot_iteration()
  174. # weight_compare.plot_figure()
  175. weight_compare.plot_figure_subplot()

结果展示

image.pngimage.png
数据为随机分布的结果 数据为偏态分布的结果

注:应该重负做100次试验,比较成本的统计结果