Talip7/scikit-learn-issues-multilabel huggingface dataset:
It would be nice if TargetEncoder could apply itself to a subset of columns which it has encoded.

### Describe the workflow you want to enable

Once a TargetEncoder has been fitted, when you go to transform another data set (e.g., in a production job after the models have been trained), the TargetEncoder.transform(X) will insist that X be the same columns as it was fitted on.  However, it's possible that the new X has only a subset of the X-columns used during fitting.  One reason (well, _my_ reason) is that a feature selection step could occur between fitting and production, and potentially many features become irrelevant and likely won't exist when TargetEncoder is asked to .transform() the production data.

Internally to TargetEncoder, each feature's encoding parameters are independent of the other features' (I've only checked this for non-hierarchical columns; if a hierarchy is provided I don't know what happens).  So it should be safe to have TargetEncoder apply the transform only to the subset of columns, and not fail with "Unexpected input dimension".


### Describe your proposed solution

If a TargetEncoder has been fitted on features A,B,C, allow that encoder to .transform() any subset of A,B,C without requiring all of them.

### Describe alternatives you've considered, if relevant

I've looked at ColumnTransformer briefly, but it appears that I need to know the column names ahead of time.  In my particular environment, the underlying data changes shape often and the column names aren't known by the person writing the code.

What I've done as a workaround is this:
            #We are very likely to be using only a subset of the columns
            #on which the target encoder was fitted.  There doesn't seem
            #to be a way to apply the transform only to selected columns.
            #Instead, we'll add empties for the missing columns, then
            #apply the transform, and then discard the unnecessary ones.
            tenc=*my previously fitted TargetEncoder which I have just retrieved from storage*
            orig_cols=score_me.columns.to_list()
            score_me=score_me.reindex(columns=tenc.get_feature_names())
            score_me=tenc.transform(score_me)
            score_me=score_me[orig_cols]

### Additional context

_No response_

COMMENT:
I don't think that we want to use this feature. You can always create a `Pipeline` where the `TargetEncoder` would fit on all columns and all columns would be transformed and subsequently use a transformer to select the columns.


```python
model = make_pipeline(
    TargetEncoder(),
    # Transformer dropping column C for instance
    FunctionTransformer(lambda X: return X["A", "B"]),
    LogisticRegression(),
)
```

You can also have a look at the feature selection user guide to see specific estimator that reduce the number of columns and that can be used in pipeline: https://scikit-learn.org/dev/modules/feature_selection.html#feature-selection

Labels: New Feature, Needs Triage
It would be nice if TargetEncoder could apply itself to a subset of columns which it has encoded.

### Describe the workflow you want to enable

Once a TargetEncoder has been fitted, when you go to transform another data set (e.g., in a production job after the models have been trained), the TargetEncoder.transform(X) will insist that X be the same columns as it was fitted on.  However, it's possible that the new X has only a subset of the X-columns used during fitting.  One reason (well, _my_ reason) is that a feature selection step could occur between fitting and production, and potentially many features become irrelevant and likely won't exist when TargetEncoder is asked to .transform() the production data.

Internally to TargetEncoder, each feature's encoding parameters are independent of the other features' (I've only checked this for non-hierarchical columns; if a hierarchy is provided I don't know what happens).  So it should be safe to have TargetEncoder apply the transform only to the subset of columns, and not fail with "Unexpected input dimension".


### Describe your proposed solution

If a TargetEncoder has been fitted on features A,B,C, allow that encoder to .transform() any subset of A,B,C without requiring all of them.

### Describe alternatives you've considered, if relevant

I've looked at ColumnTransformer briefly, but it appears that I need to know the column names ahead of time.  In my particular environment, the underlying data changes shape often and the column names aren't known by the person writing the code.

What I've done as a workaround is this:
            #We are very likely to be using only a subset of the columns
            #on which the target encoder was fitted.  There doesn't seem
            #to be a way to apply the transform only to selected columns.
            #Instead, we'll add empties for the missing columns, then
            #apply the transform, and then discard the unnecessary ones.
            tenc=*my previously fitted TargetEncoder which I have just retrieved from storage*
            orig_cols=score_me.columns.to_list()
            score_me=score_me.reindex(columns=tenc.get_feature_names())
            score_me=tenc.transform(score_me)
            score_me=score_me[orig_cols]

### Additional context

_No response_

COMMENT:
One can also use ColumnTransformer to apply a transformer on a selected set of columns and leave the rest alone.

