Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[MRG] Enhancement: Add MAPE as an evaluation metric #10711

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 55 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
d45fb57
adding MAPE as a new regression loss
mohamed-ali Feb 26, 2018
762fa19
adding MAPE api reference
mohamed-ali Feb 26, 2018
9a6b8fd
adding MAPE scorer
mohamed-ali Feb 26, 2018
ea62611
adding MAPE to metrics/__init__.py
mohamed-ali Feb 26, 2018
70ab1e0
configuring tests for MAPE scorer
mohamed-ali Feb 26, 2018
962a8e2
configuring common tests for MAPE metric
mohamed-ali Feb 26, 2018
4f16a45
correcting import in MAPE example
mohamed-ali Feb 26, 2018
e2e93b0
adding documentation under model_evaluation
mohamed-ali Feb 26, 2018
6df2446
fixing pep8
mohamed-ali Feb 26, 2018
7c516ef
adding more MAPE regression tests
mohamed-ali Feb 26, 2018
cb7d875
add whitespace around operators
mohamed-ali Feb 26, 2018
8194eb0
fix bug: missing comma
mohamed-ali Feb 26, 2018
fd28645
avoiding division by zero in test scenario
mohamed-ali Feb 27, 2018
d10af3c
adding check to validate y_true contains no zeros
mohamed-ali Feb 27, 2018
61ea463
documenting the new feature in whats_new
mohamed-ali Feb 27, 2018
088467f
precising that MAPE is a non symetric metric
mohamed-ali Feb 27, 2018
f9ac4e4
change scorer to neg_mape and fix pep8
mohamed-ali Feb 27, 2018
595bcf6
adding a metrics category for non-zero y
mohamed-ali Feb 28, 2018
7575d0e
fix pep8 issues
mohamed-ali Feb 28, 2018
a228116
undo unrelated pep8 changes
mohamed-ali Feb 28, 2018
199f038
fix conflict
mohamed-ali Feb 28, 2018
732d4dc
Merge branch 'master' into Add-MAPE-as-evaluation-metric
mohamed-ali Feb 28, 2018
7a78396
fixing scorers tests in test_score_objects.py
mohamed-ali Feb 28, 2018
0ffc824
undo autopep8 irrelevant changes
mohamed-ali Feb 28, 2018
953a6e0
undo autopep8 changes
mohamed-ali Feb 28, 2018
064833b
undo autopep8 change
mohamed-ali Feb 28, 2018
e707fc8
unduplicating tests by using randint(1,3)
mohamed-ali Feb 28, 2018
cc8cd10
use elif instead
mohamed-ali Feb 28, 2018
c780845
remove uncessary comments and add space to operator +
mohamed-ali Mar 1, 2018
9dba988
using [1, 2] as a single sample test
mohamed-ali Mar 2, 2018
ff43f92
Merge branch 'Add-MAPE-as-evaluation-metric' of https://github.com/mo…
mohamed-ali Mar 2, 2018
a48f9d7
remove excessive indentation
mohamed-ali Mar 2, 2018
a0f2cd0
test raise error when y contains zeros
mohamed-ali Mar 2, 2018
bfe2143
use memmap type for test scenario
mohamed-ali Mar 4, 2018
e4cb140
tear down nonzero_y_mm
mohamed-ali Mar 4, 2018
7312998
keep naming consistency
mohamed-ali Mar 4, 2018
9c3a776
Trigger travis after timeout
mohamed-ali Mar 4, 2018
553f1ed
Merge branch 'master' into Add-MAPE-as-evaluation-metric
mohamed-ali Mar 26, 2018
9ea3975
add comma aster n_samples
mohamed-ali Mar 26, 2018
407332a
specify that mape is between 0 and 100
mohamed-ali Mar 26, 2018
c74eabb
update MAPE description in model_evalution
mohamed-ali Mar 26, 2018
60f370d
updated mape description and add not shift-invariant demo
mohamed-ali Mar 26, 2018
e04fe37
fix travis error
mohamed-ali Mar 26, 2018
2e20a48
fix failing doctests
mohamed-ali Mar 26, 2018
203aed1
fix travis error
mohamed-ali Mar 26, 2018
ec7de1e
Merge branch 'master' into Add-MAPE-as-evaluation-metric
mohamed-ali Mar 27, 2018
96d6d4a
Merge branch 'Add-MAPE-as-evaluation-metric' of https://github.com/mo…
mohamed-ali Mar 27, 2018
7aac4c0
remove line to keep code consistent
mohamed-ali Mar 28, 2018
6f1ab55
put mape in metrics section
mohamed-ali Mar 28, 2018
0ac8bc4
resolve conflicts
mohamed-ali Aug 16, 2019
e2184f3
fix syntax error
mohamed-ali Aug 18, 2019
3dbe763
fix merge conflicts in metrics.scorer
mohamed-ali Aug 18, 2019
83c4aa5
fix merge conflicts
mohamed-ali Aug 18, 2019
6b350a5
fix merge conflicts
mohamed-ali Aug 18, 2019
bb36550
add what's new
mohamed-ali Aug 20, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/modules/classes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,7 @@ details.
metrics.explained_variance_score
metrics.max_error
metrics.mean_absolute_error
metrics.mean_absolute_percentage_error
metrics.mean_squared_error
metrics.mean_squared_log_error
metrics.median_absolute_error
Expand Down
41 changes: 41 additions & 0 deletions doc/modules/model_evaluation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ Scoring Function
'explained_variance' :func:`metrics.explained_variance_score`
'max_error' :func:`metrics.max_error`
'neg_mean_absolute_error' :func:`metrics.mean_absolute_error`
'neg_mape' :func:`metrics.mean_absolute_percentage_error`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not spell it out fully here since like all the other metrics? i.e. neg_mean_absolute_percentage_error

