Talip7/scikit-learn-issues-multilabel huggingface dataset:
Consolidate description of missing values in tree-based models `RandomForestClassifier` and `ExtraTreesClassifier`

### Describe the issue linked to the documentation

[HistGradientBoostingClassifier](https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.HistGradientBoostingClassifier.html) has a section right at the beginning that discusses how missing values are handled. 

Otoh, RandomForestClassifier and ExtraTreesClassifier does not, and it is actually unclear from the docstring how it is handled. This leads to some confusion, and users would have to go fishing within our User Guide, or even the raw Cython code to understand how the missing-ness is handled.

### Suggest a potential alternative/fix

Add the following to [RandomForestClassifier](https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html):

```
This estimator has native support for missing values (NaNs). During training, the tree grower learns at each split point whether samples with missing values should go to the left or right child, based on the potential gain. When predicting, samples with missing values are assigned to the left or right child consequently. If no missing values were encountered for a given feature during training, then samples with missing values are mapped to whichever child has the most samples.
```

Add corresponding entry in https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.ExtraTreesClassifier.html, https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestRegressor.html and https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.ExtraTreesRegressor.html.

COMMENT:
Hi @Rishab260 just checking in to see if you're working on this or still interested in submitting a PR?

Labels: Documentation
Consolidate description of missing values in tree-based models `RandomForestClassifier` and `ExtraTreesClassifier`

### Describe the issue linked to the documentation

[HistGradientBoostingClassifier](https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.HistGradientBoostingClassifier.html) has a section right at the beginning that discusses how missing values are handled. 

Otoh, RandomForestClassifier and ExtraTreesClassifier does not, and it is actually unclear from the docstring how it is handled. This leads to some confusion, and users would have to go fishing within our User Guide, or even the raw Cython code to understand how the missing-ness is handled.

### Suggest a potential alternative/fix

Add the following to [RandomForestClassifier](https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html):

```
This estimator has native support for missing values (NaNs). During training, the tree grower learns at each split point whether samples with missing values should go to the left or right child, based on the potential gain. When predicting, samples with missing values are assigned to the left or right child consequently. If no missing values were encountered for a given feature during training, then samples with missing values are mapped to whichever child has the most samples.
```

Add corresponding entry in https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.ExtraTreesClassifier.html, https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestRegressor.html and https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.ExtraTreesRegressor.html.

