Talip7/scikit-learn-issues-multilabel huggingface dataset:
Estimator creating `_more_tags` and inheriting from `BaseEstimator` will not warn about old tag infrastructure

While making the code of `skrub` compatible with scikit-learn 1.6, I found that the following is really surprising:

```python
# %%
import numpy as np
from sklearn.base import BaseEstimator, RegressorMixin

class MyRegressor(RegressorMixin, BaseEstimator):
    def __init__(self, seed=None):
        self.seed = seed

    def fit(self, X, y):
        self.rng_ = np.random.default_rng(self.seed)
        return self

    def predict(self, X):
        return self.rng_.normal(size=X.shape[0])

    def _more_tags(self):
        return {
            "multioutput": True
        }


# %%
from sklearn.datasets import make_regression

X, y = make_regression(n_samples=10, n_features=5, random_state=42)
regressor = MyRegressor(seed=42).fit(X, y)
regressor.predict(X)

# %%
from sklearn.utils import get_tags

tags = get_tags(regressor)  # does not warn because we inherit from BaseEstimator
tags.target_tags.multi_output  # does not use anymore the _more_tags and thus is wrong
```

In the code above, because we inherit from `BaseEstimator` and `RegressorMixin`, we have the default tags set with the methods `__sklearn_tags__`.

However, the previous code that we had was using `_more_tags`.

Currently, `get_tags` will not warn that something is going wrong because we will fallback on the default tags from the base class and mixins.

I think that we should:

- use the values defined in `_more_tags` and warn for the future change
- in the future we should error if we have both `_more_tags` and `__sklearn_tags__` to be sure that people stop using `_more_tags`

COMMENT:
> use the values defined in _more_tags and warn for the future change

There are two paths to continue using `_more_tags`:

