Talip7/scikit-learn-issues-multilabel huggingface dataset:
TfidfVectorizer does not preserve dtype for large size inputs

### Describe the bug

After fitting `TfidfVectorizer`, its `idf_` has `dtype` `np.float64` regardless of the provided `dtype` when the input data are large. The conversion from `np.float32` to `np.float64` happens [here](https://github.com/scikit-learn/scikit-learn/blob/d5082d32de2797f9594c9477f2810c743560a1f1/sklearn/feature_extraction/text.py#L1666).

Not propagating `dtype` to `TfidfTransformer` has been [discussed](https://github.com/scikit-learn/scikit-learn/pull/10443) in the past. Back then passing `dtype` to `np.log` or adding `.astype(dtype)` to `np.log` were not approved.

### Steps/Code to Reproduce

```python
from sklearn.feature_extraction.text import TfidfVectorizer
import pandas as pd
import numpy as np
import uuid

#check for 100 strings with output as np.float32, works fine
small_data=[str(uuid.uuid4()) for i in range(100)]
X = pd.Series(small_data)
vectorizer = TfidfVectorizer(dtype=np.float32)
vectorizer.fit(X)
print(vectorizer.idf_.dtype)

#check for 1000000 strings with output as np.float32
#the output of the following has dtype np.float64
large_data=[str(uuid.uuid4()) for i in range(1000000)]
X = pd.Series(large_data)
vectorizer = TfidfVectorizer(dtype=np.float32)
vectorizer.fit(X)
print(vectorizer.idf_.dtype)
```

### Expected Results

```python
float32
float32
```

### Actual Results

```python
float32
float64
```

### Versions

```shell
System:
    python: 3.10.13 (main, Feb  7 2024, 08:26:19) [Clang 15.0.0 (clang-1500.1.0.2.5)]
executable: .../bin/python
   machine: macOS-14.6.1-arm64-arm-64bit

Python dependencies:
      sklearn: 1.5.2
          pip: 24.2
   setuptools: 65.5.0
        numpy: 1.23.5
        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: blas
   internal_api: openblas
    num_threads: 12
         prefix: libopenblas
       filepath: .../lib/python3.10/site-packages/numpy/.dylibs/libopenblas64_.0.dylib
        version: 0.3.20
threading_layer: pthreads
   architecture: armv8

       user_api: openmp
   internal_api: openmp
    num_threads: 12
         prefix: libomp
       filepath: .../lib/python3.10/site-packages/sklearn/.dylibs/libomp.dylib
        version: None
```

COMMENT:
OK so the culprit is short:

```python
n_samples / df
```

where NumPy 2.x will use the dtype of `df` when doing the casting propagation while NumPy < 2 will just create a `np.float64`. We can make sure to manipulate an array of expected dtype and make inplace operation.

~I'll make a review in the associated PR.~

I'll make a PR ;)

Labels: Bug
TfidfVectorizer does not preserve dtype for large size inputs

### Describe the bug

After fitting `TfidfVectorizer`, its `idf_` has `dtype` `np.float64` regardless of the provided `dtype` when the input data are large. The conversion from `np.float32` to `np.float64` happens [here](https://github.com/scikit-learn/scikit-learn/blob/d5082d32de2797f9594c9477f2810c743560a1f1/sklearn/feature_extraction/text.py#L1666).

Not propagating `dtype` to `TfidfTransformer` has been [discussed](https://github.com/scikit-learn/scikit-learn/pull/10443) in the past. Back then passing `dtype` to `np.log` or adding `.astype(dtype)` to `np.log` were not approved.

### Steps/Code to Reproduce

```python
from sklearn.feature_extraction.text import TfidfVectorizer
import pandas as pd
import numpy as np
import uuid

#check for 100 strings with output as np.float32, works fine
small_data=[str(uuid.uuid4()) for i in range(100)]
X = pd.Series(small_data)
vectorizer = TfidfVectorizer(dtype=np.float32)
vectorizer.fit(X)
print(vectorizer.idf_.dtype)

#check for 1000000 strings with output as np.float32
#the output of the following has dtype np.float64
large_data=[str(uuid.uuid4()) for i in range(1000000)]
X = pd.Series(large_data)
vectorizer = TfidfVectorizer(dtype=np.float32)
vectorizer.fit(X)
print(vectorizer.idf_.dtype)
```

### Expected Results

```python
float32
float32
```

### Actual Results

```python
float32
float64
```

### Versions

```shell
System:
    python: 3.10.13 (main, Feb  7 2024, 08:26:19) [Clang 15.0.0 (clang-1500.1.0.2.5)]
executable: .../bin/python
   machine: macOS-14.6.1-arm64-arm-64bit

Python dependencies:
      sklearn: 1.5.2
          pip: 24.2
   setuptools: 65.5.0
        numpy: 1.23.5
        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: blas
   internal_api: openblas
    num_threads: 12
         prefix: libopenblas
       filepath: .../lib/python3.10/site-packages/numpy/.dylibs/libopenblas64_.0.dylib
        version: 0.3.20
threading_layer: pthreads
   architecture: armv8

       user_api: openmp
   internal_api: openmp
    num_threads: 12
         prefix: libomp
       filepath: .../lib/python3.10/site-packages/sklearn/.dylibs/libomp.dylib
        version: None
```

COMMENT:
Thank you for the quick investigation @glemaitre!

Labels: Bug
`chance_level_kw` in `RocCurveDisplay` raises an error when using valid matplotlib args

### Describe the bug

When passing additional keyword arguments to the random classifier's line via the `chance_level_kw` argument, some arguments raise an error even though they are valid `matplotlib.pyplot.plot()` arguments. The error occurs with the `c` and `ls` arguments.

The reason is that in `scikit-learn/sklearn/metrics/_plot/roc_curve.py`, the following code exists:

```python
chance_level_line_kw = {
    "label": "Chance level (AUC = 0.5)",
    "color": "k",
    "linestyle": "--",
}

if chance_level_kw is not None:
    chance_level_line_kw.update(**chance_level_kw)
```

Matplotlib raises an error when both `color` and `c`, or `linestyle` and `ls` are specified (this happens with other arguments too, but these are not relevant here since scikit-learn does not set values for them).

This behavior may also occur with other future classes, especially `CapCurveDisplay` (in development #28972). 

A quick fix might look like this:

```python
if 'ls' in chance_level_kw:
    chance_level_kw['linestyle'] = chance_level_kw['ls']
    del chance_level_kw['ls']

if 'c' in chance_level_kw:
    chance_level_kw['color'] = chance_level_kw['c']
    del chance_level_kw['c']

chance_level_line_kw = {
    "label": "Chance level (AUC = 0.5)",
    "color": "k",
    "linestyle": "--",
}

if chance_level_kw is not None:
    chance_level_line_kw.update(**chance_level_kw)
```

### Steps/Code to Reproduce

```python
from sklearn import metrics

display = metrics.RocCurveDisplay.from_predictions(
    y_true=[0, 0, 1, 1],
    y_pred=[0.1, 0.4, 0.35, 0.8],
    plot_chance_level=True,
    chance_level_kw={'ls': '--'}
)
```

### Expected Results


![Screenshot 2024-10-06 at 15 18 34](https://github.com/user-attachments/assets/eb409ce3-910e-4a49-91a5-c061156482fb)


### Actual Results

`TypeError: Got both 'linestyle' and 'ls', which are aliases of one another`

### Versions

```shell
System:
    python: 3.12.5 (main, Aug  6 2024, 19:08:49) [Clang 15.0.0 (clang-1500.3.9.4)]
executable: /Users/josephbarbier/Desktop/scikit-learn/sklearn-env/bin/python
   machine: macOS-14.6.1-arm64-arm-64bit

Python dependencies:
      sklearn: 1.6.dev0
          pip: 24.2
   setuptools: 69.5.1
        numpy: 2.1.2
        scipy: 1.14.1
       Cython: 3.0.11
       pandas: None
   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: 10
         prefix: libomp
       filepath: /opt/homebrew/Cellar/libomp/18.1.8/lib/libomp.dylib
        version: None
```

COMMENT:
Indeed this is a bug.

We should at least the support the alias for the default style meaning only. This should not make the code too much cumbersome.

Labels: Bug
`chance_level_kw` in `RocCurveDisplay` raises an error when using valid matplotlib args

### Describe the bug

When passing additional keyword arguments to the random classifier's line via the `chance_level_kw` argument, some arguments raise an error even though they are valid `matplotlib.pyplot.plot()` arguments. The error occurs with the `c` and `ls` arguments.

The reason is that in `scikit-learn/sklearn/metrics/_plot/roc_curve.py`, the following code exists:

```python
chance_level_line_kw = {
    "label": "Chance level (AUC = 0.5)",
    "color": "k",
    "linestyle": "--",
}

if chance_level_kw is not None:
    chance_level_line_kw.update(**chance_level_kw)
```

Matplotlib raises an error when both `color` and `c`, or `linestyle` and `ls` are specified (this happens with other arguments too, but these are not relevant here since scikit-learn does not set values for them).

This behavior may also occur with other future classes, especially `CapCurveDisplay` (in development #28972). 

A quick fix might look like this:

```python
if 'ls' in chance_level_kw:
    chance_level_kw['linestyle'] = chance_level_kw['ls']
    del chance_level_kw['ls']

if 'c' in chance_level_kw:
    chance_level_kw['color'] = chance_level_kw['c']
    del chance_level_kw['c']

chance_level_line_kw = {
    "label": "Chance level (AUC = 0.5)",
    "color": "k",
    "linestyle": "--",
}

if chance_level_kw is not None:
    chance_level_line_kw.update(**chance_level_kw)
```

### Steps/Code to Reproduce

```python
from sklearn import metrics

display = metrics.RocCurveDisplay.from_predictions(
    y_true=[0, 0, 1, 1],
    y_pred=[0.1, 0.4, 0.35, 0.8],
    plot_chance_level=True,
    chance_level_kw={'ls': '--'}
)
```

### Expected Results


![Screenshot 2024-10-06 at 15 18 34](https://github.com/user-attachments/assets/eb409ce3-910e-4a49-91a5-c061156482fb)


### Actual Results

`TypeError: Got both 'linestyle' and 'ls', which are aliases of one another`

### Versions

```shell
System:
    python: 3.12.5 (main, Aug  6 2024, 19:08:49) [Clang 15.0.0 (clang-1500.3.9.4)]
executable: /Users/josephbarbier/Desktop/scikit-learn/sklearn-env/bin/python
   machine: macOS-14.6.1-arm64-arm-64bit

Python dependencies:
      sklearn: 1.6.dev0
          pip: 24.2
   setuptools: 69.5.1
        numpy: 2.1.2
        scipy: 1.14.1
       Cython: 3.0.11
       pandas: None
   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: 10
         prefix: libomp
       filepath: /opt/homebrew/Cellar/libomp/18.1.8/lib/libomp.dylib
        version: None
```

COMMENT:
I am willing to open a PR for this, as well as correct the problem in #28972 . Is it preferable for this to be a separate PR?

Labels: Bug
`chance_level_kw` in `RocCurveDisplay` raises an error when using valid matplotlib args

### Describe the bug

When passing additional keyword arguments to the random classifier's line via the `chance_level_kw` argument, some arguments raise an error even though they are valid `matplotlib.pyplot.plot()` arguments. The error occurs with the `c` and `ls` arguments.

The reason is that in `scikit-learn/sklearn/metrics/_plot/roc_curve.py`, the following code exists:

```python
chance_level_line_kw = {
    "label": "Chance level (AUC = 0.5)",
    "color": "k",
    "linestyle": "--",
}

if chance_level_kw is not None:
    chance_level_line_kw.update(**chance_level_kw)
```

Matplotlib raises an error when both `color` and `c`, or `linestyle` and `ls` are specified (this happens with other arguments too, but these are not relevant here since scikit-learn does not set values for them).

This behavior may also occur with other future classes, especially `CapCurveDisplay` (in development #28972). 

A quick fix might look like this:

```python
if 'ls' in chance_level_kw:
    chance_level_kw['linestyle'] = chance_level_kw['ls']
    del chance_level_kw['ls']

if 'c' in chance_level_kw:
    chance_level_kw['color'] = chance_level_kw['c']
    del chance_level_kw['c']

chance_level_line_kw = {
    "label": "Chance level (AUC = 0.5)",
    "color": "k",
    "linestyle": "--",
}

if chance_level_kw is not None:
    chance_level_line_kw.update(**chance_level_kw)
```

### Steps/Code to Reproduce

```python
from sklearn import metrics

display = metrics.RocCurveDisplay.from_predictions(
    y_true=[0, 0, 1, 1],
    y_pred=[0.1, 0.4, 0.35, 0.8],
    plot_chance_level=True,
    chance_level_kw={'ls': '--'}
)
```

### Expected Results


![Screenshot 2024-10-06 at 15 18 34](https://github.com/user-attachments/assets/eb409ce3-910e-4a49-91a5-c061156482fb)


### Actual Results

`TypeError: Got both 'linestyle' and 'ls', which are aliases of one another`

### Versions

```shell
System:
    python: 3.12.5 (main, Aug  6 2024, 19:08:49) [Clang 15.0.0 (clang-1500.3.9.4)]
executable: /Users/josephbarbier/Desktop/scikit-learn/sklearn-env/bin/python
   machine: macOS-14.6.1-arm64-arm-64bit

Python dependencies:
      sklearn: 1.6.dev0
          pip: 24.2
   setuptools: 69.5.1
        numpy: 2.1.2
        scipy: 1.14.1
       Cython: 3.0.11
       pandas: None
   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: 10
         prefix: libomp
       filepath: /opt/homebrew/Cellar/libomp/18.1.8/lib/libomp.dylib
        version: None
```

COMMENT:
Yep a separate PR would be nice. I think that we can solve the bug in the different display that we have thought. Not all will have such default style so we should have only a subset.

@JosephBARBIERDARNAL Feel free to ping me in the PR for review.

Labels: Bug
Extending tags infrastructure is not easy anymore

In the context of bringing `imbalanced-learn` compatible with changes done in `scikit-learn` 1.6, I could see that now this is quite difficult to extend the tags infrastructure.

For instance, we added a `"dataframe"` entry to the previous `X_type` to check whether or not we should run our own test of sampler supporting dataframe.

However, because we switch from Python dictionary to Python dataclasses, mutating one of the dataclass is not possible.

@adrinjalali did we overlook at this side of the tag.

COMMENT:
The idea is that you need to subclass and add an attribute that way.

Labels: Blocker
Extending tags infrastructure is not easy anymore

In the context of bringing `imbalanced-learn` compatible with changes done in `scikit-learn` 1.6, I could see that now this is quite difficult to extend the tags infrastructure.

For instance, we added a `"dataframe"` entry to the previous `X_type` to check whether or not we should run our own test of sampler supporting dataframe.

However, because we switch from Python dictionary to Python dataclasses, mutating one of the dataclass is not possible.

@adrinjalali did we overlook at this side of the tag.

COMMENT:
But we haven't tested this

Labels: Blocker
Extending tags infrastructure is not easy anymore

In the context of bringing `imbalanced-learn` compatible with changes done in `scikit-learn` 1.6, I could see that now this is quite difficult to extend the tags infrastructure.

For instance, we added a `"dataframe"` entry to the previous `X_type` to check whether or not we should run our own test of sampler supporting dataframe.

However, because we switch from Python dictionary to Python dataclasses, mutating one of the dataclass is not possible.

@adrinjalali did we overlook at this side of the tag.

COMMENT:
OK so I'll give a try.

Labels: Blocker
Extending tags infrastructure is not easy anymore

In the context of bringing `imbalanced-learn` compatible with changes done in `scikit-learn` 1.6, I could see that now this is quite difficult to extend the tags infrastructure.

For instance, we added a `"dataframe"` entry to the previous `X_type` to check whether or not we should run our own test of sampler supporting dataframe.

However, because we switch from Python dictionary to Python dataclasses, mutating one of the dataclass is not possible.

@adrinjalali did we overlook at this side of the tag.

COMMENT:
So I'm playing with the tag in `imbalanced-learn`. Right now, I would like to extend `Tags` such that it contains a `SamplerTags`. There is two problems:

- I cannot subclass `Tags` and add `SamplerTags` because this is a default key coming after a non-default one (`InputTags` is the parent class). So I need to rewrite the class.
- The `check_estimator_get_tags_default_keys` will check that `__sklearn_tags__` provide a `sklearn.utils.Tags`. However, I'm providing a `imblearn.utils.Tags` because I don't have choice.

So it seems that this is still tricky to extend the tags for the moment.

Labels: Blocker
Extending tags infrastructure is not easy anymore

In the context of bringing `imbalanced-learn` compatible with changes done in `scikit-learn` 1.6, I could see that now this is quite difficult to extend the tags infrastructure.

For instance, we added a `"dataframe"` entry to the previous `X_type` to check whether or not we should run our own test of sampler supporting dataframe.

However, because we switch from Python dictionary to Python dataclasses, mutating one of the dataclass is not possible.

@adrinjalali did we overlook at this side of the tag.

COMMENT:
@glemaitre Can you do the following to add a new dataframe input tag?

```python
from dataclasses import dataclass, field
from sklearn.utils import Tags, InputTags, TargetTags

@dataclass
class MyInputTags(InputTags):
    dataframe: bool = False
    
@dataclass
class MyTags(Tags):
    input_tags: MyInputTags = field(default_factory=MyInputTags)

t = MyTags(
    target_tags=TargetTags(required=True),
    transformer_tags=None,
    classifier_tags=None,
    regressor_tags=None,
    input_tags=MyInputTags(dataframe=True)
)

print(t.input_tags.dataframe)
```

Labels: Blocker
Extending tags infrastructure is not easy anymore

In the context of bringing `imbalanced-learn` compatible with changes done in `scikit-learn` 1.6, I could see that now this is quite difficult to extend the tags infrastructure.

For instance, we added a `"dataframe"` entry to the previous `X_type` to check whether or not we should run our own test of sampler supporting dataframe.

However, because we switch from Python dictionary to Python dataclasses, mutating one of the dataclass is not possible.

@adrinjalali did we overlook at this side of the tag.

COMMENT:
It is indeed what I did here: https://github.com/scikit-learn-contrib/imbalanced-learn/pull/1104/files#diff-7d71613d30ea8043ed9cf51cca1d90489ad70d7e1192a3c1116342c56967f17cR10-R101

Labels: Blocker
DOC update MAPE description

### Describe the issue linked to the documentation

References #29775 

### Issue
The text in the MAPE formula is incorrect. 
```
The MAPE formula here represents a relative error and outputs a value in the 
    range [0, 1]. It is not a percentage in the range [0, 100] and a value of 100 
    does not mean 100% but 1e2. The motivation for the MAPE formula here to be in
    the range [0, 1] is to be consistent with other error metrics in scikit-learn 
    such as `accuracy_score`.
```

### Discussion for resolution
1. update the upper range
2. provide examples

- >MAPE is not in the range [0, 1], it can be arbitrarily large, right? I have to say I don't really know how to word this to make it clearer that a percentage is not returned despite the name.
- >Indeed you can make an error of 200%. We need to reformulate and drop the notion of upper bound.
- >Maybe give an example where the error is 1 (or 100%) in the user guide to clarify that the returned value is 1 and not 100?


### Suggest a potential alternative/fix

_No response_

COMMENT:
This issue is currently tackled in #30012

Labels: Documentation
Upgrade free-threading CI to run with pytest-freethreaded instead of pytest-xdist

There is a new kid on the block that should help us find out whether scikit-learn and its dependencies can be reliably considered free-threading compatible:

https://pypi.org/project/pytest-freethreaded/

Let's try to adopt it in scikit-learn.

Here is a possible plan:

- first run the tests locally a few times and see if they are tests (or a set of interacting tests) that cause a crash or a failure, open an issue for each of them, possibly upstream and then mark them as skipped under free-threading builds with a reference to the issue in the "reason" field;
- then upgrade our nightly free-threading scheduled CI run to use `pytest-freethreaded`.

Any comments @lesteve @jeremiedbb @ngoldbaum?

EDIT: anyone interested in getting hands on the first item can find this resource useful:

https://py-free-threading.github.io/

EDIT 2: there is also the [pytest-run-parallel](https://github.com/Quansight-Labs/pytest-run-parallel) plugin that can serve a similar purpose.

EDIT3: here is a TODO/plan for this problem:



- [x] resync and simplify #30041 to leverage https://github.com/Quansight-Labs/pytest-run-parallel/pull/19, https://github.com/Quansight-Labs/pytest-run-parallel/pull/33 and https://github.com/Quansight-Labs/pytest-run-parallel/pull/34
- [x] configure the `[free-threading]` CI to run with `pytest-run-parallel` #32023
- [x] enable the free-threading flag in Cython extension #31342
  - [x] investigate and fix crashes and test failures or open tracking issues and mark tests as `thread_unsafe` ;)
- [ ] benchmark the use of threading, in particular when we we expect nested parallelism between Python threads (e.g. in `GridSearchCV` with the "threading" backend of joblib) and BLAS or OpenMP native threading in the underlying estimators.
- [ ] communicate results on a blog post / pydata presentation / social media.

COMMENT:
Discussing with @lesteve we have a problem with test fixture that use on `sklearn.with_config` because the fixture is executed in the main thread while the test is run in a different thread: since `sklearn.with_config` uses thread locals, the configuration change of the fixture is not visible in the test run in a parallel thread.

Maybe there is a problem of scoping the fixture correctly, and somehow telling pytest to run the fixture in the thread running the test.

Labels: Build / CI, Needs Decision, module:test-suite, free-threading