Skip to content

Commit 5c4ebb2

Browse files
authored
Add regression metrics (#547)
* Add regression support * update docstrings * Ignore `no-any-return` errors
1 parent df4739d commit 5c4ebb2

File tree

15 files changed

+1137
-2
lines changed

15 files changed

+1137
-2
lines changed

cyclops/evaluate/metrics/experimental/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
MultilabelF1Score,
2323
MultilabelFBetaScore,
2424
)
25+
from cyclops.evaluate.metrics.experimental.mae import MeanAbsoluteError
26+
from cyclops.evaluate.metrics.experimental.mape import MeanAbsolutePercentageError
2527
from cyclops.evaluate.metrics.experimental.metric_dict import MetricDict
28+
from cyclops.evaluate.metrics.experimental.mse import MeanSquaredError
2629
from cyclops.evaluate.metrics.experimental.negative_predictive_value import (
2730
BinaryNPV,
2831
MulticlassNPV,
@@ -55,6 +58,9 @@
5558
MulticlassROC,
5659
MultilabelROC,
5760
)
61+
from cyclops.evaluate.metrics.experimental.smape import (
62+
SymmetricMeanAbsolutePercentageError,
63+
)
5864
from cyclops.evaluate.metrics.experimental.specificity import (
5965
BinarySpecificity,
6066
BinaryTNR,
@@ -63,3 +69,6 @@
6369
MultilabelSpecificity,
6470
MultilabelTNR,
6571
)
72+
from cyclops.evaluate.metrics.experimental.wmape import (
73+
WeightedMeanAbsolutePercentageError,
74+
)

