Talip7/scikit-learn-issues-multilabel huggingface dataset:
Add user friendly string options for interaction constraints in HistGradientBoosting*

### Describe the workflow you want to enable

```python
model_no_interactions = HistGradientBoostingRegressor(
    interaction_cst="no_interactions"
)

model_pairwise_interactions = HistGradientBoostingRegressor(
    interaction_cst="pairwise"
)
```

instead of
```python
model_no_interactions = HistGradientBoostingRegressor(
    interaction_cst=[[i] for i in range(X_train.shape[1])]
)

model_pairwise_interactions = HistGradientBoostingRegressor(
    interaction_cst=list(itertools.combinations(range(n_features), 2))
)
```



### Describe your proposed solution

- [x] `"no_interactions"` is straight forward.
- [x] `"pairwise"` expands to a list that is quadratic in number of features. It might be more memory efficient to use generators internally.

### Describe alternatives you've considered, if relevant

_No response_

### Additional context

This was proposed as follow-up in https://github.com/scikit-learn/scikit-learn/pull/21020#issuecomment-951233850.

COMMENT:
@betatim Go ahead.
@ogrisel We should open a new issue for feature names as argument option, for monotonic as well as interaction constraints.

Labels: New Feature, module:ensemble
Add user friendly string options for interaction constraints in HistGradientBoosting*

### Describe the workflow you want to enable

```python
model_no_interactions = HistGradientBoostingRegressor(
    interaction_cst="no_interactions"
)

model_pairwise_interactions = HistGradientBoostingRegressor(
    interaction_cst="pairwise"
)
```

instead of
```python
model_no_interactions = HistGradientBoostingRegressor(
    interaction_cst=[[i] for i in range(X_train.shape[1])]
)

model_pairwise_interactions = HistGradientBoostingRegressor(
    interaction_cst=list(itertools.combinations(range(n_features), 2))
)
```



### Describe your proposed solution

- [x] `"no_interactions"` is straight forward.
- [x] `"pairwise"` expands to a list that is quadratic in number of features. It might be more memory efficient to use generators internally.

### Describe alternatives you've considered, if relevant

_No response_

### Additional context

This was proposed as follow-up in https://github.com/scikit-learn/scikit-learn/pull/21020#issuecomment-951233850.

COMMENT:
> @ogrisel We should open a new issue for feature names as argument option, for monotonic as well as interaction constraints.

I agree. Let me do that and I will cross-link back to this issue.

Labels: New Feature, module:ensemble
OrdinalEncoder becomes slow in presence of numerous `nan` values

### Describe the bug

I want to use ordinalencoder with a feature with ~10 categories, but >99% values nan.
Execution time is very slow. ~4min for a 1e5 rows.
But strangely enough, if the feature is not sparsed, then fitting time is  ~1s

Worth mention, that if you use `category_encoders.ordinal.OrdinalEncoder` the problem with sparse cat feature doesn't happen.

### Steps/Code to Reproduce

```
import datetime as dt
import fire
import numpy as np
import pandas as pd
from sklearn.preprocessing import OrdinalEncoder
# from category_encoders.ordinal import OrdinalEncoder

def xgb_cat():
    print('xgb cat')
    num_rows = int(1e5)
    # num_cols = 200

    x = pd.DataFrame()

    encoder = OrdinalEncoder()

    x['cat'] = [f'cat_{v}' for v in np.random.randint(10, size=num_rows)]
    start_ = dt.datetime.now()
    print('\ncat without nan')
    print('start fit')
    encoder.fit(x)
    end_ = dt.datetime.now()
    print(f"execution time: {end_ - start_}")
    # execution time: 1s

    x['cat'] = np.nan
    x.loc[0:99, 'cat'] = [f'cat_{v}' for v in np.random.randint(10, size=100)]

    start_ = dt.datetime.now()
    print('\ncat with >99% nan')
    print('start fit')
    encoder.fit(x)
    end_ = dt.datetime.now()
    print(f"execution time: {end_ - start_}")
    # execution time: 4min
xgb_cat()
```

### Expected Results

execution time for sparsed column is also ~1s

### Actual Results

execution time for sparsed column is ~4min

### Versions

