Skip to content

Commit 05e01d4

Browse files
committed
Implement MAPE and SMAPE
1 parent e827ff2 commit 05e01d4

File tree

3 files changed

+59
-5
lines changed

3 files changed

+59
-5
lines changed

Orange/evaluation/scoring.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323

2424

2525
__all__ = ["CA", "Precision", "Recall", "F1", "PrecisionRecallFSupport", "AUC",
26-
"MSE", "RMSE", "MAE", "MAPE", "R2", "LogLoss", "MatthewsCorrCoefficient"]
26+
"MSE", "RMSE", "MAE", "MAPE", "SMAPE", "R2", "LogLoss",
27+
"MatthewsCorrCoefficient"]
2728

2829

2930
class ScoreMetaType(WrapperMeta):
@@ -423,14 +424,30 @@ class MAE(RegressionScore):
423424

424425

425426
class MAPE(RegressionScore):
426-
__wraps__ = skl_metrics.mean_absolute_percentage_error
427427
name = "MAPE"
428428
long_name = "Mean absolute percentage error"
429429
priority = 45
430430

431-
def compute_score(self, results):
432-
res = super().compute_score(results)
433-
return res * 100
431+
@staticmethod
432+
def __wraps__(actual, predicted):
433+
if np.any(actual == 0):
434+
return np.inf
435+
return np.sum(np.abs((actual - predicted) / actual)) / len(actual) * 100
436+
437+
438+
class SMAPE(RegressionScore):
439+
name = "sMAPE"
440+
long_name = "Symmetric mean absolute percentage error"
441+
priority = 45
442+
443+
@staticmethod
444+
def __wraps__(actual, predicted):
445+
diff = np.abs(actual - predicted)
446+
summ = np.abs(actual) + np.abs(predicted)
447+
# To avoid 0 / 0, set divisor to 1; error will be 0, as it should be
448+
summ[summ == 0] = 1.0
449+
error = diff / summ
450+
return 2 * np.sum(error) / len(actual) * 100
434451

435452

436453
# pylint: disable=invalid-name
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import unittest
2+
3+
import numpy as np
4+
5+
from Orange.evaluation import MAPE
6+
from Orange.evaluation.scoring import SMAPE
7+
8+
9+
class TestScoring(unittest.TestCase):
10+
def test_mape(self):
11+
f = MAPE.__wraps__
12+
exp = np.array([100, -200, 300, 60])
13+
pred = np.array([110, -180, 340, 60])
14+
self.assertEqual(f(exp, pred), (10 / 100 + 20 / 200 + 40 / 300) / 4 * 100)
15+
16+
exp = np.array([0, 200, 300])
17+
self.assertEqual(f(exp, pred), np.inf)
18+
19+
def test_smape(self):
20+
f = SMAPE.__wraps__
21+
exp = np.array([100, -200, 300, 60, 80])
22+
pred = np.array([110, -180, -340, 60, 50])
23+
self.assertEqual(
24+
f(exp, pred),
25+
2 * (10 / 210 + 20 / 380 + 640 / 640 + 0 / 120 + 30 / 130) / 5 * 100)
26+
27+
exp = np.array([0, -200, 300, 60, 80])
28+
self.assertEqual(
29+
f(exp, pred),
30+
2 * (110 / 110 + 20 / 380 + 640 / 640 + 0 / 120 + 30 / 130) / 5 * 100)
31+
32+
33+
if __name__ == '__main__':
34+
unittest.main()

i18n/si/msgs.jaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2254,6 +2254,9 @@ evaluation/scoring.py:
22542254
class `MAPE`:
22552255
MAPE: true
22562256
Mean absolute percentage error: Povprečna absolutna odstotna napaka
2257+
class `SMAPE`:
2258+
sMAPE: true
2259+
Symmetric mean absolute percentage error: Simetrična povprečna absolutna odstotna napaka
22572260
class `R2`:
22582261
R2: true
22592262
# Je to OK?

0 commit comments

Comments
 (0)