Repository: scikit-learn/scikit-learn
Pull request: FIX Fix ColumnTransformer.get_feature_names_out with slices
<!--
Thanks for contributing a pull request! Please ensure you have taken a look at
the contribution guidelines: https://github.com/scikit-learn/scikit-learn/blob/main/CONTRIBUTING.md
-->

#### Reference Issues/PRs
Fixes #22774
<!--
Example: Fixes #1234. See also #3456.
Please use keywords (e.g., Fixes) to create link to the issues or pull requests
you resolved, so that they will automatically be closed when your pull request
is merged. See https://github.com/blog/1506-closing-issues-via-pull-requests
-->


#### What does this implement/fix? Explain your changes.
Currently, ColumnTransformer's get_feature_names_out does not work when the columns are specified as slices. This fix makes get_feature_names_out work properly with slices as well.

#### Any other comments?


<!--
Please be aware that we are a loose team of volunteers so patience is
necessary; assistance handling other issues is very welcome. We value
all user contributions, no matter how minor they are. If we are slow to
review, either the pull request needs some benchmarking, tinkering,
convincing, etc. or more likely the reviewers are simply busy. In either
case, we ask for your understanding during the review process.
For more information, see our FAQ on this topic:
http://scikit-learn.org/dev/faq.html#why-is-my-pull-request-not-getting-any-attention.

Thanks for contributing!
-->


Problem statement:
ColumnTransformer's get_feature_names_out does not work properly with slices
### Describe the bug

Slices are a supported for selecting columns in `ColumnTransformer`. Also, `get_column_names_out` is supported to label generated columns. But get_column_names_out does not work with slices.

### Steps/Code to Reproduce

```python
import numpy as np
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import Normalizer as _Normalizer
from sklearn.base import _OneToOneFeatureMixin


class Normalizer(_OneToOneFeatureMixin, _Normalizer):
    pass

ct = ColumnTransformer([("norm1", Normalizer(norm='l1'), [0, 1]),
                        ("norm2", Normalizer(norm='l1'), slice(2, 4))],
                       verbose_feature_names_out=False)
X = np.array([[0., 1., 2., 2.], [1., 1., 0., 1.]])
ct.fit_transform(X)

#  Expecting array(['x0', 'x1', 'x2', 'x3'], dtype=object)
ct.get_feature_names_out()  # get TypeError

df = pd.DataFrame(X, columns=['c1', 'c2', 'c3', 'c4'])
ct.fit_transform(df)

# Expecting array(['c0', 'c1', 'c2', 'c3'], dtype=object)
ct.get_feature_names_out()  # get TypeError
```

### Expected Results

Expecting array(['x0', 'x1', 'x2', 'x3'], dtype=object)

and 

Expecting array(['c0', 'c1', 'c2', 'c3'], dtype=object)

### Actual Results

Produces error

### Versions

```shell
System:
    python: 3.8.10 (default, Nov 26 2021, 20:14:08)  [GCC 9.3.0]
executable: /home/popos/code/sklearn-transformer-extensions/.venv/bin/python
   machine: Linux-5.16.11-76051611-generic-x86_64-with-glibc2.29

Python dependencies:
          pip: 22.0.4
   setuptools: 60.9.3
      sklearn: 1.0.2
        numpy: 1.22.3
        scipy: 1.6.1
       Cython: None
       pandas: 1.4.1
   matplotlib: None
       joblib: 1.1.0
threadpoolctl: 3.1.0

Built with OpenMP: True
```

Hints from issue discussion:

Commit message: Added note in v1.1 whats_new and tests for new functionality

Patch to review:
diff --git a/doc/whats_new/v1.1.rst b/doc/whats_new/v1.1.rst
index 5030ed8faa4a1..0d076a4e89d34 100644
--- a/doc/whats_new/v1.1.rst
+++ b/doc/whats_new/v1.1.rst
@@ -182,6 +182,11 @@ Changelog
   `__init__` and `set_params` methods.
   :pr:`22537` by :user:`iofall <iofall>` and :user:`Arisa Y. <arisayosh>`.
 
+- |Fix| :term:`get_feature_names_out` functionality in
+  :class:`compose.ColumnTransformer` was broken when columns were specified
+  using `slice`. This is fixed in :pr:`22775` by :user:`randomgeek78
+  <randomgeek78>`
+
 :mod:`sklearn.cross_decomposition`
 ..................................
 
diff --git a/sklearn/compose/_column_transformer.py b/sklearn/compose/_column_transformer.py
index 9b3e8fa0d1dab..16abe8a47dd5f 100644
--- a/sklearn/compose/_column_transformer.py
+++ b/sklearn/compose/_column_transformer.py
@@ -453,8 +453,9 @@ def _get_feature_name_out_for_transformer(
                 f"Transformer {name} (type {type(trans).__name__}) does "
                 "not provide get_feature_names_out."
             )