1. Bring back all the `_get_tags` + `_more_tags` logic from before. Bringing back `_get_tags` will introduce some complexity when both `_get_tags` and `__sklearn_tags__` are defined.
2. Only support `_more_tags`. If `_more_tags` exist, then have `__sklearn_tags__` create a `Tag` object that uses the configuration from `_more_tags`. (This means we'll need to translate from the old dict format to the new dataclasses.)

I'm in-favor of 2.

Labels: Blocker
Estimator creating `_more_tags` and inheriting from `BaseEstimator` will not warn about old tag infrastructure

While making the code of `skrub` compatible with scikit-learn 1.6, I found that the following is really surprising:

```python
# %%
import numpy as np
from sklearn.base import BaseEstimator, RegressorMixin

class MyRegressor(RegressorMixin, BaseEstimator):
    def __init__(self, seed=None):
        self.seed = seed

    def fit(self, X, y):
        self.rng_ = np.random.default_rng(self.seed)
        return self

    def predict(self, X):
        return self.rng_.normal(size=X.shape[0])

    def _more_tags(self):
        return {
            "multioutput": True
        }


# %%
from sklearn.datasets import make_regression

X, y = make_regression(n_samples=10, n_features=5, random_state=42)
regressor = MyRegressor(seed=42).fit(X, y)
regressor.predict(X)

# %%
from sklearn.utils import get_tags

tags = get_tags(regressor)  # does not warn because we inherit from BaseEstimator
tags.target_tags.multi_output  # does not use anymore the _more_tags and thus is wrong
```

In the code above, because we inherit from `BaseEstimator` and `RegressorMixin`, we have the default tags set with the methods `__sklearn_tags__`.

However, the previous code that we had was using `_more_tags`.

Currently, `get_tags` will not warn that something is going wrong because we will fallback on the default tags from the base class and mixins.

I think that we should:

- use the values defined in `_more_tags` and warn for the future change
- in the future we should error if we have both `_more_tags` and `__sklearn_tags__` to be sure that people stop using `_more_tags`

COMMENT:
I am also in favor 2.

Labels: Blocker
Estimator creating `_more_tags` and inheriting from `BaseEstimator` will not warn about old tag infrastructure

While making the code of `skrub` compatible with scikit-learn 1.6, I found that the following is really surprising:

```python
# %%
import numpy as np
from sklearn.base import BaseEstimator, RegressorMixin

class MyRegressor(RegressorMixin, BaseEstimator):
    def __init__(self, seed=None):
        self.seed = seed

    def fit(self, X, y):
        self.rng_ = np.random.default_rng(self.seed)
        return self

    def predict(self, X):
        return self.rng_.normal(size=X.shape[0])

    def _more_tags(self):
        return {
            "multioutput": True
        }


# %%
from sklearn.datasets import make_regression

X, y = make_regression(n_samples=10, n_features=5, random_state=42)
regressor = MyRegressor(seed=42).fit(X, y)
regressor.predict(X)

# %%
from sklearn.utils import get_tags

tags = get_tags(regressor)  # does not warn because we inherit from BaseEstimator
tags.target_tags.multi_output  # does not use anymore the _more_tags and thus is wrong
```

In the code above, because we inherit from `BaseEstimator` and `RegressorMixin`, we have the default tags set with the methods `__sklearn_tags__`.

However, the previous code that we had was using `_more_tags`.

Currently, `get_tags` will not warn that something is going wrong because we will fallback on the default tags from the base class and mixins.

I think that we should:

- use the values defined in `_more_tags` and warn for the future change
- in the future we should error if we have both `_more_tags` and `__sklearn_tags__` to be sure that people stop using `_more_tags`

COMMENT:
Oh I forgot about this issue.

Labels: Blocker
Estimator creating `_more_tags` and inheriting from `BaseEstimator` will not warn about old tag infrastructure

While making the code of `skrub` compatible with scikit-learn 1.6, I found that the following is really surprising:

```python
# %%
import numpy as np
from sklearn.base import BaseEstimator, RegressorMixin

class MyRegressor(RegressorMixin, BaseEstimator):
    def __init__(self, seed=None):
        self.seed = seed

    def fit(self, X, y):
        self.rng_ = np.random.default_rng(self.seed)
        return self

    def predict(self, X):
        return self.rng_.normal(size=X.shape[0])

    def _more_tags(self):
        return {
            "multioutput": True
        }


# %%
from sklearn.datasets import make_regression

X, y = make_regression(n_samples=10, n_features=5, random_state=42)
regressor = MyRegressor(seed=42).fit(X, y)
regressor.predict(X)

# %%
from sklearn.utils import get_tags

tags = get_tags(regressor)  # does not warn because we inherit from BaseEstimator
tags.target_tags.multi_output  # does not use anymore the _more_tags and thus is wrong
```

In the code above, because we inherit from `BaseEstimator` and `RegressorMixin`, we have the default tags set with the methods `__sklearn_tags__`.

However, the previous code that we had was using `_more_tags`.

Currently, `get_tags` will not warn that something is going wrong because we will fallback on the default tags from the base class and mixins.

I think that we should:

- use the values defined in `_more_tags` and warn for the future change
- in the future we should error if we have both `_more_tags` and `__sklearn_tags__` to be sure that people stop using `_more_tags`

COMMENT:
So this issue really happen for third-party developer: https://github.com/scikit-learn/scikit-learn/issues/30324

There, I could witness that https://github.com/scikit-learn/scikit-learn/pull/30327 is solving the issue.

https://github.com/scikit-learn/scikit-learn/pull/30327 is reintroducing `_get_tags` because we will still break code of people. So there is definitely a complexity because you need to convert `__sklearn_tags__` that do inheritance into a `_more_tags` that just add tag for the class itself. But I think that I got it right with the different test. It really provide a fully backward compatible solution.

~~And I'm not sure that option would be enough. Translating `_more_tags` to a `Tags` means that you will create some default values. Since `__sklearn_tags__` use the inheritance, having the default tags will wrongly override the class of a child class I think.~~

Edit: actually @thomasjpfan, I was thinking the walk in the MRO reversed :).

Labels: Blocker
OrdinalEncoder not transforming nans as expected.

### Describe the bug

When fitting an OrdinalEncoder with a pandas Series that contains a nan, transforming an array containing only nans fails, even though nan is one of the OrdinalEncoder classes.

This seems similar to this issue https://github.com/scikit-learn/scikit-learn/issues/22628

### Steps/Code to Reproduce

```python
from sklearn import preprocessing
import numpy as np
encoder = preprocessing.OrdinalEncoder()
data = np.array(['cat', 'dog', np.nan, 'fish', 'dog']).reshape(-1, 1)
encoder.fit(data)
only_nan = np.array([np.nan]).reshape(-1, 1)
encoder.transform(only_nan)
```

### Expected Results

