Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f987102
Set the structure for implementing the evaluation strategy by events
agataben Dec 10, 2025
5362749
Implement "_point_eval_with_events_strategy()" without breakdown
agataben Dec 10, 2025
00ba2ac
Implement "_get_anomalous_events()"
agataben Dec 12, 2025
c6d5e8b
Add test on "_point_eval_with_events_strategy()"
agataben Dec 12, 2025
f0dbbed
Fix errors on "_point_eval_with_events_strategy()"
agataben Dec 12, 2025
1ec8bd8
Add the breakdown
agataben Dec 12, 2025
92ff493
Change the function "_get_anomalous_events" in “_count_anomalous_events“
agataben Dec 12, 2025
fd592de
Complete the breakdown
agataben Dec 12, 2025
2586b64
Fix a zero division
agataben Dec 12, 2025
6a3ee64
Fix error on evaluation with events strategy and point granularity
agataben Dec 12, 2025
8f3fcf2
Add NotImplemented for evaluation with events strategy and variable g…
agataben Dec 12, 2025
bd58613
Add test on evaluation with point granularity, events strategy and br…
agataben Dec 13, 2025
731ce15
Raise NotImplementedError for evaluation with variable granularity an…
agataben Dec 13, 2025
279b039
Fix bugs
agataben Dec 16, 2025
d29e9fa
Add a test to detect incorrect false positive counting
agataben Dec 16, 2025
0b1d6e2
Delete the instance of "PeriodicAverageAnomalyDetector()" in "test_co…
agataben Dec 16, 2025
f4e4298
Fix the problem with the periodic average model
agataben Dec 16, 2025
368b404
Add tests on event evaluation
agataben Dec 21, 2025
f1e5b13
Change variable names
agataben Dec 24, 2025
3d0d313
Add new returned value to "_count_anomalous_events()"
agataben Dec 24, 2025
0a91de7
Build the dictionary "event_time_slots" and add test
agataben Dec 24, 2025
f981447
Change the counting strategy of true positives
agataben Dec 30, 2025
eb6654c
Fix error in case of series ending with an anomaly
agataben Dec 30, 2025
aefe5c1
Manage series with adjoining anomalies
agataben Dec 30, 2025
b0f0490
Fix error in making the evaluation result dict
agataben Jan 2, 2026
1886ba6
Fix error in counting inserted anomalies
agataben Jan 2, 2026
c8b47c8
Add test on event evaluation
agataben Jan 2, 2026
4928615
Use function "ensure_full_reproducibility()" to fix the seed
agataben Jan 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 100 additions & 6 deletions ats/evaluators.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,6 @@ def _copy_dataset(self,dataset,models):
return dataset_copies

def evaluate(self,models={},granularity='point',strategy='flags',breakdown=False):
if strategy != 'flags':
raise NotImplementedError(f'Evaluation strategy {strategy} is not implemented')

