Skip to content

Commit a69849a

Browse files
MNT remove default behaviour deprecation from class_likelihood_ratios (scikit-learn#31331)
Co-authored-by: Jérémie du Boisberranger <jeremie@probabl.ai>
1 parent a5d7f9e commit a69849a

File tree

3 files changed

+24
-64
lines changed

3 files changed

+24
-64
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- The `raise_warning` parameter of :func:`metrics.class_likelihood_ratios` is deprecated
2+
and will be removed in 1.9. An `UndefinedMetricWarning` will always be raised in case
3+
of a division by zero.
4+
By :user:`Stefanie Senger <StefanieSenger>`.

sklearn/metrics/_classification.py

Lines changed: 16 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2052,7 +2052,6 @@ def precision_recall_fscore_support(
20522052
"sample_weight": ["array-like", None],
20532053
"raise_warning": ["boolean", Hidden(StrOptions({"deprecated"}))],
20542054
"replace_undefined_by": [
2055-
Hidden(StrOptions({"default"})),
20562055
Options(Real, {1.0, np.nan}),
20572056
dict,
20582057
],
@@ -2066,7 +2065,7 @@ def class_likelihood_ratios(
20662065
labels=None,
20672066
sample_weight=None,
20682067
raise_warning="deprecated",
2069-
replace_undefined_by="default",
2068+
replace_undefined_by=np.nan,
20702069
):
20712070
"""Compute binary classification positive and negative likelihood ratios.
20722071
@@ -2178,35 +2177,29 @@ class are present in `y_true`): both likelihood ratios are undefined.
21782177
--------
21792178
>>> import numpy as np
21802179
>>> from sklearn.metrics import class_likelihood_ratios
2181-
>>> class_likelihood_ratios([0, 1, 0, 1, 0], [1, 1, 0, 0, 0],
2182-
... replace_undefined_by=1.0)
2180+
>>> class_likelihood_ratios([0, 1, 0, 1, 0], [1, 1, 0, 0, 0])
21832181
(1.5, 0.75)
21842182
>>> y_true = np.array(["non-cat", "cat", "non-cat", "cat", "non-cat"])
21852183
>>> y_pred = np.array(["cat", "cat", "non-cat", "non-cat", "non-cat"])
2186-
>>> class_likelihood_ratios(y_true, y_pred, replace_undefined_by=1.0)
2184+
>>> class_likelihood_ratios(y_true, y_pred)
21872185
(1.33, 0.66)
21882186
>>> y_true = np.array(["non-zebra", "zebra", "non-zebra", "zebra", "non-zebra"])
21892187
>>> y_pred = np.array(["zebra", "zebra", "non-zebra", "non-zebra", "non-zebra"])
2190-
>>> class_likelihood_ratios(y_true, y_pred, replace_undefined_by=1.0)
2188+
>>> class_likelihood_ratios(y_true, y_pred)
21912189
(1.5, 0.75)
21922190
21932191
To avoid ambiguities, use the notation `labels=[negative_class,
21942192
positive_class]`
21952193
21962194
>>> y_true = np.array(["non-cat", "cat", "non-cat", "cat", "non-cat"])
21972195
>>> y_pred = np.array(["cat", "cat", "non-cat", "non-cat", "non-cat"])
2198-
>>> class_likelihood_ratios(y_true, y_pred, labels=["non-cat", "cat"],
2199-
... replace_undefined_by=1.0)
2196+
>>> class_likelihood_ratios(y_true, y_pred, labels=["non-cat", "cat"])
22002197
(1.5, 0.75)
22012198
"""
22022199
# TODO(1.9): When `raise_warning` is removed, the following changes need to be made:
22032200
# The checks for `raise_warning==True` need to be removed and we will always warn,
2204-
# the default return value of `replace_undefined_by` should be updated from `np.nan`
2205-
# (which was kept for backwards compatibility) to `1.0`, its hidden option
2206-
# ("default") is not used anymore, some warning messages can be removed, the Warns
2207-
# section in the docstring should not mention `raise_warning` anymore and the
2208-
# "Mathematical divergences" section in model_evaluation.rst needs to be updated on
2209-
# the new default behaviour of `replace_undefined_by`.
2201+
# remove `FutureWarning`, and the Warns section in the docstring should not mention
2202+
# `raise_warning` anymore.
22102203
y_true, y_pred = attach_unique(y_true, y_pred)
22112204
y_type, y_true, y_pred = _check_targets(y_true, y_pred)
22122205
if y_type != "binary":
@@ -2220,28 +2213,11 @@ class are present in `y_true`): both likelihood ratios are undefined.
22202213
"`UndefinedMetricWarning` will always be raised in case of a division by zero "
22212214
"and the value set with the `replace_undefined_by` param will be returned."
22222215
)
2223-
mgs_changed_default = (
2224-
"The default return value of `class_likelihood_ratios` in case of a division "
2225-
"by zero has been deprecated in 1.7 and will be changed to the worst scores "
2226-
"(`(1.0, 1.0)`) in version 1.9. Set `replace_undefined_by=1.0` to use the new"
2227-
"default and to silence this Warning."
2228-
)
22292216
if raise_warning != "deprecated":
2230-
warnings.warn(
2231-
" ".join((msg_deprecated_param, mgs_changed_default)), FutureWarning
2232-
)
2217+
warnings.warn(msg_deprecated_param, FutureWarning)
22332218
else:
2234-
if replace_undefined_by == "default":
2235-
# TODO(1.9): Remove. If users don't set any return values in case of a
2236-
# division by zero (`raise_warning="deprecated"` and
2237-
# `replace_undefined_by="default"`) they still get a FutureWarning about
2238-
# changing default return values:
2239-
warnings.warn(mgs_changed_default, FutureWarning)
22402219
raise_warning = True
22412220

