Register
Login
Resources
Docs Blog Datasets Glossary Case Studies Tutorials & Webinars
Product
Data Engine LLMs Platform Enterprise
Pricing Explore
Connect to our Discord channel

max_current.py 8.5 KB

You have to be logged in to leave a comment. Sign In
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
  1. # Created by: leo
  2. # Created on: 2018.10.9
  3. import pandas as pd
  4. import numpy as np
  5. from typing import Tuple, Dict
  6. from sklearn.cluster import KMeans
  7. import logging
  8. from IPython import embed
  9. CONST_ZERO = -1.1 # 输出电流恒为零时的相关系数异常值
  10. CONST_NONZERO = -1.5 # 输出电流恒为非零时的相关系数异常值
  11. LOW_CURRENT = -2.5 # 低电流异常
  12. OUTLIER = -3 # 当有效数据量不足,或者输入为空时返回的异常值
  13. # 状态值区间的边界值列表,为了保证函数参数的不可变性,使用 Tuple
  14. BINS: Tuple[float] = (-100, OUTLIER, LOW_CURRENT, CONST_NONZERO, CONST_ZERO,
  15. 0, 0.5, 1)
  16. # 状态值区间名称列表
  17. LABELS: Tuple[str] = ('outlier', 'low_current', 'nonzero_constant', 'all_zeros',
  18. 'warn', 'watch', 'normal')
  19. TIME_HEADER = 'time'
  20. CLUSTER_HEADER = 'strno'
  21. CURRENT_HEADER = 'current'
  22. def coef_maxcur(inp: pd.DataFrame, min_sample_no: int = 2) -> pd.Series:
  23. """
  24. 基于最大电流法计算指定数据集所在的时间窗口内所有组串的相关系数向量
  25. :param inp: 包含时间和电流数值的 Dataframe,需包含3列:时间戳, 组串ID, 电流值
  26. :param min_sample_no: 最小有效观测数,数据清洗后如果小于此值则返回异常值
  27. :returns: 包含每个组串与最大电流所在组串比较的相关系数向量,长度为 inp 内包含所有组串数量
  28. """
  29. if len(inp) == 0:
  30. return pd.Series([OUTLIER])
  31. # 若输入为纵表,转换为宽表(组串ID作列名)
  32. if CLUSTER_HEADER in inp.columns:
  33. raw = pd.pivot_table(inp, values=CURRENT_HEADER, index=[TIME_HEADER], columns=[CLUSTER_HEADER])
  34. data = raw.dropna()
  35. data.index = pd.to_datetime(data.index)
  36. else:
  37. data = inp
  38. # 校验有效数据长度,若低于最小阈值则返回异常指标序列
  39. if len(data) < min_sample_no:
  40. return pd.Series([OUTLIER])
  41. max_current_id = data.apply(max).idxmax()
  42. cors = data.corrwith(data[max_current_id])
  43. # 由于 numpy.var(常数向量) 恒等于 0,所以一个向量与常数向量的
  44. # [pearson correlation coefficient](https://en.wikipedia.org/wiki/Pearson_correlation_coefficient#Definition)
  45. # 值为 NaN(分母中有零),如果一个常数向量是零向量,说明光伏板坏了(没有电流),
  46. # 如果是非零常数向量,说明是传感器坏了
  47. errs = cors[cors.isna()]
  48. for idx in errs.index:
  49. if sum(abs(data[idx])) < 1e-7:
  50. cors[idx] = CONST_ZERO
  51. else:
  52. cors[idx] = CONST_NONZERO
  53. return cors
  54. def string_coef_maxcur(inp, str_id: int) -> float:
  55. """
  56. 基于最大电流法计算指定数据集所在的窗口时间内指定组串的电流相关性系数
  57. :param inp: 包含时间和电流数值的 Dataframe,需包含3列:时间戳, 组串ID, 电流值
  58. :param str_id: 指定组串的ID
  59. :return: 指定组串与最大电流所在组串比较的相关系数
  60. """
  61. coefs = coef_maxcur(inp)
  62. if len(coefs) == 1:
  63. return coefs[0]
  64. return coefs[str_id]
  65. def max_current_monthly(inp: pd.DataFrame) -> pd.Series:
  66. """
  67. 指定数据集中每月的最大电流值
  68. :param inp: 包含时间和电流数值的 Dataframe,需包含3列:时间戳, 组串ID, 电流值
  69. :return: 每月的最大电流值
  70. """
  71. inp.index = pd.to_datetime(inp.index)
  72. months = inp.groupby(pd.Grouper(freq='M'))
  73. return months[CURRENT_HEADER].max()
  74. def cur_month_max(inp: pd.DataFrame, max_cur_table: pd.Series, thr: float) -> float:
  75. """
  76. 计算输入数据所在月份的最低合理电流阈值
  77. :param inp: 包含一个时间段的数据分组。例如包含一天所有组串电流值的 dataframe
  78. :param max_cur_table: 包含所有月份对大电流值的数据表
  79. :param thr: 月度最大电流的百分比
  80. :return: 输入数据所在月份的最低合理电流阈值
  81. """
  82. if inp.empty:
  83. return np.inf
  84. return max_cur_table[inp.first_valid_index().strftime('%Y-%m')].values[0] * thr
  85. def get_thr_from_coefs(coef_table: pd.DataFrame, ngrp: int = 3) -> np.ndarray:
  86. """
  87. 根据组串相关系数计算分隔阈值
  88. 阈值计算方法:
  89. 1. 去掉相关系数中小于 -1 的值
  90. 2. 对剩余阈值做聚类
  91. 3. 对于从大到小顺序排序的 n (n=3) 个聚类,取每个条带下边界再向下聚类宽度的 20% 作为备选分隔阈值,
  92. 与下个聚类的上界比较,取较大者作为最终阈值
  93. :param coef_table: 包含各个组串每天相关系数的dict,组串ID作为key,value是这个组串在整个时域上每天的相关系数
  94. :param ngrp: 分组数量,默认分为3组:正常、关注、告警
  95. :return: 3个聚类间的2个分隔点
  96. """
  97. coef_set = np.unique(coef_table)
  98. without_outlier = np.extract(coef_set > -1, coef_set)
  99. # without_outlier = [x for x in coef_set if x > -1] # 小于 -1 的异常值不参与聚类分组
  100. # embed()
  101. coef_arr = np.array(without_outlier).reshape(-1, 1)
  102. kmeans = KMeans(n_clusters=ngrp, random_state=1) # the random_state make the group ID in clustering result fixed
  103. kmeans.fit(coef_arr)
  104. logging.debug('Cluster centroid positions:\n%s' % np.sort(kmeans.cluster_centers_, axis=0))
  105. id_coef_df = pd.DataFrame({'id': kmeans.predict(coef_arr),
  106. 'coefs': np.array(without_outlier)})
  107. coef_grps = id_coef_df.groupby('id')
  108. upper_bounds = np.sort(coef_grps.max(), axis=0)
  109. lower_bounds = np.sort(coef_grps.min(), axis=0)
  110. thr_cands = lower_bounds - (upper_bounds - lower_bounds) * 0.2
  111. logging.debug('cluster upper bounds:\n%s' % upper_bounds)
  112. logging.debug('cluster lower bounds:\n%s' % lower_bounds)
  113. return np.maximum(upper_bounds[:-1], thr_cands[1:])
  114. def calc_maxcur_coefs(inp: pd.DataFrame, thr: float) -> pd.DataFrame:
  115. """
  116. 基于最大电流方法计算所有组串在输入 DataFrame 上的所有相关系数,作为后续聚类的输入
  117. :param inp: 包含多个月份一个汇流箱内所有组串电流值的数据集,需包含3列:时间戳, 组串ID, 电流值
  118. :param thr: 月度最大电流的百分比,低于此阈值的数据被分入低电流组
  119. :return: DataFrame, index 为时间,列为输入 DataFrame 中包含的所有组串ID
  120. """
  121. max_month_cur = max_current_monthly(inp)
  122. raw = pd.pivot_table(inp, values=CURRENT_HEADER, index=[TIME_HEADER], columns=[CLUSTER_HEADER])
  123. data = raw.dropna()
  124. data.index = pd.to_datetime(data.index)
  125. daily = data.groupby(pd.Grouper(freq='D'))
  126. # 训练过程中不必区分低电流、全零电流和空数据,作为无效数据全部剔除即可
  127. # 相应地,最大电流法训练过程中标记为 OUTLIER 的值不一定是空数据,也有可能是低电流或者全零电流
  128. normal_cur = daily.filter(lambda x: max(x.max()) > cur_month_max(x, max_month_cur, thr))
  129. normal_cur_grp = normal_cur.groupby(pd.Grouper(freq='D'))
  130. coef_tbl = {str_id: normal_cur_grp.apply(string_coef_maxcur, str_id) for str_id in normal_cur.columns}
  131. return pd.DataFrame(coef_tbl)
  132. def train_thresholds(inp: pd.DataFrame, thr: float) -> np.ndarray:
  133. """
  134. 光伏故障分类算子训练函数,计算分隔阈值
  135. :param inp: 包含多个月份一个汇流箱内所有组串电流值的数据集,需包含3列:时间戳, 组串ID, 电流值
  136. :param thr: 月度最大电流的百分比,低于此阈值的数据被分入低电流组
  137. :return: 二元组,分别为正常、关注、告警组的分隔阈值
  138. """
  139. coefs = calc_maxcur_coefs(inp, thr)
  140. return get_thr_from_coefs(coefs)
  141. def classifier(inp: pd.DataFrame, lowest_peak_current: float,
  142. borders: Tuple[float] = BINS,
  143. status_names: Tuple[str] = LABELS) -> pd.Series:
  144. """
  145. 根据输入参数对输出数据做状态分类
  146. :param inp: 输入电流数据,需包含3列:时间戳, 组串ID, 电流值
  147. :param lowest_peak_current: 合理电流最低阈值
  148. :param borders: 状态边界值列表
  149. :param status_names: 状态名称列表,长度必须比状态边界列表长度小1
  150. :return: 输入数据集中每个组串的状态
  151. """
  152. peak_current = inp[CURRENT_HEADER].max()
  153. logging.debug('peak current: %s' % peak_current)
  154. if peak_current < lowest_peak_current:
  155. return pd.Series("low_current", index=inp[CLUSTER_HEADER].unique())
  156. coefs = coef_maxcur(inp)
  157. logging.debug('coefs:\n%s' % coefs)
  158. return pd.cut(coefs, bins=borders, labels=status_names)
Tip!

Press p or to see the previous file or, n or to see the next file

Comments

Loading...