Instead of the error, I'd expect the output to be `array([3])`.

### Actual Results

```bash
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/rafaelascensao/work/scikit-learn-test/.venv/lib/python3.10/site-packages/sklearn/utils/_set_output.py", line 316, in wrapped
    data_to_wrap = f(self, X, *args, **kwargs)
  File "/Users/rafaelascensao/work/scikit-learn-test/.venv/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py", line 1578, in transform
    X_int, X_mask = self._transform(
  File "/Users/rafaelascensao/work/scikit-learn-test/.venv/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py", line 206, in _transform
    diff, valid_mask = _check_unknown(Xi, self.categories_[i], return_mask=True)
  File "/Users/rafaelascensao/work/scikit-learn-test/.venv/lib/python3.10/site-packages/sklearn/utils/_encode.py", line 304, in _check_unknown
    if np.isnan(known_values).any():
TypeError: ufunc 'isnan' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''
```

### Versions

```shell
System:
    python: 3.10.11 (main, Aug 22 2024, 14:00:26) [Clang 15.0.0 (clang-1500.3.9.4)]
executable: /Users/rafaelascensao/work/scikit-learn-test/.venv/bin/python
   machine: macOS-15.0.1-arm64-arm-64bit

Python dependencies:
      sklearn: 1.5.2
          pip: 24.2
   setuptools: 69.5.1
        numpy: 2.1.3
        scipy: 1.14.1
       Cython: None
       pandas: 2.2.3
   matplotlib: 3.9.2
       joblib: 1.4.2
threadpoolctl: 3.5.0

Built with OpenMP: True

threadpoolctl info:
       user_api: openmp
   internal_api: openmp
    num_threads: 8
         prefix: libomp
       filepath: /Users/rafaelascensao/work/scikit-learn-test/.venv/lib/python3.10/site-packages/sklearn/.dylibs/libomp.dylib
        version: None
```

COMMENT:
Hi @rafaascensao

To avoid similar issues and to eliminate manual checks, we created a custom function to handle ordinals, including specific handling for NaN. This allows for greater control over the process.

If you want, I'm available.

Labels: Bug, Needs Triage
OrdinalEncoder not transforming nans as expected.

### Describe the bug

When fitting an OrdinalEncoder with a pandas Series that contains a nan, transforming an array containing only nans fails, even though nan is one of the OrdinalEncoder classes.

This seems similar to this issue https://github.com/scikit-learn/scikit-learn/issues/22628

### Steps/Code to Reproduce

```python
from sklearn import preprocessing
import numpy as np
encoder = preprocessing.OrdinalEncoder()
data = np.array(['cat', 'dog', np.nan, 'fish', 'dog']).reshape(-1, 1)
encoder.fit(data)
only_nan = np.array([np.nan]).reshape(-1, 1)
encoder.transform(only_nan)
```

### Expected Results

Instead of the error, I'd expect the output to be `array([3])`.

### Actual Results

```bash
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/rafaelascensao/work/scikit-learn-test/.venv/lib/python3.10/site-packages/sklearn/utils/_set_output.py", line 316, in wrapped
    data_to_wrap = f(self, X, *args, **kwargs)
  File "/Users/rafaelascensao/work/scikit-learn-test/.venv/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py", line 1578, in transform
    X_int, X_mask = self._transform(
  File "/Users/rafaelascensao/work/scikit-learn-test/.venv/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py", line 206, in _transform
    diff, valid_mask = _check_unknown(Xi, self.categories_[i], return_mask=True)
  File "/Users/rafaelascensao/work/scikit-learn-test/.venv/lib/python3.10/site-packages/sklearn/utils/_encode.py", line 304, in _check_unknown
    if np.isnan(known_values).any():
TypeError: ufunc 'isnan' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''
```

### Versions

```shell
System:
    python: 3.10.11 (main, Aug 22 2024, 14:00:26) [Clang 15.0.0 (clang-1500.3.9.4)]
executable: /Users/rafaelascensao/work/scikit-learn-test/.venv/bin/python
   machine: macOS-15.0.1-arm64-arm-64bit

Python dependencies:
      sklearn: 1.5.2
          pip: 24.2
   setuptools: 69.5.1
        numpy: 2.1.3
        scipy: 1.14.1
       Cython: None
       pandas: 2.2.3
   matplotlib: 3.9.2
       joblib: 1.4.2
threadpoolctl: 3.5.0

Built with OpenMP: True

threadpoolctl info:
       user_api: openmp
   internal_api: openmp
    num_threads: 8
         prefix: libomp
       filepath: /Users/rafaelascensao/work/scikit-learn-test/.venv/lib/python3.10/site-packages/sklearn/.dylibs/libomp.dylib
        version: None
```