2242-
if replace_undefined_by == "default":
2243-
replace_undefined_by = np.nan
2244-
22452221
if replace_undefined_by == 1.0:
22462222
replace_undefined_by = {"LR+": 1.0, "LR-": 1.0}
22472223

@@ -2293,12 +2269,12 @@ class are present in `y_true`): both likelihood ratios are undefined.
22932269

22942270
# if `support_pos == 0`a division by zero will occur
22952271
if support_pos == 0:
2296-
# TODO(1.9): Change return values in warning message to new default: the worst
2297-
# possible scores: `(1.0, 1.0)`
22982272
msg = (
22992273
"No samples of the positive class are present in `y_true`. "
23002274
"`positive_likelihood_ratio` and `negative_likelihood_ratio` are both set "
2301-
"to `np.nan`."
2275+
"to `np.nan`. Use the `replace_undefined_by` param to control this "
2276+
"behavior. To suppress this warning or turn it into an error, see Python's "
2277+
"`warnings` module and `warnings.catch_warnings()`."
23022278
)
23032279
warnings.warn(msg, UndefinedMetricWarning, stacklevel=2)
23042280
positive_likelihood_ratio = np.nan
@@ -2315,9 +2291,8 @@ class are present in `y_true`): both likelihood ratios are undefined.
23152291
else:
23162292
msg_beginning = "`positive_likelihood_ratio` is ill-defined and "
23172293
msg_end = "set to `np.nan`. Use the `replace_undefined_by` param to "
2318-
"control this behavior."
2319-
# TODO(1.9): Change return value in warning message to new default: `1.0`,
2320-
# which is the worst possible score for "LR+"
2294+
"control this behavior. To suppress this warning or turn it into an error, "
2295+
"see Python's `warnings` module and `warnings.catch_warnings()`."
23212296
warnings.warn(msg_beginning + msg_end, UndefinedMetricWarning, stacklevel=2)
23222297
if isinstance(replace_undefined_by, float) and np.isnan(replace_undefined_by):
23232298
positive_likelihood_ratio = replace_undefined_by
@@ -2332,11 +2307,11 @@ class are present in `y_true`): both likelihood ratios are undefined.
23322307
# if `tn == 0`a division by zero will occur
23332308
if tn == 0:
23342309
if raise_warning:
2335-
# TODO(1.9): Change return value in warning message to new default: `1.0`,
2336-
# which is the worst possible score for "LR-"
23372310
msg = (
23382311
"`negative_likelihood_ratio` is ill-defined and set to `np.nan`. "
2339-
"Use the `replace_undefined_by` param to control this behavior."
2312+
"Use the `replace_undefined_by` param to control this behavior. To "
2313+
"suppress this warning or turn it into an error, see Python's "
2314+
"`warnings` module and `warnings.catch_warnings()`."
23402315
)
23412316
warnings.warn(msg, UndefinedMetricWarning, stacklevel=2)
23422317
if isinstance(replace_undefined_by, float) and np.isnan(replace_undefined_by):