Copy link
Contributor Author

@mohamed-ali mohamed-ali Mar 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lesteve I clarified in the PR description above that the name has to be chosen/voted by all of us. Initially I used neg_mean_absolute_percentage_error but then, since mape is already a famous acronym which, also, makes the metric cleaner, I chose to switch to neg_mape. However, we can change back to the long version, If most of us think that's the right thing to do.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be in favour of neg_mean_absolute_error version personally. It is more consistent with neg_mean_absolute_error and more consistent with the metric name ( metrics.mean_absolute_percentage_error). Happy to hear what others think.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also be in favor using the explicit expanded name by default and introduce neg_mape as an alias as we do for neg_mse.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually we do not have neg_mse. I thought we had.

'neg_mean_squared_error' :func:`metrics.mean_squared_error`
'neg_root_mean_squared_error' :func:`metrics.mean_squared_error`
'neg_mean_squared_log_error' :func:`metrics.mean_squared_log_error`
Expand Down Expand Up @@ -1859,6 +1860,46 @@ Here is a small example of usage of the :func:`mean_absolute_error` function::
>>> mean_absolute_error(y_true, y_pred, multioutput=[0.3, 0.7])
0.85...

.. _mean_absolute_percentage_error:

Mean absolute percentage error
------------------------------

The :func:`mean_absolute_percentage_error` function, also known as **MAPE**, computes `mean absolute
percentage error <https://en.wikipedia.org/wiki/Mean_absolute_percentage_error>`_, a risk
metric corresponding to the expected value of the absolute percentage error loss or
:math:`l1`-norm of percentage loss.

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 mean absolute percentage error
(MAPE) estimated over :math:`n_{\text{samples}}` is defined as
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would put the (MAPE) at the first mention of mean absolute percentage error above.

Also maybe add at least one sentence of explanation, say
"MAPE computes the error relative to the true value. Therefore the same absolute distance between prediction and ground truth will lead to a smaller error if the true value is larger. In particular the metrics is not shift-invariant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


.. math::

\text{MAPE}(y, \hat{y}) = \frac{100}{n_{\text{samples}}} \sum_{i=0}^{n_{\text{samples}}-1} \left| \frac{y_i - \hat{y}_i}{y_i} \right|.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't reviewed how the rest of the document does it, but it seems excessively pedantic to say the sum starts at 0 and ends n_samples-1: it makes the formula a little harder to read, where \sum_i would suffice

