@@ -8,7 +8,7 @@ use crate::{
88 Float ,
99} ;
1010use ndarray:: prelude:: * ;
11- use ndarray:: Data ;
11+ use ndarray:: { Data , Zip } ;
1212use std:: ops:: { Div , Sub } ;
1313
1414/// Regression metrices trait for single targets.
@@ -80,6 +80,29 @@ pub trait SingleTargetRegression<F: Float, T: AsSingleTargets<Elem = F>>:
8080 . ok_or ( Error :: NotEnoughSamples )
8181 }
8282
83+ /// Symmetric mean absolute percentage error between two continuous variables.
84+ /// This implementation follows the Adjusted sMAPE (Makridakis, 1993)
85+ /// sMAPE = (200 / n) * SUM(abs(y_hat - y) / (abs(y) + abs(y_hat)))
86+ fn symmetric_mean_absolute_percentage_error ( & self , compare_to : & T ) -> Result < F > {
87+ let y = self . as_single_targets ( ) ;
88+ let y_hat = compare_to. as_single_targets ( ) ;
89+ if y. is_empty ( ) {
90+ return Err ( Error :: NotEnoughSamples ) ;
91+ }
92+ let sum: F = Zip :: from ( & y)
93+ . and ( & y_hat)
94+ . fold ( F :: cast ( 0.0 ) , |acc, & yi, & yhi| {
95+ let num = ( yhi - yi) . abs ( ) ;
96+ let den = yi. abs ( ) + yhi. abs ( ) ;
97+ if den <= F :: epsilon ( ) {
98+ acc
99+ } else {
100+ acc + ( num / den)
101+ }
102+ } ) ;
103+ Ok ( ( F :: cast ( 200.0 ) / F :: cast ( y. len ( ) ) ) * sum)
104+ }
105+
83106 /// R squared coefficient, is the proportion of the variance in the dependent variable that is
84107 /// predictable from the independent variable
85108 // r2 = 1 - sum((pred_i - y_i)^2)/sum((mean_y - y_i)^2)
@@ -193,6 +216,17 @@ pub trait MultiTargetRegression<F: Float, T: AsMultiTargets<Elem = F>>:
193216 . collect ( )
194217 }
195218
219+ /// Symmetric mean absolute percentage error between two continuous variables
220+ /// This implementation follows the Adjusted sMAPE (Makridakis, 1993)
221+ /// sMAPE = (200 / n) * SUM(abs(y_hat - y) / (abs(y) + abs(y_hat)))
222+ fn symmetric_mean_absolute_percentage_error ( & self , other : & T ) -> Result < Array1 < F > > {
223+ self . as_multi_targets ( )
224+ . axis_iter ( Axis ( 1 ) )
225+ . zip ( other. as_multi_targets ( ) . axis_iter ( Axis ( 1 ) ) )
226+ . map ( |( a, b) | a. symmetric_mean_absolute_percentage_error ( & b) )
227+ . collect ( )
228+ }
229+
196230 /// R squared coefficient, is the proportion of the variance in the dependent variable that is
197231 /// predictable from the independent variable
198232 fn r2 ( & self , other : & T ) -> Result < Array1 < F > > {
@@ -225,7 +259,7 @@ impl<F: Float, T: AsMultiTargets<Elem = F>, T2: AsMultiTargets<Elem = F>, D: Dat
225259
226260#[ cfg( test) ]
227261mod tests {
228- use super :: SingleTargetRegression ;
262+ use super :: { MultiTargetRegression , SingleTargetRegression } ;
229263 use crate :: dataset:: DatasetBase ;
230264 use approx:: assert_abs_diff_eq;
231265 use ndarray:: prelude:: * ;
@@ -242,6 +276,10 @@ mod tests {
242276 assert_abs_diff_eq ! ( a. r2( & a) . unwrap( ) , 1.0f32 ) ;
243277 assert_abs_diff_eq ! ( a. explained_variance( & a) . unwrap( ) , 1.0f32 ) ;
244278 assert_abs_diff_eq ! ( a. mean_absolute_percentage_error( & a) . unwrap( ) , 0.0f32 ) ;
279+ assert_abs_diff_eq ! (
280+ a. symmetric_mean_absolute_percentage_error( & a) . unwrap( ) ,
281+ 0.0f32
282+ ) ;
245283 }
246284
247285 #[ test]
@@ -281,6 +319,18 @@ mod tests {
281319 ) ;
282320 }
283321
322+ #[ test]
323+ fn test_symmetric_mean_absolute_percentage_error ( ) {
324+ let a = array ! [ 0.5 , 0.1 , 0.2 , 0.3 , 0.4 ] ;
325+ let b = array ! [ 0.1 , 0.2 , 0.3 , 0.4 , 0.5 ] ;
326+
327+ assert_abs_diff_eq ! (
328+ a. symmetric_mean_absolute_percentage_error( & b) . unwrap( ) ,
329+ 58.15873014693111 ,
330+ epsilon = 1e-5
331+ ) ;
332+ }
333+
284334 #[ test]
285335 fn test_max_error_for_single_targets ( ) {
286336 let records = array ! [ [ 0.0 , 0.0 ] , [ 0.1 , 0.1 ] , [ 0.2 , 0.2 ] , [ 0.3 , 0.3 ] , [ 0.4 , 0.4 ] ] ;
@@ -339,6 +389,23 @@ mod tests {
339389 assert_abs_diff_eq ! ( pct_err_from_arr1, pct_err_from_ds) ;
340390 }
341391
392+ #[ test]
393+ fn test_symmetric_mean_absolute_percentage_error_for_single_targets ( ) {
394+ let records = array ! [ [ 0.0 , 0.0 ] , [ 0.1 , 0.1 ] , [ 0.2 , 0.2 ] , [ 0.3 , 0.3 ] , [ 0.4 , 0.4 ] ] ;
395+ let targets = array ! [ 0.0 , 0.1 , 0.2 , 0.3 , 0.4 ] ;
396+ let st_dataset: DatasetBase < _ , _ > = ( records. view ( ) , targets) . into ( ) ;
397+ let prediction = array ! [ 0.1 , 0.3 , 0.2 , 0.5 , 0.7 ] ;
398+ let err_from_arr = prediction
399+ . symmetric_mean_absolute_percentage_error ( st_dataset. targets ( ) )
400+ . unwrap ( ) ;
401+ let prediction_ds: DatasetBase < _ , _ > = ( records. view ( ) , prediction) . into ( ) ;
402+ let err_from_ds = prediction_ds
403+ . symmetric_mean_absolute_percentage_error ( & st_dataset)
404+ . unwrap ( ) ;
405+ assert_abs_diff_eq ! ( err_from_arr, 80.90909086184916 , epsilon = 1e-5 ) ;
406+ assert_abs_diff_eq ! ( err_from_arr, err_from_ds) ;
407+ }
408+
342409 #[ test]
343410 fn test_mean_squared_log_error_for_single_targets ( ) {
344411 let records = array ! [ [ 0.0 , 0.0 ] , [ 0.1 , 0.1 ] , [ 0.2 , 0.2 ] , [ 0.3 , 0.3 ] , [ 0.4 , 0.4 ] ] ;
@@ -410,4 +477,13 @@ mod tests {
410477 assert_abs_diff_eq ! ( abs_err_from_arr1, 0.8 , epsilon = 1e-5 ) ;
411478 assert_abs_diff_eq ! ( abs_err_from_arr1, abs_err_from_ds) ;
412479 }
480+
481+ #[ test]
482+ fn test_symmetric_mean_absolute_percentage_error_multi_target ( ) {
483+ let a = array ! [ [ 1.0 , 2.0 ] , [ 3.0 , 4.0 ] , [ 5.0 , 6.0 ] ] ;
484+ let b = array ! [ [ 1.1 , 1.9 ] , [ 2.9 , 4.1 ] , [ 5.1 , 5.9 ] ] ;
485+ let err = a. symmetric_mean_absolute_percentage_error ( & b) . unwrap ( ) ;
486+ let expected = array ! [ 4.964612684 , 3.092671067 ] ;
487+ assert_abs_diff_eq ! ( err, expected, epsilon = 1e-5 ) ;
488+ }
413489}
0 commit comments