Labels: New Feature, Needs Triage
⚠️ CI failed on macOS.pylatest_conda_forge_mkl ⚠️

**CI failed on [macOS.pylatest_conda_forge_mkl](https://dev.azure.com/scikit-learn/scikit-learn/_build/results?buildId=57215&view=logs&j=97641769-79fb-5590-9088-a30ce9b850b9)** (Jul 21, 2023)
- test_pickle_version_warning_is_issued_when_no_version_info_in_pickle

COMMENT:
## CI is no longer failing! ✅

[Successful run](https://dev.azure.com/scikit-learn/scikit-learn/_build/results?buildId=57269&view=logs&j=97641769-79fb-5590-9088-a30ce9b850b9) on Jul 24, 2023

Labels: Needs Triage
⚠️ CI failed on macOS.pylatest_conda_forge_mkl ⚠️

**CI failed on [macOS.pylatest_conda_forge_mkl](https://dev.azure.com/scikit-learn/scikit-learn/_build/results?buildId=57215&view=logs&j=97641769-79fb-5590-9088-a30ce9b850b9)** (Jul 21, 2023)
- test_pickle_version_warning_is_issued_when_no_version_info_in_pickle

COMMENT:
I'm not sure what happened here, but it has not been failing for the last 3 days. I'm closing for now.

Labels: Needs Triage
⚠️ CI failed on macOS.pylatest_conda_forge_mkl ⚠️

**CI failed on [macOS.pylatest_conda_forge_mkl](https://dev.azure.com/scikit-learn/scikit-learn/_build/results?buildId=57215&view=logs&j=97641769-79fb-5590-9088-a30ce9b850b9)** (Jul 21, 2023)
- test_pickle_version_warning_is_issued_when_no_version_info_in_pickle

COMMENT:
This failure has been observed again in https://github.com/scikit-learn/scikit-learn/pull/27266 (see [those logs](https://dev.azure.com/scikit-learn/scikit-learn/_build/results?buildId=58609&view=logs&j=97641769-79fb-5590-9088-a30ce9b850b9&t=4745baa1-36b5-56c8-9a8e-6480742db1a6&l=623)).

Labels: Needs Triage
⚠️ CI failed on macOS.pylatest_conda_forge_mkl ⚠️

**CI failed on [macOS.pylatest_conda_forge_mkl](https://dev.azure.com/scikit-learn/scikit-learn/_build/results?buildId=57215&view=logs&j=97641769-79fb-5590-9088-a30ce9b850b9)** (Jul 21, 2023)
- test_pickle_version_warning_is_issued_when_no_version_info_in_pickle

COMMENT:
I opened a new issue to track it: https://github.com/scikit-learn/scikit-learn/issues/27268

Labels: Needs Triage
Nearest neighbor return structure takes significantly longer to garbage collect

The way output is structured in nearest neighbor classes (e.g. KDTree) leads to an order of magnitude greater time required to garbage collect the output, compared to actually generating it. For example:

```python
from sklearn.neighbors import KDTree
import time

def mwe(n):
    start = time.time()
    a = KDTree([[1]]).query_radius([[1]]*int(n), 1)
    print(f'Function completed in: {time.time()-start:.2f} seconds')

start = time.time()
mwe(1e6)
print(f'Function returned after {time.time()-start:.1f} seconds')

start = time.time()
mwe(1e7)
print(f'Function returned after {time.time()-start:.1f} seconds')
```

This results in the output:
```
Function completed in: 0.36 seconds
Function returned after 4.4 seconds

Function completed in: 3.75 seconds
Function returned after 44.0 seconds
```

Compare this to the output of an equivalent script which uses `scipy.spatial.KDTree`:
```
Function completed in: 0.86 seconds
Function returned after 0.9 seconds

Function completed in: 9.17 seconds
Function returned after 9.3 seconds
```

Though the query operation is slower using scipy, the garbage collection time for the same output is inconsequential; this seems to be due to the nested objects being lists rather than arrays.
 
Also, I'm aware that by switching the build/query data, the problem goes away on this contrived example - but this results in a completely different output representation. In the data I'm actually working with, swapping the two leads to a runtime approaching the garbage collect time shown above, while still having a relatively large impact from garbage collection. 


```shell
System:
    python: 3.10.12 | packaged by Anaconda, Inc. | (main, Jul  5 2023, 19:09:20) [MSC v.1916 64 bit (AMD64)]
executable: env_crest\python.exe
   machine: Windows-10-10.0.19045-SP0

Python dependencies:
      sklearn: 1.2.1
          pip: 23.1.2
   setuptools: 67.8.0
        numpy: 1.23.5
        scipy: 1.10.1
       Cython: None
       pandas: 1.5.3
   matplotlib: 3.7.1
       joblib: 1.2.0
threadpoolctl: 2.2.0

Built with OpenMP: True

threadpoolctl info:
       filepath: env_crest\Library\bin\mkl_rt.2.dll
         prefix: mkl_rt
       user_api: blas
   internal_api: mkl
        version: 2023.1-Product
    num_threads: 6
threading_layer: intel

       filepath: env_crest\vcomp140.dll
         prefix: vcomp
       user_api: openmp
   internal_api: openmp
        version: None
    num_threads: 12
```

COMMENT:
Seems more like numpy issue. This issue exists from numpy>=1.22.0

```
NumPy version: 1.21.6
Function completed in: 0.96 seconds
Function returned after 1.1 seconds
Function completed in: 9.97 seconds
Function returned after 11.4 seconds

NumPy version: 1.22.0
Function completed in: 0.66 seconds
Function returned after 7.7 seconds
Function completed in: 6.94 seconds
Function returned after 84.8 seconds
```

Labels: Bug, Needs Triage
Nearest neighbor return structure takes significantly longer to garbage collect

The way output is structured in nearest neighbor classes (e.g. KDTree) leads to an order of magnitude greater time required to garbage collect the output, compared to actually generating it. For example:

```python
from sklearn.neighbors import KDTree
import time

def mwe(n):
    start = time.time()
    a = KDTree([[1]]).query_radius([[1]]*int(n), 1)
    print(f'Function completed in: {time.time()-start:.2f} seconds')

start = time.time()
mwe(1e6)
print(f'Function returned after {time.time()-start:.1f} seconds')

start = time.time()
mwe(1e7)
print(f'Function returned after {time.time()-start:.1f} seconds')
```

This results in the output:
```
Function completed in: 0.36 seconds
Function returned after 4.4 seconds

Function completed in: 3.75 seconds
Function returned after 44.0 seconds
```

Compare this to the output of an equivalent script which uses `scipy.spatial.KDTree`:
```
Function completed in: 0.86 seconds
Function returned after 0.9 seconds

Function completed in: 9.17 seconds
Function returned after 9.3 seconds
```

Though the query operation is slower using scipy, the garbage collection time for the same output is inconsequential; this seems to be due to the nested objects being lists rather than arrays.
 
Also, I'm aware that by switching the build/query data, the problem goes away on this contrived example - but this results in a completely different output representation. In the data I'm actually working with, swapping the two leads to a runtime approaching the garbage collect time shown above, while still having a relatively large impact from garbage collection. 


```shell
System:
    python: 3.10.12 | packaged by Anaconda, Inc. | (main, Jul  5 2023, 19:09:20) [MSC v.1916 64 bit (AMD64)]
executable: env_crest\python.exe
   machine: Windows-10-10.0.19045-SP0

Python dependencies:
      sklearn: 1.2.1
          pip: 23.1.2
   setuptools: 67.8.0
        numpy: 1.23.5
        scipy: 1.10.1
       Cython: None
       pandas: 1.5.3
   matplotlib: 3.7.1
       joblib: 1.2.0
threadpoolctl: 2.2.0

Built with OpenMP: True

threadpoolctl info:
       filepath: env_crest\Library\bin\mkl_rt.2.dll
         prefix: mkl_rt
       user_api: blas
   internal_api: mkl
        version: 2023.1-Product
    num_threads: 6
threading_layer: intel

       filepath: env_crest\vcomp140.dll
         prefix: vcomp
       user_api: openmp
   internal_api: openmp
        version: None
    num_threads: 12
```

COMMENT:
Sure thing, I've updated the original post with the full versions. I can also confirm that the issue disappears when using numpy==1.21.6 - I went ahead and posted the issue to [numpy's repository](https://github.com/numpy/numpy/issues/24232#issue-1816776176). Thanks for narrowing it down!

Labels: Bug, Needs Triage
Nearest neighbor return structure takes significantly longer to garbage collect

The way output is structured in nearest neighbor classes (e.g. KDTree) leads to an order of magnitude greater time required to garbage collect the output, compared to actually generating it. For example:

```python
from sklearn.neighbors import KDTree
import time

def mwe(n):
    start = time.time()
    a = KDTree([[1]]).query_radius([[1]]*int(n), 1)
    print(f'Function completed in: {time.time()-start:.2f} seconds')

start = time.time()
mwe(1e6)
print(f'Function returned after {time.time()-start:.1f} seconds')

start = time.time()
mwe(1e7)
print(f'Function returned after {time.time()-start:.1f} seconds')
```

This results in the output:
```
Function completed in: 0.36 seconds
Function returned after 4.4 seconds

Function completed in: 3.75 seconds
Function returned after 44.0 seconds
```

Compare this to the output of an equivalent script which uses `scipy.spatial.KDTree`:
```
Function completed in: 0.86 seconds
Function returned after 0.9 seconds

Function completed in: 9.17 seconds
Function returned after 9.3 seconds
```

Though the query operation is slower using scipy, the garbage collection time for the same output is inconsequential; this seems to be due to the nested objects being lists rather than arrays.
 
Also, I'm aware that by switching the build/query data, the problem goes away on this contrived example - but this results in a completely different output representation. In the data I'm actually working with, swapping the two leads to a runtime approaching the garbage collect time shown above, while still having a relatively large impact from garbage collection. 


```shell
System:
    python: 3.10.12 | packaged by Anaconda, Inc. | (main, Jul  5 2023, 19:09:20) [MSC v.1916 64 bit (AMD64)]
executable: env_crest\python.exe
   machine: Windows-10-10.0.19045-SP0

Python dependencies:
      sklearn: 1.2.1
          pip: 23.1.2
   setuptools: 67.8.0
        numpy: 1.23.5
        scipy: 1.10.1
       Cython: None
       pandas: 1.5.3
   matplotlib: 3.7.1
       joblib: 1.2.0
threadpoolctl: 2.2.0

Built with OpenMP: True

threadpoolctl info:
       filepath: env_crest\Library\bin\mkl_rt.2.dll
         prefix: mkl_rt
       user_api: blas
   internal_api: mkl
        version: 2023.1-Product
    num_threads: 6
threading_layer: intel

       filepath: env_crest\vcomp140.dll
         prefix: vcomp
       user_api: openmp
   internal_api: openmp
        version: None
    num_threads: 12
```

COMMENT:
Works as expected in WSL. The issue seems to be on Windows only.

```
NumPy version: 1.25.1
Function completed in: 0.69 seconds
Function returned after 0.8 seconds
Function completed in: 27.80 seconds
Function returned after 28.9 seconds
```

Labels: Bug, Needs Triage
Nearest neighbor return structure takes significantly longer to garbage collect

The way output is structured in nearest neighbor classes (e.g. KDTree) leads to an order of magnitude greater time required to garbage collect the output, compared to actually generating it. For example:

```python
from sklearn.neighbors import KDTree
import time

def mwe(n):
    start = time.time()
    a = KDTree([[1]]).query_radius([[1]]*int(n), 1)
    print(f'Function completed in: {time.time()-start:.2f} seconds')

start = time.time()
mwe(1e6)
print(f'Function returned after {time.time()-start:.1f} seconds')

start = time.time()
mwe(1e7)
print(f'Function returned after {time.time()-start:.1f} seconds')
```

This results in the output:
```
Function completed in: 0.36 seconds
Function returned after 4.4 seconds

Function completed in: 3.75 seconds
Function returned after 44.0 seconds
```

Compare this to the output of an equivalent script which uses `scipy.spatial.KDTree`:
```
Function completed in: 0.86 seconds
Function returned after 0.9 seconds

Function completed in: 9.17 seconds
Function returned after 9.3 seconds
```

Though the query operation is slower using scipy, the garbage collection time for the same output is inconsequential; this seems to be due to the nested objects being lists rather than arrays.
 
Also, I'm aware that by switching the build/query data, the problem goes away on this contrived example - but this results in a completely different output representation. In the data I'm actually working with, swapping the two leads to a runtime approaching the garbage collect time shown above, while still having a relatively large impact from garbage collection. 


```shell
System:
    python: 3.10.12 | packaged by Anaconda, Inc. | (main, Jul  5 2023, 19:09:20) [MSC v.1916 64 bit (AMD64)]
executable: env_crest\python.exe
   machine: Windows-10-10.0.19045-SP0

Python dependencies:
      sklearn: 1.2.1
          pip: 23.1.2
   setuptools: 67.8.0
        numpy: 1.23.5
        scipy: 1.10.1
       Cython: None
       pandas: 1.5.3
   matplotlib: 3.7.1
       joblib: 1.2.0
threadpoolctl: 2.2.0

Built with OpenMP: True

threadpoolctl info:
       filepath: env_crest\Library\bin\mkl_rt.2.dll
         prefix: mkl_rt
       user_api: blas
   internal_api: mkl
        version: 2023.1-Product
    num_threads: 6
threading_layer: intel

       filepath: env_crest\vcomp140.dll
         prefix: vcomp
       user_api: openmp
   internal_api: openmp
        version: None
    num_threads: 12
```

COMMENT:
So I guess we can close this as not a scikit-learn issue anyway.

Labels: Bug, Needs Triage
Outline for the main encoding example

(migrated from #657)

As discussed with Gaël, here is a proposed outline for the main encoding example. Please submit your suggestions!

- Start with a dirty dataset, probably employee_salaries
- First, use the TableVectorizer, just to say "with little trouble, it works ootb"
- Then go into what it did, and the internals (GapEncoder, DatetimeEncoder)
- Use the MinHashEncoder for a larger dataset (maybe the largest one we have)

Notes:
- Should we talk about the SimilarityEncoder?
- Should we introduce the problem of dirty categories in this notebook?
- We should integrate the [cross-validation example from](https://github.com/skrub-data/skrub/blob/3e312f9a41ff1e1fe4123e2af80ac93f15724b99/examples/encoding/grid_search_with_the_tablevectorizer.py) #546, but not sure where

cc @myungkim930

COMMENT:
Woops, wrong repo :joy:

Labels: Needs Triage
RFC Cap uppper versions of dependencies

We've been seeing quite a few issues where people try to install scikit-learn in an environment for which there are no wheels and therefore pip trying to build the package from the source distribution, but failing.

Making scikit-learn work with new versions of dependencies, including python is not trivial, and there are no guarantees for the package to work correctly even if `pip` somehow magically manages to build from the sdist.

I propose we set the higher limit of our dependencies to whatever's released at the time of the release, so that when somebody tries to do `pip install scikit-learn==1.4` in 2-3 years from now, it either fails or downgrades the dependencies to the right ones. The same thing for supporting versions of python.

We can also stop putting sdist out there. If the users want to build the project, they probably can build it from github.

cc @scikit-learn/core-devs

COMMENT:
> an environment for which there are no wheels and therefore pip trying to build the package from the source distribution

An environment for which there are no wheels is not related to our dependencies, right ? It only depends on the os and python version. So I don't see how setting an upper bound on our dependencies would solve this issue.

> pip install scikit-learn==1.4 in 2-3 years from now,

I can see that a new version of a dependency can break an old version of sklearn, but usually our answer in this situation is to tell to use a more recent version of scikit-learn. I think not setting upper bound helps sklearn users moving forward and not stay stuck with an old version, and I'm afraid that setting upper bounds would give an opportunity for many users to pin a specific version of sklearn forever.

Labels: RFC
RFC Cap uppper versions of dependencies

We've been seeing quite a few issues where people try to install scikit-learn in an environment for which there are no wheels and therefore pip trying to build the package from the source distribution, but failing.

Making scikit-learn work with new versions of dependencies, including python is not trivial, and there are no guarantees for the package to work correctly even if `pip` somehow magically manages to build from the sdist.

I propose we set the higher limit of our dependencies to whatever's released at the time of the release, so that when somebody tries to do `pip install scikit-learn==1.4` in 2-3 years from now, it either fails or downgrades the dependencies to the right ones. The same thing for supporting versions of python.

We can also stop putting sdist out there. If the users want to build the project, they probably can build it from github.

cc @scikit-learn/core-devs

COMMENT:
Personally I am not a huge fan, I kind of see it as overconstraining the dependencies graph in a defensive manner. This may help in some cases but make the situation worse in others.

Here is [one case](https://github.com/jupyterlite/jupyterlite-sphinx/issues/83#issuecomment-1415113159) in a JupyterLite context (where they like upper-bound pinning for dependencies).

In a scikit-learn context this would be something like this:
- say we release scikit-learn 1.4 and we pin scipy upper-bound to `<1.13`
- there is a new scipy 1.13 release
- `pip install scipy==1.13 scikit-learn` will install scikit-learn 1.3 since it does not have upper-bound pinning, which is even less likely to be compatible with the new scipy version than scikit-learn 1.4

To avoid this kind of thing we would need to have done upper-bound pinning from the start ...

Labels: RFC