Copy link
Contributor Author

@mohamed-ali mohamed-ali Feb 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to follow the formula from Wikipedia, but your \sum_i is clearer.
EDIT: in fact, it's inspired from the other metrics formulas like: accuracy score, MAE, MSE, ..
see for instance: http://scikit-learn.org/stable/modules/model_evaluation.html#mean-absolute-error.

We can make the change in another PR if enough people agree with it :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jnothman, I think it's better to keep it as is, to remain consistent with the other metrics definitions. We can create an issue to apply the change to all metrics separately.


Here is a small example of usage of the :func:`mean_absolute_percentage_error` function::

>>> from sklearn.metrics import mean_absolute_percentage_error
>>> y_true = [3, -0.5, 2, 7]
>>> y_pred = [2.5, 0.0, 2, 8]
>>> mean_absolute_percentage_error(y_true, y_pred)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add an example of it not being shift-invariant, i.e. add 10 to y_true and y_pred and show that the error is much smaller and add a sentence to explain.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

32.738...

MAPE computes the error relative to the true value. Therefore the same absolute distance between
prediction and ground truth will lead to a smaller error if the true value is larger.
In particular the metric is not shift-invariant. For example, if :math:`y_{true}` and :math:`y_{pred}`
in the example above are shifted by adding 10, the error becomes smaller:

>>> from sklearn.metrics import mean_absolute_percentage_error
>>> import numpy as np
>>> y_true = np.array([3, -0.5, 2, 7])
>>> y_pred = np.array([2.5, 0.0, 2, 8])
>>> y_true = y_true + 10
>>> y_pred = y_pred + 10
>>> mean_absolute_percentage_error(y_true, y_pred)
3.747...

.. _mean_squared_error:

Mean squared error
Expand Down
1 change: 0 additions & 1 deletion doc/whats_new/v0.20.rst
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,6 @@ Support for Python 3.3 has been officially dropped.
by `Andreas Müller`_ and :user:`Guillaume Lemaitre <glemaitre>`.



:mod:`sklearn.covariance`
.........................

Expand Down
2 changes: 2 additions & 0 deletions doc/whats_new/v0.22.rst
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ Changelog
precomputed distance matrix contains non-zero diagonal entries.
:pr:`12258` by :user:`Stephen Tierney <sjtrny>`.

- |Feature| Added the :func:`metrics.mean_absolute_percentage_error` metric and the associated scorer for regression problems. :issue:`10711` by :user:`Mohamed Ali Jamaoui <mohamed-ali>`

:mod:`sklearn.model_selection`
..............................

Expand Down
2 changes: 2 additions & 0 deletions sklearn/metrics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
from .regression import explained_variance_score
from .regression import max_error
from .regression import mean_absolute_error
from .regression import mean_absolute_percentage_error
from .regression import mean_squared_error
from .regression import mean_squared_log_error
from .regression import median_absolute_error
Expand Down Expand Up @@ -118,6 +119,7 @@
'matthews_corrcoef',
'max_error',
'mean_absolute_error',
'mean_absolute_percentage_error',
'mean_squared_error',
'mean_squared_log_error',
'mean_poisson_deviance',
Expand Down
43 changes: 43 additions & 0 deletions sklearn/metrics/regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
# Michael Eickenberg <[email protected]>
# Konstantin Shmelkov <[email protected]>
# Christian Lorentzen <[email protected]>
# Mohamed Ali Jamaoui <[email protected]>
# License: BSD 3 clause


