TransformerChain

### Describe the workflow you want to enable

1. To be able to encapsulate extraction and encoding of features from a source feature
2. To be able to turn off features derived from a source feature easier than having to turn off the extraction and encoding stages of the pipeline.
3. To be able to see the outcome of chaining many transformers without having to use a dummy estimator at the latest step of the pipeline.

### Describe your proposed solution

To have a new special transformer called `TransformerChain` which basically differs from `Pipeline` in that **all** of its steps have to be transformers. It simply gets a sequence of transformers as its constructor input and each step `fit`s on the transformed input of the previous step.

### Describe alternatives you've considered, if relevant

_No response_

### Additional context

To set an example, suppose we have a feature of type `string`, of which we extract 3 separate features using a *custom transformer*. Then we want to encode these features using proper encoders.

Currently, we could extract the features with the combination of a `ColumnTransformer` and a *custom transformer* and then encode those extracted features with proper encoder at the next step of the pipeline. This workflow is depicted in the following figure:
![image](https://user-images.githubusercontent.com/22097587/202909568-b8da4e6e-ab02-4240-99bf-34553816ac3b.png)

As stated above, this workflow has the drawback in its design that the extraction and encoding of those derived features is scattered across two separate steps of the pipeline. Moreover if were to turn off those features, we have to turn off two pipeline steps.

Another solution for user is to encapsulate all the extraction and encoding in the *custom transformer* and take care of many things him/herself. This approach is depicted below. Besides user's having to take care of many things him/herself this approach does not make the most of the Scikit's compositionality and modularity.
![image](https://user-images.githubusercontent.com/22097587/202910439-bf73effe-b271-422a-8c5d-dcf19542c6ac.png)

The proposed solution is shown in the image below. 
![image](https://user-images.githubusercontent.com/22097587/202910611-94f905c8-d565-420d-b2e7-f450bc26826b.png)

COMMENT:
I think `Pipeline` is already doing what you want.

Let's generate some data, almost as shown in your example:

```python
import string
import numpy as np

rng = np.random.default_rng(0)
n_samples = 30
X_1 = rng.uniform(size=(n_samples, 1))
X_2 = rng.choice(["A", "B", "C"], size=(n_samples, 1))
random_strings = [
    "".join(rng.choice(list(string.ascii_uppercase)) for _ in range(3))
    for _ in range(10)
]
X_3 = rng.choice(random_strings, size=(n_samples, 1))

X = np.concatenate([X_1, X_2, X_3], axis=1, dtype=object)
```

Then, a processor as you showed in the last stage would be:

```python
from sklearn.compose import make_column_transformer
from sklearn.preprocessing import FunctionTransformer, OrdinalEncoder
from sklearn.pipeline import make_pipeline


def _custom_transform(X):
    return np.array([list(sample) for column in X_3 for sample in column])


preprocessor = make_column_transformer(
    (OrdinalEncoder(), [1]),
    (make_pipeline(FunctionTransformer(_custom_transform), OrdinalEncoder()), [2]),
    remainder="passthrough",
)
preprocessor.fit_transform(X)
```

So no need for using 2 `ColumnTransformer`s and instead use a `Pipeline` made of 2 transformers. There is no need for the pipeline to end with a classifier or regressor.

Closing this issue then. Feel free to comment if I miss something. I would reopen it then.

Labels: New Feature, Needs Triage