Allow column names to pass through when fitting `narwhals` dataframes

### Describe the workflow you want to enable

Currently when fitting with a `narwhals` DataFrame, the feature names do not pass through because it does not implement a `__dataframe__` method.

Example:

```python
import narwhals as nw
import pandas as pd
import polars as pl
from sklearn.preprocessing import StandardScaler

df_pd = pd.DataFrame({"a": [0, 1, 2], "b": [3, 4, 5]})
df_pl = pl.DataFrame(df_pd)
df_nw = nw.from_native(df_pd)

s_pd, s_pl, s_nw = StandardScaler(), StandardScaler(), StandardScaler()
s_pd.fit(df_pd)
s_pl.fit(df_pl)
s_nw.fit(df_nw)

print(s_pd.feature_names_in_)
print(s_pl.feature_names_in_)
print(s_nw.feature_names_in_)
```

**Expected output**

```
['a' 'b']
['a' 'b']
['a' 'b']
```

**Actual output**
```
['a' 'b']
['a' 'b']
AttributeError: 'StandardScaler' object has no attribute 'feature_names_in_'
```

All other attributes on `s_nw` are what I'd expect.

### Describe your proposed solution

This should be easy enough to implement by adding another check within `sklearn.utils.validation._get_feature_names`:

1. Add `_is_narwhals_df` method, borrowing logic from [`_is_pandas_df`](https://github.com/scikit-learn/scikit-learn/blob/main/sklearn/utils/validation.py#L2343)

```python
def _is_narwhals_df(X):
    """Return True if the X is a narwhals dataframe."""
    try:
        nw = sys.modules["narwhals"]
    except KeyError:
        return False
    return isinstance(X, nw.DataFrame)
```

2. Add an additional check to [`_get_feature_names`](https://github.com/scikit-learn/scikit-learn/blob/main/sklearn/utils/validation.py#L2393-L2408):

```python
    elif _is_narwhals_df(X):
        feature_names = np.asarray(X.columns, dtype=object)
```

### Describe alternatives you've considered, if relevant

_No response_

### Additional context

https://github.com/narwhals-dev/narwhals/issues/355#issuecomment-2734066008

COMMENT:
Hi @ryansheabla,

scikit-learn can accept pandas or polars (only most recent versions), but narwhals dataframes are not passable and you need to call `.to_native()` on them first (if they are natively pandas or polars).

I think narwhals is to be used internally by libraries and users then could reliably pass polars, pandas or pyarrow frames.

This said, scikit-learn is not using dataframes internally a lot. Input validation, especially `check_array()` converts user inputs into numpy arrays and some information like `feature_names_in_` are stored as attributes on the objects and passed to the next object if several operations are lined up. Also, the output of predict methods or transformers are numpy arrays by default and users can set a different output type using `set_output()`.

So the design - **if** scikit-learn would decide to use narwhals, which has not been discussed yet - would be something like a) store attributes from inputs and if the user has requested to have a different return type than numpy with `set_output()`: b) store input types, c) return predictions or transformed data in same namespace.

Narwhals could be used in steps b) and c), but maybe in step a) it is not necessary to have it.

Labels: New Feature, Needs Triage