Expand All @@ -36,6 +37,7 @@
__ALL__ = [
"max_error",
"mean_absolute_error",
"mean_absolute_percentage_error",
"mean_squared_error",
"mean_squared_log_error",
"median_absolute_error",
Expand Down Expand Up @@ -189,6 +191,47 @@ def mean_absolute_error(y_true, y_pred,
return np.average(output_errors, weights=multioutput)


def mean_absolute_percentage_error(y_true, y_pred):
"""Mean absolute percentage error regression loss

Read more in the :ref:`User Guide <mean_absolute_percentage_error>`.

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
-------
loss : float
A positive floating point value between 0.0 and 100.0,
the best value is 0.0.

Examples
--------
>>> from sklearn.metrics import mean_absolute_percentage_error
>>> y_true = [3, -0.5, 2, 7]
>>> y_pred = [2.5, 0.0, 2, 8]
>>> mean_absolute_percentage_error(y_true, y_pred)
32.738...
"""
y_type, y_true, y_pred, _ = _check_reg_targets(y_true, y_pred,
'uniform_average')

if y_type == 'continuous-multioutput':
raise ValueError("Multioutput not supported "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine not to support it for now, but I think there is little doubt that multi-output mape would be the same on the flattened input: this would give an identical measure to macro-averaging. If we supported the variant mentioned in Wikipedia where you divide by the mean y_true, that is a different matter, because the mean across all columns may be inappropriate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess there is a possibility to add the two kinds of implementations of MAPE and allow the user to change between them.

We can also do that, when users request to have it :)

"in mean_absolute_percentage_error")

if (y_true == 0).any():
raise ValueError("mean_absolute_percentage_error requires"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not currently executed in any tests. It should be tested

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a test case called test_raise_value_error_y_with_zeros for this particular error

" y_true to not include zeros")

return np.mean(np.abs((y_true - y_pred) / y_true)) * 100


def mean_squared_error(y_true, y_pred,
sample_weight=None,
multioutput='uniform_average', squared=True):
Expand Down
6 changes: 4 additions & 2 deletions sklearn/metrics/scorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,13 @@
from collections.abc import Iterable

import numpy as np

from . import (r2_score, median_absolute_error, max_error, mean_absolute_error,
mean_squared_error, mean_squared_log_error,
mean_tweedie_deviance, accuracy_score,
f1_score, roc_auc_score, average_precision_score,
precision_score, recall_score, log_loss,
balanced_accuracy_score, explained_variance_score,
brier_score_loss, jaccard_score)
brier_score_loss, jaccard_score, mean_absolute_percentage_error)