cyclops/evaluate/metrics/experimental/functional/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222
multilabel_f1_score,
2323
multilabel_fbeta_score,
2424
)
25+
from cyclops.evaluate.metrics.experimental.functional.mae import mean_absolute_error
26+
from cyclops.evaluate.metrics.experimental.functional.mape import (
27+
mean_absolute_percentage_error,
28+
)
29+
from cyclops.evaluate.metrics.experimental.functional.mse import mean_squared_error
2530
from cyclops.evaluate.metrics.experimental.functional.negative_predictive_value import (
2631
binary_npv,
2732
multiclass_npv,
@@ -51,6 +56,9 @@
5156
multiclass_roc,
5257
multilabel_roc,
5358
)
59+
from cyclops.evaluate.metrics.experimental.functional.smape import (
60+
symmetric_mean_absolute_percentage_error,
61+
)
5462
from cyclops.evaluate.metrics.experimental.functional.specificity import (
5563
binary_specificity,
5664
binary_tnr,
@@ -59,3 +67,6 @@
5967
multilabel_specificity,
6068
multilabel_tnr,
6169
)
70+
from cyclops.evaluate.metrics.experimental.functional.wmape import (
71+
weighted_mean_absolute_percentage_error,
72+
)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""Functional interface for the mean absolute error metric."""
2+
from typing import Tuple, Union
3+
4+
import array_api_compat as apc
5+
6+
from cyclops.evaluate.metrics.experimental.utils.types import Array
7+
from cyclops.evaluate.metrics.experimental.utils.validation import (
8+
_basic_input_array_checks,
9+
_check_same_shape,
10+
is_floating_point,
11+
)
12+
13+
14+
def _mean_absolute_error_update(target: Array, preds: Array) -> Tuple[Array, int]:
15+
"""Update and return variables required to compute Mean Absolute Error."""
16+
_basic_input_array_checks(target, preds)
17+
_check_same_shape(target, preds)
18+
xp = apc.array_namespace(target, preds)
19+
20+
target = target if is_floating_point(target) else xp.astype(target, xp.float32)
21+
preds = preds if is_floating_point(preds) else xp.astype(preds, xp.float32)
22+
23+
sum_abs_error = xp.sum(xp.abs(preds - target), dtype=xp.float32)
24+
num_obs = int(apc.size(target) or 0)
25+
return sum_abs_error, num_obs
26+
27+
28+
def _mean_absolute_error_compute(
29+
sum_abs_error: Array,
30+
num_obs: Union[int, Array],
31+
) -> Array:
32+
"""Compute Mean Absolute Error.
33+
34+
Parameters
35+
----------
36+
sum_abs_error : Array
37+
Sum of absolute value of errors over all observations.
38+
num_obs : int, Array
39+
Total number of observations.
40+
41+
Returns
42+
-------
43+
Array
44+
The mean absolute error.
45+
46+
"""
47+
return sum_abs_error / num_obs # type: ignore[no-any-return]
48+
49+
50+
def mean_absolute_error(target: Array, preds: Array) -> Array:
51+
"""Compute the mean absolute error.
52+
53+
Parameters
54+
----------
55+
target : Array
56+
Ground truth target values.
57+
preds : Array
58+
Estimated target values.
59+
60+
Return
61+
------
62+
Array
63+
The mean absolute error.
64+
65+
Raises
66+
------
67+
TypeError
68+
If `target` or `preds` is not an array object that is compatible with
69+
the Python array API standard.
70+
ValueError
71+
If `target` or `preds` is empty.
72+
ValueError
73+
If `target` or `preds` is not a numeric array.
74+
ValueError
75+
If the shape of `target` and `preds` are not the same.
76+
77+
Examples
78+
--------
79+
>>> import numpy.array_api as anp
80+
>>> from cyclops.evaluate.metrics.experimental.functional import mean_absolute_error
81+
>>> target = anp.asarray([0.009, 1.05, 2., 3.])
82+
>>> preds = anp.asarray([0., 1., 2., 2.])
83+
>>> mean_absolute_error(target, preds)
84+
Array(0.26475, dtype=float32)
85+
86+
"""
87+
sum_abs_error, num_obs = _mean_absolute_error_update(target, preds)
88+
return _mean_absolute_error_compute(sum_abs_error, num_obs)
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
"""Functional interface for the mean absolute percentage error metric."""
2+
from typing import Tuple, Union
3+
4+
import array_api_compat as apc
5+
6+
from cyclops.evaluate.metrics.experimental.utils.types import Array
7+
from cyclops.evaluate.metrics.experimental.utils.validation import (
8+
_basic_input_array_checks,
9+
_check_same_shape,
10+
)
11+
12+
13+
def _mean_absolute_percentage_error_update(
14+
target: Array,
15+
preds: Array,
16+
epsilon: float = 1.17e-06,
17+
) -> Tuple[Array, int]:
18+
"""Update and return variables required to compute the Mean Percentage Error.
19+
20+
Parameters
21+
----------
22+
target : Array
23+
Ground truth target values.
24+
preds : Array
25+
Estimated target values.
26+
epsilon : float, optional, default=1.17e-06
27+
Specifies the lower bound for target values. Any target value below epsilon
28+
is set to epsilon (avoids division by zero errors).
29+
30+
Returns
31+
-------
32+
Tuple[Array, int]
33+
Sum of absolute value of percentage errors over all observations and number
34+
of observations.
35+
36+
"""
37+
_basic_input_array_checks(target, preds)
38+
_check_same_shape(target, preds)
39+
xp = apc.array_namespace(target, preds)
40+
41+
abs_diff = xp.abs(preds - target)
42+
abs_target = xp.abs(target)
43+
clamped_abs_target = xp.where(
44+
abs_target < epsilon,
45+
xp.asarray(epsilon, dtype=abs_target.dtype, device=apc.device(abs_target)),
46+
abs_target,
47+
)
48+
abs_per_error = abs_diff / clamped_abs_target
49+
50+
sum_abs_per_error = xp.sum(abs_per_error, dtype=xp.float32)
51+
52+
num_obs = int(apc.size(target) or 0)
53+
54+
return sum_abs_per_error, num_obs
55+
56+
57+
def _mean_absolute_percentage_error_compute(
58+
sum_abs_per_error: Array,
59+
num_obs: Union[int, Array],
60+
) -> Array:
61+
"""Compute the Mean Absolute Percentage Error.
62+
63+
Parameters
64+
----------
65+
sum_abs_per_error : Array
66+
Sum of absolute value of percentage errors over all observations.
67+
``(percentage error = (target - prediction) / target)``
68+
num_obs : int, Array
69+
Number of observations.
70+
71+
Returns
72+
-------
73+
Array
74+
The mean absolute percentage error.
75+
76+
"""
77+
return sum_abs_per_error / num_obs # type: ignore[no-any-return]
78+
79+
80+
def mean_absolute_percentage_error(target: Array, preds: Array) -> Array:
81+
"""Compute the mean absolute percentage error.
82+
83+
Parameters
84+
----------
85+
target : Array
86+
Ground truth target values.
87+
preds : Array
88+
Estimated target values.
89+
90+
Returns
91+
-------
92+
Array
93+
The mean absolute percentage error.
94+
95+
Raises
96+
------
97+
TypeError
98+
If `target` or `preds` is not an array object that is compatible with
99+
the Python array API standard.
100+
ValueError
101+
If `target` or `preds` is empty.
102+
ValueError
103+
If `target` or `preds` is not a numeric array.
104+
ValueError
105+
If the shape of `target` and `preds` are not the same.
106+
107+
Notes
108+
-----
109+
The epsilon value is taken from `scikit-learn's implementation of MAPE`.
110+
111+
Examples
112+
--------
113+
>>> import numpy.array_api as anp
114+
>>> from cyclops.evaluate.metrics.experimental.functional import (
115+
... mean_absolute_percentage_error
116+
... )
117+
>>> target = anp.asarray([1., 10., 1e6])
118+
>>> preds = anp.asarray([0.9, 15., 1.2e6])
119+
>>> mean_absolute_percentage_error(target, preds)
120+
Array(0.26666668, dtype=float32)
121+
122+
"""
123+
sum_abs_per_error, num_obs = _mean_absolute_percentage_error_update(target, preds)
124+
return _mean_absolute_percentage_error_compute(sum_abs_per_error, num_obs)

0 commit comments

Comments
 (0)