-        if isinstance(column, Iterable) and not all(
-            isinstance(col, str) for col in column
+        if isinstance(column, slice) or (
+            isinstance(column, Iterable)
+            and not all(isinstance(col, str) for col in column)
         ):
             column = _safe_indexing(feature_names_in, column)
         return trans.get_feature_names_out(column)
diff --git a/sklearn/compose/tests/test_column_transformer.py b/sklearn/compose/tests/test_column_transformer.py
index e7f2915aa4232..32a2eba511f45 100644
--- a/sklearn/compose/tests/test_column_transformer.py
+++ b/sklearn/compose/tests/test_column_transformer.py
@@ -1757,6 +1757,30 @@ def get_feature_names_out(self, input_features=None):
             "drop",
             [],
         ),
+        (
+            [
+                ("bycol1", TransWithNames(), slice(1, 2)),
+                ("bycol2", "drop", ["d"]),
+            ],
+            "passthrough",
+            ["bycol1__b", "remainder__a", "remainder__c"],
+        ),
+        (
+            [
+                ("bycol1", TransWithNames(), ["b"]),
+                ("bycol2", "drop", slice(3, 4)),
+            ],
+            "passthrough",
+            ["bycol1__b", "remainder__a", "remainder__c"],
+        ),
+        (
+            [
+                ("bycol1", TransWithNames(), ["d", "c"]),
+                ("bycol2", "passthrough", slice(3, 4)),
+            ],
+            "passthrough",
+            ["bycol1__d", "bycol1__c", "bycol2__d", "remainder__a", "remainder__b"],
+        ),
     ],
 )
 def test_verbose_feature_names_out_true(transformers, remainder, expected_names):
@@ -1832,6 +1856,30 @@ def test_verbose_feature_names_out_true(transformers, remainder, expected_names)
             "drop",
             [],
         ),
+        (
+            [
+                ("bycol1", TransWithNames(), slice(1, 2)),
+                ("bycol2", "drop", ["d"]),
+            ],
+            "passthrough",
+            ["b", "a", "c"],
+        ),
+        (
+            [
+                ("bycol1", TransWithNames(), ["b"]),
+                ("bycol2", "drop", slice(3, 4)),
+            ],
+            "passthrough",
+            ["b", "a", "c"],
+        ),
+        (
+            [
+                ("bycol1", TransWithNames(), ["d", "c"]),
+                ("bycol2", "passthrough", slice(0, 1)),
+            ],
+            "drop",
+            ["d", "c", "a"],
+        ),
     ],
 )
 def test_verbose_feature_names_out_false(transformers, remainder, expected_names):
@@ -1920,6 +1968,24 @@ def test_verbose_feature_names_out_false(transformers, remainder, expected_names
             "passthrough",
             "['pca0', 'pca1', 'pca2', 'pca3', 'pca4', ...]",
         ),
+        (
+            [
+                ("bycol1", TransWithNames(["a", "b"]), slice(1, 2)),
+                ("bycol2", "passthrough", ["a"]),
+                ("bycol3", TransWithNames(["b"]), ["c"]),
+            ],
+            "passthrough",
+            "['a', 'b']",
+        ),
+        (
+            [
+                ("bycol1", TransWithNames(["a", "b"]), ["b"]),
+                ("bycol2", "passthrough", slice(0, 1)),
+                ("bycol3", TransWithNames(["b"]), ["c"]),
+            ],
+            "passthrough",
+            "['a', 'b']",
+        ),
     ],
 )
 def test_verbose_feature_names_out_false_errors(


Code review comments:
diff_hunk: @@ -1832,6 +1856,30 @@ def test_verbose_feature_names_out_true(transformers, remainder, expected_names)
             "drop",
             [],
         ),
+        (
+            [
+                ("bycol1", TransWithNames(), slice(1, 2)),
+                ("bycol2", "drop", ["d"]),
+            ],
+            "passthrough",
+            ["b", "a", "c"],
+        ),
+        (
+            [
+                ("bycol1", TransWithNames(), ["b"]),
+                ("bycol2", "drop", slice(3, 4)),
+            ],
+            "passthrough",
+            ["b", "a", "c"],
+        ),
+        (
+            [
+                ("bycol1", TransWithNames(), ["d", "c"]),
+                ("bycol2", "passthrough", slice(0, 1)),
+            ],
+            "drop",
+            ["d", "c", "a"],
path: sklearn/compose/tests/test_column_transformer.py
text: @user1:
Same here regarding slicing with more than one element:

```suggestion
            [
                ("bycol1", TransWithNames(), ["d", "c"]),
                ("bycol2", "passthrough", slice(0, 2)),
            ],
            "drop",
            ["d", "c", "a", "b"],
```

@author:
@user2 @user1 Thanks for your feedback. I have pushed another commit incorporating your suggestions. Tx