-
-
Notifications
You must be signed in to change notification settings - Fork 25.8k
ENH add pos_label to confusion_matrix #26839
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
base: main
Are you sure you want to change the base?
ENH add pos_label to confusion_matrix #26839
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
And the current CIs failure shows that we currently badly handle the ambiguity ;) |
We should probably make a similar change to the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR @glemaitre. Just a few minor changes mostly related to the docs otherwise looks good overall.
Co-authored-by: Omar Salman <omar.salman@arbisoft.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we also have a test to check that passing pos_label
correctly inverts the output matrix?
This test is already integrated with some parametrization in others, e.g. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, I am concerned with all the extra calls to _check_targets
, which adds more calls to unique
.
doc/whats_new/v1.4.rst
Outdated
:mod:`sklearn.pipeline` | ||
....................... | ||
|
||
- |Feature| :class:`pipeline.Pipeline` now supports metadata routing according |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like a merge error.
@@ -670,7 +698,17 @@ class labels [2]_. | |||
.. [3] `Wikipedia entry for the Cohen's kappa | |||
<https://en.wikipedia.org/wiki/Cohen%27s_kappa>`_. | |||
""" | |||
confusion = confusion_matrix(y1, y2, labels=labels, sample_weight=sample_weight) | |||
y_type, y1, y2 = _check_targets(y1, y2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since _check_targets
call unique
, this adds more overhead to the computation.
REF: #26820
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know how to go around that. Somehow having private function to call could help and passing the unique label could help but I would not block this PR for it.
pinging @OmarManzoor, @thomasjpfan, and @lucyleeow on this one. |
Sorry mis-click. In light of #10010 (comment), do we want to set default to |
|
||
if y_type == "binary": | ||
pos_label = _check_pos_label_consistency(pos_label, y_true) | ||
elif pos_label is not None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am wondering if we are not shooting in our own foot with this ValueError
.
If we change the default pos_label=1
then we will by default raise this error whenever y_type != "binary"
. If we choose, pos_label=1
as a default, then we need to have the same strategy than the precision recall meaning that we need to ignore pos_label
.
Am I right @lucyleeow or I missed something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another advantages to implicitely ignoring pos_label
is that we don't need anymore to call _check_target
and thus the unique function because we can pass the default value most of the time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hen we will by default raise this error whenever y_type != "binary"
I think we could set default to 1 and change this to:
elif pos_label is not None: | |
elif pos_label != 1: |
_check_set_wise_labels
does similar checking:
scikit-learn/sklearn/metrics/_classification.py
Lines 1549 to 1556 in e5178c5
elif pos_label not in (None, 1): | |
warnings.warn( | |
"Note that pos_label (set to %r) is ignored when " | |
"average != 'binary' (got %r). You may use " | |
"labels=[pos_label] to specify a single positive class." | |
% (pos_label, average), | |
UserWarning, | |
) |
the precision_recall_fscore_support
functions use it and default is pos_label=1
(None
option is allowed byt not documented)
(I may have gotten something wrong, this stuff gets confusing)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realise you could technically just flip the labels by using the 'reorder' functionality of labels
though it is less clear and not consistent with other places where we use pos_label
for binary case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'reorder' functionality of labels
Indeed but once I found this out, I was surprise because the API is kind of not consistent. I find it a bit better to have consistency across metrics if possible.
I think we could set default to 1 and change this to:
The problem is that you let the case where someone pass pos_label=1
explicitly in case other than "binary". So if we skip this test, I would rather prefer to be lenient as in other metrics.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you mean you would not raise an error if pos_label is not the default value and it is not the binary case?
Yes, no raising any error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Options:
- default is
None
and mention that it is equivalent to 1 in binary case - default is
1
and completely ignore it in cases other than binary classification (do not raise error/warning) - default is
1
, in case off binary classification, raise warning ifpos_label
is not the default
Now I do not know what my preference would be. I would think that it is okay to raise a warning when pos_label
is not default but target is not binary. We do miss the case when the user wants pos_label
to be default. (1) has the problem of pos_label=None
being ill-defined.
But maybe more opinions are needed here. I will try to summarise in #10010
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I will try to ping more dev in #10010 such that we can settle on a solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would be in favor of defaulting to None
everywhere to make it easy to spot cases where users provide a non-default value in case we need to raise an informative error message.
For the specific case of confusion_matrix
I think it we should not pass a pos_label
as the matrix is always defined irrespective of what we consider positive or not (it does not care). What matters would be to pass the labels=class_labels
when calling confusion_matrix
indirectly from a public function such as f1_score
which itself asks the user to specify a pos_label
, so that there is no ambiguity about the meaning of the rows and columns of the CM.
To make this more efficient, I think type_of_target
could be extended to also have an alternative type_of_target_and_class_labels
to avoid computing xp.unique
many times.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, when y
is a dataframe or series
we should use y.unique
to make this more efficient (pandas does not need to sort the data to compute the unique values).
@@ -1921,11 +1968,18 @@ class after being classified as negative. This is the case when the | |||
f"problems, got targets of type: {y_type}" | |||
) | |||
|
|||
if labels is None: | |||
classes = np.unique(y_true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here, we should probably call _unique
instead just to be sure.
I assume that we could introduce a pos_label
as well.
While working on #26120, we figure out that we don't have a
pos_label
parameter in theconfusion_matrix
function. To follow the documentation and since we don't report the column/index name, we should not change the positions of the TP/TN/FP/FN.pos_label
allows flipping the matrix in case this is not the expected positive label.I also added the
pos_label
to theConfusionMatrixDisplay
to be complete.