Visitar URL original
MNT Narwhals in `utils._input_validations` by EdAbati · Pull Request #1533 · fairlearn/fairlearn · GitHub
Skip to content

MNT Narwhals in utils._input_validations - #1533

Open
EdAbati wants to merge 35 commits into
fairlearn:mainfrom
EdAbati:narwhals-in-utils
Open

EdAbati wants to merge 35 commits into
fairlearn:mainfrom
EdAbati:narwhals-in-utils

Conversation

@EdAbati

@EdAbati EdAbati commented Mar 25, 2025 •

Copy link
Copy Markdown
Contributor

Description

Towards #1522
(thanks @StefanieSenger for your refactoring PR, made this work way easier :) )

This PR makes the utils _input_validations.py narwhals compatible. Since all the rest doesn't expect narwhals yet, I convert everything to native before the return. This should be updated in the future :)

FYI @TamaraAtanasoska I made the PR to narwhals-integration that is behind main (but branched of main): that's why you see some unrelated commits. We should update narwhals-integration to see a cleaner diff.

Tests

  • no new tests required
  • new tests added
  • existing tests adjusted

Documentation

  • no documentation changes needed
  • user guide added or updated
  • API docs added or updated
  • example notebook added or updated

Screenshots

Tagging the narwhalifying experts @FBruzzesi @MarcoGorelli for suggestions on how to improve this

Comment thread test/conftest.py Outdated
Comment on lines +12 to +18
# TODO: sklearn.utils.validation.check_array does not support it
# def pandas_nullable_constructor(obj) -> IntoDataFrame:
# return pd.DataFrame(obj).convert_dtypes(dtype_backend="numpy_nullable") # type: ignore[no-any-return]

# TODO: sklearn.utils.validation.check_array does not support it
# def pandas_pyarrow_constructor(obj) -> IntoDataFrame:
# return pd.DataFrame(obj).convert_dtypes(dtype_backend="pyarrow") # type: ignore[no-any-return]

@EdAbati EdAbati Mar 25, 2025 •

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.

This is an interesing one.

To reproduce:

import pandas as pd
from sklearn.utils.validation import check_array

df = pd.DataFrame({"alpha": ["a", "a", "b"], "beta": [1, 2, 1]})
df = df.convert_dtypes(dtype_backend="pyarrow")
# or
# df = df.convert_dtypes(dtype_backend="numpy_nullable")

check_array(df, dtype=None)

Did you encounter this already @FBruzzesi? Maybe in scikit-lego?

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.

I believe scikit-lego was very early days of Narwhals, and we are not testing with different pandas dtype_backend 🙈

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.

The root cause is that _validate_and_reformat_input routes X through check_array, which materializes to NumPy — so a string[pyarrow] (or nullable) column can't be cast and it blows up. It's not really fixable as long as validation goes through check_array; the proper fix is to validate via narwhals instead, which is out of scope here.

After merging main, the shared test/conftest.py now also parametrizes over the nullable/pyarrow dtype backends (other modules rely on them). To keep that coverage without breaking the utils tests, I added a local test/unit/utils/conftest.py that narrows constructor to the backends check_array actually supports (pandas, polars, native pyarrow table). I can open a follow-up issue to track proper support for pandas extension-dtype backends in input validation. Wdyt?

Comment thread fairlearn/utils/_input_validation.py Resolved
@TamaraAtanasoska

Copy link
Copy Markdown
Contributor

Hi @EdAbati, just acknowledging today that I saw this PR! I think that the frequency of my engagement here is not enough to keep everything up to date on the spin-off branch fully unfortunately, I was expecting a different situation these weeks but here we are 😄. We have a maintainer meeting tomorrow, I might discuss to move the merges directly into main, as we are very likely going to postpone the patch release that we planned for. Not sure how fast we will move, but it would be nice to wrap up the first stable edition of the integration by the summer.