COMMENT:
Yeah I think in this case the user should make sure the dtype is correct. It's an edge case and users' data shouldn't be all `nan` in the first place. If that happens, something else needs fixing on the user side.

Labels: Bug, Needs Triage
OrdinalEncoder not transforming nans as expected.

### Describe the bug

When fitting an OrdinalEncoder with a pandas Series that contains a nan, transforming an array containing only nans fails, even though nan is one of the OrdinalEncoder classes.

This seems similar to this issue https://github.com/scikit-learn/scikit-learn/issues/22628

### Steps/Code to Reproduce

```python
from sklearn import preprocessing
import numpy as np
encoder = preprocessing.OrdinalEncoder()
data = np.array(['cat', 'dog', np.nan, 'fish', 'dog']).reshape(-1, 1)
encoder.fit(data)
only_nan = np.array([np.nan]).reshape(-1, 1)
encoder.transform(only_nan)
```

### Expected Results

Instead of the error, I'd expect the output to be `array([3])`.

### Actual Results

```bash
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/rafaelascensao/work/scikit-learn-test/.venv/lib/python3.10/site-packages/sklearn/utils/_set_output.py", line 316, in wrapped
    data_to_wrap = f(self, X, *args, **kwargs)
  File "/Users/rafaelascensao/work/scikit-learn-test/.venv/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py", line 1578, in transform
    X_int, X_mask = self._transform(
  File "/Users/rafaelascensao/work/scikit-learn-test/.venv/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py", line 206, in _transform
    diff, valid_mask = _check_unknown(Xi, self.categories_[i], return_mask=True)
  File "/Users/rafaelascensao/work/scikit-learn-test/.venv/lib/python3.10/site-packages/sklearn/utils/_encode.py", line 304, in _check_unknown
    if np.isnan(known_values).any():
TypeError: ufunc 'isnan' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''
```

### Versions

```shell
System:
    python: 3.10.11 (main, Aug 22 2024, 14:00:26) [Clang 15.0.0 (clang-1500.3.9.4)]
executable: /Users/rafaelascensao/work/scikit-learn-test/.venv/bin/python
   machine: macOS-15.0.1-arm64-arm-64bit

Python dependencies:
      sklearn: 1.5.2
          pip: 24.2
   setuptools: 69.5.1
        numpy: 2.1.3
        scipy: 1.14.1
       Cython: None
       pandas: 2.2.3
   matplotlib: 3.9.2
       joblib: 1.4.2
threadpoolctl: 3.5.0

Built with OpenMP: True

threadpoolctl info:
       user_api: openmp
   internal_api: openmp
    num_threads: 8
         prefix: libomp
       filepath: /Users/rafaelascensao/work/scikit-learn-test/.venv/lib/python3.10/site-packages/sklearn/.dylibs/libomp.dylib
        version: None
```

COMMENT:
@glemaitre yeah that makes a lot of sense. 

And you are right @adrinjalali, this happened to me because I was running a script in pyspark cluster and one of the executors failed with this error because the data that the executor got had one column full of NaN. But is certainly an edge case and I can mitigate this error with some dtype verification. Thank you all!

Labels: Bug, Needs Triage
Notes to update the release process

This issue is used to consolidate the point that needs to be updated in the release process, notably due to the adoption of towncrier.

### RC release process:

- when requesting to bump the version number of `dev0` in `main`, we need to request changing the root RST file targeted by towncrier in the `pyproject.toml` file.
- add that we should generate the changelog with `towncrier`: Generate the changelog with towncrier but keep the fragments: `towncrier build --keep --version 1.6.0` (*we should adapat the version number*)

COMMENT:
we also need a new step to generate the changelog from the fragments

Labels: Documentation
Notes to update the release process

This issue is used to consolidate the point that needs to be updated in the release process, notably due to the adoption of towncrier.

### RC release process:

- when requesting to bump the version number of `dev0` in `main`, we need to request changing the root RST file targeted by towncrier in the `pyproject.toml` file.
- add that we should generate the changelog with `towncrier`: Generate the changelog with towncrier but keep the fragments: `towncrier build --keep --version 1.6.0` (*we should adapat the version number*)

COMMENT:
Indeed, without removing the fragment moreover.

Labels: Documentation
Notes to update the release process

This issue is used to consolidate the point that needs to be updated in the release process, notably due to the adoption of towncrier.

### RC release process:

- when requesting to bump the version number of `dev0` in `main`, we need to request changing the root RST file targeted by towncrier in the `pyproject.toml` file.
- add that we should generate the changelog with `towncrier`: Generate the changelog with towncrier but keep the fragments: `towncrier build --keep --version 1.6.0` (*we should adapat the version number*)

COMMENT:
We should also add more details for the conda-forge PR for release candidates since it's usually a bit more complicated. First it must target the rc branch and we need to sync with main and backport stuff like numpy 2 or new python versions that may have happen in between the last RC and this one that were only done in main.

Labels: Documentation
Notes to update the release process

This issue is used to consolidate the point that needs to be updated in the release process, notably due to the adoption of towncrier.

### RC release process:

- when requesting to bump the version number of `dev0` in `main`, we need to request changing the root RST file targeted by towncrier in the `pyproject.toml` file.
- add that we should generate the changelog with `towncrier`: Generate the changelog with towncrier but keep the fragments: `towncrier build --keep --version 1.6.0` (*we should adapat the version number*)

COMMENT:
I also think that these 2 items from the RC check list are wrong:
```
[ ] Update news and what's new date in main branch
[ ] Backport news and what's new date in release branch
```
although there's currently an issue because the what's new for 1.6 is not available on stable (but it is on dev)

Labels: Documentation
Missing link to how to install dev version on the top bar

We used to have a link to "how to install this dev version via nightly releases" when the user explores the `/dev/` version of the website. But now we have:

![image](https://github.com/user-attachments/assets/4c32df98-6058-4357-813b-59fa1aad7173)


I'm not sure how to put it back.

@Charlie-XIAO maybe?

COMMENT:
This would be tricky. I remembered trying to retain that link, but `pydata-sphinx-theme` don't really support version warning banner customization (there are feature requests on that: https://github.com/pydata/pydata-sphinx-theme/issues/1486). There can be temporary workarounds (I see [Apache arrow](https://arrow.apache.org/docs/13.0/python/index.html) and [MNE](https://mne.tools/dev/index.html) having some customizations to their version warning banners), but I need to figure it out. Perhaps we can switch back to using https://github.com/scikit-learn/scikit-learn.github.io/blob/main/versionwarning.js (of course with some updates to adapt to the new theme).

Labels: Documentation
Clarification on Kruskal Stress as an Optimization Target in Metric and Non-metric MDS

### Describe the issue linked to the documentation

I am working on research involving the optimization targets used in metric and non-metric MDS, and I have some questions regarding how scikit-learn's implementation of MDS defines and calculates stress, particularly Kruskal Stress. While reviewing the official documentation, I noticed that specific formulas for stress calculations are not explicitly provided, and I would appreciate some clarification.

Non-metric MDS: My understanding is that non-metric MDS typically minimizes Kruskal Stress, defined as:

![屏幕截图 2024-11-08 163808](https://github.com/user-attachments/assets/d289f4f0-afa0-4c80-a982-c2186c103e27)

in the reduced space. Could you confirm if scikit-learn's non-metric MDS implementation uses this definition, or if it employs an alternative method?

Metric MDS: Does metric MDS in scikit-learn also optimize for Kruskal Stress, or does it use a different stress formula? If a different approach is used, would it be possible to provide some insight or references on the stress function applied here?


### Suggest a potential alternative/fix


Documentation Clarification: It would be incredibly helpful if the documentation could include specific details on the stress formulas used in both metric and non-metric MDS. This addition would help researchers and users better understand the theoretical underpinnings of the algorithm in scikit-learn.
Thank you very much for your guidance and clarification on these points. Your insights would be instrumental in my work with MDS.

COMMENT:
Seems this is very similar or duplicate of https://github.com/scikit-learn/scikit-learn/issues/15272, could you please add your thoughts there?

Also, note that https://github.com/scikit-learn/scikit-learn/pull/22330 tries to add to the existing module.

Labels: Documentation, Needs Triage