if not models:
raise ValueError('There are no models to evaluate')
if not self.test_data:
Expand All @@ -138,11 +135,23 @@ def evaluate(self,models={},granularity='point',strategy='flags',breakdown=False
flagged_dataset = _get_model_output(dataset_copies[j],model)
for i,sample_df in enumerate(flagged_dataset):
if granularity == 'point':
single_model_evaluation[f'sample_{i+1}'] = _point_granularity_evaluation(sample_df,anomaly_labels_list[i],breakdown=breakdown)
if strategy == 'flags':
single_model_evaluation[f'sample_{i+1}'] = _point_granularity_evaluation(sample_df,anomaly_labels_list[i],breakdown=breakdown)
elif strategy == 'events':
single_model_evaluation[f'sample_{i+1}'] = _point_eval_with_events_strategy(sample_df,anomaly_labels_list[i],breakdown=breakdown)

elif granularity == 'variable':
single_model_evaluation[f'sample_{i+1}'] = _variable_granularity_evaluation(sample_df,anomaly_labels_list[i], breakdown = breakdown)
if strategy == 'flags':
single_model_evaluation[f'sample_{i+1}'] = _variable_granularity_evaluation(sample_df,anomaly_labels_list[i], breakdown = breakdown)
elif strategy == 'events':
single_model_evaluation[f'sample_{i+1}'] = _variable_eval_with_events_strategy(sample_df,anomaly_labels_list[i],breakdown=breakdown)

elif granularity == 'series':
single_model_evaluation[f'sample_{i+1}'] = _series_granularity_evaluation(sample_df,anomaly_labels_list[i], breakdown = breakdown)
if strategy == 'flags':
single_model_evaluation[f'sample_{i+1}'] = _series_granularity_evaluation(sample_df,anomaly_labels_list[i], breakdown = breakdown)
elif strategy == 'events':
single_model_evaluation[f'sample_{i+1}'] = _series_eval_with_events_strategy(sample_df,anomaly_labels_list[i],breakdown=breakdown)

else:
raise ValueError(f'Unknown granularity {granularity}')

Expand Down Expand Up @@ -360,3 +369,88 @@ def _series_granularity_evaluation(flagged_timeseries_df,anomaly_labels_df,break
return one_series_evaluation_result | breakdown_info
else:
return one_series_evaluation_result

def _variable_eval_with_events_strategy(sample_df,anomaly_labels_df,breakdown=False):
raise NotImplementedError('Evaluation with events strategy and variable granularity not implemented')

def _point_eval_with_events_strategy(flagged_timeseries_df,anomaly_labels_df,breakdown=False):
detected_events_n = 0
false_positives_n = 0
total_events_n = 0
events_n, event_type_counts, event_time_slots = _count_anomalous_events(anomaly_labels_df)
evaluation_result = {}
breakdown_info = {}

flags_df = flagged_timeseries_df.filter(like='anomaly')
previous_point_info = {'label': 0}
for timestamp in flagged_timeseries_df.index:
anomaly_label = anomaly_labels_df.loc[timestamp]
# True if there is at least 1 variable detected as anomalous
is_anomalous = flags_df.loc[timestamp].any()
point_info = {anomaly_label: is_anomalous}
if point_info != previous_point_info and is_anomalous:
if anomaly_label is None:
false_positives_n += 1
previous_point_info = point_info

evaluation_result['false_positives_count'] = false_positives_n
evaluation_result['false_positives_ratio'] = false_positives_n/len(anomaly_labels_df)

for anomaly, anomaly_time_slots in event_time_slots.items():
anomaly_n = int(len(anomaly_time_slots))/2
total_events_n += anomaly_n
detected_anomaly_n = 0
for i in range(0,len(anomaly_time_slots),2):
start = anomaly_time_slots[i]
stop = anomaly_time_slots[i+1]
is_detected = flags_df.loc[start:stop].any().any()
if is_detected:
detected_anomaly_n +=1
breakdown_info[anomaly + '_true_positives_count'] = detected_anomaly_n
breakdown_info[anomaly + '_true_positives_rate'] = detected_anomaly_n/anomaly_n
detected_events_n += detected_anomaly_n

evaluation_result['true_positives_count'] = detected_events_n
if events_n:
evaluation_result['true_positives_rate'] = detected_events_n/total_events_n
else:
evaluation_result['true_positives_rate'] = None

if breakdown:
return evaluation_result | breakdown_info
else:
return evaluation_result

def _series_eval_with_events_strategy(sample_df,anomaly_labels_df,breakdown=False):
raise NotImplementedError('Evaluation with events strategy and series granularity not implemented')

def _count_anomalous_events(anomaly_labels_df):
events_n = 0
event_type_counts = {}
event_time_slots = {}
previous_anomaly_label = None
for timestamp in anomaly_labels_df.index:
anomaly_label = anomaly_labels_df.loc[timestamp]
if anomaly_label != previous_anomaly_label:
if anomaly_label is not None:
events_n += 1
# To manage series with adjoining anomalies
if previous_anomaly_label is not None:
event_time_slots[key].append(previous_timestamp)
key = anomaly_label
if anomaly_label in event_type_counts.keys():
event_type_counts[key] +=1
event_time_slots[key].append(timestamp)
else:
event_type_counts[key] =1
event_time_slots[key] = [timestamp]

else:
event_time_slots[key].append(previous_timestamp)

previous_timestamp = timestamp
previous_anomaly_label = anomaly_label
# To manage series ending with an anomaly
if previous_anomaly_label is not None:
event_time_slots[key].append(previous_timestamp)
return events_n , event_type_counts, event_time_slots
Loading