```shell
System:
    python: 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)]
executable: C:\--\python.exe
   machine: Windows-10-10.0.19043-SP0
Python dependencies:
      sklearn: 1.1.3
          pip: 22.3.1
   setuptools: 65.4.1
        numpy: 1.23.4
        scipy: 1.9.3
       Cython: None
       pandas: 1.5.1
   matplotlib: None
       joblib: 1.2.0
threadpoolctl: 3.1.0
Built with OpenMP: True
threadpoolctl info:
       user_api: openmp
   internal_api: openmp
         prefix: vcomp
       filepath: C:\--\Lib\site-packages\sklearn\.libs\vcomp140.dll
        version: None
    num_threads: 16
       user_api: blas
   internal_api: openblas
         prefix: libopenblas
       filepath: C:\--\Lib\site-packages\numpy\.libs\libopenblas.FB5AE2TYXYH2IJRDKGDGQ3XBKLKTF43H.gfortran-win_amd64.dll
        version: 0.3.20
threading_layer: pthreads
   architecture: SkylakeX
    num_threads: 16
       user_api: blas
   internal_api: openblas
         prefix: libopenblas
       filepath: C:\--\Lib\site-packages\scipy.libs\libopenblas-57db09cfe174768fb409a6bb5a530d4c.dll
        version: 0.3.18
threading_layer: pthreads
   architecture: Prescott
    num_threads: 16
```

COMMENT:
Actually, this is not a sparse matrix, this is a dense matrix containing `nan` values.
A sparse matrix is of the following format: https://docs.scipy.org/doc/scipy/reference/sparse.html

So here, we use the features of the `OrdinalEncoder` to be lenient with `np.nan` values. We should check what is the reason for the slowdown then.

Labels: Enhancement, Performance
OrdinalEncoder becomes slow in presence of numerous `nan` values

### Describe the bug

I want to use ordinalencoder with a feature with ~10 categories, but >99% values nan.
Execution time is very slow. ~4min for a 1e5 rows.
But strangely enough, if the feature is not sparsed, then fitting time is  ~1s

Worth mention, that if you use `category_encoders.ordinal.OrdinalEncoder` the problem with sparse cat feature doesn't happen.

### Steps/Code to Reproduce

```
import datetime as dt
import fire
import numpy as np
import pandas as pd
from sklearn.preprocessing import OrdinalEncoder
# from category_encoders.ordinal import OrdinalEncoder

def xgb_cat():
    print('xgb cat')
    num_rows = int(1e5)
    # num_cols = 200

    x = pd.DataFrame()

    encoder = OrdinalEncoder()

    x['cat'] = [f'cat_{v}' for v in np.random.randint(10, size=num_rows)]
    start_ = dt.datetime.now()
    print('\ncat without nan')
    print('start fit')
    encoder.fit(x)
    end_ = dt.datetime.now()
    print(f"execution time: {end_ - start_}")
    # execution time: 1s

    x['cat'] = np.nan
    x.loc[0:99, 'cat'] = [f'cat_{v}' for v in np.random.randint(10, size=100)]

    start_ = dt.datetime.now()
    print('\ncat with >99% nan')
    print('start fit')
    encoder.fit(x)
    end_ = dt.datetime.now()
    print(f"execution time: {end_ - start_}")
    # execution time: 4min
xgb_cat()
```

### Expected Results

execution time for sparsed column is also ~1s

### Actual Results

execution time for sparsed column is ~4min

### Versions

```shell
System:
    python: 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)]
executable: C:\--\python.exe
   machine: Windows-10-10.0.19043-SP0
Python dependencies:
      sklearn: 1.1.3
          pip: 22.3.1
   setuptools: 65.4.1
        numpy: 1.23.4
        scipy: 1.9.3
       Cython: None
       pandas: 1.5.1
   matplotlib: None
       joblib: 1.2.0
threadpoolctl: 3.1.0
Built with OpenMP: True
threadpoolctl info:
       user_api: openmp
   internal_api: openmp
         prefix: vcomp
       filepath: C:\--\Lib\site-packages\sklearn\.libs\vcomp140.dll
        version: None
    num_threads: 16
       user_api: blas
   internal_api: openblas
         prefix: libopenblas
       filepath: C:\--\Lib\site-packages\numpy\.libs\libopenblas.FB5AE2TYXYH2IJRDKGDGQ3XBKLKTF43H.gfortran-win_amd64.dll
        version: 0.3.20
threading_layer: pthreads
   architecture: SkylakeX
    num_threads: 16
       user_api: blas
   internal_api: openblas
         prefix: libopenblas
       filepath: C:\--\Lib\site-packages\scipy.libs\libopenblas-57db09cfe174768fb409a6bb5a530d4c.dll
        version: 0.3.18
threading_layer: pthreads
   architecture: Prescott
    num_threads: 16
```

COMMENT:
Looking deeper, the `nan != nan` issue is prominent when categorizing it as `np.array` and not when as a `list`