sklearn/metrics/tests/test_classification.py

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -709,9 +709,7 @@ def test_likelihood_ratios_warnings(params, warn_msg):
709709
# least one of the ratios is ill-defined.
710710

711711
with pytest.warns(UserWarning, match=warn_msg):
712-
# TODO(1.9): remove setting `replace_undefined_by` since this will be set by
713-
# default
714-
class_likelihood_ratios(replace_undefined_by=1.0, **params)
712+
class_likelihood_ratios(**params)
715713

716714

717715
@pytest.mark.parametrize(
@@ -736,32 +734,27 @@ def test_likelihood_ratios_errors(params, err_msg):
736734
class_likelihood_ratios(**params)
737735

738736

739-
# TODO(1.9): remove setting `replace_undefined_by` since this will be set by default
740737
def test_likelihood_ratios():
741738
# Build confusion matrix with tn=9, fp=8, fn=1, tp=2,
742739
# sensitivity=2/3, specificity=9/17, prevalence=3/20,
743740
# LR+=34/24, LR-=17/27
744741
y_true = np.array([1] * 3 + [0] * 17)
745742
y_pred = np.array([1] * 2 + [0] * 10 + [1] * 8)
746743

747-
pos, neg = class_likelihood_ratios(y_true, y_pred, replace_undefined_by=np.nan)
744+
pos, neg = class_likelihood_ratios(y_true, y_pred)
748745
assert_allclose(pos, 34 / 24)
749746
assert_allclose(neg, 17 / 27)
750747

751748
# Build limit case with y_pred = y_true
752-
pos, neg = class_likelihood_ratios(y_true, y_true, replace_undefined_by=np.nan)
753-
# TODO(1.9): replace next line with `assert_array_equal(pos, 1.0)`, since
754-
# `replace_undefined_by` has a new default:
749+
pos, neg = class_likelihood_ratios(y_true, y_true)
755750
assert_array_equal(pos, np.nan * 2)
756751
assert_allclose(neg, np.zeros(2), rtol=1e-12)
757752

758753
# Ignore last 5 samples to get tn=9, fp=3, fn=1, tp=2,
759754
# sensitivity=2/3, specificity=9/12, prevalence=3/20,
760755
# LR+=24/9, LR-=12/27
761756
sample_weight = np.array([1.0] * 15 + [0.0] * 5)
762-
pos, neg = class_likelihood_ratios(
763-
y_true, y_pred, sample_weight=sample_weight, replace_undefined_by=np.nan
764-
)
757+
pos, neg = class_likelihood_ratios(y_true, y_pred, sample_weight=sample_weight)
765758
assert_allclose(pos, 24 / 9)
766759
assert_allclose(neg, 12 / 27)
767760

@@ -779,18 +772,6 @@ def test_likelihood_ratios_raise_warning_deprecation(raise_warning):
779772
class_likelihood_ratios(y_true, y_pred, raise_warning=raise_warning)
780773

781774

782-
# TODO(1.9): remove test
783-
def test_likelihood_ratios_raise_default_deprecation():
784-
"""Test that class_likelihood_ratios raises a `FutureWarning` when `raise_warning`
785-
and `replace_undefined_by` are both default."""
786-
y_true = np.array([1, 0])
787-
y_pred = np.array([1, 0])
788-
789-
msg = "The default return value of `class_likelihood_ratios` in case of a"
790-
with pytest.warns(FutureWarning, match=msg):
791-
class_likelihood_ratios(y_true, y_pred)
792-
793-
794775
def test_likelihood_ratios_replace_undefined_by_worst():
795776
"""Test that class_likelihood_ratios returns the worst scores `1.0` for both LR+ and
796777
LR- when `replace_undefined_by=1` is set."""

0 commit comments

Comments
 (0)