from .cluster import adjusted_rand_score
from .cluster import homogeneity_score
Expand Down Expand Up @@ -493,6 +492,8 @@ def make_scorer(score_func, greater_is_better=True, needs_proba=False,
greater_is_better=False)
neg_mean_absolute_error_scorer = make_scorer(mean_absolute_error,
greater_is_better=False)
neg_mape_scorer = make_scorer(mean_absolute_percentage_error,
greater_is_better=False)
neg_median_absolute_error_scorer = make_scorer(median_absolute_error,
greater_is_better=False)
neg_root_mean_squared_error_scorer = make_scorer(mean_squared_error,
Expand Down Expand Up @@ -547,6 +548,7 @@ def make_scorer(score_func, greater_is_better=True, needs_proba=False,

SCORERS = dict(explained_variance=explained_variance_scorer,
r2=r2_scorer,
neg_mape=neg_mape_scorer,
max_error=max_error_scorer,
neg_median_absolute_error=neg_median_absolute_error_scorer,
neg_mean_absolute_error=neg_mean_absolute_error_scorer,
Expand Down
35 changes: 27 additions & 8 deletions sklearn/metrics/tests/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
from sklearn.metrics import max_error
from sklearn.metrics import matthews_corrcoef
from sklearn.metrics import mean_absolute_error
from sklearn.metrics import mean_absolute_percentage_error
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_tweedie_deviance
from sklearn.metrics import mean_poisson_deviance
Expand Down Expand Up @@ -98,6 +99,7 @@
REGRESSION_METRICS = {
"max_error": max_error,
"mean_absolute_error": mean_absolute_error,
"mean_absolute_percentage_error": mean_absolute_percentage_error,
"mean_squared_error": mean_squared_error,
"median_absolute_error": median_absolute_error,
"explained_variance_score": explained_variance_score,
Expand Down Expand Up @@ -476,18 +478,24 @@ def precision_recall_curve_padded_thresholds(*args, **kwargs):
"macro_f0.5_score", "macro_f2_score", "macro_precision_score",
"macro_recall_score", "log_loss", "hinge_loss",
"mean_gamma_deviance", "mean_poisson_deviance",
"mean_compound_poisson_deviance"
"mean_compound_poisson_deviance", "mean_absolute_percentage_error"
}


# No Sample weight support
METRICS_WITHOUT_SAMPLE_WEIGHT = {
"median_absolute_error",
"mean_absolute_percentage_error",
"max_error",
"ovo_roc_auc",
"weighted_ovo_roc_auc"
}

# Metrics that only support non-zero y
METRICS_WITH_NON_ZERO_Y = [
"mean_absolute_percentage_error",
"mean_absolute_percentage_error"
]

METRICS_REQUIRE_POSITIVE_Y = {
"mean_poisson_deviance",
"mean_gamma_deviance",
Expand Down Expand Up @@ -520,8 +528,8 @@ def test_symmetry_consistency():
def test_symmetric_metric(name):
# Test the symmetry of score and loss functions
random_state = check_random_state(0)
y_true = random_state.randint(0, 2, size=(20, ))
y_pred = random_state.randint(0, 2, size=(20, ))
y_true = random_state.randint(1, 3, size=(20, ))
y_pred = random_state.randint(1, 3, size=(20, ))

if name in METRICS_REQUIRE_POSITIVE_Y:
y_true, y_pred = _require_positive_targets(y_true, y_pred)
Expand Down Expand Up @@ -583,8 +591,6 @@ def test_sample_order_invariance(name):
@ignore_warnings
def test_sample_order_invariance_multilabel_and_multioutput():
random_state = check_random_state(0)

# Generate some data
y_true = random_state.randint(0, 2, size=(20, 25))
y_pred = random_state.randint(0, 2, size=(20, 25))
y_score = random_state.normal(size=y_true.shape)
Expand Down Expand Up @@ -621,8 +627,8 @@ def test_sample_order_invariance_multilabel_and_multioutput():
sorted(set(ALL_METRICS) - METRIC_UNDEFINED_BINARY_MULTICLASS))
def test_format_invariance_with_1d_vectors(name):
random_state = check_random_state(0)
y1 = random_state.randint(0, 2, size=(20, ))
y2 = random_state.randint(0, 2, size=(20, ))
y1 = random_state.randint(1, 3, size=(20, ))
y2 = random_state.randint(1, 3, size=(20, ))

if name in METRICS_REQUIRE_POSITIVE_Y:
y1, y2 = _require_positive_targets(y1, y2)
Expand Down Expand Up @@ -1305,6 +1311,19 @@ def test_no_averaging_labels():
assert_array_equal(score_labels, score[inverse_labels])


def test_raise_value_error_y_with_zeros():
random_state = check_random_state(0)
y_true = random_state.randint(0, 2, size=(20, ))
y_pred = random_state.randint(0, 2, size=(20, ))

for name in METRICS_WITH_NON_ZERO_Y:
metric = ALL_METRICS[name]
assert_raise_message(ValueError,
"mean_absolute_percentage_error requires"
" y_true to not include zeros",
metric, y_true, y_pred)


@pytest.mark.parametrize(
'name',
sorted(MULTILABELS_METRICS - {"unnormalized_multilabel_confusion_matrix"}))
Expand Down
7 changes: 6 additions & 1 deletion sklearn/metrics/tests/test_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from sklearn.metrics import explained_variance_score
from sklearn.metrics import mean_absolute_error
from sklearn.metrics import mean_absolute_percentage_error
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_squared_log_error
from sklearn.metrics import median_absolute_error
Expand All @@ -32,6 +33,11 @@ def test_regression_metrics(n_samples=50):
mean_squared_error(np.log(1 + y_true),
np.log(1 + y_pred)))
assert_almost_equal(mean_absolute_error(y_true, y_pred), 1.)
# comparing (y_true + 1) and (y_pred + 1) instead of
# y_true and y_pred to avoid division by zero
assert_almost_equal(mean_absolute_percentage_error(1 + y_true,
1 + y_pred),
8.998, 2)
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)
Expand Down Expand Up @@ -136,7 +142,6 @@ def test_regression_metrics_at_limits():
match="deviance is only defined for p<=0 and p>=1."):
mean_tweedie_deviance([0.], [0.], p=0.5)


