Repository: sktime/sktime

Issue:
[BUG] `FeatureUnion` raises error with transformers with primitive output
**Describe the bug**
Feature Union raises error with transformers with primitive output, i.e., when the output is not a series or a panel sktime raises an exception.

**To Reproduce**
```python
from sktime.datasets import load_gunpoint
from sktime.transformations.panel.rocket import Rocket
X_train, y_train = load_gunpoint(split="train", return_X_y=True)
union = Rocket() + Rocket()
X_train_transformed = union.fit_transform(X_train)
```

raises: 
```
No valid mtype could be identified for object of type <class 'pandas.core.frame.DataFrame'>. Errors returned are as follows, in format [mtype]: [error message] 
df-list: obj must be list of pd.DataFrame, found <class 'pandas.core.frame.DataFrame'>
numpy3D: obj must be a numpy.ndarray, found <class 'pandas.core.frame.DataFrame'>
pd-multiindex: obj must have a MultiIndex, found <class 'pandas.core.indexes.range.RangeIndex'>
nested_univ: obj All columns must be object, found <class 'pandas.core.frame.DataFrame'>
```

**Expected behavior**
I think the expected behavior should be to simply concatenate the primitives on axis=1. With `set_config(**{"output_conversion": "off"})` the transformation seems to work fine.

**Additional context**
Maybe this has something to do with this to-do in the init:
```python
# todo: check for transform-input, transform-output
#   for now, we assume it's always Series/Series or Series/Panel
#   but no error is currently raised
```

**Versions**
System:
    python: 3.9.16 | packaged by conda-forge | (main, Feb  1 2023, 21:42:20)  [Clang 14.0.6 ]
   machine: macOS-13.2.1-x86_64-i386-64bit

Python dependencies:
          pip: 23.2.1
       sktime: 0.27.0
      sklearn: 1.3.0
       skbase: 0.7.5
        numpy: 1.24.0
        scipy: 1.11.1
       pandas: 2.0.3
   matplotlib: 3.7.2
       joblib: 1.3.1
        numba: 0.57.1
  statsmodels: None
     pmdarima: None
statsforecast: None
      tsfresh: None
      tslearn: None
        torch: None
   tensorflow: None
tensorflow_probability: None



Discussion:
this might relate to recent changes introduced in https://github.com/sktime/sktime/pull/5351 - or not. Either way, should not be too difficult to fix by going through the output conversion cases.
seems like the `output_scitype` variable is `"Series"` here, incorrectly inferred
no, not actually related to #5351.

The diagnosis of @fspinna was accurate, it was the missing logic for setting the output tag correctly.

Resolving patch:
diff --git a/sktime/transformations/compose/_featureunion.py b/sktime/transformations/compose/_featureunion.py
--- a/sktime/transformations/compose/_featureunion.py
+++ b/sktime/transformations/compose/_featureunion.py
@@ -80,9 +80,10 @@ def __init__(
         flatten_transform_index=True,
     ):
         self.transformer_list = transformer_list
-        self.transformer_list_ = self._check_estimators(
+        transformer_list_ = self._check_estimators(
             transformer_list, cls_type=BaseTransformer
         )
+        self.transformer_list_ = transformer_list_
 
         self.n_jobs = n_jobs
         self.transformer_weights = transformer_weights
@@ -90,9 +91,11 @@ def __init__(
 
         super().__init__()
 
-        # todo: check for transform-input, transform-output
-        #   for now, we assume it's always Series/Series or Series/Panel
-        #   but no error is currently raised
+        t_outs = [t.get_tag("scitype:transform-output") for _, t in transformer_list_]
+        t_ins = [t.get_tag("scitype:transform-input") for _, t in transformer_list_]
+        # todo: error or special case handling if these are not all the same
+        self.set_tags(**{"scitype:transform-output": t_outs[0]})
+        self.set_tags(**{"scitype:transform-input": t_ins[0]})
 
         # abbreviate for readability
         ests = self.transformer_list_


Test patch:
diff --git a/sktime/transformations/tests/test_compose.py b/sktime/transformations/tests/test_compose.py
--- a/sktime/transformations/tests/test_compose.py
+++ b/sktime/transformations/tests/test_compose.py
@@ -8,7 +8,7 @@
 import pytest
 from sklearn.preprocessing import StandardScaler
 
-from sktime.datasets import load_airline
+from sktime.datasets import load_airline, load_unit_test
 from sktime.datatypes import get_examples
 from sktime.transformations.bootstrap import STLBootstrapTransformer
 from sktime.transformations.compose import (
@@ -173,6 +173,22 @@ def test_featureunion_transform_cols():
     assert deep_equals(Xt.columns, expected_cols), msg
 
 
+def test_featureunion_primitives():
+    """Test that FeatureUnion is correctly applied to primitives.
+
+    Failure case of bug #6077.
+    """
+    X, _ = load_unit_test(split="train", return_X_y=True)
+
+    fu = SummaryTransformer() + SummaryTransformer()
+    Xt = fu.fit_transform(X)
+
+    assert isinstance(Xt, pd.DataFrame)
+    assert len(Xt) == len(X)
+    assert Xt.shape[1] == 2 * 9  # 9-feature summary statistics
+    assert Xt.columns[0] == "SummaryTransformer_1__mean"  # unique naming
+
+
 def test_sklearn_after_primitives():
     """Test that sklearn transformer after primitives is correctly applied."""
     t = SummaryTransformer() * StandardScaler()