diff --git a/orion/evaluation/point.py b/orion/evaluation/point.py index 4a0bde8f..f9989def 100644 --- a/orion/evaluation/point.py +++ b/orion/evaluation/point.py @@ -5,13 +5,13 @@ def _point_partition(expected, observed, start=None, end=None): expected = set(expected) observed = set(observed) - edge_start = min(expected.union(observed)) - if start is not None: - edge_start = start + edge_start = start + if edge_start is None: + edge_start = min(expected.union(observed)) - edge_end = max(expected.union(observed)) - if end is not None: - edge_end = end + edge_end = end + if edge_end is None: + edge_end = max(expected.union(observed)) length = int(edge_end) - int(edge_start) + 1 @@ -62,6 +62,13 @@ def _ws(x, y, z, w): if not isinstance(observed, list): observed = list(observed['timestamp']) + if not expected and not observed and (start is None or end is None): + # Without any anomalies there is no range to partition, and none was + # supplied through ``data``/``start``/``end``. There are no true or + # false positives and no false negatives, but the number of true + # negatives is unknown. + return None, 0, 0, 0 + return _ws(expected, observed, start, end) diff --git a/tests/unit/evaluation/test_point.py b/tests/unit/evaluation/test_point.py index 31d9faec..ed1ea1df 100644 --- a/tests/unit/evaluation/test_point.py +++ b/tests/unit/evaluation/test_point.py @@ -67,3 +67,36 @@ def test_point_f1_score_nan(): observed = pd.DataFrame({"timestamp": [4, 5]}) returned = point_f1_score(expected, observed) assert np.isnan(returned) + + +def test_point_confusion_matrix_empty(): + empty = pd.DataFrame({"timestamp": []}) + + returned = point_confusion_matrix(empty, empty) + + # There is no range to partition, so the number of true negatives is + # unknown, but there are no positives and no false negatives. + assert returned == (None, 0, 0, 0) + + +def test_point_confusion_matrix_empty_with_range(): + empty = pd.DataFrame({"timestamp": []}) + + returned = point_confusion_matrix(empty, empty, start=0, end=9) + + # Every point in the range is correctly considered normal. + np.testing.assert_array_equal(np.array(returned), np.array((10, 0, 0, 0))) + + +def test_point_scores_empty(): + empty = pd.DataFrame({"timestamp": []}) + + assert np.isnan(point_precision(empty, empty)) + assert np.isnan(point_recall(empty, empty)) + assert np.isnan(point_f1_score(empty, empty)) + + # Accuracy needs the true negatives, which are unknown without a range. + with pytest.raises(ValueError): + point_accuracy(empty, empty) + + assert point_accuracy(empty, empty, start=0, end=9) == 1.0