We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN 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


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `sktime/transformations/compose/_featureunion.py`
Content:
```
1 """Feature union."""
2 # copyright: sktime developers, BSD-3-Clause License (see LICENSE file)
3 
4 __author__ = ["fkiraly", "mloning"]
5 __all__ = ["FeatureUnion"]
6 
7 import pandas as pd
8 
9 from sktime.base._meta import _HeterogenousMetaEstimator
10 from sktime.transformations.base import BaseTransformer
11 from sktime.transformations.compose._common import _coerce_to_sktime
12 from sktime.utils.multiindex import flatten_multiindex
13 
14 
15 class FeatureUnion(_HeterogenousMetaEstimator, BaseTransformer):
16     """Concatenates results of multiple transformer objects.
17 
18     This estimator applies a list of transformer objects in parallel to the
19     input data, then concatenates the results. This is useful to combine
20     several feature extraction mechanisms into a single transformer.
21     Parameters of the transformations may be set using its name and the
22     parameter name separated by a '__'. A transformer may be replaced entirely by
23     setting the parameter with its name to another transformer,
24     or removed by setting to 'drop' or ``None``.
25 
26     Parameters
27     ----------
28     transformer_list : list of (string, transformer) tuples
29         List of transformer objects to be applied to the data. The first
30         half of each tuple is the name of the transformer.
31     n_jobs : int or None, optional (default=None)
32         Number of jobs to run in parallel.
33         ``None`` means 1 unless in a :obj:`joblib.parallel_backend`
34         context.
35         ``-1`` means using all processors.
36     transformer_weights : dict, optional
37         Multiplicative weights for features per transformer.
38         Keys are transformer names, values the weights.
39     flatten_transform_index : bool, optional (default=True)
40         if True, columns of return DataFrame are flat, by "transformer__variablename"
41         if False, columns are MultiIndex (transformer, variablename)
42         has no effect if return mtype is one without column names
43     """
44 
45     _tags = {
46         "authors": ["fkiraly", "mloning"],
47         "scitype:transform-input": "Series",
48         "scitype:transform-output": "Series",
49         "scitype:transform-labels": "None",
50         "scitype:instancewise": False,  # depends on components
51         "univariate-only": False,  # depends on components
52         "handles-missing-data": False,  # depends on components
53         "X_inner_mtype": ["pd.DataFrame", "pd-multiindex", "pd_multiindex_hier"],
54         "y_inner_mtype": "None",
55         "X-y-must-have-same-index": False,
56         "enforce_index_type": None,
57         "fit_is_empty": False,
58         "transform-returns-same-time-index": False,
59         "skip-inverse-transform": False,
60         "capability:inverse_transform": False,
61         # unclear what inverse transform should be, since multiple inverse_transform
62         #   would have to inverse transform to one
63     }
64 
65     # for default get_params/set_params from _HeterogenousMetaEstimator
66     # _steps_attr points to the attribute of self
67     # which contains the heterogeneous set of estimators
68     # this must be an iterable of (name: str, estimator) pairs for the default
69     _steps_attr = "_transformer_list"
70     # if the estimator is fittable, _HeterogenousMetaEstimator also
71     # provides an override for get_fitted_params for params from the fitted estimators
72     # the fitted estimators should be in a different attribute, _steps_fitted_attr
73     _steps_fitted_attr = "transformer_list_"
74 
75     def __init__(
76         self,
77         transformer_list,
78         n_jobs=None,
79         transformer_weights=None,
80         flatten_transform_index=True,
81     ):
82         self.transformer_list = transformer_list
83         self.transformer_list_ = self._check_estimators(
84             transformer_list, cls_type=BaseTransformer
85         )
86 
87         self.n_jobs = n_jobs
88         self.transformer_weights = transformer_weights
89         self.flatten_transform_index = flatten_transform_index
90 
91         super().__init__()
92 
93         # todo: check for transform-input, transform-output
94         #   for now, we assume it's always Series/Series or Series/Panel
95         #   but no error is currently raised
96 
97         # abbreviate for readability
98         ests = self.transformer_list_
99 
100         # set property tags based on tags of components
101         self._anytag_notnone_set("y_inner_mtype", ests)
102         self._anytag_notnone_set("scitype:transform-labels", ests)
103 
104         self._anytagis_then_set("scitype:instancewise", False, True, ests)
105         self._anytagis_then_set("X-y-must-have-same-index", True, False, ests)
106         self._anytagis_then_set("fit_is_empty", False, True, ests)
107         self._anytagis_then_set("transform-returns-same-time-index", False, True, ests)
108         self._anytagis_then_set("skip-inverse-transform", True, False, ests)
109         # self._anytagis_then_set("capability:inverse_transform", False, True, ests)
110         self._anytagis_then_set("handles-missing-data", False, True, ests)
111         self._anytagis_then_set("univariate-only", True, False, ests)
112 
113     @property
114     def _transformer_list(self):
115         return self._get_estimator_tuples(self.transformer_list, clone_ests=False)
116 
117     @_transformer_list.setter
118     def _transformer_list(self, value):
119         self.transformer_list = value
120         self.transformer_list_ = self._check_estimators(value, cls_type=BaseTransformer)
121 
122     def __add__(self, other):
123         """Magic + method, return (right) concatenated FeatureUnion.
124 
125         Implemented for ``other`` being a transformer, otherwise returns
126         ``NotImplemented``.
127 
128         Parameters
129         ----------
130         other: ``sktime`` transformer, must inherit from BaseTransformer
131             otherwise, ``NotImplemented`` is returned
132 
133         Returns
134         -------
135         TransformerPipeline object, concatenation of ``self`` (first) with ``other``
136         (last).
137             not nested, contains only non-FeatureUnion ``sktime`` transformers
138         """
139         other = _coerce_to_sktime(other)
140         return self._dunder_concat(
141             other=other,
142             base_class=BaseTransformer,
143             composite_class=FeatureUnion,
144             attr_name="transformer_list",
145             concat_order="left",
146         )
147 
148     def __radd__(self, other):
149         """Magic + method, return (left) concatenated FeatureUnion.
150 
151         Implemented for ``other`` being a transformer, otherwise returns
152         ``NotImplemented``.
153 
154         Parameters
155         ----------
156         other: ``sktime`` transformer, must inherit from BaseTransformer
157             otherwise, ``NotImplemented`` is returned
158 
159         Returns
160         -------
161         TransformerPipeline object, concatenation of ``self`` (last) with ``other``
162         (first).
163             not nested, contains only non-FeatureUnion ``sktime`` transformers
164         """
165         other = _coerce_to_sktime(other)
166         return self._dunder_concat(
167             other=other,
168             base_class=BaseTransformer,
169             composite_class=FeatureUnion,
170             attr_name="transformer_list",
171             concat_order="right",
172         )
173 
174     def _fit(self, X, y=None):
175         """Fit transformer to X and y.
176 
177         private _fit containing the core logic, called from fit
178 
179         Parameters
180         ----------
181         X : pd.DataFrame, Series, Panel, or Hierarchical mtype format
182             Data to fit transform to
183         y : Series or Panel of mtype y_inner_mtype, default=None
184             Additional data, e.g., labels for transformation
185 
186         Returns
187         -------
188         self: reference to self
189         """
190         self.transformer_list_ = self._check_estimators(
191             self.transformer_list, cls_type=BaseTransformer
192         )
193 
194         for _, transformer in self.transformer_list_:
195             transformer.fit(X=X, y=y)
196 
197         return self
198 
199     def _transform(self, X, y=None):
200         """Transform X and return a transformed version.
201 
202         private _transform containing core logic, called from transform
203 
204         Parameters
205         ----------
206         X : pd.DataFrame, Series, Panel, or Hierarchical mtype format
207             Data to be transformed
208         y : Series or Panel of mtype y_inner_mtype, default=None
209             Additional data, e.g., labels for transformation
210 
211         Returns
212         -------
213         transformed version of X
214         """
215         # retrieve fitted transformers, apply to the new data individually
216         transformers = self._get_estimator_list(self.transformer_list_)
217         if not self.get_tag("fit_is_empty", False):
218             Xt_list = [trafo.transform(X, y) for trafo in transformers]
219         else:
220             Xt_list = [trafo.fit_transform(X, y) for trafo in transformers]
221 
222         transformer_names = self._get_estimator_names(self.transformer_list_)
223 
224         Xt = pd.concat(
225             Xt_list, axis=1, keys=transformer_names, names=["transformer", "variable"]
226         )
227 
228         if self.flatten_transform_index:
229             Xt.columns = flatten_multiindex(Xt.columns)
230 
231         return Xt
232 
233     @classmethod
234     def get_test_params(cls, parameter_set="default"):
235         """Test parameters for FeatureUnion."""
236         from sktime.transformations.series.boxcox import BoxCoxTransformer
237         from sktime.transformations.series.exponent import ExponentTransformer
238 
239         # with name and estimator tuple, all transformers don't have fit
240         TRANSFORMERS = [
241             ("transformer1", ExponentTransformer(power=4)),
242             ("transformer2", ExponentTransformer(power=0.25)),
243         ]
244         params1 = {"transformer_list": TRANSFORMERS}
245 
246         # only with estimators, some transformers have fit, some not
247         params2 = {
248             "transformer_list": [
249                 ExponentTransformer(power=4),
250                 ExponentTransformer(power=0.25),
251                 BoxCoxTransformer(),
252             ]
253         }
254 
255         return [params1, params2]
256
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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 @@
         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 @@
 
         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_

{"golden_diff": "diff --git a/sktime/transformations/compose/_featureunion.py b/sktime/transformations/compose/_featureunion.py\n--- a/sktime/transformations/compose/_featureunion.py\n+++ b/sktime/transformations/compose/_featureunion.py\n@@ -80,9 +80,10 @@\n         flatten_transform_index=True,\n     ):\n         self.transformer_list = transformer_list\n-        self.transformer_list_ = self._check_estimators(\n+        transformer_list_ = self._check_estimators(\n             transformer_list, cls_type=BaseTransformer\n         )\n+        self.transformer_list_ = transformer_list_\n \n         self.n_jobs = n_jobs\n         self.transformer_weights = transformer_weights\n@@ -90,9 +91,11 @@\n \n         super().__init__()\n \n-        # todo: check for transform-input, transform-output\n-        #   for now, we assume it's always Series/Series or Series/Panel\n-        #   but no error is currently raised\n+        t_outs = [t.get_tag(\"scitype:transform-output\") for _, t in transformer_list_]\n+        t_ins = [t.get_tag(\"scitype:transform-input\") for _, t in transformer_list_]\n+        # todo: error or special case handling if these are not all the same\n+        self.set_tags(**{\"scitype:transform-output\": t_outs[0]})\n+        self.set_tags(**{\"scitype:transform-input\": t_ins[0]})\n \n         # abbreviate for readability\n         ests = self.transformer_list_\n", "issue": "[BUG] `FeatureUnion` raises error with transformers with primitive output\n**Describe the bug**\r\nFeature Union raises error with transformers with primitive output, i.e., when the output is not a series or a panel sktime raises an exception.\r\n\r\n**To Reproduce**\r\n```python\r\nfrom sktime.datasets import load_gunpoint\r\nfrom sktime.transformations.panel.rocket import Rocket\r\nX_train, y_train = load_gunpoint(split=\"train\", return_X_y=True)\r\nunion = Rocket() + Rocket()\r\nX_train_transformed = union.fit_transform(X_train)\r\n```\r\n\r\nraises: \r\n```\r\nNo valid mtype could be identified for object of type <class 'pandas.core.frame.DataFrame'>. Errors returned are as follows, in format [mtype]: [error message] \r\ndf-list: obj must be list of pd.DataFrame, found <class 'pandas.core.frame.DataFrame'>\r\nnumpy3D: obj must be a numpy.ndarray, found <class 'pandas.core.frame.DataFrame'>\r\npd-multiindex: obj must have a MultiIndex, found <class 'pandas.core.indexes.range.RangeIndex'>\r\nnested_univ: obj All columns must be object, found <class 'pandas.core.frame.DataFrame'>\r\n```\r\n\r\n**Expected behavior**\r\nI 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.\r\n\r\n**Additional context**\r\nMaybe this has something to do with this to-do in the init:\r\n```python\r\n# todo: check for transform-input, transform-output\r\n#   for now, we assume it's always Series/Series or Series/Panel\r\n#   but no error is currently raised\r\n```\r\n\r\n**Versions**\r\nSystem:\r\n    python: 3.9.16 | packaged by conda-forge | (main, Feb  1 2023, 21:42:20)  [Clang 14.0.6 ]\r\n   machine: macOS-13.2.1-x86_64-i386-64bit\r\n\r\nPython dependencies:\r\n          pip: 23.2.1\r\n       sktime: 0.27.0\r\n      sklearn: 1.3.0\r\n       skbase: 0.7.5\r\n        numpy: 1.24.0\r\n        scipy: 1.11.1\r\n       pandas: 2.0.3\r\n   matplotlib: 3.7.2\r\n       joblib: 1.3.1\r\n        numba: 0.57.1\r\n  statsmodels: None\r\n     pmdarima: None\r\nstatsforecast: None\r\n      tsfresh: None\r\n      tslearn: None\r\n        torch: None\r\n   tensorflow: None\r\ntensorflow_probability: None\r\n\n", "before_files": [{"content": "\"\"\"Feature union.\"\"\"\n# copyright: sktime developers, BSD-3-Clause License (see LICENSE file)\n\n__author__ = [\"fkiraly\", \"mloning\"]\n__all__ = [\"FeatureUnion\"]\n\nimport pandas as pd\n\nfrom sktime.base._meta import _HeterogenousMetaEstimator\nfrom sktime.transformations.base import BaseTransformer\nfrom sktime.transformations.compose._common import _coerce_to_sktime\nfrom sktime.utils.multiindex import flatten_multiindex\n\n\nclass FeatureUnion(_HeterogenousMetaEstimator, BaseTransformer):\n    \"\"\"Concatenates results of multiple transformer objects.\n\n    This estimator applies a list of transformer objects in parallel to the\n    input data, then concatenates the results. This is useful to combine\n    several feature extraction mechanisms into a single transformer.\n    Parameters of the transformations may be set using its name and the\n    parameter name separated by a '__'. A transformer may be replaced entirely by\n    setting the parameter with its name to another transformer,\n    or removed by setting to 'drop' or ``None``.\n\n    Parameters\n    ----------\n    transformer_list : list of (string, transformer) tuples\n        List of transformer objects to be applied to the data. The first\n        half of each tuple is the name of the transformer.\n    n_jobs : int or None, optional (default=None)\n        Number of jobs to run in parallel.\n        ``None`` means 1 unless in a :obj:`joblib.parallel_backend`\n        context.\n        ``-1`` means using all processors.\n    transformer_weights : dict, optional\n        Multiplicative weights for features per transformer.\n        Keys are transformer names, values the weights.\n    flatten_transform_index : bool, optional (default=True)\n        if True, columns of return DataFrame are flat, by \"transformer__variablename\"\n        if False, columns are MultiIndex (transformer, variablename)\n        has no effect if return mtype is one without column names\n    \"\"\"\n\n    _tags = {\n        \"authors\": [\"fkiraly\", \"mloning\"],\n        \"scitype:transform-input\": \"Series\",\n        \"scitype:transform-output\": \"Series\",\n        \"scitype:transform-labels\": \"None\",\n        \"scitype:instancewise\": False,  # depends on components\n        \"univariate-only\": False,  # depends on components\n        \"handles-missing-data\": False,  # depends on components\n        \"X_inner_mtype\": [\"pd.DataFrame\", \"pd-multiindex\", \"pd_multiindex_hier\"],\n        \"y_inner_mtype\": \"None\",\n        \"X-y-must-have-same-index\": False,\n        \"enforce_index_type\": None,\n        \"fit_is_empty\": False,\n        \"transform-returns-same-time-index\": False,\n        \"skip-inverse-transform\": False,\n        \"capability:inverse_transform\": False,\n        # unclear what inverse transform should be, since multiple inverse_transform\n        #   would have to inverse transform to one\n    }\n\n    # for default get_params/set_params from _HeterogenousMetaEstimator\n    # _steps_attr points to the attribute of self\n    # which contains the heterogeneous set of estimators\n    # this must be an iterable of (name: str, estimator) pairs for the default\n    _steps_attr = \"_transformer_list\"\n    # if the estimator is fittable, _HeterogenousMetaEstimator also\n    # provides an override for get_fitted_params for params from the fitted estimators\n    # the fitted estimators should be in a different attribute, _steps_fitted_attr\n    _steps_fitted_attr = \"transformer_list_\"\n\n    def __init__(\n        self,\n        transformer_list,\n        n_jobs=None,\n        transformer_weights=None,\n        flatten_transform_index=True,\n    ):\n        self.transformer_list = transformer_list\n        self.transformer_list_ = self._check_estimators(\n            transformer_list, cls_type=BaseTransformer\n        )\n\n        self.n_jobs = n_jobs\n        self.transformer_weights = transformer_weights\n        self.flatten_transform_index = flatten_transform_index\n\n        super().__init__()\n\n        # todo: check for transform-input, transform-output\n        #   for now, we assume it's always Series/Series or Series/Panel\n        #   but no error is currently raised\n\n        # abbreviate for readability\n        ests = self.transformer_list_\n\n        # set property tags based on tags of components\n        self._anytag_notnone_set(\"y_inner_mtype\", ests)\n        self._anytag_notnone_set(\"scitype:transform-labels\", ests)\n\n        self._anytagis_then_set(\"scitype:instancewise\", False, True, ests)\n        self._anytagis_then_set(\"X-y-must-have-same-index\", True, False, ests)\n        self._anytagis_then_set(\"fit_is_empty\", False, True, ests)\n        self._anytagis_then_set(\"transform-returns-same-time-index\", False, True, ests)\n        self._anytagis_then_set(\"skip-inverse-transform\", True, False, ests)\n        # self._anytagis_then_set(\"capability:inverse_transform\", False, True, ests)\n        self._anytagis_then_set(\"handles-missing-data\", False, True, ests)\n        self._anytagis_then_set(\"univariate-only\", True, False, ests)\n\n    @property\n    def _transformer_list(self):\n        return self._get_estimator_tuples(self.transformer_list, clone_ests=False)\n\n    @_transformer_list.setter\n    def _transformer_list(self, value):\n        self.transformer_list = value\n        self.transformer_list_ = self._check_estimators(value, cls_type=BaseTransformer)\n\n    def __add__(self, other):\n        \"\"\"Magic + method, return (right) concatenated FeatureUnion.\n\n        Implemented for ``other`` being a transformer, otherwise returns\n        ``NotImplemented``.\n\n        Parameters\n        ----------\n        other: ``sktime`` transformer, must inherit from BaseTransformer\n            otherwise, ``NotImplemented`` is returned\n\n        Returns\n        -------\n        TransformerPipeline object, concatenation of ``self`` (first) with ``other``\n        (last).\n            not nested, contains only non-FeatureUnion ``sktime`` transformers\n        \"\"\"\n        other = _coerce_to_sktime(other)\n        return self._dunder_concat(\n            other=other,\n            base_class=BaseTransformer,\n            composite_class=FeatureUnion,\n            attr_name=\"transformer_list\",\n            concat_order=\"left\",\n        )\n\n    def __radd__(self, other):\n        \"\"\"Magic + method, return (left) concatenated FeatureUnion.\n\n        Implemented for ``other`` being a transformer, otherwise returns\n        ``NotImplemented``.\n\n        Parameters\n        ----------\n        other: ``sktime`` transformer, must inherit from BaseTransformer\n            otherwise, ``NotImplemented`` is returned\n\n        Returns\n        -------\n        TransformerPipeline object, concatenation of ``self`` (last) with ``other``\n        (first).\n            not nested, contains only non-FeatureUnion ``sktime`` transformers\n        \"\"\"\n        other = _coerce_to_sktime(other)\n        return self._dunder_concat(\n            other=other,\n            base_class=BaseTransformer,\n            composite_class=FeatureUnion,\n            attr_name=\"transformer_list\",\n            concat_order=\"right\",\n        )\n\n    def _fit(self, X, y=None):\n        \"\"\"Fit transformer to X and y.\n\n        private _fit containing the core logic, called from fit\n\n        Parameters\n        ----------\n        X : pd.DataFrame, Series, Panel, or Hierarchical mtype format\n            Data to fit transform to\n        y : Series or Panel of mtype y_inner_mtype, default=None\n            Additional data, e.g., labels for transformation\n\n        Returns\n        -------\n        self: reference to self\n        \"\"\"\n        self.transformer_list_ = self._check_estimators(\n            self.transformer_list, cls_type=BaseTransformer\n        )\n\n        for _, transformer in self.transformer_list_:\n            transformer.fit(X=X, y=y)\n\n        return self\n\n    def _transform(self, X, y=None):\n        \"\"\"Transform X and return a transformed version.\n\n        private _transform containing core logic, called from transform\n\n        Parameters\n        ----------\n        X : pd.DataFrame, Series, Panel, or Hierarchical mtype format\n            Data to be transformed\n        y : Series or Panel of mtype y_inner_mtype, default=None\n            Additional data, e.g., labels for transformation\n\n        Returns\n        -------\n        transformed version of X\n        \"\"\"\n        # retrieve fitted transformers, apply to the new data individually\n        transformers = self._get_estimator_list(self.transformer_list_)\n        if not self.get_tag(\"fit_is_empty\", False):\n            Xt_list = [trafo.transform(X, y) for trafo in transformers]\n        else:\n            Xt_list = [trafo.fit_transform(X, y) for trafo in transformers]\n\n        transformer_names = self._get_estimator_names(self.transformer_list_)\n\n        Xt = pd.concat(\n            Xt_list, axis=1, keys=transformer_names, names=[\"transformer\", \"variable\"]\n        )\n\n        if self.flatten_transform_index:\n            Xt.columns = flatten_multiindex(Xt.columns)\n\n        return Xt\n\n    @classmethod\n    def get_test_params(cls, parameter_set=\"default\"):\n        \"\"\"Test parameters for FeatureUnion.\"\"\"\n        from sktime.transformations.series.boxcox import BoxCoxTransformer\n        from sktime.transformations.series.exponent import ExponentTransformer\n\n        # with name and estimator tuple, all transformers don't have fit\n        TRANSFORMERS = [\n            (\"transformer1\", ExponentTransformer(power=4)),\n            (\"transformer2\", ExponentTransformer(power=0.25)),\n        ]\n        params1 = {\"transformer_list\": TRANSFORMERS}\n\n        # only with estimators, some transformers have fit, some not\n        params2 = {\n            \"transformer_list\": [\n                ExponentTransformer(power=4),\n                ExponentTransformer(power=0.25),\n                BoxCoxTransformer(),\n            ]\n        }\n\n        return [params1, params2]\n", "path": "sktime/transformations/compose/_featureunion.py"}], "after_files": [{"content": "\"\"\"Feature union.\"\"\"\n# copyright: sktime developers, BSD-3-Clause License (see LICENSE file)\n\n__author__ = [\"fkiraly\", \"mloning\"]\n__all__ = [\"FeatureUnion\"]\n\nimport pandas as pd\n\nfrom sktime.base._meta import _HeterogenousMetaEstimator\nfrom sktime.transformations.base import BaseTransformer\nfrom sktime.transformations.compose._common import _coerce_to_sktime\nfrom sktime.utils.multiindex import flatten_multiindex\n\n\nclass FeatureUnion(_HeterogenousMetaEstimator, BaseTransformer):\n    \"\"\"Concatenates results of multiple transformer objects.\n\n    This estimator applies a list of transformer objects in parallel to the\n    input data, then concatenates the results. This is useful to combine\n    several feature extraction mechanisms into a single transformer.\n    Parameters of the transformations may be set using its name and the\n    parameter name separated by a '__'. A transformer may be replaced entirely by\n    setting the parameter with its name to another transformer,\n    or removed by setting to 'drop' or ``None``.\n\n    Parameters\n    ----------\n    transformer_list : list of (string, transformer) tuples\n        List of transformer objects to be applied to the data. The first\n        half of each tuple is the name of the transformer.\n    n_jobs : int or None, optional (default=None)\n        Number of jobs to run in parallel.\n        ``None`` means 1 unless in a :obj:`joblib.parallel_backend`\n        context.\n        ``-1`` means using all processors.\n    transformer_weights : dict, optional\n        Multiplicative weights for features per transformer.\n        Keys are transformer names, values the weights.\n    flatten_transform_index : bool, optional (default=True)\n        if True, columns of return DataFrame are flat, by \"transformer__variablename\"\n        if False, columns are MultiIndex (transformer, variablename)\n        has no effect if return mtype is one without column names\n    \"\"\"\n\n    _tags = {\n        \"authors\": [\"fkiraly\", \"mloning\"],\n        \"scitype:transform-input\": \"Series\",\n        \"scitype:transform-output\": \"Series\",\n        \"scitype:transform-labels\": \"None\",\n        \"scitype:instancewise\": False,  # depends on components\n        \"univariate-only\": False,  # depends on components\n        \"handles-missing-data\": False,  # depends on components\n        \"X_inner_mtype\": [\"pd.DataFrame\", \"pd-multiindex\", \"pd_multiindex_hier\"],\n        \"y_inner_mtype\": \"None\",\n        \"X-y-must-have-same-index\": False,\n        \"enforce_index_type\": None,\n        \"fit_is_empty\": False,\n        \"transform-returns-same-time-index\": False,\n        \"skip-inverse-transform\": False,\n        \"capability:inverse_transform\": False,\n        # unclear what inverse transform should be, since multiple inverse_transform\n        #   would have to inverse transform to one\n    }\n\n    # for default get_params/set_params from _HeterogenousMetaEstimator\n    # _steps_attr points to the attribute of self\n    # which contains the heterogeneous set of estimators\n    # this must be an iterable of (name: str, estimator) pairs for the default\n    _steps_attr = \"_transformer_list\"\n    # if the estimator is fittable, _HeterogenousMetaEstimator also\n    # provides an override for get_fitted_params for params from the fitted estimators\n    # the fitted estimators should be in a different attribute, _steps_fitted_attr\n    _steps_fitted_attr = \"transformer_list_\"\n\n    def __init__(\n        self,\n        transformer_list,\n        n_jobs=None,\n        transformer_weights=None,\n        flatten_transform_index=True,\n    ):\n        self.transformer_list = transformer_list\n        transformer_list_ = self._check_estimators(\n            transformer_list, cls_type=BaseTransformer\n        )\n        self.transformer_list_ = transformer_list_\n\n        self.n_jobs = n_jobs\n        self.transformer_weights = transformer_weights\n        self.flatten_transform_index = flatten_transform_index\n\n        super().__init__()\n\n        t_outs = [t.get_tag(\"scitype:transform-output\") for _, t in transformer_list_]\n        t_ins = [t.get_tag(\"scitype:transform-input\") for _, t in transformer_list_]\n        # todo: error or special case handling if these are not all the same\n        self.set_tags(**{\"scitype:transform-output\": t_outs[0]})\n        self.set_tags(**{\"scitype:transform-input\": t_ins[0]})\n\n        # abbreviate for readability\n        ests = self.transformer_list_\n\n        # set property tags based on tags of components\n        self._anytag_notnone_set(\"y_inner_mtype\", ests)\n        self._anytag_notnone_set(\"scitype:transform-labels\", ests)\n\n        self._anytagis_then_set(\"scitype:instancewise\", False, True, ests)\n        self._anytagis_then_set(\"X-y-must-have-same-index\", True, False, ests)\n        self._anytagis_then_set(\"fit_is_empty\", False, True, ests)\n        self._anytagis_then_set(\"transform-returns-same-time-index\", False, True, ests)\n        self._anytagis_then_set(\"skip-inverse-transform\", True, False, ests)\n        # self._anytagis_then_set(\"capability:inverse_transform\", False, True, ests)\n        self._anytagis_then_set(\"handles-missing-data\", False, True, ests)\n        self._anytagis_then_set(\"univariate-only\", True, False, ests)\n\n    @property\n    def _transformer_list(self):\n        return self._get_estimator_tuples(self.transformer_list, clone_ests=False)\n\n    @_transformer_list.setter\n    def _transformer_list(self, value):\n        self.transformer_list = value\n        self.transformer_list_ = self._check_estimators(value, cls_type=BaseTransformer)\n\n    def __add__(self, other):\n        \"\"\"Magic + method, return (right) concatenated FeatureUnion.\n\n        Implemented for ``other`` being a transformer, otherwise returns\n        ``NotImplemented``.\n\n        Parameters\n        ----------\n        other: ``sktime`` transformer, must inherit from BaseTransformer\n            otherwise, ``NotImplemented`` is returned\n\n        Returns\n        -------\n        TransformerPipeline object, concatenation of ``self`` (first) with ``other``\n        (last).\n            not nested, contains only non-FeatureUnion ``sktime`` transformers\n        \"\"\"\n        other = _coerce_to_sktime(other)\n        return self._dunder_concat(\n            other=other,\n            base_class=BaseTransformer,\n            composite_class=FeatureUnion,\n            attr_name=\"transformer_list\",\n            concat_order=\"left\",\n        )\n\n    def __radd__(self, other):\n        \"\"\"Magic + method, return (left) concatenated FeatureUnion.\n\n        Implemented for ``other`` being a transformer, otherwise returns\n        ``NotImplemented``.\n\n        Parameters\n        ----------\n        other: ``sktime`` transformer, must inherit from BaseTransformer\n            otherwise, ``NotImplemented`` is returned\n\n        Returns\n        -------\n        TransformerPipeline object, concatenation of ``self`` (last) with ``other``\n        (first).\n            not nested, contains only non-FeatureUnion ``sktime`` transformers\n        \"\"\"\n        other = _coerce_to_sktime(other)\n        return self._dunder_concat(\n            other=other,\n            base_class=BaseTransformer,\n            composite_class=FeatureUnion,\n            attr_name=\"transformer_list\",\n            concat_order=\"right\",\n        )\n\n    def _fit(self, X, y=None):\n        \"\"\"Fit transformer to X and y.\n\n        private _fit containing the core logic, called from fit\n\n        Parameters\n        ----------\n        X : pd.DataFrame, Series, Panel, or Hierarchical mtype format\n            Data to fit transform to\n        y : Series or Panel of mtype y_inner_mtype, default=None\n            Additional data, e.g., labels for transformation\n\n        Returns\n        -------\n        self: reference to self\n        \"\"\"\n        self.transformer_list_ = self._check_estimators(\n            self.transformer_list, cls_type=BaseTransformer\n        )\n\n        for _, transformer in self.transformer_list_:\n            transformer.fit(X=X, y=y)\n\n        return self\n\n    def _transform(self, X, y=None):\n        \"\"\"Transform X and return a transformed version.\n\n        private _transform containing core logic, called from transform\n\n        Parameters\n        ----------\n        X : pd.DataFrame, Series, Panel, or Hierarchical mtype format\n            Data to be transformed\n        y : Series or Panel of mtype y_inner_mtype, default=None\n            Additional data, e.g., labels for transformation\n\n        Returns\n        -------\n        transformed version of X\n        \"\"\"\n        # retrieve fitted transformers, apply to the new data individually\n        transformers = self._get_estimator_list(self.transformer_list_)\n        if not self.get_tag(\"fit_is_empty\", False):\n            Xt_list = [trafo.transform(X, y) for trafo in transformers]\n        else:\n            Xt_list = [trafo.fit_transform(X, y) for trafo in transformers]\n\n        transformer_names = self._get_estimator_names(self.transformer_list_)\n\n        Xt = pd.concat(\n            Xt_list, axis=1, keys=transformer_names, names=[\"transformer\", \"variable\"]\n        )\n\n        if self.flatten_transform_index:\n            Xt.columns = flatten_multiindex(Xt.columns)\n\n        return Xt\n\n    @classmethod\n    def get_test_params(cls, parameter_set=\"default\"):\n        \"\"\"Test parameters for FeatureUnion.\"\"\"\n        from sktime.transformations.series.boxcox import BoxCoxTransformer\n        from sktime.transformations.series.exponent import ExponentTransformer\n\n        # with name and estimator tuple, all transformers don't have fit\n        TRANSFORMERS = [\n            (\"transformer1\", ExponentTransformer(power=4)),\n            (\"transformer2\", ExponentTransformer(power=0.25)),\n        ]\n        params1 = {\"transformer_list\": TRANSFORMERS}\n\n        # only with estimators, some transformers have fit, some not\n        params2 = {\n            \"transformer_list\": [\n                ExponentTransformer(power=4),\n                ExponentTransformer(power=0.25),\n                BoxCoxTransformer(),\n            ]\n        }\n\n        return [params1, params2]\n", "path": "sktime/transformations/compose/_featureunion.py"}]}