limited_power_solar.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import pandas as pd
  2. import os, argparse
  3. import numpy as np
  4. np.random.seed(42)
  5. import matplotlib
  6. matplotlib.use('Agg')
  7. import matplotlib.pyplot as plt
  8. current_path = os.path.dirname(__file__)
  9. class LimitPower(object):
  10. def __init__(self, weather_power):
  11. self.opt = argparse.Namespace(**{'usable_power': {
  12. 'bias': 3.304,
  13. 'coe': 4,
  14. 'down_fractile': 30,
  15. 'env': 'C_GLOBALR',
  16. 'k': 0.03506,
  17. 'outliers_threshold': 1.5,
  18. 'up_fractile': 70}, 'cap': 30})
  19. self.weather_power = weather_power
  20. def segment_statis(self):
  21. """
  22. 对总辐射-实际功率进行分段处理,获取分度的中位点,四分位间距和斜率
  23. :return: glob_rp 总辐射分段
  24. """
  25. segs = [x for x in range(50, 2000, 100)] # 对辐照度以100为间隔进行分段
  26. xs = [segs[i-1]+x if i>0 else 25 for i, x in enumerate([50 for _ in segs])] # 分段的中间点
  27. glob_rp = {} # dict: key 辐照度分段中间点 value 分段内的实际功率
  28. for index, row in self.weather_power.iterrows():
  29. glob_ = row[self.opt.usable_power["env"]]
  30. rp = row['C_REAL_VALUE']
  31. for i, seg in enumerate(segs):
  32. if glob_ <= seg and not (i > 0 and rp < 1):
  33. glob_rp.setdefault(xs[i], []).append(rp)
  34. break
  35. for i, x in enumerate(xs):
  36. rps = glob_rp.get(x)
  37. if rps is None:
  38. glob_rp = {k: v for k, v in glob_rp.items() if k not in xs[xs.index(x):]}
  39. break
  40. x_l = xs[i-1] if i > 0 else 0
  41. q2_l = glob_rp[xs[i-1]][0] if i > 0 else 0
  42. q1 = np.percentile(rps, self.opt.usable_power['down_fractile']) # 实际功率下四分位点
  43. q2 = np.percentile(rps, 50) # 实际功率中位点
  44. q3 = np.percentile(rps, self.opt.usable_power['up_fractile']) # 实际功率上四分位点
  45. iqr = q3 -q1 # 四分位间距
  46. k1 = round(q2/x, 5) # 整体斜率
  47. k2 = round((q2-q2_l)/(x-x_l), 5) # 趋势斜率,相对上一个中位点
  48. glob_rp[x] = [q2, iqr, k1, k2] # 更新dict
  49. return glob_rp
  50. def mapping_relation(self, glob_rp):
  51. """
  52. 拟合分段处理后的斜率和偏移量
  53. :param glob_rp: 总辐射分段
  54. :return: k_final 斜率 bias 实际功率的分布宽度, glob_rp 总辐射分段
  55. """
  56. ks, iqrs, delete_x, tag_x = [], [], [], [] # ks所有分段斜率集合,iqrs所有分段间距集合,delete_x删除的x坐标集合
  57. for x, values in glob_rp.items():
  58. k1 = values[-2]
  59. k2 = values[-1]
  60. iqrs.append(values[-3])
  61. if k1 > 0 and k2 > 0: # 清除趋势小于等于0的斜率
  62. ks.append(k1)
  63. tag_x.append(x)
  64. else:
  65. delete_x.append(x)
  66. # print("删除的斜率:", k1, k2)
  67. bias = round(np.median(iqrs), 3) # 中位点
  68. # print("++++1", ks)
  69. mean = np.mean(ks) # 均值
  70. std = np.std(ks) # 标准差
  71. ks = np.array(ks)
  72. z_score = (ks-mean)/std # z均值
  73. # print("----", z_score)
  74. outliers = np.abs(z_score) > self.opt.usable_power['outliers_threshold'] # 超过阈值为离群点
  75. ks = ks[~outliers] # 消除离群点
  76. delete_x1 = list(np.array(tag_x)[outliers]) # 清除大于阈值的离群点
  77. k_final = round(np.mean(ks), 5) # 对清洗后的斜率做平均
  78. # print("++++2:", ks)
  79. delete_x.extend(delete_x1)
  80. print("拟合可用功率,删除的斜率:" + ' '.join([str(x) for x in delete_x]))
  81. glob_rp = {k: v for k, v in glob_rp.items() if k not in delete_x} # 清洗后剩下的分段点位
  82. return k_final, bias, glob_rp
  83. def filter_unlimited_power(self, zfs, real_power, k, b):
  84. """
  85. 预测可用功主方法
  86. :param zfs: 要预测可用功率的总辐射
  87. :param k: 斜率
  88. :param b: 偏移量
  89. :return: 预测的可用功率
  90. """
  91. high = k*zfs+b/2 if k*zfs+b/2 < self.opt.cap else self.opt.cap
  92. low = k*zfs-b/2 if k*zfs-b/2 > 0 else 0
  93. if low <= real_power <= high:
  94. return True
  95. else:
  96. return False
  97. def clean_limited_power(self, name, is_repair=False):
  98. if is_repair is True:
  99. glob_rp = self.segment_statis()
  100. k_final, bias, glob_rp = self.mapping_relation(glob_rp)
  101. self.opt.usable_power['k'] = float(k_final)
  102. self.opt.usable_power['bias'] = float(bias)
  103. new_weather_power = []
  104. for index, row in self.weather_power.iterrows():
  105. zfs = row[self.opt.usable_power["env"]]
  106. rp = row['C_REAL_VALUE']
  107. if self.filter_unlimited_power(zfs, rp, self.opt.usable_power['k'], self.opt.usable_power['bias'] * self.opt.usable_power['coe']):
  108. row['c'] = 'red'
  109. new_weather_power.append(row)
  110. else:
  111. row['c'] = 'blue'
  112. new_weather_power.append(row)
  113. new_weather_power = pd.concat(new_weather_power, axis=1).T
  114. new_weather_power.plot.scatter(x=self.opt.usable_power["env"], y='C_REAL_VALUE', c='c')
  115. plt.savefig(current_path + './data/测光法{}.png'.format(name))
  116. new_weather_power = new_weather_power[new_weather_power['c'] == 'red']
  117. number = len(new_weather_power)
  118. print("测光法-未清洗限电前,总共有:{}条数据".format(len(self.weather_power)))
  119. print("测光法-清除限电后保留的点有:" + str(number) + " 占比:" + str(round(number / len(self.weather_power), 2)))
  120. return new_weather_power.loc[:, ['C_TIME', 'C_REAL_VALUE', 'C_ABLE_VALUE']]
  121. def clean_limited_power_by_signal(self, name):
  122. weather_power1 = self.weather_power.copy()
  123. weather_power1["signal"] = weather_power1.apply(lambda x: self.signal_result(x["C_IS_RATIONING_BY_MANUAL_CONTROL"], x["C_IS_RATIONING_BY_AUTO_CONTROL"]), axis=1)
  124. weather_power1['c'] = weather_power1.apply(lambda x: 'cornflowerblue' if bool(x["signal"]) is True else 'pink', axis=1)
  125. weather_power1.plot.scatter(x=self.opt.usable_power["env"], y='C_REAL_VALUE', c='c')
  126. plt.savefig(current_path + '/figs/信号法{}.png'.format(name))
  127. weather_power1 = weather_power1[weather_power1['signal'] == False]
  128. print("信号法-未清洗限电前,总共有:{}条数据".format(len(self.weather_power)))
  129. print("信号法-清除限电后保留的点有:" + str(len(weather_power1)) + " 占比:" + str(round(len(weather_power1) / len(self.weather_power), 2)))
  130. return weather_power1.loc[:, ['C_TIME', 'C_REAL_VALUE', 'C_ABLE_VALUE']]
  131. def signal_result(self, manual, auto):
  132. if int(manual) == 0:
  133. if int(auto) == 0:
  134. return False
  135. else:
  136. return True
  137. else:
  138. if int(auto) == 1:
  139. return True
  140. else:
  141. return False
  142. if __name__ == '__main__':
  143. power = pd.read_csv('2023-12-01至2023-12-23实际功率导出文件.csv', date_parser=['时间'])
  144. weather = pd.read_csv('2023-12-01至2023-12-23气象站数据导出文件.csv', date_parser=['时间'])
  145. weather_power = pd.merge(weather, power, on='时间') # 联立数据
  146. # glob_rp = segment_statis(weather_power)
  147. # k_final, bias, glob_rp = mapping_relation(glob_rp)