Comment thread fairlearn/utils/_narwhals_compat.py Resolved
Comment thread fairlearn/utils/_narwhals_compat.py Resolved
@EdAbati
EdAbati changed the base branch from narwhals-integration to main March 25, 2025 14:32
@EdAbati

EdAbati commented Mar 25, 2025

Copy link
Copy Markdown
Contributor Author

No problem at all @TamaraAtanasoska :)

I changed the base branch to main to make the review easier then.
Let me know if I should switch back to the integration one

@TamaraAtanasoska TamaraAtanasoska mentioned this pull request Apr 10, 2025
1 of 19 tasks

@FBruzzesi FBruzzesi left a comment •

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.

@EdAbati I found some time to skim through this again! Left a couple of comments actually one relevant comment and it does look 🔥 😁

Comment thread fairlearn/utils/_narwhals_compat.py Resolved
Comment thread fairlearn/utils/_narwhals_compat.py Resolved
Comment thread fairlearn/utils/_input_validation.py Resolved
@EdAbati

EdAbati commented May 4, 2025

Copy link
Copy Markdown
Contributor Author

Apologies for the delay, but I finally found time to address the comments! 😄

Thank you for the review everyone, please let me know if there is anything else that doesn't look right

Comment on lines +123 to +124
# TODO: remove this once we supports all narwhals inputs in this module
default_backend=nw.Implementation.PANDAS,

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.

I had to add this in places were narwhals is not yet supported. Since we import polars in the tests, it becomes the default backend.

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.

This is fine as a temporary workaround and the TODO makes the intent clear, so no objection to keeping it for this PR. The only reason it's needed is the .values calls right after, which are pandas-only. If we swap those for np.asarray(y_true) / np.asarray(sensitive_features) (works on both pandas and polars Series), the forced backend goes away entirely. Happy to make that small change here or leave it for the follow-up that removes these temporary shims — your call.

Comment on lines +23 to +31
if get_polars():
return Implementation.POLARS
if get_pandas():
return Implementation.PANDAS
if get_pyarrow():
return Implementation.PYARROW
raise ImportError(
"No supported dataframe backend found. Please install either polars, pandas or pyarrow."
)

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.

is everyone happy about this priority?

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.

One thing to weigh: this default only kicks in when inputs aren't dataframes/series (e.g. NumPy) and no explicit backend is passed — it decides what type comes back out. With polars first, a user passing NumPy arrays gets polars Series back whenever polars is installed, which is a behavior shift for our historically pandas-centric users (and it's also what makes the default_backend=PANDAS workaround in the other thread necessary in tests).

My inclination is pandas > polars > pyarrow for backward compatibility, unless we explicitly want to push polars as the default output. Since it's a user-facing policy call, this feels like a good one to confirm with @fairlearn/fairlearn-maintainers @FBruzzesi, thoughts? I am fine with it either way.

romanlutz and others added 2 commits June 29, 2026 05:46
Resolve conflicts after main raised min scikit-learn to 1.6 (fairlearn#1654) and reorganized changelogs:

- fairlearn/utils/_fixes.py: deleted (compat shim removed upstream); _input_validation now imports check_array directly from sklearn.utils.validation.

- test/conftest.py: kept main's version (pandas nullable + pyarrow extension-dtype constructors). Added test/unit/utils/conftest.py overriding 'constructor' to backends supported by check_array (pandas, polars, native pyarrow), since pandas pyarrow-backed string columns can't be converted by check_array.

- docs: took main's v0.13.0.rst; moved the PR 1533 changelog entry into v0.15.0.rst under a new 'Narwhals integration' section.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resolve conflict in fairlearn/utils/_input_validation.py:

- Imports: adopt main's 'from collections.abc import Sequence' and keep 'from typing import Any' (still used by _standardize_features).

- Kept the narwhals-based result_y construction and all_to_native return over main's pandas-only versions (core of this PR).

- Kept the hardcoded _MESSAGE_SENSITIVE_FEATURES_NONE string rather than reintroducing the _KW_SENSITIVE_FEATURES constant that this PR removed when it switched to an explicit sensitive_features parameter.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants