Talip7/scikit-learn-issues-multilabel huggingface dataset:
"Improve documentation on FeatureUnion behavior with polars DataFrame output causing duplicate column names"

### Describe the issue linked to the documentation

The current documentation for FeatureUnion describes its behavior with pandas DataFrames but does not mention how it behaves when used with polars DataFrames. Specifically, FeatureUnion concatenates outputs of its transformers before the set_output wrapper renames columns based on get_feature_names_out. This works fine with pandas but causes issues with polars since polars does not allow creating a DataFrame with duplicate column names. This leads to errors when using FeatureUnion with polars outputs.

### Suggest a potential alternative/fix

The documentation should mention the behavior difference when using FeatureUnion with polars DataFrames, specifically that concatenation occurs before column renaming. This can cause duplicate column names errors in polars.

It would be helpful to add a warning or note about this limitation, and suggest possible workarounds, such as manually renaming columns or converting to pandas DataFrame before using FeatureUnion.

Including a minimal example demonstrating the issue and how to avoid it would further improve clarity for users.

COMMENT:
The `verbose_feature_names_out` already has the necessary information .

Labels: Documentation, Needs Triage
RFC Deprecate FeatureUnion and make_union

Unless I'm missing something, to me `FeatureUnion` is just a `ColumnTransformer` where all transformers are applied to all features. So it's just a special case of `ColumnTransformer`.

```py
import pandas as pd
from sklearn.pipeline import FeatureUnion
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler

df = pd.DataFrame({"a": [1, 2, 3, 4], "b": [1, 2, 3, 4]})

fu = FeatureUnion([("std_1", StandardScaler()), ("std_2", StandardScaler())])
fu.set_output(transform="pandas")
print(fu.fit_transform(df))
   std_1__a  std_1__b  std_2__a  std_2__b
0 -1.341641 -1.341641 -1.341641 -1.341641
1 -0.447214 -0.447214 -0.447214 -0.447214
2  0.447214  0.447214  0.447214  0.447214
3  1.341641  1.341641  1.341641  1.341641

all_cols = slice(None)
ct = ColumnTransformer([("std_1", StandardScaler(), all_cols), ("std_2", StandardScaler(), all_cols)])
ct.set_output(transform="pandas")
print(ct.fit_transform(df))
   std_1__a  std_1__b  std_2__a  std_2__b
0 -1.341641 -1.341641 -1.341641 -1.341641
1 -0.447214 -0.447214 -0.447214 -0.447214
2  0.447214  0.447214  0.447214  0.447214
3  1.341641  1.341641  1.341641  1.341641
```

In addition, the parameters of `FeatureUnion` is a subset of the parameters of `ColumnTransformer`, so I don't see anything that one would be able to do with `FeatureUnion` but not with `ColumnTransformer`.

From a maintenance view, it duplicates the burden because they share almost no code and it's common that they suffer from the same bugs and  fixes have to be repeated in both classes. And usually one or the other keeps the bug for a while because we only implement a fix for one and forget about the other. In general the forgotten one is `FeatureUnion` btw :) (e.g. the latest one https://github.com/scikit-learn/scikit-learn/issues/32104 for `FeatureUnion` that was detected and fixed a while ago for `ColumnTransformer` https://github.com/scikit-learn/scikit-learn/issues/28260)

Finally, `FeatureUnion` has unresolved long standing issues like not cloning the transformers before fitting and modifying init parameters during fit, meaning that it does not follow our Transformer API.

Considering all that, I propose to deprecate `FeatureUnion`.

ping @scikit-learn/core-devs, did I miss something obvious ? what's your opinion about that ?

COMMENT:
I never used `FeatureUnion` (in production) and as long as nothing sever is overlooked, I am +1 for its deprecation.

(I am convinced that cleaning up the API will really help future maintainers!)

Labels: API, RFC, module:compose, module:pipeline
RFC Deprecate FeatureUnion and make_union

Unless I'm missing something, to me `FeatureUnion` is just a `ColumnTransformer` where all transformers are applied to all features. So it's just a special case of `ColumnTransformer`.

```py
import pandas as pd
from sklearn.pipeline import FeatureUnion
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler

df = pd.DataFrame({"a": [1, 2, 3, 4], "b": [1, 2, 3, 4]})

fu = FeatureUnion([("std_1", StandardScaler()), ("std_2", StandardScaler())])
fu.set_output(transform="pandas")
print(fu.fit_transform(df))
   std_1__a  std_1__b  std_2__a  std_2__b
0 -1.341641 -1.341641 -1.341641 -1.341641
1 -0.447214 -0.447214 -0.447214 -0.447214
2  0.447214  0.447214  0.447214  0.447214
3  1.341641  1.341641  1.341641  1.341641

all_cols = slice(None)
ct = ColumnTransformer([("std_1", StandardScaler(), all_cols), ("std_2", StandardScaler(), all_cols)])
ct.set_output(transform="pandas")
print(ct.fit_transform(df))
   std_1__a  std_1__b  std_2__a  std_2__b
0 -1.341641 -1.341641 -1.341641 -1.341641
1 -0.447214 -0.447214 -0.447214 -0.447214
2  0.447214  0.447214  0.447214  0.447214
3  1.341641  1.341641  1.341641  1.341641
```

In addition, the parameters of `FeatureUnion` is a subset of the parameters of `ColumnTransformer`, so I don't see anything that one would be able to do with `FeatureUnion` but not with `ColumnTransformer`.

From a maintenance view, it duplicates the burden because they share almost no code and it's common that they suffer from the same bugs and  fixes have to be repeated in both classes. And usually one or the other keeps the bug for a while because we only implement a fix for one and forget about the other. In general the forgotten one is `FeatureUnion` btw :) (e.g. the latest one https://github.com/scikit-learn/scikit-learn/issues/32104 for `FeatureUnion` that was detected and fixed a while ago for `ColumnTransformer` https://github.com/scikit-learn/scikit-learn/issues/28260)

Finally, `FeatureUnion` has unresolved long standing issues like not cloning the transformers before fitting and modifying init parameters during fit, meaning that it does not follow our Transformer API.

Considering all that, I propose to deprecate `FeatureUnion`.

ping @scikit-learn/core-devs, did I miss something obvious ? what's your opinion about that ?

COMMENT:
I'm okay with deprecating, but only actually removing it in 2.0. Given how long `FeatureUnion` has been around, I think this is a major breaking change.

As for cloning, we had a similar discussion with `Pipeline` for not following the transformer API: https://github.com/scikit-learn/scikit-learn/issues/8157 but did not get to it. Some may consider the non-cloning behavior a "feature".

In recent memory, an interesting use case for `FeatureUnion` is https://github.com/koaning/scikit-playtime which has it's own selectors. @koaning Do you recall why you preferred `FeatureUnion` over `ColumnTransformer`?

Labels: API, RFC, module:compose, module:pipeline
RFC Deprecate FeatureUnion and make_union

Unless I'm missing something, to me `FeatureUnion` is just a `ColumnTransformer` where all transformers are applied to all features. So it's just a special case of `ColumnTransformer`.

```py
import pandas as pd
from sklearn.pipeline import FeatureUnion
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler

df = pd.DataFrame({"a": [1, 2, 3, 4], "b": [1, 2, 3, 4]})

fu = FeatureUnion([("std_1", StandardScaler()), ("std_2", StandardScaler())])
fu.set_output(transform="pandas")
print(fu.fit_transform(df))
   std_1__a  std_1__b  std_2__a  std_2__b
0 -1.341641 -1.341641 -1.341641 -1.341641
1 -0.447214 -0.447214 -0.447214 -0.447214
2  0.447214  0.447214  0.447214  0.447214
3  1.341641  1.341641  1.341641  1.341641

all_cols = slice(None)
ct = ColumnTransformer([("std_1", StandardScaler(), all_cols), ("std_2", StandardScaler(), all_cols)])
ct.set_output(transform="pandas")
print(ct.fit_transform(df))
   std_1__a  std_1__b  std_2__a  std_2__b
0 -1.341641 -1.341641 -1.341641 -1.341641
1 -0.447214 -0.447214 -0.447214 -0.447214
2  0.447214  0.447214  0.447214  0.447214
3  1.341641  1.341641  1.341641  1.341641
```

In addition, the parameters of `FeatureUnion` is a subset of the parameters of `ColumnTransformer`, so I don't see anything that one would be able to do with `FeatureUnion` but not with `ColumnTransformer`.

From a maintenance view, it duplicates the burden because they share almost no code and it's common that they suffer from the same bugs and  fixes have to be repeated in both classes. And usually one or the other keeps the bug for a while because we only implement a fix for one and forget about the other. In general the forgotten one is `FeatureUnion` btw :) (e.g. the latest one https://github.com/scikit-learn/scikit-learn/issues/32104 for `FeatureUnion` that was detected and fixed a while ago for `ColumnTransformer` https://github.com/scikit-learn/scikit-learn/issues/28260)

Finally, `FeatureUnion` has unresolved long standing issues like not cloning the transformers before fitting and modifying init parameters during fit, meaning that it does not follow our Transformer API.

Considering all that, I propose to deprecate `FeatureUnion`.

ping @scikit-learn/core-devs, did I miss something obvious ? what's your opinion about that ?

COMMENT:
If my code is more verbose but easier to memorise/reason about it might still be a win. To take the extreme argument why not call rename `StandardScaler` to `StdScaler`? That's less verbose too. The argument here is slightly silly, I know, but it serves to point out that "being less verbose" is probably not the end-goal one should have in mind.

Labels: API, RFC, module:compose, module:pipeline
RFC Deprecate FeatureUnion and make_union

Unless I'm missing something, to me `FeatureUnion` is just a `ColumnTransformer` where all transformers are applied to all features. So it's just a special case of `ColumnTransformer`.

```py
import pandas as pd
from sklearn.pipeline import FeatureUnion
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler

df = pd.DataFrame({"a": [1, 2, 3, 4], "b": [1, 2, 3, 4]})

fu = FeatureUnion([("std_1", StandardScaler()), ("std_2", StandardScaler())])
fu.set_output(transform="pandas")
print(fu.fit_transform(df))
   std_1__a  std_1__b  std_2__a  std_2__b
0 -1.341641 -1.341641 -1.341641 -1.341641
1 -0.447214 -0.447214 -0.447214 -0.447214
2  0.447214  0.447214  0.447214  0.447214
3  1.341641  1.341641  1.341641  1.341641

all_cols = slice(None)
ct = ColumnTransformer([("std_1", StandardScaler(), all_cols), ("std_2", StandardScaler(), all_cols)])
ct.set_output(transform="pandas")
print(ct.fit_transform(df))
   std_1__a  std_1__b  std_2__a  std_2__b
0 -1.341641 -1.341641 -1.341641 -1.341641
1 -0.447214 -0.447214 -0.447214 -0.447214
2  0.447214  0.447214  0.447214  0.447214
3  1.341641  1.341641  1.341641  1.341641
```

In addition, the parameters of `FeatureUnion` is a subset of the parameters of `ColumnTransformer`, so I don't see anything that one would be able to do with `FeatureUnion` but not with `ColumnTransformer`.

From a maintenance view, it duplicates the burden because they share almost no code and it's common that they suffer from the same bugs and  fixes have to be repeated in both classes. And usually one or the other keeps the bug for a while because we only implement a fix for one and forget about the other. In general the forgotten one is `FeatureUnion` btw :) (e.g. the latest one https://github.com/scikit-learn/scikit-learn/issues/32104 for `FeatureUnion` that was detected and fixed a while ago for `ColumnTransformer` https://github.com/scikit-learn/scikit-learn/issues/28260)

Finally, `FeatureUnion` has unresolved long standing issues like not cloning the transformers before fitting and modifying init parameters during fit, meaning that it does not follow our Transformer API.

Considering all that, I propose to deprecate `FeatureUnion`.

ping @scikit-learn/core-devs, did I miss something obvious ? what's your opinion about that ?

COMMENT:
And in @koaning 's book, I guess one can even do this:

```py
make_union(
    ColumnSelector(["a", "b"]) | StandardScaler(),
    ColumnSelector(["c", "d"]) | OneHotEncoder(),
)
```

which I quite like 😁

Labels: API, RFC, module:compose, module:pipeline
RFC Deprecate FeatureUnion and make_union

Unless I'm missing something, to me `FeatureUnion` is just a `ColumnTransformer` where all transformers are applied to all features. So it's just a special case of `ColumnTransformer`.

```py
import pandas as pd
from sklearn.pipeline import FeatureUnion
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler

df = pd.DataFrame({"a": [1, 2, 3, 4], "b": [1, 2, 3, 4]})

fu = FeatureUnion([("std_1", StandardScaler()), ("std_2", StandardScaler())])
fu.set_output(transform="pandas")
print(fu.fit_transform(df))
   std_1__a  std_1__b  std_2__a  std_2__b
0 -1.341641 -1.341641 -1.341641 -1.341641
1 -0.447214 -0.447214 -0.447214 -0.447214
2  0.447214  0.447214  0.447214  0.447214
3  1.341641  1.341641  1.341641  1.341641

all_cols = slice(None)
ct = ColumnTransformer([("std_1", StandardScaler(), all_cols), ("std_2", StandardScaler(), all_cols)])
ct.set_output(transform="pandas")
print(ct.fit_transform(df))
   std_1__a  std_1__b  std_2__a  std_2__b
0 -1.341641 -1.341641 -1.341641 -1.341641
1 -0.447214 -0.447214 -0.447214 -0.447214
2  0.447214  0.447214  0.447214  0.447214
3  1.341641  1.341641  1.341641  1.341641
```

In addition, the parameters of `FeatureUnion` is a subset of the parameters of `ColumnTransformer`, so I don't see anything that one would be able to do with `FeatureUnion` but not with `ColumnTransformer`.

From a maintenance view, it duplicates the burden because they share almost no code and it's common that they suffer from the same bugs and  fixes have to be repeated in both classes. And usually one or the other keeps the bug for a while because we only implement a fix for one and forget about the other. In general the forgotten one is `FeatureUnion` btw :) (e.g. the latest one https://github.com/scikit-learn/scikit-learn/issues/32104 for `FeatureUnion` that was detected and fixed a while ago for `ColumnTransformer` https://github.com/scikit-learn/scikit-learn/issues/28260)

Finally, `FeatureUnion` has unresolved long standing issues like not cloning the transformers before fitting and modifying init parameters during fit, meaning that it does not follow our Transformer API.

Considering all that, I propose to deprecate `FeatureUnion`.

ping @scikit-learn/core-devs, did I miss something obvious ? what's your opinion about that ?

COMMENT:
Well I find the columntransformer snippet a lot more easy to read than the featureunion one. But that's subjective and not my main point. In the end my goal is to find a way to avoid the maintenance cost of having 2 objects doing the same thing (a subset of what the other do to be precise) but sharing no code.

Labels: API, RFC, module:compose, module:pipeline
RFC Deprecate FeatureUnion and make_union

Unless I'm missing something, to me `FeatureUnion` is just a `ColumnTransformer` where all transformers are applied to all features. So it's just a special case of `ColumnTransformer`.

```py
import pandas as pd
from sklearn.pipeline import FeatureUnion
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler

df = pd.DataFrame({"a": [1, 2, 3, 4], "b": [1, 2, 3, 4]})

fu = FeatureUnion([("std_1", StandardScaler()), ("std_2", StandardScaler())])
fu.set_output(transform="pandas")
print(fu.fit_transform(df))
   std_1__a  std_1__b  std_2__a  std_2__b
0 -1.341641 -1.341641 -1.341641 -1.341641
1 -0.447214 -0.447214 -0.447214 -0.447214
2  0.447214  0.447214  0.447214  0.447214
3  1.341641  1.341641  1.341641  1.341641

all_cols = slice(None)
ct = ColumnTransformer([("std_1", StandardScaler(), all_cols), ("std_2", StandardScaler(), all_cols)])
ct.set_output(transform="pandas")
print(ct.fit_transform(df))
   std_1__a  std_1__b  std_2__a  std_2__b
0 -1.341641 -1.341641 -1.341641 -1.341641
1 -0.447214 -0.447214 -0.447214 -0.447214
2  0.447214  0.447214  0.447214  0.447214
3  1.341641  1.341641  1.341641  1.341641
```

In addition, the parameters of `FeatureUnion` is a subset of the parameters of `ColumnTransformer`, so I don't see anything that one would be able to do with `FeatureUnion` but not with `ColumnTransformer`.

From a maintenance view, it duplicates the burden because they share almost no code and it's common that they suffer from the same bugs and  fixes have to be repeated in both classes. And usually one or the other keeps the bug for a while because we only implement a fix for one and forget about the other. In general the forgotten one is `FeatureUnion` btw :) (e.g. the latest one https://github.com/scikit-learn/scikit-learn/issues/32104 for `FeatureUnion` that was detected and fixed a while ago for `ColumnTransformer` https://github.com/scikit-learn/scikit-learn/issues/28260)

Finally, `FeatureUnion` has unresolved long standing issues like not cloning the transformers before fitting and modifying init parameters during fit, meaning that it does not follow our Transformer API.

Considering all that, I propose to deprecate `FeatureUnion`.

ping @scikit-learn/core-devs, did I miss something obvious ? what's your opinion about that ?

COMMENT:
I'd be okay with a FIX pr to make `FeatureUnion` call `ColumnTransformer` though.

Labels: API, RFC, module:compose, module:pipeline
RFC Deprecate FeatureUnion and make_union

Unless I'm missing something, to me `FeatureUnion` is just a `ColumnTransformer` where all transformers are applied to all features. So it's just a special case of `ColumnTransformer`.

```py
import pandas as pd
from sklearn.pipeline import FeatureUnion
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler

df = pd.DataFrame({"a": [1, 2, 3, 4], "b": [1, 2, 3, 4]})

fu = FeatureUnion([("std_1", StandardScaler()), ("std_2", StandardScaler())])
fu.set_output(transform="pandas")
print(fu.fit_transform(df))
   std_1__a  std_1__b  std_2__a  std_2__b
0 -1.341641 -1.341641 -1.341641 -1.341641
1 -0.447214 -0.447214 -0.447214 -0.447214
2  0.447214  0.447214  0.447214  0.447214
3  1.341641  1.341641  1.341641  1.341641

all_cols = slice(None)
ct = ColumnTransformer([("std_1", StandardScaler(), all_cols), ("std_2", StandardScaler(), all_cols)])
ct.set_output(transform="pandas")
print(ct.fit_transform(df))
   std_1__a  std_1__b  std_2__a  std_2__b
0 -1.341641 -1.341641 -1.341641 -1.341641
1 -0.447214 -0.447214 -0.447214 -0.447214
2  0.447214  0.447214  0.447214  0.447214
3  1.341641  1.341641  1.341641  1.341641
```

In addition, the parameters of `FeatureUnion` is a subset of the parameters of `ColumnTransformer`, so I don't see anything that one would be able to do with `FeatureUnion` but not with `ColumnTransformer`.