```python
>>> import numpy as np
>>> a = [np.nan,np.nan]
>>> set(a)
{nan}
>>> a = np.array(a)
>>> set(a)
{nan, nan}
```

Labels: Enhancement, Performance
OrdinalEncoder becomes slow in presence of numerous `nan` values

### Describe the bug

I want to use ordinalencoder with a feature with ~10 categories, but >99% values nan.
Execution time is very slow. ~4min for a 1e5 rows.
But strangely enough, if the feature is not sparsed, then fitting time is  ~1s

Worth mention, that if you use `category_encoders.ordinal.OrdinalEncoder` the problem with sparse cat feature doesn't happen.

### Steps/Code to Reproduce

```
import datetime as dt
import fire
import numpy as np
import pandas as pd
from sklearn.preprocessing import OrdinalEncoder
# from category_encoders.ordinal import OrdinalEncoder

def xgb_cat():
    print('xgb cat')
    num_rows = int(1e5)
    # num_cols = 200

    x = pd.DataFrame()

    encoder = OrdinalEncoder()

    x['cat'] = [f'cat_{v}' for v in np.random.randint(10, size=num_rows)]
    start_ = dt.datetime.now()
    print('\ncat without nan')
    print('start fit')
    encoder.fit(x)
    end_ = dt.datetime.now()
    print(f"execution time: {end_ - start_}")
    # execution time: 1s

    x['cat'] = np.nan
    x.loc[0:99, 'cat'] = [f'cat_{v}' for v in np.random.randint(10, size=100)]

    start_ = dt.datetime.now()
    print('\ncat with >99% nan')
    print('start fit')
    encoder.fit(x)
    end_ = dt.datetime.now()
    print(f"execution time: {end_ - start_}")
    # execution time: 4min
xgb_cat()
```

### Expected Results

execution time for sparsed column is also ~1s

### Actual Results

execution time for sparsed column is ~4min

### Versions

```shell
System:
    python: 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)]
executable: C:\--\python.exe
   machine: Windows-10-10.0.19043-SP0
Python dependencies:
      sklearn: 1.1.3
          pip: 22.3.1
   setuptools: 65.4.1
        numpy: 1.23.4
        scipy: 1.9.3
       Cython: None
       pandas: 1.5.1
   matplotlib: None
       joblib: 1.2.0
threadpoolctl: 3.1.0
Built with OpenMP: True
threadpoolctl info:
       user_api: openmp
   internal_api: openmp
         prefix: vcomp
       filepath: C:\--\Lib\site-packages\sklearn\.libs\vcomp140.dll
        version: None
    num_threads: 16
       user_api: blas
   internal_api: openblas
         prefix: libopenblas
       filepath: C:\--\Lib\site-packages\numpy\.libs\libopenblas.FB5AE2TYXYH2IJRDKGDGQ3XBKLKTF43H.gfortran-win_amd64.dll
        version: 0.3.20
threading_layer: pthreads
   architecture: SkylakeX
    num_threads: 16
       user_api: blas
   internal_api: openblas
         prefix: libopenblas
       filepath: C:\--\Lib\site-packages\scipy.libs\libopenblas-57db09cfe174768fb409a6bb5a530d4c.dll
        version: 0.3.18
threading_layer: pthreads
   architecture: Prescott
    num_threads: 16
```

COMMENT:
This is more complex than that. It would depend on the NaN used:

```python
In [6]: >>> import numpy as np
   ...: >>> a = [float("nan"), float("nan")]
   ...: >>> set(a)
Out[6]: {nan, nan}
```

However, it is really not clear why the list of `np.nan` works this way.

Labels: Enhancement, Performance
OrdinalEncoder becomes slow in presence of numerous `nan` values

### Describe the bug

I want to use ordinalencoder with a feature with ~10 categories, but >99% values nan.
Execution time is very slow. ~4min for a 1e5 rows.
But strangely enough, if the feature is not sparsed, then fitting time is  ~1s

Worth mention, that if you use `category_encoders.ordinal.OrdinalEncoder` the problem with sparse cat feature doesn't happen.

### Steps/Code to Reproduce

```
import datetime as dt
import fire
import numpy as np
import pandas as pd
from sklearn.preprocessing import OrdinalEncoder
# from category_encoders.ordinal import OrdinalEncoder

def xgb_cat():
    print('xgb cat')
    num_rows = int(1e5)
    # num_cols = 200

    x = pd.DataFrame()

    encoder = OrdinalEncoder()

    x['cat'] = [f'cat_{v}' for v in np.random.randint(10, size=num_rows)]
    start_ = dt.datetime.now()
    print('\ncat without nan')
    print('start fit')
    encoder.fit(x)
    end_ = dt.datetime.now()
    print(f"execution time: {end_ - start_}")
    # execution time: 1s

    x['cat'] = np.nan
    x.loc[0:99, 'cat'] = [f'cat_{v}' for v in np.random.randint(10, size=100)]

    start_ = dt.datetime.now()
    print('\ncat with >99% nan')
    print('start fit')
    encoder.fit(x)
    end_ = dt.datetime.now()
    print(f"execution time: {end_ - start_}")
    # execution time: 4min
xgb_cat()
```

