diff --git a/doc/modules/classes.rst b/doc/modules/classes.rst index 57ccfb5cff704..0084bb3f76b85 100644 --- a/doc/modules/classes.rst +++ b/doc/modules/classes.rst @@ -865,6 +865,7 @@ details. :template: function.rst metrics.explained_variance_score + metrics.max_error metrics.mean_absolute_error metrics.mean_squared_error metrics.mean_squared_log_error diff --git a/doc/modules/model_evaluation.rst b/doc/modules/model_evaluation.rst index 89bc3bb4a84e9..9c4e7efdacf6f 100644 --- a/doc/modules/model_evaluation.rst +++ b/doc/modules/model_evaluation.rst @@ -84,6 +84,7 @@ Scoring Function **Regression** 'explained_variance' :func:`metrics.explained_variance_score` +'max_error' :func:`metrics.max_error` 'neg_mean_absolute_error' :func:`metrics.mean_absolute_error` 'neg_mean_squared_error' :func:`metrics.mean_squared_error` 'neg_mean_squared_log_error' :func:`metrics.mean_squared_log_error` @@ -1530,6 +1531,38 @@ function:: ... # doctest: +ELLIPSIS 0.990... +.. _max_error: + +Max error +------------------- + +The :func:`max_error` function computes the maximum `residual error +`_ , a metric +that captures the worst case error between the predicted value and +the true value. In a perfectly fitted single output regression +model, ``max_error`` would be ``0`` on the training set and though this +would be highly unlikely in the real world, this metric shows the +extent of error that the model had when it was fitted. + + +If :math:`\hat{y}_i` is the predicted value of the :math:`i`-th sample, +and :math:`y_i` is the corresponding true value, then the max error is +defined as + +.. math:: + + \text{Max Error}(y, \hat{y}) = max(| y_i - \hat{y}_i |) + +Here is a small example of usage of the :func:`max_error` function:: + + >>> from sklearn.metrics import max_error + >>> y_true = [3, 2, 7, 1] + >>> y_pred = [9, 2, 7, 1] + >>> max_error(y_true, y_pred) + 6 + +The :func:`max_error` does not support multioutput. + .. _mean_absolute_error: Mean absolute error diff --git a/doc/whats_new/v0.21.rst b/doc/whats_new/v0.21.rst index 27a756d9eefe5..d560f980beb91 100644 --- a/doc/whats_new/v0.21.rst +++ b/doc/whats_new/v0.21.rst @@ -52,6 +52,15 @@ Support for Python 3.4 and below has been officially dropped. graph, which would add explicitly zeros on the diagonal even when already present. :issue:`12105` by `Tom Dupre la Tour`_. + +:mod:`sklearn.metrics` +...................... + +- |Feature| Added the :func:`metrics.max_error` metric and a corresponding + ``'max_error'`` scorer for single output regression. + :issue:`12232` by :user:`Krishna Sangeeth `. + + Multiple modules ................ diff --git a/sklearn/metrics/__init__.py b/sklearn/metrics/__init__.py index 2d19e24db8683..6aa885a9d5315 100644 --- a/sklearn/metrics/__init__.py +++ b/sklearn/metrics/__init__.py @@ -55,12 +55,14 @@ from .pairwise import pairwise_distances_chunked from .regression import explained_variance_score +from .regression import max_error from .regression import mean_absolute_error from .regression import mean_squared_error from .regression import mean_squared_log_error from .regression import median_absolute_error from .regression import r2_score + from .scorer import check_scoring from .scorer import make_scorer from .scorer import SCORERS @@ -99,6 +101,7 @@ 'log_loss', 'make_scorer', 'matthews_corrcoef', + 'max_error', 'mean_absolute_error', 'mean_squared_error', 'mean_squared_log_error', diff --git a/sklearn/metrics/regression.py b/sklearn/metrics/regression.py index e9084a4276e18..f4854ff244bc4 100644 --- a/sklearn/metrics/regression.py +++ b/sklearn/metrics/regression.py @@ -31,6 +31,7 @@ __ALL__ = [ + "max_error", "mean_absolute_error", "mean_squared_error", "mean_squared_log_error", @@ -573,3 +574,37 @@ def r2_score(y_true, y_pred, sample_weight=None, avg_weights = multioutput return np.average(output_scores, weights=avg_weights) + + +def max_error(y_true, y_pred): + """ + max_error metric calculates the maximum residual error. + + Read more in the :ref:`User Guide `. + + Parameters + ---------- + y_true : array-like of shape = (n_samples) + Ground truth (correct) target values. + + y_pred : array-like of shape = (n_samples) + Estimated target values. + + Returns + ------- + max_error : float + A positive floating point value (the best value is 0.0). + + Examples + -------- + >>> from sklearn.metrics import max_error + >>> y_true = [3, 2, 7, 1] + >>> y_pred = [4, 2, 7, 1] + >>> max_error(y_true, y_pred) + 1 + """ + y_type, y_true, y_pred, _ = _check_reg_targets(y_true, y_pred, None) + check_consistent_length(y_true, y_pred) + if y_type == 'continuous-multioutput': + raise ValueError("Multioutput not supported in max_error") + return np.max(np.abs(y_true - y_pred)) diff --git a/sklearn/metrics/scorer.py b/sklearn/metrics/scorer.py index 2661a379b4e53..f596d41637155 100644 --- a/sklearn/metrics/scorer.py +++ b/sklearn/metrics/scorer.py @@ -22,7 +22,7 @@ import numpy as np -from . import (r2_score, median_absolute_error, mean_absolute_error, +from . import (r2_score, median_absolute_error, max_error, mean_absolute_error, mean_squared_error, mean_squared_log_error, accuracy_score, f1_score, roc_auc_score, average_precision_score, precision_score, recall_score, log_loss, @@ -454,6 +454,8 @@ def make_scorer(score_func, greater_is_better=True, needs_proba=False, # Standard regression scores explained_variance_scorer = make_scorer(explained_variance_score) r2_scorer = make_scorer(r2_score) +max_error_scorer = make_scorer(max_error, + greater_is_better=False) neg_mean_squared_error_scorer = make_scorer(mean_squared_error, greater_is_better=False) neg_mean_squared_log_error_scorer = make_scorer(mean_squared_log_error, @@ -498,6 +500,7 @@ def make_scorer(score_func, greater_is_better=True, needs_proba=False, SCORERS = dict(explained_variance=explained_variance_scorer, r2=r2_scorer, + max_error=max_error_scorer, neg_median_absolute_error=neg_median_absolute_error_scorer, neg_mean_absolute_error=neg_mean_absolute_error_scorer, neg_mean_squared_error=neg_mean_squared_error_scorer, diff --git a/sklearn/metrics/tests/test_common.py b/sklearn/metrics/tests/test_common.py index 16e4f5d4c76da..21c5e97444db6 100644 --- a/sklearn/metrics/tests/test_common.py +++ b/sklearn/metrics/tests/test_common.py @@ -40,6 +40,7 @@ from sklearn.metrics import label_ranking_average_precision_score from sklearn.metrics import label_ranking_loss from sklearn.metrics import log_loss +from sklearn.metrics import max_error from sklearn.metrics import matthews_corrcoef from sklearn.metrics import mean_absolute_error from sklearn.metrics import mean_squared_error @@ -89,6 +90,7 @@ # REGRESSION_METRICS = { + "max_error": max_error, "mean_absolute_error": mean_absolute_error, "mean_squared_error": mean_squared_error, "median_absolute_error": median_absolute_error, @@ -399,7 +401,7 @@ def precision_recall_curve_padded_thresholds(*args, **kwargs): "micro_precision_score", "micro_recall_score", "matthews_corrcoef_score", "mean_absolute_error", "mean_squared_error", - "median_absolute_error", + "median_absolute_error", "max_error", "cohen_kappa_score", } @@ -429,6 +431,7 @@ def precision_recall_curve_padded_thresholds(*args, **kwargs): # No Sample weight support METRICS_WITHOUT_SAMPLE_WEIGHT = { "median_absolute_error", + "max_error" } diff --git a/sklearn/metrics/tests/test_regression.py b/sklearn/metrics/tests/test_regression.py index 2faaaad3a39f2..86849bd159358 100644 --- a/sklearn/metrics/tests/test_regression.py +++ b/sklearn/metrics/tests/test_regression.py @@ -14,6 +14,7 @@ from sklearn.metrics import mean_squared_error from sklearn.metrics import mean_squared_log_error from sklearn.metrics import median_absolute_error +from sklearn.metrics import max_error from sklearn.metrics import r2_score from sklearn.metrics.regression import _check_reg_targets @@ -29,6 +30,7 @@ def test_regression_metrics(n_samples=50): np.log(1 + y_pred))) assert_almost_equal(mean_absolute_error(y_true, y_pred), 1.) assert_almost_equal(median_absolute_error(y_true, y_pred), 1.) + assert_almost_equal(max_error(y_true, y_pred), 1.) assert_almost_equal(r2_score(y_true, y_pred), 0.995, 2) assert_almost_equal(explained_variance_score(y_true, y_pred), 1.) @@ -59,6 +61,7 @@ def test_regression_metrics_at_limits(): assert_almost_equal(mean_squared_log_error([0.], [0.]), 0.00, 2) assert_almost_equal(mean_absolute_error([0.], [0.]), 0.00, 2) assert_almost_equal(median_absolute_error([0.], [0.]), 0.00, 2) + assert_almost_equal(max_error([0.], [0.]), 0.00, 2) assert_almost_equal(explained_variance_score([0.], [0.]), 1.00, 2) assert_almost_equal(r2_score([0., 1], [0., 1]), 1.00, 2) assert_raises_regex(ValueError, "Mean Squared Logarithmic Error cannot be " diff --git a/sklearn/metrics/tests/test_score_objects.py b/sklearn/metrics/tests/test_score_objects.py index da04b4215dce0..633caf9a9b339 100644 --- a/sklearn/metrics/tests/test_score_objects.py +++ b/sklearn/metrics/tests/test_score_objects.py @@ -47,7 +47,8 @@ 'neg_mean_absolute_error', 'neg_mean_squared_error', 'neg_mean_squared_log_error', 'neg_median_absolute_error', 'mean_absolute_error', - 'mean_squared_error', 'median_absolute_error'] + 'mean_squared_error', 'median_absolute_error', + 'max_error'] CLF_SCORERS = ['accuracy', 'balanced_accuracy', 'f1', 'f1_weighted', 'f1_macro', 'f1_micro',