tsadmetrics 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,320 @@
1
+ import numpy as np
2
+ from _tsadeval.metrics import Binary_anomalies, pointwise_to_full_series, segmentwise_to_full_series, DelayThresholdedPointAdjust
3
+ def get_tp_tn_fp_fn_point_wise(y_true: np.array,y_pred: np.array):
4
+ TP,TN,FP,FN=0,0,0,0
5
+ for true,pred in zip(y_true,y_pred):
6
+ if true==pred:
7
+ if true==1:
8
+ TP+=1
9
+ else:
10
+ TN+=1
11
+ else:
12
+ if true==1:
13
+ FN+=1
14
+ else:
15
+ FP+=1
16
+ return TP,TN,FP,FN
17
+
18
+
19
+ def get_events(y_true,anomaly=True):
20
+ events = []
21
+ start_idx = None
22
+ v = 0
23
+ if anomaly:
24
+ v = 1
25
+ else:
26
+ v = 0
27
+
28
+ for i, val in enumerate(y_true):
29
+ if val == v: # Si encontramos el inicio de un evento
30
+ if start_idx is None:
31
+ start_idx = i # Establecemos el inicio del evento
32
+ elif start_idx is not None: # Si encontramos el final de un evento
33
+ events.append((start_idx, i - 1)) # Agregamos el evento a la lista de eventos
34
+ start_idx = None # Restablecemos el inicio del evento
35
+
36
+ if start_idx is not None: # Si al final de la secuencia aún estamos dentro de un evento
37
+ events.append((start_idx, len(y_true) - 1)) # Agregamos el evento final a la lista de eventos
38
+
39
+
40
+ return events
41
+
42
+ def calculate_intersection(event1, event2):
43
+ start_intersection = max(event1[0], event2[0])
44
+ end_intersection = min(event1[1], event2[1])
45
+
46
+ # If there is an intersection, return the range of the intersection, otherwise return None
47
+ if start_intersection <= end_intersection:
48
+ return [start_intersection, end_intersection]
49
+ else:
50
+ return None
51
+
52
+ def get_tp_tn_fp_fn_point_adjusted(y_true: np.array,y_pred: np.array):
53
+ TP, TN, FP, FN = get_tp_tn_fp_fn_point_wise(y_true, y_pred)
54
+ TP=0
55
+ FN=0
56
+ y_true_events = get_events(y_true,anomaly=True)
57
+ y_pred_events = get_events(y_pred,anomaly=True)
58
+
59
+ i_true = 0
60
+ i_pred = 0
61
+ while i_true<len(y_true_events):
62
+ detected = False
63
+ while i_pred<len(y_pred_events) and y_true_events[i_true][1]>y_pred_events[i_pred][0]:
64
+ if calculate_intersection(y_true_events[i_true],y_pred_events[i_pred]) is not None:
65
+ TP+= y_true_events[i_true][1]-y_true_events[i_true][0]+1
66
+ detected=True
67
+ break
68
+ elif y_true_events[i_true][0]>y_pred_events[i_pred][1]:
69
+ i_pred+=1
70
+
71
+ if not detected:
72
+ FN+= y_true_events[i_true][1]-y_true_events[i_true][0]+1
73
+ i_true+=1
74
+
75
+ return TP, TN, FP, FN
76
+
77
+ def get_tp_tn_fp_fn_delay_th_point_adjusted(y_true: np.array,y_pred: np.array,k: int):
78
+ TP, TN, FP, FN = get_tp_tn_fp_fn_point_wise(y_true, y_pred)
79
+ TP=0
80
+ FN=0
81
+ y_true_events = get_events(y_true,anomaly=True)
82
+ y_pred_events = get_events(y_pred,anomaly=True)
83
+
84
+ i_true = 0
85
+ i_pred = 0
86
+ while i_true<len(y_true_events):
87
+ detected = False
88
+ while i_pred<len(y_pred_events) and y_true_events[i_true][1]>y_pred_events[i_pred][0]:
89
+ intersec = calculate_intersection(y_true_events[i_true],y_pred_events[i_pred])
90
+ if intersec is not None and intersec[0]-y_true_events[i_true][0]<k:
91
+ TP+= y_true_events[i_true][1]-y_true_events[i_true][0]+1
92
+ detected=True
93
+ break
94
+ else:
95
+ i_pred+=1
96
+
97
+ if not detected:
98
+ FN+= y_true_events[i_true][1]-y_true_events[i_true][0]+1
99
+ i_true+=1
100
+
101
+ return TP, TN, FP, FN
102
+
103
+ def get_tp_tn_fp_fn_point_adjusted_at_k(y_true: np.array,y_pred: np.array, k: float):
104
+ TP, TN, FP, FN = get_tp_tn_fp_fn_point_wise(y_true, y_pred)
105
+ TP=0
106
+ FN=0
107
+ y_true_events = get_events(y_true,anomaly=True)
108
+ y_pred_events = get_events(y_pred,anomaly=True)
109
+
110
+ i_true = 0
111
+ i_pred = 0
112
+ while i_true<len(y_true_events):
113
+ detected = False
114
+ while i_pred<len(y_pred_events) and y_true_events[i_true][1]>y_pred_events[i_pred][0]:
115
+ intersec = calculate_intersection(y_true_events[i_true],y_pred_events[i_pred])
116
+ if intersec is not None:
117
+ event_size = y_true_events[i_true][1]-y_true_events[i_true][0]+1
118
+ intersec_size = intersec[1]-intersec[0]+1
119
+ if intersec is not None and intersec_size/event_size>=k:
120
+
121
+ TP+= y_true_events[i_true][1]-y_true_events[i_true][0]+1
122
+ detected=True
123
+ break
124
+ else:
125
+ i_pred+=1
126
+
127
+ if not detected:
128
+ FN+= y_true_events[i_true][1]-y_true_events[i_true][0]+1
129
+ i_true+=1
130
+
131
+ return TP, TN, FP, FN
132
+
133
+
134
+ def get_tp_tn_fp_fn_latency_sparsity_aw(y_true: np.array, y_pred: np.array, ni: int):
135
+ batched_shape = (int(np.ceil(y_pred.shape[0] / ni)), 1)
136
+ label_batch = np.zeros(batched_shape)
137
+ pred_batch = np.zeros(batched_shape)
138
+ actual = np.copy(y_true)
139
+ predict = np.copy(y_pred)
140
+ detect_state = False # triggered when a True anomaly is detected by model
141
+ anomaly_batch_count = 0
142
+ i, i_ni = 0, 0
143
+ step = ni
144
+
145
+ while i < len(y_true) and step > 1:
146
+ j = min(i + step, len(y_true)) # end of ni (batch) starting at i
147
+
148
+ # Adjust step size if needed
149
+ if step > 2 and actual[i:j].sum() > 1:
150
+ if np.diff(np.where(actual[i:j])).max() > 1: # if it finds an interruption in the true label continuity
151
+ step = min(int((j - i) / 2), 2) # reduce step size
152
+ label_batch = np.append(label_batch, [[0]], axis=0)
153
+ pred_batch = np.append(pred_batch, [[0]], axis=0) # increase size
154
+ j = i + step
155
+ else:
156
+ step = ni
157
+ else:
158
+ step = ni
159
+
160
+ # Start rolling window scoring
161
+ if actual[i:j].max(): # If label = T
162
+ if not actual[i]: # if first value is normal
163
+ detect_state = False
164
+ s = actual[i:j].argmax() # this is the index of the first occurrence
165
+ if detect_state: # if anomaly was previously detected by model
166
+ anomaly_batch_count += 1
167
+ pred_batch[i_ni], label_batch[i_ni], predict[i + s:j] = 1, 1, 1
168
+ elif predict[i:j].max(): # if alert was detected with T
169
+ detect_state = True # turn on detection state
170
+ anomaly_batch_count += 1
171
+ pred_batch[i_ni], label_batch[i_ni], predict[i + s:j] = 1, 1, 1
172
+ else:
173
+ detect_state = False
174
+ label_batch[i_ni] = 1
175
+ else:
176
+ detect_state = False
177
+ if predict[i:j].max(): # if False positive
178
+ pred_batch[i_ni] = 1
179
+ i += step
180
+ i_ni += 1
181
+
182
+ if ni == 1:
183
+ return get_tp_tn_fp_fn_point_wise(actual, predict)
184
+
185
+ return get_tp_tn_fp_fn_point_wise(label_batch.flatten().astype(int), pred_batch.flatten().astype(int))
186
+
187
+ def get_tp_fp_fn_segment_wise(y_true: np.array,y_pred: np.array):
188
+
189
+
190
+ y_true_anomaly_events = get_events(y_true)
191
+ pred_anomaly_events = get_events(y_pred)
192
+ y_true_normal_events = get_events(y_true,False)
193
+ pred_normal_events = get_events(y_pred,False)
194
+
195
+ TP = 0
196
+ FN = 0
197
+ FP = 0
198
+ #TP
199
+ i = 0
200
+ for e_p in pred_anomaly_events:
201
+
202
+ c, d = e_p
203
+ while i<len(y_true_anomaly_events):
204
+ e_g = y_true_anomaly_events[i]
205
+ a, b = e_g
206
+ if a>d:
207
+ break
208
+
209
+ if b<c:
210
+ i+=1
211
+ continue
212
+
213
+ else:
214
+ if max(a, c) <= min(b, d):
215
+ TP+=1
216
+
217
+
218
+ i+=1
219
+
220
+ #FN
221
+ FN = len(y_true_anomaly_events) - TP
222
+ #FP
223
+ i = 0
224
+ for e_p in y_true_normal_events:
225
+
226
+ c, d = e_p
227
+ while i<len(pred_anomaly_events):
228
+ e_g = pred_anomaly_events[i]
229
+ a, b = e_g
230
+ if a>d:
231
+ break
232
+ if b<c:
233
+ i+=1
234
+ continue
235
+ if calculate_intersection(e_g, e_p) is not None:
236
+ FP+=1
237
+ i+=1
238
+ return TP, FP, FN
239
+
240
+ def is_full_series(length: int, anomalies: np.array):
241
+ # [1 0 1 1 0]
242
+ return len(anomalies.shape) == 1 and len(anomalies) == length
243
+
244
+ def is_pointwise(length: int, anomalies: np.array):
245
+ # [0 2 3]
246
+ return len(anomalies.shape) == 1 and len(anomalies) < length
247
+
248
+ def is_segmentwise(length: int, anomalies: np.array):
249
+ # [[0 0] [2 3]]
250
+ return len(anomalies.shape) == 2
251
+
252
+
253
+ def transform_to_full_series(length: int, anomalies: np.array):
254
+ if is_full_series(length, anomalies):
255
+ return anomalies
256
+ elif is_pointwise(anomalies):
257
+ return pointwise_to_full_series(anomalies, length)
258
+ elif is_segmentwise(length, anomalies):
259
+ return segmentwise_to_full_series(anomalies, length)
260
+ else:
261
+ raise ValueError(f"Illegal shape of anomalies:\n{anomalies}")
262
+
263
+ def counting_method(y_true: np.array, y_pred: np.array, k: int):
264
+ em,da,ma,fa = 0,0,0,0
265
+ for gt,pa in zip(y_true,y_pred):
266
+ if gt==1 and pa==1:
267
+ em+=1
268
+ elif gt==0 and pa==1:
269
+ fa+=1
270
+ else:
271
+ pass
272
+ b = DelayThresholdedPointAdjust(len(y_true),y_true,y_pred,k=k)
273
+ da = b.tp-em
274
+ ma = b.fn
275
+
276
+ return em,da,ma,fa
277
+
278
+
279
+ #Range Based utils
280
+
281
+ def cardinality(n_intersections,mode):
282
+ if mode == 'one':
283
+ return 1
284
+ elif mode == 'reciprocal':
285
+ if n_intersections==0:
286
+ return 1
287
+ else:
288
+ return float(1/n_intersections)
289
+ else:
290
+ raise Exception("Error, wrong cardinality mode.")
291
+
292
+
293
+ def size(anomaly_range, overlap_set, position, bias):
294
+ if overlap_set == None:
295
+ return 0
296
+
297
+ my_value = 0
298
+ max_value = 0
299
+ anomaly_length = anomaly_range[1] - anomaly_range[0] + 1
300
+ for i in range(1,anomaly_length+1):
301
+ bias_value = position(i, anomaly_length,bias)
302
+ max_value += bias_value
303
+ if anomaly_range[0]+i-1 >= overlap_set[0] and anomaly_range[0]+i-1 <= overlap_set[1]:
304
+ my_value += bias_value
305
+ return my_value / max_value
306
+
307
+ def position(i, anomaly_length,bias):
308
+ if bias == 'flat':
309
+ return 1
310
+ elif bias == 'front-end':
311
+ return anomaly_length - i + 1
312
+ elif bias == 'back-end':
313
+ return i
314
+ elif bias == 'middle':
315
+ if i <= anomaly_length / 2:
316
+ return i
317
+ else:
318
+ return anomaly_length - i + 1
319
+ else:
320
+ raise Exception("Error, wrong bias value.")
@@ -0,0 +1,92 @@
1
+ import numpy as np
2
+ from _tsadeval.metrics import *
3
+ from .metric_utils import transform_to_full_series
4
+ from sklearn.metrics import auc
5
+ from .binary_metrics import point_adjusted_precision, point_adjusted_recall, segment_wise_precision, segment_wise_recall
6
+ from pate.PATE_metric import PATE
7
+ def precision_at_k(y_true : np.array ,y_anomaly_scores: np.array):
8
+
9
+ m = PatK_pw(y_true,y_anomaly_scores)
10
+
11
+ return m.get_score()
12
+
13
+ def auc_roc_pw(y_true : np.array ,y_anomaly_scores: np.array):
14
+
15
+ m = AUC_ROC(y_true,y_anomaly_scores)
16
+
17
+ return m.get_score()
18
+
19
+
20
+ def auc_pr_pw(y_true : np.array ,y_anomaly_scores: np.array):
21
+
22
+ m = AUC_PR_pw(y_true,y_anomaly_scores)
23
+
24
+ return m.get_score()
25
+
26
+
27
+
28
+ def auc_pr_pa(y_true: np.array, y_anomaly_scores: np.array):
29
+ thresholds = np.unique(y_anomaly_scores)[::-1] # Descending order
30
+ precisions = [1]
31
+ recalls = [0]
32
+ for t in thresholds[:-1]:
33
+
34
+ y_pred = (y_anomaly_scores >= t).astype(int)
35
+
36
+
37
+ precisions.append(point_adjusted_precision(y_true, y_pred))
38
+ recalls.append(point_adjusted_recall(y_true, y_pred))
39
+
40
+ recalls.append(1)
41
+ precisions.append(0)
42
+ auc_value = auc(recalls, precisions)
43
+ return auc_value
44
+
45
+
46
+
47
+
48
+ def auc_pr_sw(y_true: np.array, y_anomaly_scores: np.array):
49
+ thresholds = np.unique(y_anomaly_scores)[::-1] # Descending order
50
+ precisions = [1]
51
+ recalls = [0]
52
+
53
+ for t in thresholds[:-1]:
54
+ y_pred = (y_anomaly_scores >= t).astype(int)
55
+ precisions.append(segment_wise_precision(y_true, y_pred))
56
+ recalls.append(segment_wise_recall(y_true, y_pred))
57
+ recalls.append(1)
58
+ precisions.append(0)
59
+ auc_value = auc(recalls, precisions)
60
+ return auc_value
61
+
62
+
63
+ def vus_roc(y_true : np.array ,y_anomaly_scores: np.array, window=4):
64
+
65
+ m = VUS_ROC(y_true,y_anomaly_scores,max_window=window)
66
+
67
+ return m.get_score()
68
+
69
+
70
+ def vus_pr(y_true : np.array ,y_anomaly_scores: np.array, window=4):
71
+
72
+ m = VUS_PR(y_true,y_anomaly_scores,max_window=window)
73
+
74
+ return m.get_score()
75
+
76
+
77
+ def real_pate(y_true: np.array, y_anomaly_scores: np.array, early: int, delay: int):
78
+ """
79
+ Calculate PATE score for anomaly detection in time series.
80
+ The PATE score is the ratio of the number of true positives to the sum of true positives, false positives, and false negatives, within a given early and delay range.
81
+
82
+ Parameters:
83
+ y_true (np.array): The ground truth binary labels for the time series data.
84
+ y_anomaly_scores (np.array): The predicted binary labels for the time series data.
85
+ early (int): The maximum number of time steps before an anomaly must be predicted to be considered early.
86
+ delay (int): The maximum number of time steps after an anomaly must be predicted to be considered delayed.
87
+
88
+ Returns:
89
+ float: The PATE score.
90
+ """
91
+
92
+ return PATE(y_true, y_anomaly_scores, early, delay, binary_scores=False)
tsadmetrics/py.typed ADDED
File without changes
@@ -0,0 +1,2 @@
1
+ from .utils import get_events
2
+
tsadmetrics/utils.py ADDED
@@ -0,0 +1,42 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ def compute_metrics(y_true: np.array,y_pred: np.array,metrics: list, metrics_params: dict, is_anomaly_score = False):
5
+ """
6
+ Computes the specified metrics for the given true and predicted values.
7
+
8
+ Parameters:
9
+ - y_true (np.array): True labels.
10
+ - y_pred (np.array): Predicted labels or scores.
11
+ - metrics (list): List of metric names to compute.
12
+ - metrics_params (dict): Dictionary of parameters for each metric.
13
+ - is_anomaly_score (bool): Flag indicating if y_true and y_pred are anomaly scores. Otherwise, they are treated as binary labels.
14
+
15
+ Returns:
16
+ - metrics_df (DataFrame): DataFrame containing the computed metrics and their values.
17
+ """
18
+
19
+ if not is_anomaly_score:
20
+ #Chech if y_true and y_pred are binary labels
21
+ if not (np.array_equal(np.unique(y_true), [0, 1]) and np.array_equal(np.unique(y_pred), [0, 1])):
22
+ raise ValueError("y_true and y_pred must be binary labels (0 or 1) when is_anomaly_score is False. Which is the default.")
23
+ else:
24
+ # Check if y_true and y_pred are anomaly scores
25
+ if not (np.array_equal(np.unique(y_true), [0, 1]) and np.array_equal(np.unique(y_pred), [0, 1])):
26
+ raise ValueError("y_true and y_pred must be anomaly scores in range [0,1] when is_anomaly_score is True.")
27
+ results = {}
28
+
29
+ for metric in metrics:
30
+ metric_name = metric[0]
31
+ metric_func = metric[1]
32
+
33
+ metric_value = metric_func(y_true, y_pred, **metrics_params.get(metric_name, {}))
34
+
35
+ # Store the result in the DataFrame
36
+ results[metric_name] = metric_value
37
+
38
+ metrics_df = pd.DataFrame(columns=['metric_name', 'metric_value'])
39
+ metrics_df['metric_name'] = results.keys()
40
+ metrics_df['metric_value'] = results.values()
41
+
42
+ return metrics_df
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.1
2
+ Name: tsadmetrics
3
+ Version: 0.1.0
4
+ Summary: Librería para evaluación de detección de anomalías en series temporales
5
+ Home-page: https://github.com/pathsko/TSADmetrics
6
+ Author: Pedro Rafael Velasco Priego
7
+ Author-email: Pedro Rafael Velasco Priego <i12veprp@uco.es>
8
+ Requires-Python: >=3.6
9
+ Description-Content-Type: text/markdown
10
+
@@ -0,0 +1,11 @@
1
+ tsadmetrics/__init__.py,sha256=sETF4u0XMjY-1h2RJb7GyEAngvQ1e62RC7TolT-cKJ0,1571
2
+ tsadmetrics/binary_metrics.py,sha256=6yp4Dqg0_M_bJvyoWVgjAr4rLnGIDzHd7CzCRKqJVso,37739
3
+ tsadmetrics/metric_utils.py,sha256=HtcgQ3sqnfoOQ-QnDctNiaRGDXRuFvGzMGhoS2HWYIg,10476
4
+ tsadmetrics/non_binary_metrics.py,sha256=2F_qsV7wf-IMAB158uA9U0DC5l2apTAyK6XPN2roI7k,2895
5
+ tsadmetrics/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ tsadmetrics/ts_aware_utils.py,sha256=RbgSAVLWeMtA0ZrvBw-_lT0b0ygFn9a1TxSW5cQAwmg,31
7
+ tsadmetrics/utils.py,sha256=G0yWxgTZ9MBzyB0XDLrO2TMwmtm4hssDp5Sr0CG9FqY,1834
8
+ tsadmetrics-0.1.0.dist-info/METADATA,sha256=uJkICqyBIEcjkBuDUrj_28MGzwmUCBepr8wYU3ob3eY,350
9
+ tsadmetrics-0.1.0.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
10
+ tsadmetrics-0.1.0.dist-info/top_level.txt,sha256=rRMFvkwJRUuenl0__YY_3BNr-rkdhAdj28iICkpC5a4,12
11
+ tsadmetrics-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.3.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ tsadmetrics