### Expected Results

execution time for sparsed column is also ~1s

### Actual Results

execution time for sparsed column is ~4min

### Versions

```shell
System:
    python: 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)]
executable: C:\--\python.exe
   machine: Windows-10-10.0.19043-SP0
Python dependencies:
      sklearn: 1.1.3
          pip: 22.3.1
   setuptools: 65.4.1
        numpy: 1.23.4
        scipy: 1.9.3
       Cython: None
       pandas: 1.5.1
   matplotlib: None
       joblib: 1.2.0
threadpoolctl: 3.1.0
Built with OpenMP: True
threadpoolctl info:
       user_api: openmp
   internal_api: openmp
         prefix: vcomp
       filepath: C:\--\Lib\site-packages\sklearn\.libs\vcomp140.dll
        version: None
    num_threads: 16
       user_api: blas
   internal_api: openblas
         prefix: libopenblas
       filepath: C:\--\Lib\site-packages\numpy\.libs\libopenblas.FB5AE2TYXYH2IJRDKGDGQ3XBKLKTF43H.gfortran-win_amd64.dll
        version: 0.3.20
threading_layer: pthreads
   architecture: SkylakeX
    num_threads: 16
       user_api: blas
   internal_api: openblas
         prefix: libopenblas
       filepath: C:\--\Lib\site-packages\scipy.libs\libopenblas-57db09cfe174768fb409a6bb5a530d4c.dll
        version: 0.3.18
threading_layer: pthreads
   architecture: Prescott
    num_threads: 16
```

COMMENT:
I investigated more only to get more questions than answers :) 
```python
>>> import numpy as np
>>> np.__version__
'1.22.4'
>>> a = np.nan
>>> b = np.nan
>>> a == b
False
>>> c = [a,b]
>>> set(c)
{nan}
>>> d = np.array(c)
>>> set(d)
{nan, nan}
>>> e = list(d)
>>> set(e)
{nan, nan}
```

Labels: Enhancement, Performance
OrdinalEncoder becomes slow in presence of numerous `nan` values

### Describe the bug

I want to use ordinalencoder with a feature with ~10 categories, but >99% values nan.
Execution time is very slow. ~4min for a 1e5 rows.
But strangely enough, if the feature is not sparsed, then fitting time is  ~1s

Worth mention, that if you use `category_encoders.ordinal.OrdinalEncoder` the problem with sparse cat feature doesn't happen.

### Steps/Code to Reproduce

```
import datetime as dt
import fire
import numpy as np
import pandas as pd
from sklearn.preprocessing import OrdinalEncoder
# from category_encoders.ordinal import OrdinalEncoder

def xgb_cat():
    print('xgb cat')
    num_rows = int(1e5)
    # num_cols = 200

    x = pd.DataFrame()

    encoder = OrdinalEncoder()

    x['cat'] = [f'cat_{v}' for v in np.random.randint(10, size=num_rows)]
    start_ = dt.datetime.now()
    print('\ncat without nan')
    print('start fit')
    encoder.fit(x)
    end_ = dt.datetime.now()
    print(f"execution time: {end_ - start_}")
    # execution time: 1s

    x['cat'] = np.nan
    x.loc[0:99, 'cat'] = [f'cat_{v}' for v in np.random.randint(10, size=100)]

    start_ = dt.datetime.now()
    print('\ncat with >99% nan')
    print('start fit')
    encoder.fit(x)
    end_ = dt.datetime.now()
    print(f"execution time: {end_ - start_}")
    # execution time: 4min
xgb_cat()
```

### Expected Results

execution time for sparsed column is also ~1s

### Actual Results

execution time for sparsed column is ~4min

### Versions