COMMENT:
> Hi [@Rishab260](https://github.com/Rishab260) just checking in to see if you're working on this or still interested in submitting a PR?

Hi @adam2392 , I had picked up a few other issues to work on, and will raise a PR for this soon.

Labels: Documentation
Consolidate description of missing values in tree-based models `RandomForestClassifier` and `ExtraTreesClassifier`

### Describe the issue linked to the documentation

[HistGradientBoostingClassifier](https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.HistGradientBoostingClassifier.html) has a section right at the beginning that discusses how missing values are handled. 

Otoh, RandomForestClassifier and ExtraTreesClassifier does not, and it is actually unclear from the docstring how it is handled. This leads to some confusion, and users would have to go fishing within our User Guide, or even the raw Cython code to understand how the missing-ness is handled.

### Suggest a potential alternative/fix

Add the following to [RandomForestClassifier](https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html):

```
This estimator has native support for missing values (NaNs). During training, the tree grower learns at each split point whether samples with missing values should go to the left or right child, based on the potential gain. When predicting, samples with missing values are assigned to the left or right child consequently. If no missing values were encountered for a given feature during training, then samples with missing values are mapped to whichever child has the most samples.
```

Add corresponding entry in https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.ExtraTreesClassifier.html, https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestRegressor.html and https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.ExtraTreesRegressor.html.

COMMENT:
Hi @adam2392, I have raised the PR  for this, PTAL  #30955. Thanks.

Labels: Documentation
UnsetMetadataPassedError can point towards the wrong method

### Describe the bug

When `enable_metadata_routing=True`, for a missing `set_score_request`, `UnsetMetadataPassedError` message states that a `set_fit_request` is missing.

### Steps/Code to Reproduce

```python
from sklearn import set_config
from sklearn.exceptions import UnsetMetadataPassedError
from sklearn.model_selection import cross_validate
from sklearn.linear_model import LogisticRegression
import numpy as np

rng = np.random.RandomState(22)
n_samples, n_features = 10, 4
X = rng.rand(n_samples, n_features)
y = rng.randint(0, 2, size=n_samples)
sw = rng.randint(0, 5, size=n_samples)

set_config(enable_metadata_routing=True)
# missing set_score_request
logreg = LogisticRegression().set_fit_request(sample_weight=True)
try:
    cross_validate(
        logreg, X, y, 
        params={"sample_weight":sw}, 
        error_score='raise'
    )
except UnsetMetadataPassedError as e:
    print(e)
```

### Expected Results

I would expect an error message pointing towards the missing `set_score_request`, and perhaps a less verbose message when only one metadata is passed. Something like:


'sample_weight' are passed to cross validation but are not explicitly set as requested or not requested for cross_validate's estimator: LogisticRegression. Call `.set_score_request(sample_weight=True)` on the estimator for using 'sample_weight' or `sample_weight=False` for not using it. See the Metadata Routing User guide...

### Actual Results

['sample_weight'] are passed to cross validation but are not explicitly set as requested or not requested for cross_validate's estimator: LogisticRegression. Call `.set_fit_request({{metadata}}=True)` on the estimator for each metadata in ['sample_weight'] that you want to use and `metadata=False` for not using it. See the Metadata Routing User guide...

### Versions

```shell
sklearn: 1.7.dev0
```

COMMENT:
> The same patch can fix the other functions in the same file. Wanna tackle this @StefanieSenger ?

Yes, I will prepare a PR with the fixes. Thanks @antoinebaker and @adrinjalali!

Labels: Bug, Metadata Routing
UnsetMetadataPassedError can point towards the wrong method

### Describe the bug

When `enable_metadata_routing=True`, for a missing `set_score_request`, `UnsetMetadataPassedError` message states that a `set_fit_request` is missing.

### Steps/Code to Reproduce

```python
from sklearn import set_config
from sklearn.exceptions import UnsetMetadataPassedError
from sklearn.model_selection import cross_validate
from sklearn.linear_model import LogisticRegression
import numpy as np

rng = np.random.RandomState(22)
n_samples, n_features = 10, 4
X = rng.rand(n_samples, n_features)
y = rng.randint(0, 2, size=n_samples)
sw = rng.randint(0, 5, size=n_samples)

set_config(enable_metadata_routing=True)
# missing set_score_request
logreg = LogisticRegression().set_fit_request(sample_weight=True)
try:
    cross_validate(
        logreg, X, y, 
        params={"sample_weight":sw}, 
        error_score='raise'
    )
except UnsetMetadataPassedError as e:
    print(e)
```

### Expected Results

I would expect an error message pointing towards the missing `set_score_request`, and perhaps a less verbose message when only one metadata is passed. Something like:


'sample_weight' are passed to cross validation but are not explicitly set as requested or not requested for cross_validate's estimator: LogisticRegression. Call `.set_score_request(sample_weight=True)` on the estimator for using 'sample_weight' or `sample_weight=False` for not using it. See the Metadata Routing User guide...

### Actual Results

['sample_weight'] are passed to cross validation but are not explicitly set as requested or not requested for cross_validate's estimator: LogisticRegression. Call `.set_fit_request({{metadata}}=True)` on the estimator for each metadata in ['sample_weight'] that you want to use and `metadata=False` for not using it. See the Metadata Routing User guide...

### Versions

```shell
sklearn: 1.7.dev0
```

COMMENT:
I haven't started this yet and I know it will take me a few more days until I can have a look. Just commenting for expectation management.

Labels: Bug, Metadata Routing
sample_weight is silently ignored in LogisticRegressionCV.score when metadata routing is enabled

### Describe the bug

I'm not sure if it is a proper bug, or my lack of understanding of the metadata routing API ;)

When `enable_metadata_routing=True`, the `score` method of a `LogisticRegressionCV` estimator will ignore `sample_weight`.
```python
set_config(enable_metadata_routing=True)
logreg_cv = LogisticRegressionCV().fit(X, y)
logreg_cv.score(X, y, sample_weight=sw)==logreg_cv.score(X, y) #unweighted accuracy
```
I found it surprising, because the `score` method works fine when `enable_metadata_routing=False`, so the same piece of code behaves differently depending on the metadata routing config.
```python
set_config(enable_metadata_routing=False)
logreg_cv = LogisticRegressionCV().fit(X, y)
logreg_cv.score(X, y, sample_weight=sw) #weighted accuracy
```

If I understood the metadata routing API correctly, to make the `score` method `sample_weight` aware we need to explicitly pass a scorer that request it:
```python
set_config(enable_metadata_routing=True)
weighted_accuracy = make_scorer(accuracy_score).set_score_request(sample_weight=True)
logreg_cv = LogisticRegressionCV(scoring=weighted_accuracy).fit(X, y)
logreg_cv.score(X, y, sample_weight=sw) #weighted accuracy
```

If it's the intended behavior of the metadata routing API, maybe we should warn the user or raise an error in the first case, instead of silently ignoring `sample_weight` ?

### Steps/Code to Reproduce

```python
from sklearn import set_config
from sklearn.metrics import make_scorer, accuracy_score
from sklearn.linear_model import LogisticRegressionCV
import numpy as np

rng = np.random.RandomState(22)
n_samples, n_features = 10, 4
X = rng.rand(n_samples, n_features)
y = rng.randint(0, 2, size=n_samples)
sw = rng.randint(0, 5, size=n_samples)

set_config(enable_metadata_routing=True)
logreg_cv = LogisticRegressionCV()
logreg_cv.fit(X, y)
# sample_weight is silently ignored in logreg_cv.score
assert logreg_cv.score(X, y) == logreg_cv.score(X, y, sample_weight=sw) 
assert not logreg_cv.score(X, y, sample_weight=sw)==accuracy_score(logreg_cv.predict(X),y, sample_weight=sw)
```

### Expected Results

Either `logreg_cv.score(X, y, sample_weight=sw)` raises an error/warning or the assertions are false.

### Actual Results

The assertions are true.

### Versions

```shell
sklearn: 1.7.dev0
```

COMMENT:
I confirm it's a bug, trying to figure out a fix.

Labels: Bug, Metadata Routing
sample_weight is silently ignored in LogisticRegressionCV.score when metadata routing is enabled

### Describe the bug

I'm not sure if it is a proper bug, or my lack of understanding of the metadata routing API ;)

When `enable_metadata_routing=True`, the `score` method of a `LogisticRegressionCV` estimator will ignore `sample_weight`.
```python
set_config(enable_metadata_routing=True)
logreg_cv = LogisticRegressionCV().fit(X, y)
logreg_cv.score(X, y, sample_weight=sw)==logreg_cv.score(X, y) #unweighted accuracy
```
I found it surprising, because the `score` method works fine when `enable_metadata_routing=False`, so the same piece of code behaves differently depending on the metadata routing config.
```python
set_config(enable_metadata_routing=False)
logreg_cv = LogisticRegressionCV().fit(X, y)
logreg_cv.score(X, y, sample_weight=sw) #weighted accuracy
```

If I understood the metadata routing API correctly, to make the `score` method `sample_weight` aware we need to explicitly pass a scorer that request it:
```python
set_config(enable_metadata_routing=True)
weighted_accuracy = make_scorer(accuracy_score).set_score_request(sample_weight=True)
logreg_cv = LogisticRegressionCV(scoring=weighted_accuracy).fit(X, y)
logreg_cv.score(X, y, sample_weight=sw) #weighted accuracy
```

If it's the intended behavior of the metadata routing API, maybe we should warn the user or raise an error in the first case, instead of silently ignoring `sample_weight` ?

### Steps/Code to Reproduce

```python
from sklearn import set_config
from sklearn.metrics import make_scorer, accuracy_score
from sklearn.linear_model import LogisticRegressionCV
import numpy as np

rng = np.random.RandomState(22)
n_samples, n_features = 10, 4
X = rng.rand(n_samples, n_features)
y = rng.randint(0, 2, size=n_samples)
sw = rng.randint(0, 5, size=n_samples)

set_config(enable_metadata_routing=True)
logreg_cv = LogisticRegressionCV()
logreg_cv.fit(X, y)
# sample_weight is silently ignored in logreg_cv.score
assert logreg_cv.score(X, y) == logreg_cv.score(X, y, sample_weight=sw) 
assert not logreg_cv.score(X, y, sample_weight=sw)==accuracy_score(logreg_cv.predict(X),y, sample_weight=sw)
```

### Expected Results

Either `logreg_cv.score(X, y, sample_weight=sw)` raises an error/warning or the assertions are false.

### Actual Results

The assertions are true.

### Versions

```shell
sklearn: 1.7.dev0
```

COMMENT:
/take

Labels: Bug, Metadata Routing
sample_weight is silently ignored in LogisticRegressionCV.score when metadata routing is enabled

### Describe the bug

I'm not sure if it is a proper bug, or my lack of understanding of the metadata routing API ;)

When `enable_metadata_routing=True`, the `score` method of a `LogisticRegressionCV` estimator will ignore `sample_weight`.
```python
set_config(enable_metadata_routing=True)
logreg_cv = LogisticRegressionCV().fit(X, y)
logreg_cv.score(X, y, sample_weight=sw)==logreg_cv.score(X, y) #unweighted accuracy
```
I found it surprising, because the `score` method works fine when `enable_metadata_routing=False`, so the same piece of code behaves differently depending on the metadata routing config.
```python
set_config(enable_metadata_routing=False)
logreg_cv = LogisticRegressionCV().fit(X, y)
logreg_cv.score(X, y, sample_weight=sw) #weighted accuracy
```

If I understood the metadata routing API correctly, to make the `score` method `sample_weight` aware we need to explicitly pass a scorer that request it:
```python
set_config(enable_metadata_routing=True)
weighted_accuracy = make_scorer(accuracy_score).set_score_request(sample_weight=True)
logreg_cv = LogisticRegressionCV(scoring=weighted_accuracy).fit(X, y)
logreg_cv.score(X, y, sample_weight=sw) #weighted accuracy
```

If it's the intended behavior of the metadata routing API, maybe we should warn the user or raise an error in the first case, instead of silently ignoring `sample_weight` ?

### Steps/Code to Reproduce

```python
from sklearn import set_config
from sklearn.metrics import make_scorer, accuracy_score
from sklearn.linear_model import LogisticRegressionCV
import numpy as np

rng = np.random.RandomState(22)
n_samples, n_features = 10, 4
X = rng.rand(n_samples, n_features)
y = rng.randint(0, 2, size=n_samples)
sw = rng.randint(0, 5, size=n_samples)

set_config(enable_metadata_routing=True)
logreg_cv = LogisticRegressionCV()
logreg_cv.fit(X, y)
# sample_weight is silently ignored in logreg_cv.score
assert logreg_cv.score(X, y) == logreg_cv.score(X, y, sample_weight=sw) 
assert not logreg_cv.score(X, y, sample_weight=sw)==accuracy_score(logreg_cv.predict(X),y, sample_weight=sw)
```

### Expected Results

Either `logreg_cv.score(X, y, sample_weight=sw)` raises an error/warning or the assertions are false.

### Actual Results

The assertions are true.

### Versions

```shell
sklearn: 1.7.dev0
```

COMMENT:
@Dalesrox do you know how to fix this?

Labels: Bug, Metadata Routing
⚠️ CI failed on Linux_Runs.pylatest_conda_forge_mkl (last failure: Feb 12, 2025) ⚠️

**CI failed on [Linux_Runs.pylatest_conda_forge_mkl](https://dev.azure.com/scikit-learn/scikit-learn/_build/results?buildId=74075&view=logs&j=dde5042c-7464-5d47-9507-31bdd2ee0a3a)** (Feb 12, 2025)
- Test Collection Failure

COMMENT:
HTTP 403 with Figshare when running the test (Internal error in conftest.py) #30761. This will likely go away tomorrow.

Labels: Needs Triage
⚠️ CI failed on Linux_Runs.pylatest_conda_forge_mkl (last failure: Feb 12, 2025) ⚠️

**CI failed on [Linux_Runs.pylatest_conda_forge_mkl](https://dev.azure.com/scikit-learn/scikit-learn/_build/results?buildId=74075&view=logs&j=dde5042c-7464-5d47-9507-31bdd2ee0a3a)** (Feb 12, 2025)
- Test Collection Failure

COMMENT:
## CI is no longer failing! ✅

[Successful run](https://dev.azure.com/scikit-learn/scikit-learn/_build/results?buildId=74075&view=logs&j=de937867-5818-5907-5645-0b22541236da) on Feb 12, 2025

Labels: Needs Triage
Are there any pitfalls by combining `n_jobs` and `random_state`?

### Discussed in https://github.com/scikit-learn/scikit-learn/discussions/30809

<div type='discussions-op-text'>

<sup>Originally posted by **adosar** February 11, 2025</sup>
In [Controlling randomness](https://scikit-learn.org/stable/common_pitfalls.html#common-pitfalls-and-recommended-practices), the guide is discussing how to properly control randomness either for an estimator or CV or when using both. However, there is no mention if `random_state` and `n_jobs > 1` interact in any unexpected way.

Lets consider a typical use case where a user cross validates a `RandomForestClassifier` with `KFold`:
```python
estimator = RandomForestClassifer(random_state=np.random.RandomState(1))  # Recommended to pass RandomState instance.
kfold = KFold(shuffle=True, random_state=42)  # Recommended to pass int.
cross_val_score(estimator, n_jobs=-1, ..., cv=kfold)
```
Since `n_jobs=-1` this means that multiple cores will be used for cross validation (e.g. 1 core per fold).

Would the same state be used for the different folds, since during multiprocessing the estimator and hence the `rng` passed to it, is copied via fork?

</div>

COMMENT:
We've had many discussions in this regard. You can have a look at these: https://github.com/scikit-learn/enhancement_proposals/pull/24, https://github.com/scikit-learn/enhancement_proposals/pull/88 and other related discussions. This is a rather complex issue we've been dealing with.

Closing as duplicate.

Labels: Needs Triage
Are there any pitfalls by combining `n_jobs` and `random_state`?

### Discussed in https://github.com/scikit-learn/scikit-learn/discussions/30809

<div type='discussions-op-text'>

<sup>Originally posted by **adosar** February 11, 2025</sup>
In [Controlling randomness](https://scikit-learn.org/stable/common_pitfalls.html#common-pitfalls-and-recommended-practices), the guide is discussing how to properly control randomness either for an estimator or CV or when using both. However, there is no mention if `random_state` and `n_jobs > 1` interact in any unexpected way.

Lets consider a typical use case where a user cross validates a `RandomForestClassifier` with `KFold`:
```python
estimator = RandomForestClassifer(random_state=np.random.RandomState(1))  # Recommended to pass RandomState instance.
kfold = KFold(shuffle=True, random_state=42)  # Recommended to pass int.
cross_val_score(estimator, n_jobs=-1, ..., cv=kfold)
```
Since `n_jobs=-1` this means that multiple cores will be used for cross validation (e.g. 1 core per fold).

Would the same state be used for the different folds, since during multiprocessing the estimator and hence the `rng` passed to it, is copied via fork?

</div>

COMMENT:
@adrinjalali From the linked discussion and the links therein, there is no mention to what happens when `n_jobs > 1`. That was the motivation behind this question/issue.

Labels: Needs Triage
Are there any pitfalls by combining `n_jobs` and `random_state`?

### Discussed in https://github.com/scikit-learn/scikit-learn/discussions/30809

<div type='discussions-op-text'>

<sup>Originally posted by **adosar** February 11, 2025</sup>
In [Controlling randomness](https://scikit-learn.org/stable/common_pitfalls.html#common-pitfalls-and-recommended-practices), the guide is discussing how to properly control randomness either for an estimator or CV or when using both. However, there is no mention if `random_state` and `n_jobs > 1` interact in any unexpected way.

Lets consider a typical use case where a user cross validates a `RandomForestClassifier` with `KFold`:
```python
estimator = RandomForestClassifer(random_state=np.random.RandomState(1))  # Recommended to pass RandomState instance.
kfold = KFold(shuffle=True, random_state=42)  # Recommended to pass int.
cross_val_score(estimator, n_jobs=-1, ..., cv=kfold)
```
Since `n_jobs=-1` this means that multiple cores will be used for cross validation (e.g. 1 core per fold).

Would the same state be used for the different folds, since during multiprocessing the estimator and hence the `rng` passed to it, is copied via fork?

</div>

COMMENT:
Somewhere in those discussions we also touch on issues with parallelism.

You can check for yourself pretty easily, how things go south in some cases:

```py
In [1]: import numpy as np

In [2]: from joblib import delayed, Parallel

In [3]: def f(rng):
   ...:     return rng.integers(0, 10, 10)
   ...: 

In [4]: rng = np.random.default_rng()

In [5]: Parallel(n_jobs=5)(delayed(f)(rng) for x in range(10))
Out[5]: 
[array([0, 7, 1, 6, 3, 6, 7, 9, 1, 6]),
 array([0, 7, 1, 6, 3, 6, 7, 9, 1, 6]),
 array([0, 7, 1, 6, 3, 6, 7, 9, 1, 6]),
 array([0, 7, 1, 6, 3, 6, 7, 9, 1, 6]),
 array([0, 7, 1, 6, 3, 6, 7, 9, 1, 6]),
 array([0, 7, 1, 6, 3, 6, 7, 9, 1, 6]),
 array([0, 7, 1, 6, 3, 6, 7, 9, 1, 6]),
 array([0, 7, 1, 6, 3, 6, 7, 9, 1, 6]),
 array([0, 7, 1, 6, 3, 6, 7, 9, 1, 6]),
 array([0, 7, 1, 6, 3, 6, 7, 9, 1, 6])]
```

As you see, those are identical results, since the `rng` is cloned. Right now the only way to make sure things keep being random and separate parallel processes generate different numbers, is to pass `None` as random seed.

Labels: Needs Triage