Skip to content

FEA Add array API support to coverage_error#32626

Open
jaffourt wants to merge 11 commits intoscikit-learn:mainfrom
jaffourt:aapi_coverage_error
Open

FEA Add array API support to coverage_error#32626
jaffourt wants to merge 11 commits intoscikit-learn:mainfrom
jaffourt:aapi_coverage_error

Conversation

@jaffourt
Copy link
Copy Markdown
Contributor

Reference Issues/PRs

Towards #26024

What does this implement/fix? Explain your changes.

Add array API support to coverage_error

Any other comments?

@github-actions
Copy link
Copy Markdown

github-actions bot commented Oct 31, 2025

✔️ Linting Passed

All linting checks passed. Your pull request is in excellent shape! ☀️

Generated for commit: 28e6c5a. Link to the linter CI: here

@jaffourt jaffourt changed the title Add array API support to coverage_error FEA Add array API support to coverage_error Oct 31, 2025
I think numpy converts a mixed int/float array (used in the test_ranking.py tests for `coverage_error`) to np.float64. Which is causing errors on MPS
@jaffourt
Copy link
Copy Markdown
Contributor Author

jaffourt commented Nov 5, 2025

Unit tests are passing, but the following are not finishing successfully:

  • Azure Pipelines / scikit-learn.scikit-learn
  • scikit-learn.scikit-learn (Linux pylatest_pip_openblas_pandas)
  • scikit-learn.scikit-learn (macOS pylatest_conda_forge_mkl_no_openmp)

Azure pipeline is showing the following errors:

Action Error
Linux pylatest_pip_openblas_pandas The job running on agent Azure Pipelines 5 ran longer than the maximum time of 120 minutes. For more information, see https://go.microsoft.com/fwlink/?linkid=2077134.
Linux pylatest_pip_openblas_pandas • Install The Operation will be canceled. The next steps may not contain expected logs.
Linux pylatest_pip_openblas_pandas • Install The operation was canceled.
macOS pylatest_conda_forge_mkl_no_openmp This is a scheduled macos-13 brownout. The macOS-13 based runner images are being deprecated. For more details, see actions/runner-images#13046.
macOS pylatest_conda_forge_mkl_no_openmp This is a scheduled macos-13 brownout. The macOS-13 based runner images are being deprecated. For more details, see actions/runner-images#13046.
macOS pylatest_conda_forge_mkl_no_openmp The remote provider was unable to process the request.

coverage = (y_score >= y_min_relevant).sum(axis=1)
coverage = coverage.filled(0)
y_true_bool = xp.astype(y_true, xp.bool, device=device_, copy=False)
y_score_masked = xp.where(y_true_bool, y_score, xp.inf)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We are replacing the masked values with inf values, since the masked-array (y_score_mask) was only used for finding the minimum value in each row.

Nothing is larger than xp.inf so functionally this is the same.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are we confident that y_score will not have any xp.inf?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are we confident that y_score will not have any xp.inf?

I've been thinking about this too. Just in case, for y_true as the function input, then check_array will catch any inf values and raise a ValueError, so that part is covered.

On the other hand, y_score_masked can contain inf, which happens when y_true contains a zero row. However, since we do:

y_min_relevant = xp.reshape(xp.min(y_score_masked, axis=1), (-1, 1))
coverage = xp.count_nonzero(y_score >= y_min_relevant, axis=1)

coverage will only contain finite values, so we should be fine here too.

@jaffourt
Copy link
Copy Markdown
Contributor Author

@OmarManzoor @lucyleeow This is ready for review when either of you have a chance to take a look :)

@lucyleeow lucyleeow moved this to In Progress in Array API Nov 18, 2025
Copy link
Copy Markdown
Contributor

@OmarManzoor OmarManzoor left a comment

Choose a reason for hiding this comment

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

LGTM. Thank you @jaffourt

coverage = (y_score >= y_min_relevant).sum(axis=1)
coverage = coverage.filled(0)
y_true_bool = xp.astype(y_true, xp.bool, device=device_, copy=False)
y_score_masked = xp.where(y_true_bool, y_score, xp.inf)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are we confident that y_score will not have any xp.inf?

@OmarManzoor
Copy link
Copy Markdown
Contributor

CC: @lucyleeow @virchan for a second review

Copy link
Copy Markdown
Member

@virchan virchan left a comment

Choose a reason for hiding this comment

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

Thanks for PR, @jaffourt.

I have some comments. Otherwise, LGTM.

coverage = (y_score >= y_min_relevant).sum(axis=1)
coverage = coverage.filled(0)
y_true_bool = xp.astype(y_true, xp.bool, device=device_, copy=False)
y_score_masked = xp.where(y_true_bool, y_score, xp.inf)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are we confident that y_score will not have any xp.inf?

I've been thinking about this too. Just in case, for y_true as the function input, then check_array will catch any inf values and raise a ValueError, so that part is covered.

On the other hand, y_score_masked can contain inf, which happens when y_true contains a zero row. However, since we do:

y_min_relevant = xp.reshape(xp.min(y_score_masked, axis=1), (-1, 1))
coverage = xp.count_nonzero(y_score >= y_min_relevant, axis=1)

coverage will only contain finite values, so we should be fine here too.

@lucyleeow
Copy link
Copy Markdown
Member

Haven't had time to do a proper review, but tests for continuous metrics had been added in #32422, which added several continuous metrics e.g., brier_score_loss etc. I've factorized the tests to test_common.py in #32793 and I am thinking it may be better to use those here - it adds tests for string y_true and multiclass and multilabel cases.

resolve @virchan PR comment

Co-authored-by: Virgil Chan <virchan.math@gmail.com>
@jaffourt
Copy link
Copy Markdown
Contributor Author

jaffourt commented Dec 1, 2025

@OmarManzoor @virchan @lucyleeow Thank you for your thoughtful reviews!

Regarding the xp.inf values in y_score discussion, I think this implementation handles that case correctly as @virchan noted. However, should we add an explicit test for this somewhere? Maybe we could add an np.inf value to one of the input arrays in test_common.py?

Haven't had time to do a proper review, but tests for continuous metrics had been added in #32422, which added several continuous metrics e.g., brier_score_loss etc. I've factorized the tests to test_common.py in #32793 and I am thinking it may be better to use those here - it adds tests for string y_true and multiclass and multilabel cases.

This makes sense to me, I can use the new tests in test_common.py once #32793 is merged. Slightly confused if #32755 is also relevant to this though?

@lucyleeow
Copy link
Copy Markdown
Member

I think this implementation handles that case correctly as @virchan noted. However, should we add an explicit test for this somewhere?

We have test_continuous_inf_nan_input which I think should do it. I think tests that check functionality of the metric can be numpy only, so I don't think we need a separate array API version of the test.

I can use the new tests in test_common.py once #32793 is merged. Slightly confused if #32755 is also relevant to this though?

#32793 sort of depends on #32755, so sorry this PR is 3rd in the order of PRs to be merged! The original tests that I wanted to factorise out in #32793 included the mixed array input tests (as well as general array API compliance test), so I didn't want to delete the original tests without adding both the tests in #32755 and #32793

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Array API module:metrics Waiting for Second Reviewer First reviewer is done, need a second one!

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

5 participants