```shell
System:
    python: 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)]
executable: C:\--\python.exe
   machine: Windows-10-10.0.19043-SP0
Python dependencies:
      sklearn: 1.1.3
          pip: 22.3.1
   setuptools: 65.4.1
        numpy: 1.23.4
        scipy: 1.9.3
       Cython: None
       pandas: 1.5.1
   matplotlib: None
       joblib: 1.2.0
threadpoolctl: 3.1.0
Built with OpenMP: True
threadpoolctl info:
       user_api: openmp
   internal_api: openmp
         prefix: vcomp
       filepath: C:\--\Lib\site-packages\sklearn\.libs\vcomp140.dll
        version: None
    num_threads: 16
       user_api: blas
   internal_api: openblas
         prefix: libopenblas
       filepath: C:\--\Lib\site-packages\numpy\.libs\libopenblas.FB5AE2TYXYH2IJRDKGDGQ3XBKLKTF43H.gfortran-win_amd64.dll
        version: 0.3.20
threading_layer: pthreads
   architecture: SkylakeX
    num_threads: 16
       user_api: blas
   internal_api: openblas
         prefix: libopenblas
       filepath: C:\--\Lib\site-packages\scipy.libs\libopenblas-57db09cfe174768fb409a6bb5a530d4c.dll
        version: 0.3.18
threading_layer: pthreads
   architecture: Prescott
    num_threads: 16
```

COMMENT:
On this topic, using `set` is native python but quite slow to find uniques.
Why not use `pandas.unique` or `numpy.unique`? which have high speed?
[benchmark](https://stackoverflow.com/questions/46839277/series-unique-vs-list-of-set-performance)

Labels: Enhancement, Performance
OrdinalEncoder becomes slow in presence of numerous `nan` values

### Describe the bug

I want to use ordinalencoder with a feature with ~10 categories, but >99% values nan.
Execution time is very slow. ~4min for a 1e5 rows.
But strangely enough, if the feature is not sparsed, then fitting time is  ~1s

Worth mention, that if you use `category_encoders.ordinal.OrdinalEncoder` the problem with sparse cat feature doesn't happen.

### Steps/Code to Reproduce

```
import datetime as dt
import fire
import numpy as np
import pandas as pd
from sklearn.preprocessing import OrdinalEncoder
# from category_encoders.ordinal import OrdinalEncoder

def xgb_cat():
    print('xgb cat')
    num_rows = int(1e5)
    # num_cols = 200

    x = pd.DataFrame()

    encoder = OrdinalEncoder()

    x['cat'] = [f'cat_{v}' for v in np.random.randint(10, size=num_rows)]
    start_ = dt.datetime.now()
    print('\ncat without nan')
    print('start fit')
    encoder.fit(x)
    end_ = dt.datetime.now()
    print(f"execution time: {end_ - start_}")
    # execution time: 1s

    x['cat'] = np.nan
    x.loc[0:99, 'cat'] = [f'cat_{v}' for v in np.random.randint(10, size=100)]

    start_ = dt.datetime.now()
    print('\ncat with >99% nan')
    print('start fit')
    encoder.fit(x)
    end_ = dt.datetime.now()
    print(f"execution time: {end_ - start_}")
    # execution time: 4min
xgb_cat()
```

### Expected Results

execution time for sparsed column is also ~1s

### Actual Results

execution time for sparsed column is ~4min

### Versions

```shell
System:
    python: 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)]
executable: C:\--\python.exe
   machine: Windows-10-10.0.19043-SP0
Python dependencies:
      sklearn: 1.1.3
          pip: 22.3.1
   setuptools: 65.4.1
        numpy: 1.23.4
        scipy: 1.9.3
       Cython: None
       pandas: 1.5.1
   matplotlib: None
       joblib: 1.2.0
threadpoolctl: 3.1.0
Built with OpenMP: True
threadpoolctl info:
       user_api: openmp
   internal_api: openmp
         prefix: vcomp
       filepath: C:\--\Lib\site-packages\sklearn\.libs\vcomp140.dll
        version: None
    num_threads: 16
       user_api: blas
   internal_api: openblas
         prefix: libopenblas
       filepath: C:\--\Lib\site-packages\numpy\.libs\libopenblas.FB5AE2TYXYH2IJRDKGDGQ3XBKLKTF43H.gfortran-win_amd64.dll
        version: 0.3.20
threading_layer: pthreads
   architecture: SkylakeX
    num_threads: 16
       user_api: blas
   internal_api: openblas
         prefix: libopenblas
       filepath: C:\--\Lib\site-packages\scipy.libs\libopenblas-57db09cfe174768fb409a6bb5a530d4c.dll
        version: 0.3.18
threading_layer: pthreads
   architecture: Prescott
    num_threads: 16
```

COMMENT:
Unique from bumpy is only efficient for numerical arrays. On object dtype array going with the Python set is the most efficient. We have a dispatching depending on the dtype and we have been benchmark if I recall properly.

Labels: Enhancement, Performance