From a maintenance view, it duplicates the burden because they share almost no code and it's common that they suffer from the same bugs and  fixes have to be repeated in both classes. And usually one or the other keeps the bug for a while because we only implement a fix for one and forget about the other. In general the forgotten one is `FeatureUnion` btw :) (e.g. the latest one https://github.com/scikit-learn/scikit-learn/issues/32104 for `FeatureUnion` that was detected and fixed a while ago for `ColumnTransformer` https://github.com/scikit-learn/scikit-learn/issues/28260)

Finally, `FeatureUnion` has unresolved long standing issues like not cloning the transformers before fitting and modifying init parameters during fit, meaning that it does not follow our Transformer API.

Considering all that, I propose to deprecate `FeatureUnion`.

ping @scikit-learn/core-devs, did I miss something obvious ? what's your opinion about that ?

COMMENT:
> I'd be okay with a FIX pr to make `FeatureUnion` call `FeatureUnion` though.

I am a big fan of recursion, but only recursion of the non-infinite nature ;)

Labels: API, RFC, module:compose, module:pipeline
RFC Deprecate FeatureUnion and make_union

Unless I'm missing something, to me `FeatureUnion` is just a `ColumnTransformer` where all transformers are applied to all features. So it's just a special case of `ColumnTransformer`.

```py
import pandas as pd
from sklearn.pipeline import FeatureUnion
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler

df = pd.DataFrame({"a": [1, 2, 3, 4], "b": [1, 2, 3, 4]})

fu = FeatureUnion([("std_1", StandardScaler()), ("std_2", StandardScaler())])
fu.set_output(transform="pandas")
print(fu.fit_transform(df))
   std_1__a  std_1__b  std_2__a  std_2__b
0 -1.341641 -1.341641 -1.341641 -1.341641
1 -0.447214 -0.447214 -0.447214 -0.447214
2  0.447214  0.447214  0.447214  0.447214
3  1.341641  1.341641  1.341641  1.341641

all_cols = slice(None)
ct = ColumnTransformer([("std_1", StandardScaler(), all_cols), ("std_2", StandardScaler(), all_cols)])
ct.set_output(transform="pandas")
print(ct.fit_transform(df))
   std_1__a  std_1__b  std_2__a  std_2__b
0 -1.341641 -1.341641 -1.341641 -1.341641
1 -0.447214 -0.447214 -0.447214 -0.447214
2  0.447214  0.447214  0.447214  0.447214
3  1.341641  1.341641  1.341641  1.341641
```

In addition, the parameters of `FeatureUnion` is a subset of the parameters of `ColumnTransformer`, so I don't see anything that one would be able to do with `FeatureUnion` but not with `ColumnTransformer`.

From a maintenance view, it duplicates the burden because they share almost no code and it's common that they suffer from the same bugs and  fixes have to be repeated in both classes. And usually one or the other keeps the bug for a while because we only implement a fix for one and forget about the other. In general the forgotten one is `FeatureUnion` btw :) (e.g. the latest one https://github.com/scikit-learn/scikit-learn/issues/32104 for `FeatureUnion` that was detected and fixed a while ago for `ColumnTransformer` https://github.com/scikit-learn/scikit-learn/issues/28260)

Finally, `FeatureUnion` has unresolved long standing issues like not cloning the transformers before fitting and modifying init parameters during fit, meaning that it does not follow our Transformer API.

Considering all that, I propose to deprecate `FeatureUnion`.

ping @scikit-learn/core-devs, did I miss something obvious ? what's your opinion about that ?

COMMENT:
> And in @koaning 's book, I guess one can even do this:

I would go a step further and say, you can do this: 

`scale("a", "b") + one_hot("c", "d,)`

Not implemented yet, haven't touched the playtime project in months, but still...

Labels: API, RFC, module:compose, module:pipeline