tsadmetrics 0.1.17__py3-none-any.whl → 1.0.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.
Files changed (149) hide show
  1. {docs_manual → docs/api_doc}/conf.py +3 -26
  2. docs/{conf.py → full_doc/conf.py} +1 -1
  3. {docs_api → docs/manual_doc}/conf.py +3 -26
  4. examples/example_direct_data.py +28 -0
  5. examples/example_direct_single_data.py +25 -0
  6. examples/example_file_reference.py +24 -0
  7. examples/example_global_config_file.py +13 -0
  8. examples/example_metric_config_file.py +19 -0
  9. examples/example_simple_metric.py +8 -0
  10. examples/specific_examples/AbsoluteDetectionDistance_example.py +24 -0
  11. examples/specific_examples/AffiliationbasedFScore_example.py +24 -0
  12. examples/specific_examples/AverageDetectionCount_example.py +24 -0
  13. examples/specific_examples/CompositeFScore_example.py +24 -0
  14. examples/specific_examples/DelayThresholdedPointadjustedFScore_example.py +24 -0
  15. examples/specific_examples/DetectionAccuracyInRange_example.py +24 -0
  16. examples/specific_examples/EnhancedTimeseriesAwareFScore_example.py +24 -0
  17. examples/specific_examples/LatencySparsityawareFScore_example.py +24 -0
  18. examples/specific_examples/MeanTimeToDetect_example.py +24 -0
  19. examples/specific_examples/NabScore_example.py +24 -0
  20. examples/specific_examples/PateFScore_example.py +24 -0
  21. examples/specific_examples/Pate_example.py +24 -0
  22. examples/specific_examples/PointadjustedAtKFScore_example.py +24 -0
  23. examples/specific_examples/PointadjustedAucPr_example.py +24 -0
  24. examples/specific_examples/PointadjustedAucRoc_example.py +24 -0
  25. examples/specific_examples/PointadjustedFScore_example.py +24 -0
  26. examples/specific_examples/RangebasedFScore_example.py +24 -0
  27. examples/specific_examples/SegmentwiseFScore_example.py +24 -0
  28. examples/specific_examples/TemporalDistance_example.py +24 -0
  29. examples/specific_examples/TimeTolerantFScore_example.py +24 -0
  30. examples/specific_examples/TimeseriesAwareFScore_example.py +24 -0
  31. examples/specific_examples/TotalDetectedInRange_example.py +24 -0
  32. examples/specific_examples/VusPr_example.py +24 -0
  33. examples/specific_examples/VusRoc_example.py +24 -0
  34. examples/specific_examples/WeightedDetectionDifference_example.py +24 -0
  35. tests/test_dpm.py +212 -0
  36. tests/test_ptdm.py +366 -0
  37. tests/test_registry.py +58 -0
  38. tests/test_runner.py +185 -0
  39. tests/test_spm.py +213 -0
  40. tests/test_tmem.py +198 -0
  41. tests/test_tpdm.py +369 -0
  42. tests/test_tstm.py +338 -0
  43. tsadmetrics/__init__.py +0 -21
  44. tsadmetrics/base/Metric.py +188 -0
  45. tsadmetrics/evaluation/Report.py +25 -0
  46. tsadmetrics/evaluation/Runner.py +253 -0
  47. tsadmetrics/metrics/Registry.py +141 -0
  48. tsadmetrics/metrics/__init__.py +2 -0
  49. tsadmetrics/metrics/spm/PointwiseAucPr.py +62 -0
  50. tsadmetrics/metrics/spm/PointwiseAucRoc.py +63 -0
  51. tsadmetrics/metrics/spm/PointwiseFScore.py +86 -0
  52. tsadmetrics/metrics/spm/PrecisionAtK.py +81 -0
  53. tsadmetrics/metrics/spm/__init__.py +9 -0
  54. tsadmetrics/metrics/tem/dpm/DelayThresholdedPointadjustedFScore.py +83 -0
  55. tsadmetrics/metrics/tem/dpm/LatencySparsityawareFScore.py +76 -0
  56. tsadmetrics/metrics/tem/dpm/MeanTimeToDetect.py +47 -0
  57. tsadmetrics/metrics/tem/dpm/NabScore.py +60 -0
  58. tsadmetrics/metrics/tem/dpm/__init__.py +11 -0
  59. tsadmetrics/metrics/tem/ptdm/AverageDetectionCount.py +53 -0
  60. tsadmetrics/metrics/tem/ptdm/DetectionAccuracyInRange.py +66 -0
  61. tsadmetrics/metrics/tem/ptdm/PointadjustedAtKFScore.py +80 -0
  62. tsadmetrics/metrics/tem/ptdm/TimeseriesAwareFScore.py +248 -0
  63. tsadmetrics/metrics/tem/ptdm/TotalDetectedInRange.py +65 -0
  64. tsadmetrics/metrics/tem/ptdm/WeightedDetectionDifference.py +97 -0
  65. tsadmetrics/metrics/tem/ptdm/__init__.py +12 -0
  66. tsadmetrics/metrics/tem/tmem/AbsoluteDetectionDistance.py +48 -0
  67. tsadmetrics/metrics/tem/tmem/EnhancedTimeseriesAwareFScore.py +252 -0
  68. tsadmetrics/metrics/tem/tmem/TemporalDistance.py +68 -0
  69. tsadmetrics/metrics/tem/tmem/__init__.py +9 -0
  70. tsadmetrics/metrics/tem/tpdm/CompositeFScore.py +104 -0
  71. tsadmetrics/metrics/tem/tpdm/PointadjustedAucPr.py +123 -0
  72. tsadmetrics/metrics/tem/tpdm/PointadjustedAucRoc.py +119 -0
  73. tsadmetrics/metrics/tem/tpdm/PointadjustedFScore.py +96 -0
  74. tsadmetrics/metrics/tem/tpdm/RangebasedFScore.py +236 -0
  75. tsadmetrics/metrics/tem/tpdm/SegmentwiseFScore.py +73 -0
  76. tsadmetrics/metrics/tem/tpdm/__init__.py +12 -0
  77. tsadmetrics/metrics/tem/tstm/AffiliationbasedFScore.py +68 -0
  78. tsadmetrics/metrics/tem/tstm/Pate.py +62 -0
  79. tsadmetrics/metrics/tem/tstm/PateFScore.py +61 -0
  80. tsadmetrics/metrics/tem/tstm/TimeTolerantFScore.py +85 -0
  81. tsadmetrics/metrics/tem/tstm/VusPr.py +51 -0
  82. tsadmetrics/metrics/tem/tstm/VusRoc.py +55 -0
  83. tsadmetrics/metrics/tem/tstm/__init__.py +15 -0
  84. tsadmetrics/{_tsadeval/affiliation/_integral_interval.py → utils/functions_affiliation.py} +377 -9
  85. tsadmetrics/utils/functions_auc.py +393 -0
  86. tsadmetrics/utils/functions_conversion.py +63 -0
  87. tsadmetrics/utils/functions_counting_metrics.py +26 -0
  88. tsadmetrics/{_tsadeval/latency_sparsity_aware.py → utils/functions_latency_sparsity_aware.py} +1 -1
  89. tsadmetrics/{_tsadeval/nabscore.py → utils/functions_nabscore.py} +15 -1
  90. tsadmetrics-1.0.0.dist-info/METADATA +69 -0
  91. tsadmetrics-1.0.0.dist-info/RECORD +99 -0
  92. tsadmetrics-1.0.0.dist-info/top_level.txt +4 -0
  93. entorno/bin/activate_this.py +0 -32
  94. entorno/bin/rst2html.py +0 -23
  95. entorno/bin/rst2html4.py +0 -26
  96. entorno/bin/rst2html5.py +0 -33
  97. entorno/bin/rst2latex.py +0 -26
  98. entorno/bin/rst2man.py +0 -27
  99. entorno/bin/rst2odt.py +0 -28
  100. entorno/bin/rst2odt_prepstyles.py +0 -20
  101. entorno/bin/rst2pseudoxml.py +0 -23
  102. entorno/bin/rst2s5.py +0 -24
  103. entorno/bin/rst2xetex.py +0 -27
  104. entorno/bin/rst2xml.py +0 -23
  105. entorno/bin/rstpep2html.py +0 -25
  106. tests/test_binary.py +0 -946
  107. tests/test_non_binary.py +0 -450
  108. tests/test_utils.py +0 -49
  109. tsadmetrics/_tsadeval/affiliation/_affiliation_zone.py +0 -86
  110. tsadmetrics/_tsadeval/affiliation/_single_ground_truth_event.py +0 -68
  111. tsadmetrics/_tsadeval/affiliation/generics.py +0 -135
  112. tsadmetrics/_tsadeval/affiliation/metrics.py +0 -114
  113. tsadmetrics/_tsadeval/auc_roc_pr_plot.py +0 -295
  114. tsadmetrics/_tsadeval/discontinuity_graph.py +0 -109
  115. tsadmetrics/_tsadeval/eTaPR_pkg/DataManage/File_IO.py +0 -175
  116. tsadmetrics/_tsadeval/eTaPR_pkg/DataManage/Range.py +0 -50
  117. tsadmetrics/_tsadeval/eTaPR_pkg/DataManage/Time_Plot.py +0 -184
  118. tsadmetrics/_tsadeval/eTaPR_pkg/__init__.py +0 -0
  119. tsadmetrics/_tsadeval/eTaPR_pkg/etapr.py +0 -386
  120. tsadmetrics/_tsadeval/eTaPR_pkg/tapr.py +0 -362
  121. tsadmetrics/_tsadeval/metrics.py +0 -698
  122. tsadmetrics/_tsadeval/prts/__init__.py +0 -0
  123. tsadmetrics/_tsadeval/prts/base/__init__.py +0 -0
  124. tsadmetrics/_tsadeval/prts/base/time_series_metrics.py +0 -165
  125. tsadmetrics/_tsadeval/prts/basic_metrics_ts.py +0 -121
  126. tsadmetrics/_tsadeval/prts/time_series_metrics/__init__.py +0 -0
  127. tsadmetrics/_tsadeval/prts/time_series_metrics/fscore.py +0 -61
  128. tsadmetrics/_tsadeval/prts/time_series_metrics/precision.py +0 -86
  129. tsadmetrics/_tsadeval/prts/time_series_metrics/precision_recall.py +0 -21
  130. tsadmetrics/_tsadeval/prts/time_series_metrics/recall.py +0 -85
  131. tsadmetrics/_tsadeval/tests.py +0 -376
  132. tsadmetrics/_tsadeval/threshold_plt.py +0 -30
  133. tsadmetrics/_tsadeval/time_tolerant.py +0 -33
  134. tsadmetrics/binary_metrics.py +0 -1652
  135. tsadmetrics/metric_utils.py +0 -98
  136. tsadmetrics/non_binary_metrics.py +0 -372
  137. tsadmetrics/scripts/__init__.py +0 -0
  138. tsadmetrics/scripts/compute_metrics.py +0 -42
  139. tsadmetrics/utils.py +0 -124
  140. tsadmetrics/validation.py +0 -35
  141. tsadmetrics-0.1.17.dist-info/METADATA +0 -54
  142. tsadmetrics-0.1.17.dist-info/RECORD +0 -66
  143. tsadmetrics-0.1.17.dist-info/entry_points.txt +0 -2
  144. tsadmetrics-0.1.17.dist-info/top_level.txt +0 -6
  145. /tsadmetrics/{_tsadeval → base}/__init__.py +0 -0
  146. /tsadmetrics/{_tsadeval/affiliation → evaluation}/__init__.py +0 -0
  147. /tsadmetrics/{_tsadeval/eTaPR_pkg/DataManage → metrics/tem}/__init__.py +0 -0
  148. /tsadmetrics/{_tsadeval/vus_utils.py → utils/functions_vus.py} +0 -0
  149. {tsadmetrics-0.1.17.dist-info → tsadmetrics-1.0.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,96 @@
1
+ from ....base.Metric import Metric
2
+ import numpy as np
3
+ from ....utils.functions_conversion import full_series_to_segmentwise
4
+
5
+ class PointadjustedFScore(Metric):
6
+ """
7
+ Point-adjusted F-score for anomaly detection in time series.
8
+
9
+ This metric modifies the standard F-score by applying a temporal adjustment
10
+ to the predictions:
11
+
12
+ - For each ground-truth anomalous segment, if at least one point is predicted
13
+ as anomalous, all points in that segment are considered correctly detected.
14
+ - The adjusted predictions are then compared to the ground-truth labels using
15
+ the standard point-wise F-score formula.
16
+
17
+ Reference:
18
+ Implementation based on:
19
+ https://link.springer.com/article/10.1007/s10618-023-00988-8
20
+
21
+ Original paper for point-adjusted evaluation:
22
+ https://doi.org/10.1145/3178876.3185996
23
+
24
+ Attributes:
25
+ name (str):
26
+ Fixed name identifier for this metric: `"paf"`.
27
+ binary_prediction (bool):
28
+ Indicates that this metric expects binary predictions.
29
+ param_schema (dict):
30
+ Defines supported parameters. Includes:
31
+ - beta (float): Weighting factor for recall in the F-score
32
+ calculation. Default = 1.0.
33
+
34
+ Raises:
35
+ ValueError:
36
+ If `y_true` and `y_pred` have mismatched lengths.
37
+ TypeError:
38
+ If inputs are not array-like.
39
+ """
40
+
41
+ name = "paf"
42
+ binary_prediction = True
43
+ param_schema = {
44
+ "beta": {
45
+ "default": 1.0,
46
+ "type": float
47
+ }
48
+ }
49
+
50
+ def __init__(self, **kwargs):
51
+ """
52
+ Initialize the PointadjustedFScore metric.
53
+
54
+ Parameters:
55
+ **kwargs:
56
+ Optional keyword arguments passed to the base `Metric` class.
57
+ Supported parameter:
58
+ - beta (float): Weight factor for recall in the F-score.
59
+ """
60
+ super().__init__(name="paf", **kwargs)
61
+
62
+ def _compute(self, y_true, y_pred):
63
+ """
64
+ Compute the point-adjusted F-score.
65
+
66
+ Parameters:
67
+ y_true (np.ndarray):
68
+ Ground-truth binary labels for the time series (0 = normal, 1 = anomaly).
69
+ y_pred (np.ndarray):
70
+ Predicted binary labels for the time series (0 = normal, 1 = anomaly).
71
+
72
+ Returns:
73
+ float:
74
+ The computed point-adjusted F-score. Returns 0 if either precision or
75
+ recall is 0.
76
+ """
77
+ adjusted_prediction = y_pred.copy()
78
+
79
+ for start, end in full_series_to_segmentwise(y_true):
80
+ if np.any(adjusted_prediction[start:end + 1]):
81
+ adjusted_prediction[start:end + 1] = 1
82
+ else:
83
+ adjusted_prediction[start:end + 1] = 0
84
+
85
+ tp = np.sum(adjusted_prediction * y_true)
86
+ fp = np.sum(adjusted_prediction * (1 - y_true))
87
+ fn = np.sum((1 - adjusted_prediction) * y_true)
88
+
89
+ precision = tp / (tp + fp) if (tp + fp) > 0 else 0
90
+ recall = tp / (tp + fn) if (tp + fn) > 0 else 0
91
+
92
+ if precision == 0 or recall == 0:
93
+ return 0
94
+
95
+ beta = self.params['beta']
96
+ return ((1 + beta**2) * precision * recall) / (beta**2 * precision + recall)
@@ -0,0 +1,236 @@
1
+ from ....base.Metric import Metric
2
+ import numpy as np
3
+
4
+ class RangebasedFScore(Metric):
5
+ """
6
+ Range-based F-score for anomaly detection in time series.
7
+
8
+ This metric evaluates anomaly detection performance over temporal ranges,
9
+ combining range-based precision and recall into a harmonic mean. It accounts
10
+ for positional bias, existence and overlap rewards, and cardinality penalties,
11
+ allowing fine-grained control over missed detections and false alarms.
12
+
13
+ References:
14
+ - Implementation: https://link.springer.com/article/10.1007/s10618-023-00988-8
15
+ - Original range-based anomaly detection paper:
16
+ https://proceedings.neurips.cc/paper_files/paper/2018/file/8f468c873a32bb0619eaeb2050ba45d1-Paper.pdf
17
+
18
+ Parameters:
19
+ p_alpha (float):
20
+ Relative importance of existence reward for precision (0 <= alpha_p <= 1).
21
+ r_alpha (float):
22
+ Relative importance of existence reward for recall (0 <= alpha_r <= 1).
23
+ p_bias (str):
24
+ Positional bias for precision ("flat", "front", "middle", "back").
25
+ r_bias (str):
26
+ Positional bias for recall ("flat", "front", "middle", "back").
27
+ cardinality_mode (str, optional):
28
+ Cardinality factor type ("one", "reciprocal", "udf_gamma").
29
+ beta (float):
30
+ Weight of precision in the F-score. Default = 1.
31
+
32
+ Attributes:
33
+ name (str):
34
+ Fixed metric name `"rbf"`.
35
+ binary_prediction (bool):
36
+ True, this metric expects binary predictions.
37
+ param_schema (dict):
38
+ Parameter schema and defaults.
39
+ """
40
+
41
+ name = "rbf"
42
+ binary_prediction = True
43
+ param_schema = {
44
+ "beta": {"default": 1.0, "type": float},
45
+ "p_alpha": {"default": 0.5, "type": float},
46
+ "r_alpha": {"default": 0.5, "type": float},
47
+ "p_bias": {"default": "flat", "type": str},
48
+ "r_bias": {"default": "flat", "type": str},
49
+ "cardinality_mode": {"default": "one", "type": str},
50
+ }
51
+
52
+ def __init__(self, **kwargs):
53
+ """
54
+ Initialize the RangebasedFScore metric.
55
+
56
+ Parameters:
57
+ **kwargs: Additional parameters matching the param_schema.
58
+ """
59
+ super().__init__(name="rbf", **kwargs)
60
+
61
+ # -----------------------
62
+ # Utility functions
63
+ # -----------------------
64
+
65
+ def _udf_gamma(self):
66
+ """User-defined gamma function (default implementation)."""
67
+ return 1.0
68
+
69
+ def _gamma_select(self, gamma: str, overlap: int) -> float:
70
+ """
71
+ Select gamma value based on cardinality mode and overlap.
72
+
73
+ Args:
74
+ gamma (str): 'one', 'reciprocal', or 'udf_gamma'.
75
+ overlap (int): Number of overlapping ranges.
76
+
77
+ Returns:
78
+ float: Selected gamma factor.
79
+ """
80
+ assert isinstance(overlap, int), TypeError("overlap must be int")
81
+
82
+ if gamma == "one":
83
+ return 1.0
84
+ elif gamma == "reciprocal":
85
+ return 1.0 / overlap if overlap > 1 else 1.0
86
+ elif gamma == "udf_gamma":
87
+ return 1.0 / self._udf_gamma() if overlap > 1 else 1.0
88
+ else:
89
+ raise ValueError(f"Invalid gamma type: {gamma}")
90
+
91
+ def _gamma_function(self, overlap_count):
92
+ """Compute gamma factor from overlap count based on cardinality mode."""
93
+ return self._gamma_select(self.params['cardinality_mode'], overlap_count[0])
94
+
95
+ def _compute_omega_reward(self, bias, r1, r2, overlap_count):
96
+ """
97
+ Compute omega reward based on overlap of two ranges with positional bias.
98
+
99
+ Args:
100
+ bias (str): Positional bias type.
101
+ r1 (np.array): First range [start, end].
102
+ r2 (np.array): Second range [start, end].
103
+ overlap_count (list): List to track number of overlaps.
104
+
105
+ Returns:
106
+ float: Omega reward.
107
+ """
108
+ if r1[1] < r2[0] or r1[0] > r2[1]:
109
+ return 0
110
+ overlap_count[0] += 1
111
+ overlap = np.zeros(r1.shape)
112
+ overlap[0] = max(r1[0], r2[0])
113
+ overlap[1] = min(r1[1], r2[1])
114
+ return self._omega_function(bias, r1, overlap)
115
+
116
+ def _omega_function(self, bias, rrange, overlap):
117
+ """Compute normalized positional omega function for range overlap."""
118
+ anomaly_length = rrange[1] - rrange[0] + 1
119
+ my_positional_bias, max_positional_bias = 0, 0
120
+
121
+ for i in range(1, anomaly_length + 1):
122
+ temp_bias = self._delta_function(bias, i, anomaly_length)
123
+ max_positional_bias += temp_bias
124
+ j = rrange[0] + i - 1
125
+ if overlap[0] <= j <= overlap[1]:
126
+ my_positional_bias += temp_bias
127
+
128
+ return my_positional_bias / max_positional_bias if max_positional_bias > 0 else 0
129
+
130
+ def _delta_function(self, bias, t, anomaly_length):
131
+ """Compute positional delta function for omega."""
132
+ return self._delta_select(bias, t, anomaly_length)
133
+
134
+ def _delta_select(self, delta, t, anomaly_length):
135
+ """Select positional bias value based on delta type."""
136
+ if delta == "flat":
137
+ return 1.0
138
+ elif delta == "front":
139
+ return float(anomaly_length - t + 1)
140
+ elif delta == "middle":
141
+ return float(t if t <= anomaly_length / 2 else anomaly_length - t + 1)
142
+ elif delta == "back":
143
+ return float(t)
144
+ elif delta == "udf_delta":
145
+ return self._udf_delta(t, anomaly_length)
146
+ else:
147
+ raise ValueError("Invalid positional bias value")
148
+
149
+ def _udf_delta(self, t=None, anomaly_length=None):
150
+ """User-defined delta function (default returns 1.0)."""
151
+ return 1.0
152
+
153
+ def _shift(self, arr, num, fill_value=np.nan):
154
+ """Shift array by `num` positions, filling empty slots."""
155
+ arr = np.roll(arr, num)
156
+ if num < 0:
157
+ arr[num:] = fill_value
158
+ elif num > 0:
159
+ arr[:num] = fill_value
160
+ return arr
161
+
162
+ def _prepare_data(self, values_real, values_pred):
163
+ """
164
+ Prepare ranges for real and predicted anomalies.
165
+
166
+ Returns:
167
+ Tuple of numpy arrays: (real_anomalies, predicted_anomalies)
168
+ """
169
+ assert len(values_real) == len(values_pred)
170
+ predicted_anomalies_ = np.argwhere(values_pred == 1).ravel()
171
+ predicted_anomalies = self._extract_ranges(predicted_anomalies_)
172
+ real_anomalies_ = np.argwhere(values_real == 1).ravel()
173
+ real_anomalies = self._extract_ranges(real_anomalies_)
174
+ return real_anomalies, predicted_anomalies
175
+
176
+ def _extract_ranges(self, anomaly_indices):
177
+ shifted_fwd = self._shift(anomaly_indices, 1, fill_value=anomaly_indices[0])
178
+ shifted_bwd = self._shift(anomaly_indices, -1, fill_value=anomaly_indices[-1])
179
+ start = np.argwhere((shifted_fwd - anomaly_indices) != -1).ravel()
180
+ end = np.argwhere((anomaly_indices - shifted_bwd) != -1).ravel()
181
+ return np.hstack([anomaly_indices[start].reshape(-1, 1), anomaly_indices[end].reshape(-1, 1)])
182
+
183
+ # -----------------------
184
+ # Range-based precision & recall
185
+ # -----------------------
186
+
187
+ def _compute_precision(self, y_true, y_pred):
188
+ """Compute range-based precision."""
189
+ alpha = self.params["p_alpha"]
190
+ if len(y_pred) == 0:
191
+ return 0
192
+ precision = 0
193
+ for range_p in y_pred:
194
+ omega_reward, overlap_count = 0, [0]
195
+ for range_r in y_true:
196
+ omega_reward += self._compute_omega_reward(self.params["p_bias"], range_p, range_r, overlap_count)
197
+ overlap_reward = self._gamma_function(overlap_count) * omega_reward
198
+ existence_reward = 1 if overlap_count[0] > 0 else 0
199
+ precision += alpha * existence_reward + (1 - alpha) * overlap_reward
200
+ return precision / len(y_pred)
201
+
202
+ def _compute_recall(self, y_true, y_pred):
203
+ """Compute range-based recall."""
204
+ alpha = self.params["r_alpha"]
205
+ if len(y_true) == 0:
206
+ return 0
207
+ recall = 0
208
+ for range_r in y_true:
209
+ omega_reward, overlap_count = 0, [0]
210
+ for range_p in y_pred:
211
+ omega_reward += self._compute_omega_reward(self.params["r_bias"], range_r, range_p, overlap_count)
212
+ overlap_reward = self._gamma_function(overlap_count) * omega_reward
213
+ existence_reward = 1 if overlap_count[0] > 0 else 0
214
+ recall += alpha * existence_reward + (1 - alpha) * overlap_reward
215
+ return recall / len(y_true)
216
+
217
+ # -----------------------
218
+ # Metric computation
219
+ # -----------------------
220
+
221
+ def _compute(self, y_true, y_pred):
222
+ """
223
+ Compute the range-based F-score.
224
+
225
+ Returns:
226
+ float: F-beta score using range-based precision and recall.
227
+ """
228
+ if np.sum(y_pred) == 0:
229
+ return 0
230
+
231
+ beta = self.params['beta']
232
+ y_true_mod, y_pred_mod = self._prepare_data(y_true, y_pred)
233
+ precision = self._compute_precision(y_true_mod, y_pred_mod)
234
+ recall = self._compute_recall(y_true_mod, y_pred_mod)
235
+
236
+ return (1 + beta ** 2) * precision * recall / (beta ** 2 * precision + recall) if precision + recall > 0 else 0
@@ -0,0 +1,73 @@
1
+ from ....base.Metric import Metric
2
+ import numpy as np
3
+ from ....utils.functions_conversion import full_series_to_segmentwise
4
+
5
+ class SegmentwiseFScore(Metric):
6
+ """
7
+ Segment-wise F-score for anomaly detection in time series.
8
+
9
+ This metric computes the F-score at the segment level rather than point-wise.
10
+ Each contiguous segment of anomalies in the ground truth is treated as a unit.
11
+ - True positive (TP): at least one predicted anomaly within a ground-truth segment.
12
+ - False negative (FN): no predicted anomaly in a ground-truth segment.
13
+ - False positive (FP): predicted segment with no overlap with any ground-truth segment.
14
+
15
+ References:
16
+ - Implementation: https://link.springer.com/article/10.1007/s10618-023-00988-8
17
+ - Original paper: https://doi.org/10.1145/3219819.3219845
18
+
19
+ Parameters:
20
+ beta (float): Weight of precision in the harmonic mean.
21
+ Default is 1.0 (balanced F1-score).
22
+ """
23
+
24
+ name = "swf"
25
+ binary_prediction = True
26
+ param_schema = {
27
+ "beta": {"default": 1.0, "type": float}
28
+ }
29
+
30
+ def __init__(self, **kwargs):
31
+ """
32
+ Initialize the SegmentwiseFScore metric.
33
+
34
+ Parameters:
35
+ **kwargs: Additional parameters matching param_schema.
36
+ """
37
+ super().__init__(name="swf", **kwargs)
38
+
39
+ def _compute(self, y_true, y_pred):
40
+ """
41
+ Compute the segment-wise F-score.
42
+
43
+ Parameters:
44
+ y_true (np.array): Ground truth binary labels.
45
+ y_pred (np.array): Predicted binary labels.
46
+
47
+ Returns:
48
+ float: Segment-wise F-score.
49
+ """
50
+ beta = self.params["beta"]
51
+
52
+ # Count true positives and false negatives per ground-truth segment
53
+ tp, fn = 0, 0
54
+ for gt_segment in full_series_to_segmentwise(y_true):
55
+ if np.any(y_pred[gt_segment[0]:gt_segment[1]+1]):
56
+ tp += 1
57
+ else:
58
+ fn += 1
59
+
60
+ # Count false positives per predicted segment
61
+ fp = 0
62
+ for pred_segment in full_series_to_segmentwise(y_pred):
63
+ if not np.any(y_true[pred_segment[0]:pred_segment[1]+1]):
64
+ fp += 1
65
+
66
+ # Compute precision and recall
67
+ precision = tp / (tp + fp) if (tp + fp) > 0 else 0
68
+ recall = tp / (tp + fn) if (tp + fn) > 0 else 0
69
+
70
+ # Return F-beta score
71
+ if precision == 0 or recall == 0:
72
+ return 0.0
73
+ return (1 + beta**2) * precision * recall / (beta**2 * precision + recall)
@@ -0,0 +1,12 @@
1
+ from .CompositeFScore import CompositeFScore
2
+ from .PointadjustedAucPr import PointadjustedAucPr
3
+ from .PointadjustedAucRoc import PointadjustedAucRoc
4
+ from .SegmentwiseFScore import SegmentwiseFScore
5
+ from .RangebasedFScore import RangebasedFScore
6
+ from .PointadjustedFScore import PointadjustedFScore
7
+ __all__ = ['CompositeFScore',
8
+ 'PointadjustedAucPr',
9
+ 'PointadjustedAucRoc',
10
+ 'SegmentwiseFScore',
11
+ 'RangebasedFScore',
12
+ 'PointadjustedFScore']
@@ -0,0 +1,68 @@
1
+ from ....base.Metric import Metric
2
+ import numpy as np
3
+ from ....utils.functions_conversion import full_series_to_segmentwise
4
+ from ....utils.functions_affiliation import pr_from_events, reformat_segments
5
+
6
+ class AffiliationbasedFScore(Metric):
7
+ """
8
+ Calculate affiliation based F-score for anomaly detection in time series.
9
+
10
+ This metric combines the affiliation-based precision and recall into a single score
11
+ using the harmonic mean, adjusted by a weight :math:`{\\beta}` to control the relative importance
12
+ of recall versus precision. Since both precision and recall are distance-based,
13
+ the F-score reflects a balance between how well predicted anomalies align with true
14
+ anomalies and vice versa.
15
+
16
+ Implementation of https://link.springer.com/article/10.1007/s10618-023-00988-8
17
+
18
+ For more information, see the original paper:
19
+ https://dl.acm.org/doi/10.1145/3534678.3539339
20
+
21
+ Parameters:
22
+ beta (float):
23
+ The beta value, which determines the weight of precision in the combined score.
24
+ Default is 1, which gives equal weight to precision and recall.
25
+ """
26
+ name = "aff_f"
27
+ binary_prediction = True
28
+ param_schema = {
29
+ "beta": {
30
+ "default": 1.0,
31
+ "type": float
32
+ }
33
+ }
34
+
35
+ def __init__(self, **kwargs):
36
+ super().__init__(name="aff_f", **kwargs)
37
+
38
+ def _compute(self, y_true, y_pred):
39
+ """
40
+ Calculate the affiliation based F-score.
41
+
42
+ Parameters:
43
+ y_true (np.array):
44
+ The ground truth binary labels for the time series data.
45
+ y_pred (np.array):
46
+ The predicted binary labels for the time series data.
47
+
48
+ Returns:
49
+ float: The affiliation based F-score.
50
+ """
51
+
52
+ if np.sum(y_pred) == 0:
53
+ return 0
54
+
55
+ pr_output = pr_from_events(
56
+ reformat_segments(full_series_to_segmentwise(y_pred)),
57
+ reformat_segments(full_series_to_segmentwise(y_true)),
58
+ (0, len(y_true)),
59
+ )
60
+
61
+ precision = pr_output['precision']
62
+ recall = pr_output['recall']
63
+
64
+ if precision == 0 or recall == 0:
65
+ return 0
66
+
67
+ beta = self.params["beta"]
68
+ return ((1 + beta**2) * precision * recall) / (beta**2 * precision + recall)
@@ -0,0 +1,62 @@
1
+ from ....base.Metric import Metric
2
+ from pate.PATE_metric import PATE
3
+ import numpy as np
4
+
5
+
6
+ class Pate(Metric):
7
+ """
8
+ Calculate PATE score for anomaly detection in time series using real-valued anomaly scores.
9
+
10
+ This version of PATE evaluates real-valued anomaly scores by assigning weights to predictions
11
+ based on their temporal proximity to the true anomaly intervals. It defines an early buffer of
12
+ length `early` before each anomaly and a delay buffer of length `delay` after it. Detections with
13
+ high scores within the anomaly interval receive full weight, while those in the buffer zones are
14
+ assigned linearly decaying weights depending on their distance from the interval. Scores outside
15
+ these zones contribute to false positives, and intervals with insufficient detection are penalized
16
+ as false negatives.
17
+
18
+ The final PATE score aggregates these weighted contributions across all time steps, yielding
19
+ a smooth performance measure that is sensitive to both the timing and confidence of the predictions.
20
+
21
+ Implementation of https://arxiv.org/abs/2405.12096
22
+
23
+ For more information, see the original paper:
24
+ https://arxiv.org/abs/2405.12096
25
+
26
+ Parameters:
27
+ early (int):
28
+ Length of the early buffer zone before each anomaly interval.
29
+ delay (int):
30
+ Length of the delay buffer zone after each anomaly interval.
31
+ """
32
+ name = "pate"
33
+ binary_prediction = False
34
+ param_schema = {
35
+ "early": {
36
+ "default": 5,
37
+ "type": int
38
+ },
39
+ "delay": {
40
+ "default": 5,
41
+ "type": int
42
+ }
43
+ }
44
+
45
+ def __init__(self, **kwargs):
46
+ super().__init__(name="pate", **kwargs)
47
+
48
+ def _compute(self, y_true, y_anomaly_scores):
49
+ """
50
+ Calculate the real-valued PATE score.
51
+
52
+ Parameters:
53
+ y_true (np.array):
54
+ Ground truth binary labels (0 = normal, 1 = anomaly).
55
+ y_anomaly_scores (np.array):
56
+ Real-valued anomaly scores for each time point.
57
+
58
+ Returns:
59
+ float: The real-valued PATE score.
60
+ """
61
+
62
+ return PATE(y_true, y_anomaly_scores, self.params["early"], self.params["delay"], binary_scores=False)
@@ -0,0 +1,61 @@
1
+ from ....base.Metric import Metric
2
+ from pate.PATE_metric import PATE
3
+
4
+
5
+ class PateFScore(Metric):
6
+ """
7
+ Calculate PATE score for anomaly detection in time series.
8
+
9
+ PATE evaluates predictions by assigning weighted scores based on temporal proximity
10
+ to true anomaly intervals. It uses buffer zones around each true anomaly: an early buffer of length
11
+ `early` preceding the interval and a delay buffer of length `delay` following it. Detections within
12
+ the true interval receive full weight, while those in the early or delay buffers receive linearly
13
+ decaying weights based on distance from the interval edges. Predictions outside these zones are
14
+ treated as false positives, and missed intervals as false negatives. The final score balances these
15
+ weighted detections into a single measure of performance.
16
+
17
+ Implementation of https://arxiv.org/abs/2405.12096
18
+
19
+ For more information, see the original paper:
20
+ https://arxiv.org/abs/2405.12096
21
+
22
+ Parameters:
23
+ early (int):
24
+ The maximum number of time steps before an anomaly must be predicted to be considered early.
25
+ delay (int):
26
+ The maximum number of time steps after an anomaly must be predicted to be considered delayed.
27
+ """
28
+ name = "pate_f1"
29
+ binary_prediction = True
30
+ param_schema = {
31
+ "early": {
32
+ "default": 5,
33
+ "type": int
34
+ },
35
+ "delay": {
36
+ "default": 5,
37
+ "type": int
38
+ }
39
+ }
40
+
41
+ def __init__(self, **kwargs):
42
+ super().__init__(name="pate_f1", **kwargs)
43
+
44
+ def _compute(self, y_true, y_pred):
45
+ """
46
+ Calculate the PATE score.
47
+
48
+ Parameters:
49
+ y_true (np.array):
50
+ The ground truth binary labels for the time series data.
51
+ y_pred (np.array):
52
+ The predicted binary labels for the time series data.
53
+
54
+ Returns:
55
+ float: The PATE score.
56
+ """
57
+
58
+ early = self.params["early"]
59
+ delay = self.params["delay"]
60
+
61
+ return PATE(y_true, y_pred, early, delay, binary_scores=True)