def test__check_reg_targets():
# All of length 3
EXAMPLES = [
Expand Down
36 changes: 26 additions & 10 deletions sklearn/metrics/tests/test_score_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@
from sklearn.multiclass import OneVsRestClassifier


REGRESSION_SCORERS = ['explained_variance', 'r2',
'neg_mean_absolute_error', 'neg_mean_squared_error',
REGRESSION_SCORERS = ['explained_variance', 'r2', 'neg_mean_absolute_error',
'neg_mape', 'neg_mean_squared_error',
'neg_mean_squared_log_error',
'neg_median_absolute_error',
'neg_root_mean_squared_error',
Expand Down Expand Up @@ -80,6 +80,8 @@ def _require_positive_y(y):
y = y + offset
return y

y_nozero_SCORERS = ['neg_mape']


def _make_estimators(X_train, y_train, y_ml_train):
# Make estimators that make sense to test various scoring methods
Expand All @@ -98,28 +100,30 @@ def _make_estimators(X_train, y_train, y_ml_train):
)


X_mm, y_mm, y_ml_mm = None, None, None
X_mm, y_mm, y_ml_mm, y_nozero_mm = None, None, None, None
ESTIMATORS = None
TEMP_FOLDER = None


def setup_module():
# Create some memory mapped data
global X_mm, y_mm, y_ml_mm, TEMP_FOLDER, ESTIMATORS
global X_mm, y_mm, y_ml_mm, y_nozero_mm, TEMP_FOLDER, ESTIMATORS
TEMP_FOLDER = tempfile.mkdtemp(prefix='sklearn_test_score_objects_')
X, y = make_classification(n_samples=30, n_features=5, random_state=0)
_, y_ml = make_multilabel_classification(n_samples=X.shape[0],
random_state=0)
y_nozero = y + 1
filename = os.path.join(TEMP_FOLDER, 'test_data.pkl')
joblib.dump((X, y, y_ml), filename)
X_mm, y_mm, y_ml_mm = joblib.load(filename, mmap_mode='r')
joblib.dump((X, y, y_ml, y_nozero), filename)
X_mm, y_mm, y_ml_mm, y_nozero_mm = joblib.load(filename, mmap_mode='r')
ESTIMATORS = _make_estimators(X_mm, y_mm, y_ml_mm)


def teardown_module():
global X_mm, y_mm, y_ml_mm, TEMP_FOLDER, ESTIMATORS
global X_mm, y_mm, y_ml_mm, y_nozero_mm, TEMP_FOLDER, ESTIMATORS
# GC closes the mmap file descriptors
X_mm, y_mm, y_ml_mm, ESTIMATORS = None, None, None, None
y_nozero_mm = None
shutil.rmtree(TEMP_FOLDER)


Expand Down Expand Up @@ -509,9 +513,21 @@ def test_scorer_sample_weight():
ignored))

except TypeError as e:
assert "sample_weight" in str(e), (
"scorer {0} raises unhelpful exception when called "
"with sample weights: {1}".format(name, str(e)))
assert_true("sample_weight" in str(e),
"scorer {0} raises unhelpful exception when called "
"with sample weights: {1}".format(name, str(e)))


@ignore_warnings # UndefinedMetricWarning for P / R scores
def check_scorer_memmap(scorer_name):
scorer, estimator = SCORERS[scorer_name], ESTIMATORS[scorer_name]
if scorer_name in MULTILABEL_ONLY_SCORERS:
score = scorer(estimator, X_mm, y_ml_mm)
elif scorer_name in y_nozero_SCORERS:
score = scorer(estimator, X_mm, y_nozero_mm)
else:
score = scorer(estimator, X_mm, y_mm)
assert isinstance(score, numbers.Number), scorer_name


@pytest.mark.parametrize('name', SCORERS)
Expand Down