Repository: scikit-learn/scikit-learn
Pull request: ENH Adds FeatureUnion.__getitem__ to access transformers
<!--
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
<!--
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
-->
Closes #24906


#### What does this implement/fix? Explain your changes.
This PR adds `__getitem__` to `FeatureUnion` to access transformers.

<!--
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:
Add __get_item__() to FeatureUnion 
### Describe the bug

When debugging larger pipelines for a consistent syntax we’d like to reference individual sub components of `FeatureUnion` by index, slice or tuple name in the same way as the `Pipeline`. This way user does not need to know the types of the aggregate components.


### Steps/Code to Reproduce

for example:
```python
model = Pipeline(steps=[
   (“step1”, Component()),
   (“step2” FeatureUnion(transformer_list=[
       (“step2a”, Component()),
       (“step2b”, Component()),
   ])),
])
```

### Expected Results

would be accessed as:
```python
comp = model[“step2”][“step2a”]
```

### Actual Results

rather than accessing as:
```python
comp = model[“step2”].transformer_list[0][1]  # transformer_list is a implementation detail to FeatureUnion only.
```

### Versions

```shell
Latest
```

Hints from issue discussion:
Looks like a nice UX improvement to me.
Would be similar to `Pipeline`, would be nice to have.

Commit message: DOC Adds whats new number

Patch to review:
diff --git a/doc/whats_new/v1.3.rst b/doc/whats_new/v1.3.rst
index 6012ac7a336a4..a7838f7c2ec8f 100644
--- a/doc/whats_new/v1.3.rst
+++ b/doc/whats_new/v1.3.rst
@@ -36,6 +36,12 @@ Changelog
     :pr:`123456` by :user:`Joe Bloggs <joeongithub>`.
     where 123456 is the *pull request* number, not the issue number.
 
+:mod:`sklearn.pipeline`
+.......................
+- |Feature| :class:`pipeline.FeatureUnion` can now use indexing notation (e.g.
+  `feature_union["scalar"]`) to access transformers by name. :pr:`25093` by
+  `Thomas Fan`_.
+
 Code and Documentation Contributors
 -----------------------------------
 
diff --git a/sklearn/pipeline.py b/sklearn/pipeline.py
index b3a4d180a4c68..5f13f1316ff64 100644
--- a/sklearn/pipeline.py
+++ b/sklearn/pipeline.py
@@ -1286,6 +1286,10 @@ def _sk_visual_block_(self):
         names, transformers = zip(*self.transformer_list)
         return _VisualBlock("parallel", transformers, names=names)
 
+    def __getitem__(self, name):
+        """Return transformer with name."""
+        return self.named_transformers[name]
+
 
 def make_union(*transformers, n_jobs=None, verbose=False):
     """Construct a FeatureUnion from the given transformers.
diff --git a/sklearn/tests/test_pipeline.py b/sklearn/tests/test_pipeline.py
index 07e3f7170efdf..423c3260a4fd8 100644
--- a/sklearn/tests/test_pipeline.py
+++ b/sklearn/tests/test_pipeline.py
@@ -1658,3 +1658,21 @@ def test_feature_union_set_output():
     assert isinstance(X_trans, pd.DataFrame)
     assert_array_equal(X_trans.columns, union.get_feature_names_out())
     assert_array_equal(X_trans.index, X_test.index)
+
+
+def test_feature_union_getitem():
+    """Check FeatureUnion.__getitem__ returns expected results."""
+    scalar = StandardScaler()
+    pca = PCA()
+    union = FeatureUnion(
+        [
+            ("scalar", scalar),
+            ("pca", pca),
+            ("pass", "passthrough"),
+            ("drop_me", "drop"),
+        ]
+    )
+    assert union["scalar"] is scalar
+    assert union["pca"] is pca
+    assert union["scalar"] is scalar
+    assert union["pass"] == "passthrough"


Code review comments:
diff_hunk: @@ -1658,3 +1658,21 @@ def test_feature_union_set_output():
     assert isinstance(X_trans, pd.DataFrame)
     assert_array_equal(X_trans.columns, union.get_feature_names_out())
     assert_array_equal(X_trans.index, X_test.index)
+
+
+def test_feature_union_getitem():
+    """Check FeatureUnion.__getitem__ returns expected results."""
+    scalar = StandardScaler()
+    pca = PCA()
+    union = FeatureUnion(
+        [
+            ("scalar", scalar),
+            ("pca", pca),
+            ("pass", "passthrough"),
+            ("drop_me", "drop"),
+        ]
+    )
+    assert union["scalar"] is scalar
+    assert union["pca"] is pca
+    assert union["scalar"] is scalar
+    assert union["pass"] == "passthrough"
path: sklearn/tests/test_pipeline.py
text: @user1:
```suggestion
    assert union["pass"] == "passthrough"
    assert union["drop_me"] == "drop"
```