Repository: sktime/sktime
Pull request #5625 (2023-12-14 18:48:52)

[ENH] Correct inference of `TransformerPipeline` output type tag
Fixes https://github.com/sktime/sktime/issues/5624.

The output tag would not be correctly inferred if a series-to-panel transformer is followed by series-to-series.
This has been corrected.

Unfortunately, the solution is somewhat involved. I think this hints that we may want to incode the input/output type information differently, using number of dimensions or similar.


Patch:
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 68675014c..42659a05e 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -2479,6 +2479,24 @@
       "contributions": [
         "code"
       ]
+    },
+    {
+      "login": "wayneadams",
+      "name": "Wayne Adams",
+      "avatar_url": "https://avatars.githubusercontent.com/u/15034841?s=400&u=d717e9945910bcc844c5e64cd56d570c6cc4e8e6&v=4",
+      "profile": "https://github.com/wayneadams",
+      "contributions": [
+        "doc"
+      ]
+    },
+    {
+      "login": "sssilvar",
+      "name": "Santiago Smith Silva",
+      "avatar_url": "https://avatars.githubusercontent.com/u/16252054?v=4",
+      "profile": "https://github.com/sssilvar",
+      "contributions": [
+        "code"
+      ]
     }
   ]
 }
diff --git a/.github/workflows/validate_datasets.yml b/.github/workflows/validate_datasets.yml
index d7eabb9b3..ee51b8ee1 100644
--- a/.github/workflows/validate_datasets.yml
+++ b/.github/workflows/validate_datasets.yml
@@ -31,7 +31,7 @@ jobs:
         run: git fetch --depth 1
         shell: bash
       - name: python environment step
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@v5
         with:
           python-version: ${{ matrix.python-version }}
       - name: dependencies installation step
@@ -66,7 +66,7 @@ jobs:
         run: git fetch --depth 1
         shell: bash
       - name: python environment step
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@v5
         with:
           python-version: ${{ matrix.python-version }}
       - name: dependencies installation step
diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml
index d5c15dbac..4f40c1f68 100644
--- a/.github/workflows/wheels.yml
+++ b/.github/workflows/wheels.yml
@@ -12,7 +12,7 @@ jobs:
     steps:
       - uses: actions/checkout@v4
 
-      - uses: actions/setup-python@v4
+      - uses: actions/setup-python@v5
         with:
           python-version: '3.10'
 
@@ -22,7 +22,7 @@ jobs:
           python -m build --wheel --sdist --outdir wheelhouse
 
       - name: Store wheels
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: wheels
           path: wheelhouse/*
@@ -39,11 +39,11 @@ jobs:
 
     steps:
       - uses: actions/checkout@v4
-      - uses: actions/setup-python@v4
+      - uses: actions/setup-python@v5
         with:
           python-version: ${{ matrix.python-version }}
 
-      - uses: actions/download-artifact@v3
+      - uses: actions/download-artifact@v4
         with:
           name: wheels
           path: wheelhouse
@@ -72,11 +72,11 @@ jobs:
 
     steps:
       - uses: actions/checkout@v4
-      - uses: actions/setup-python@v4
+      - uses: actions/setup-python@v5
         with:
           python-version: ${{ matrix.python-version }}
 
-      - uses: actions/download-artifact@v3
+      - uses: actions/download-artifact@v4
         with:
           name: wheels
           path: wheelhouse
@@ -99,7 +99,7 @@ jobs:
     needs: [build_wheels,test_unix_wheels,test_windows_wheels]
 
     steps:
-      - uses: actions/download-artifact@v3
+      - uses: actions/download-artifact@v4
         with:
           name: wheels
           path: wheelhouse
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 3bda1e001..a6e0d48bc 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,9 +1,10 @@
 repos:
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.4.0
+    rev: v4.5.0
     hooks:
       - id: check-added-large-files
-        args: ["--maxkb=1000"]
+        args:
+          - --maxkb=1000
       - id: check-case-conflict
       - id: check-merge-conflict
       - id: check-symlinks
@@ -18,24 +19,23 @@ repos:
       - id: trailing-whitespace
 
   - repo: https://github.com/asottile/pyupgrade
-    rev: v3.10.1
+    rev: v3.15.0
     hooks:
       - id: pyupgrade
         args:
           - --py38-plus
 
   - repo: https://github.com/pycqa/isort
-    rev: 5.12.0
+    rev: 5.13.2
     hooks:
       - id: isort
         name: isort
 
   - repo: https://github.com/psf/black
-    rev: 23.7.0
+    rev: 23.12.0
     hooks:
       - id: black
         language_version: python3
-        # args: [--line-length 79]
 
   - repo: https://github.com/pycqa/flake8
     rev: 6.1.0
@@ -51,28 +51,30 @@ repos:
         stages: [manual]
 
   - repo: https://github.com/nbQA-dev/nbQA
-    rev: 1.7.0
+    rev: 1.7.1
     hooks:
       - id: nbqa-black
-        args: [--nbqa-mutate, --nbqa-dont-skip-bad-cells]
-        additional_dependencies: [black==22.3.0]
+        args:
+          - --nbqa-dont-skip-bad-cells
       - id: nbqa-isort
-        args: [--nbqa-mutate, --nbqa-dont-skip-bad-cells]
-        additional_dependencies: [isort==5.6.4]
+        args:
+          - --nbqa-dont-skip-bad-cells
       - id: nbqa-flake8
-        args: [--nbqa-dont-skip-bad-cells, "--extend-ignore=E402,E203"]
-        additional_dependencies: [flake8==3.8.3]
+        args:
+          - --nbqa-dont-skip-bad-cells
+          - --extend-ignore=E402,E203
 
   - repo: https://github.com/pycqa/pydocstyle
     rev: 6.3.0
     hooks:
       - id: pydocstyle
-        args: ["--config=setup.cfg"]
+        args:
+          - --config=setup.cfg
 
   # We use the Python version instead of the original version which seems to require Docker
   # https://github.com/koalaman/shellcheck-precommit
   - repo: https://github.com/shellcheck-py/shellcheck-py
-    rev: v0.9.0.5
+    rev: v0.9.0.6
     hooks:
       - id: shellcheck
         name: shellcheck
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 02b377ccc..286575323 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -2,7 +2,7 @@ Contributors
 ============
 
 <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
-[![All Contributors](https://img.shields.io/badge/all_contributors-241-orange.svg)](#contributors)
+[![All Contributors](https://img.shields.io/badge/all_contributors-242-orange.svg)](#contributors)
 <!-- ALL-CONTRIBUTORS-BADGE:END -->
 
 This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
@@ -214,6 +214,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
     </tr>
     <tr>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/Piyush1729"><img src="https://avatars2.githubusercontent.com/u/64950012?v=4?s=100" width="100px;" alt="Piyush Gade"/><br /><sub><b>Piyush Gade</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=Piyush1729" title="Code">💻</a> <a href="https://github.com/sktime/sktime/pulls?q=is%3Apr+reviewed-by%3APiyush1729" title="Reviewed Pull Requests">👀</a></td>
+      <td align="center" valign="top" width="11.11%"><a href="https://github.com/rahulporuri"><img src="https://avatars.githubusercontent.com/u/1926457?v=4?s=100" width="100px;" alt="Poruri Sai Rahul"/><br /><sub><b>Poruri Sai Rahul</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=rahulporuri" title="Documentation">📖</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://www.linkedin.com/in/pranav-prajapati-a5b413226/"><img src="https://avatars.githubusercontent.com/u/94780581?v=4?s=100" width="100px;" alt="Pranav Prajapati"/><br /><sub><b>Pranav Prajapati</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=pranavvp16" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=pranavvp16" title="Tests">⚠️</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/pul95"><img src="https://avatars.githubusercontent.com/pul95?s=100" width="100px;" alt="Pulkit Verma"/><br /><sub><b>Pulkit Verma</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=pul95" title="Documentation">📖</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/Quaterion"><img src="https://avatars2.githubusercontent.com/u/23200273?v=4?s=100" width="100px;" alt="Quaterion"/><br /><sub><b>Quaterion</b></sub></a><br /><a href="https://github.com/sktime/sktime/issues?q=author%3AQuaterion" title="Bug reports">🐛</a></td>
@@ -221,9 +222,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/Ram0nB"><img src="https://avatars.githubusercontent.com/u/45173421?s=100" width="100px;" alt="Ramon Bussing"/><br /><sub><b>Ramon Bussing</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=Ram0nB" title="Documentation">📖</a> <a href="https://github.com/sktime/sktime/commits?author=Ram0nB" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/RavenRudi"><img src="https://avatars.githubusercontent.com/u/46402968?v=4?s=100" width="100px;" alt="RavenRudi"/><br /><sub><b>RavenRudi</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=RavenRudi" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/wolph"><img src="?s=100" width="100px;" alt="Rick van Hattem"/><br /><sub><b>Rick van Hattem</b></sub></a><br /><a href="#infra-wolph" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
-      <td align="center" valign="top" width="11.11%"><a href="https://github.com/Ris-Bali"><img src="https://avatars.githubusercontent.com/u/81592570?v=4?s=100" width="100px;" alt="Rishabh Bali"/><br /><sub><b>Rishabh Bali</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=Ris-Bali" title="Code">💻</a></td>
     </tr>
     <tr>
+      <td align="center" valign="top" width="11.11%"><a href="https://github.com/Ris-Bali"><img src="https://avatars.githubusercontent.com/u/81592570?v=4?s=100" width="100px;" alt="Rishabh Bali"/><br /><sub><b>Rishabh Bali</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=Ris-Bali" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/RishiKumarRay"><img src="https://avatars.githubusercontent.com/u/87641376?v=4?s=100" width="100px;" alt="Rishi Kumar Ray"/><br /><sub><b>Rishi Kumar Ray</b></sub></a><br /><a href="#infra-RishiKumarRay" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/Riyabelle25"><img src="https://avatars.githubusercontent.com/u/55790848?v=4?s=100" width="100px;" alt="Riya Elizabeth John"/><br /><sub><b>Riya Elizabeth John</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=Riyabelle25" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=Riyabelle25" title="Tests">⚠️</a> <a href="https://github.com/sktime/sktime/commits?author=Riyabelle25" title="Documentation">📖</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://www.linkedin.com/in/romanlutz/"><img src="https://avatars.githubusercontent.com/u/10245648?v=4?s=100" width="100px;" alt="Roman Lutz"/><br /><sub><b>Roman Lutz</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=romanlutz" title="Documentation">📖</a></td>
@@ -232,9 +233,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/achieveordie"><img src="https://avatars.githubusercontent.com/u/54197164?v=4?s=100" width="100px;" alt="Sagar Mishra"/><br /><sub><b>Sagar Mishra</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=achieveordie" title="Tests">⚠️</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://sajay.online"><img src="https://avatars2.githubusercontent.com/u/25329624?v=4?s=100" width="100px;" alt="Sajaysurya Ganesh"/><br /><sub><b>Sajaysurya Ganesh</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=sajaysurya" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=sajaysurya" title="Documentation">📖</a> <a href="#design-sajaysurya" title="Design">🎨</a> <a href="#example-sajaysurya" title="Examples">💡</a> <a href="#ideas-sajaysurya" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/sktime/sktime/commits?author=sajaysurya" title="Tests">⚠️</a> <a href="#tutorial-sajaysurya" title="Tutorials">✅</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/SamiAlavi"><img src="https://avatars.githubusercontent.com/u/32700289?v=4?s=100" width="100px;" alt="Sami Alavi"/><br /><sub><b>Sami Alavi</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=SamiAlavi" title="Code">💻</a> <a href="#maintenance-SamiAlavi" title="Maintenance">🚧</a></td>
-      <td align="center" valign="top" width="11.11%"><a href="https://github.com/Saransh-cpp"><img src="https://avatars.githubusercontent.com/u/74055102?v=4?s=100" width="100px;" alt="Saransh Chopra"/><br /><sub><b>Saransh Chopra</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=Saransh-cpp" title="Documentation">📖</a> <a href="#infra-Saransh-cpp" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
     </tr>
     <tr>
+      <td align="center" valign="top" width="11.11%"><a href="https://github.com/Saransh-cpp"><img src="https://avatars.githubusercontent.com/u/74055102?v=4?s=100" width="100px;" alt="Saransh Chopra"/><br /><sub><b>Saransh Chopra</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=Saransh-cpp" title="Documentation">📖</a> <a href="#infra-Saransh-cpp" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://www.linkedin.com/in/satya-pattnaik-77a430144/"><img src="https://avatars.githubusercontent.com/u/22102468?v=4?s=100" width="100px;" alt="Satya Prakash Pattnaik"/><br /><sub><b>Satya Prakash Pattnaik</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=satya-pattnaik" title="Documentation">📖</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/dasgupsa"><img src="https://avatars2.githubusercontent.com/u/10398956?v=4?s=100" width="100px;" alt="Saurabh Dasgupta"/><br /><sub><b>Saurabh Dasgupta</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=dasgupsa" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/SebasKoel"><img src="https://avatars3.githubusercontent.com/u/66252156?v=4?s=100" width="100px;" alt="Sebastiaan Koel"/><br /><sub><b>Sebastiaan Koel</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=SebasKoel" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=SebasKoel" title="Documentation">📖</a></td>
@@ -243,9 +244,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/AurumnPegasus"><img src="https://avatars.githubusercontent.com/u/54315149?v=4?s=100" width="100px;" alt="Shivansh Subramanian"/><br /><sub><b>Shivansh Subramanian</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=AurumnPegasus" title="Documentation">📖</a> <a href="https://github.com/sktime/sktime/commits?author=AurumnPegasus" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://www.linkedin.com/in/solomon-botchway-a1383821b/"><img src="https://avatars.githubusercontent.com/u/62394255?v=4?s=100" width="100px;" alt="Solomon Botchway"/><br /><sub><b>Solomon Botchway</b></sub></a><br /><a href="#maintenance-snnbotchway" title="Maintenance">🚧</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/khrapovs"><img src="https://avatars.githubusercontent.com/u/3774663?v=4?s=100" width="100px;" alt="Stanislav Khrapov"/><br /><sub><b>Stanislav Khrapov</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=khrapovs" title="Code">💻</a></td>
-      <td align="center" valign="top" width="11.11%"><a href="https://github.com/SveaMeyer13"><img src="https://avatars.githubusercontent.com/u/46671894?v=4?s=100" width="100px;" alt="Svea Marie Meyer"/><br /><sub><b>Svea Marie Meyer</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=SveaMeyer13" title="Documentation">📖</a> <a href="https://github.com/sktime/sktime/commits?author=SveaMeyer13" title="Code">💻</a></td>
     </tr>
     <tr>
+      <td align="center" valign="top" width="11.11%"><a href="https://github.com/SveaMeyer13"><img src="https://avatars.githubusercontent.com/u/46671894?v=4?s=100" width="100px;" alt="Svea Marie Meyer"/><br /><sub><b>Svea Marie Meyer</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=SveaMeyer13" title="Documentation">📖</a> <a href="https://github.com/sktime/sktime/commits?author=SveaMeyer13" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/TNTran92"><img src="https://avatars.githubusercontent.com/u/55965636?v=4?s=100" width="100px;" alt="TNTran92"/><br /><sub><b>TNTran92</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=TNTran92" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/Taise228"><img src="https://avatars.githubusercontent.com/u/95762401?s=400&v=4?s=100" width="100px;" alt="Taisei Yamamoto"/><br /><sub><b>Taisei Yamamoto</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=Taise228" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://thayeylolu.github.io/portfolio/"><img src="https://avatars.githubusercontent.com/u/13348874?v=4?s=100" width="100px;" alt="Taiwo Owoseni"/><br /><sub><b>Taiwo Owoseni</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=thayeylolu" title="Code">💻</a></td>
@@ -254,9 +255,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/tombh"><img src="https://avatars.githubusercontent.com/u/160835?s=80&v=4?s=100" width="100px;" alt="Thomas Buckley-Houston"/><br /><sub><b>Thomas Buckley-Houston</b></sub></a><br /><a href="https://github.com/sktime/sktime/issues?q=author%3Atombh" title="Bug reports">🐛</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/xxl4tomxu98"><img src="https://avatars.githubusercontent.com/u/62292177?s=40&v=4?s=100" width="100px;" alt="Tom Xu"/><br /><sub><b>Tom Xu</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=xxl4tomxu98" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=xxl4tomxu98" title="Documentation">📖</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/tpvasconcelos"><img src="https://avatars.githubusercontent.com/u/17701527?v=4?s=100" width="100px;" alt="Tomas P. de Vasconcelos"/><br /><sub><b>Tomas P. de Vasconcelos</b></sub></a><br /><a href="https://github.com/sktime/sktime/issues?q=author%3Atpvasconcelos" title="Bug reports">🐛</a> <a href="https://github.com/sktime/sktime/commits?author=tpvasconcelos" title="Code">💻</a></td>
-      <td align="center" valign="top" width="11.11%"><a href="https://github.com/tch"><img src="https://avatars3.githubusercontent.com/u/184076?v=4?s=100" width="100px;" alt="Tomasz Chodakowski"/><br /><sub><b>Tomasz Chodakowski</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=tch" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=tch" title="Documentation">📖</a> <a href="https://github.com/sktime/sktime/issues?q=author%3Atch" title="Bug reports">🐛</a></td>
     </tr>
     <tr>
+      <td align="center" valign="top" width="11.11%"><a href="https://github.com/tch"><img src="https://avatars3.githubusercontent.com/u/184076?v=4?s=100" width="100px;" alt="Tomasz Chodakowski"/><br /><sub><b>Tomasz Chodakowski</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=tch" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=tch" title="Documentation">📖</a> <a href="https://github.com/sktime/sktime/issues?q=author%3Atch" title="Bug reports">🐛</a></td>
       <td align="center" valign="top" width="11.11%"><a href="http://www.timeseriesclassification.com"><img src="https://avatars1.githubusercontent.com/u/9594042?v=4?s=100" width="100px;" alt="Tony Bagnall"/><br /><sub><b>Tony Bagnall</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=TonyBagnall" title="Code">💻</a> <a href="#business-TonyBagnall" title="Business development">💼</a> <a href="https://github.com/sktime/sktime/commits?author=TonyBagnall" title="Documentation">📖</a> <a href="#design-TonyBagnall" title="Design">🎨</a> <a href="#eventOrganizing-TonyBagnall" title="Event Organizing">📋</a> <a href="#fundingFinding-TonyBagnall" title="Funding Finding">🔍</a> <a href="#ideas-TonyBagnall" title="Ideas, Planning, & Feedback">🤔</a> <a href="#projectManagement-TonyBagnall" title="Project Management">📆</a> <a href="#question-TonyBagnall" title="Answering Questions">💬</a> <a href="https://github.com/sktime/sktime/pulls?q=is%3Apr+reviewed-by%3ATonyBagnall" title="Reviewed Pull Requests">👀</a> <a href="#talk-TonyBagnall" title="Talks">📢</a> <a href="#data-TonyBagnall" title="Data">🔣</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/utsavcoding"><img src="https://avatars3.githubusercontent.com/u/55446385?v=4?s=100" width="100px;" alt="Utsav Kumar Tiwari"/><br /><sub><b>Utsav Kumar Tiwari</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=utsavcoding" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=utsavcoding" title="Documentation">📖</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/Vasudeva-bit"><img src="https://avatars.githubusercontent.com/u/70791259?v=4?s=100" width="100px;" alt="Vasudeva Kilaru"/><br /><sub><b>Vasudeva Kilaru</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=Vasudeva-bit" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=Vasudeva-bit" title="Documentation">📖</a></td>
@@ -265,9 +266,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/VyomkeshVyas"><img src="?s=100" width="100px;" alt="Vyomkesh Vyas"/><br /><sub><b>Vyomkesh Vyas</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=VyomkeshVyas" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=VyomkeshVyas" title="Documentation">📖</a> <a href="#example-VyomkeshVyas" title="Examples">💡</a> <a href="https://github.com/sktime/sktime/commits?author=VyomkeshVyas" title="Tests">⚠️</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://www.linkedin.com/in/templierw/"><img src="https://github.com/templierw.png?s=100" width="100px;" alt="William Templier"/><br /><sub><b>William Templier</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=templierw" title="Documentation">📖</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/magittan"><img src="https://avatars0.githubusercontent.com/u/14024202?v=4?s=100" width="100px;" alt="William Zheng"/><br /><sub><b>William Zheng</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=magittan" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=magittan" title="Tests">⚠️</a></td>
-      <td align="center" valign="top" width="11.11%"><a href="https://github.com/yairbeer"><img src="https://avatars.githubusercontent.com/yairbeer?s=100" width="100px;" alt="Yair Beer"/><br /><sub><b>Yair Beer</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=yairbeer" title="Code">💻</a></td>
     </tr>
     <tr>
+      <td align="center" valign="top" width="11.11%"><a href="https://github.com/yairbeer"><img src="https://avatars.githubusercontent.com/yairbeer?s=100" width="100px;" alt="Yair Beer"/><br /><sub><b>Yair Beer</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=yairbeer" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://www.linkedin.com/in/yann-hallouard/"><img src="https://avatars.githubusercontent.com/YHallouard?s=100" width="100px;" alt="Yann Hallouard"/><br /><sub><b>Yann Hallouard</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=YHallouard" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=YHallouard" title="Tests">⚠️</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/yashlamba"><img src="https://avatars.githubusercontent.com/u/44164398?v=4?s=100" width="100px;" alt="Yash Lamba"/><br /><sub><b>Yash Lamba</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=yashlamba" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/xuyxu"><img src="https://avatars2.githubusercontent.com/u/22359569?v=4?s=100" width="100px;" alt="Yi-Xuan Xu"/><br /><sub><b>Yi-Xuan Xu</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=xuyxu" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=xuyxu" title="Tests">⚠️</a> <a href="#maintenance-xuyxu" title="Maintenance">🚧</a> <a href="https://github.com/sktime/sktime/commits?author=xuyxu" title="Documentation">📖</a></td>
@@ -276,9 +277,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/aa25desh"><img src="https://avatars1.githubusercontent.com/u/29518290?v=4?s=100" width="100px;" alt="aa25desh"/><br /><sub><b>aa25desh</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=aa25desh" title="Code">💻</a> <a href="https://github.com/sktime/sktime/issues?q=author%3Aaa25desh" title="Bug reports">🐛</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/abandus"><img src="https://avatars2.githubusercontent.com/u/46486474?v=4?s=100" width="100px;" alt="abandus"/><br /><sub><b>abandus</b></sub></a><br /><a href="#ideas-abandus" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/sktime/sktime/commits?author=abandus" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/adoherty21"><img src="https://avatars.githubusercontent.com/u/52799751?s=400&v=4?s=100" width="100px;" alt="adoherty21"/><br /><sub><b>adoherty21</b></sub></a><br /><a href="https://github.com/sktime/sktime/issues?q=author%3Aadoherty21" title="Bug reports">🐛</a></td>
-      <td align="center" valign="top" width="11.11%"><a href="https://github.com/bethrice44"><img src="https://avatars.githubusercontent.com/u/11226988?v=4?s=100" width="100px;" alt="bethrice44"/><br /><sub><b>bethrice44</b></sub></a><br /><a href="https://github.com/sktime/sktime/issues?q=author%3Abethrice44" title="Bug reports">🐛</a> <a href="https://github.com/sktime/sktime/commits?author=bethrice44" title="Code">💻</a> <a href="https://github.com/sktime/sktime/pulls?q=is%3Apr+reviewed-by%3Abethrice44" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sktime/sktime/commits?author=bethrice44" title="Tests">⚠️</a></td>
     </tr>
     <tr>
+      <td align="center" valign="top" width="11.11%"><a href="https://github.com/bethrice44"><img src="https://avatars.githubusercontent.com/u/11226988?v=4?s=100" width="100px;" alt="bethrice44"/><br /><sub><b>bethrice44</b></sub></a><br /><a href="https://github.com/sktime/sktime/issues?q=author%3Abethrice44" title="Bug reports">🐛</a> <a href="https://github.com/sktime/sktime/commits?author=bethrice44" title="Code">💻</a> <a href="https://github.com/sktime/sktime/pulls?q=is%3Apr+reviewed-by%3Abethrice44" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sktime/sktime/commits?author=bethrice44" title="Tests">⚠️</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/big-o"><img src="https://avatars1.githubusercontent.com/u/1134151?v=4?s=100" width="100px;" alt="big-o"/><br /><sub><b>big-o</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=big-o" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=big-o" title="Tests">⚠️</a> <a href="#design-big-o" title="Design">🎨</a> <a href="#ideas-big-o" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/sktime/sktime/pulls?q=is%3Apr+reviewed-by%3Abig-o" title="Reviewed Pull Requests">👀</a> <a href="#tutorial-big-o" title="Tutorials">✅</a> <a href="#mentoring-big-o" title="Mentoring">🧑‍🏫</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/bobbys-dev"><img src="https://avatars.githubusercontent.com/bobbys-dev?s=100" width="100px;" alt="bobbys"/><br /><sub><b>bobbys</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=bobbys-dev" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/brettkoonce"><img src="https://avatars2.githubusercontent.com/u/11281814?v=4?s=100" width="100px;" alt="brett koonce"/><br /><sub><b>brett koonce</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=brettkoonce" title="Documentation">📖</a></td>
@@ -287,9 +288,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/chrisholder"><img src="https://avatars.githubusercontent.com/u/4674372?v=4?s=100" width="100px;" alt="chrisholder"/><br /><sub><b>chrisholder</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=chrisholder" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=chrisholder" title="Tests">⚠️</a> <a href="https://github.com/sktime/sktime/commits?author=chrisholder" title="Documentation">📖</a> <a href="#design-chrisholder" title="Design">🎨</a> <a href="#example-chrisholder" title="Examples">💡</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/danbartl"><img src="https://avatars.githubusercontent.com/u/19947407?v=4?s=100" width="100px;" alt="danbartl"/><br /><sub><b>danbartl</b></sub></a><br /><a href="https://github.com/sktime/sktime/issues?q=author%3Adanbartl" title="Bug reports">🐛</a> <a href="https://github.com/sktime/sktime/commits?author=danbartl" title="Code">💻</a> <a href="https://github.com/sktime/sktime/pulls?q=is%3Apr+reviewed-by%3Adanbartl" title="Reviewed Pull Requests">👀</a> <a href="#talk-danbartl" title="Talks">📢</a> <a href="https://github.com/sktime/sktime/commits?author=danbartl" title="Tests">⚠️</a> <a href="#tutorial-danbartl" title="Tutorials">✅</a> <a href="#video-danbartl" title="Videos">📹</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/hamzahiqb"><img src="https://avatars3.githubusercontent.com/u/10302415?v=4?s=100" width="100px;" alt="hamzahiqb"/><br /><sub><b>hamzahiqb</b></sub></a><br /><a href="#infra-hamzahiqb" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
-      <td align="center" valign="top" width="11.11%"><a href="https://github.com/hiqbal2"><img src="https://avatars3.githubusercontent.com/u/10302415?v=4?s=100" width="100px;" alt="hiqbal2"/><br /><sub><b>hiqbal2</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=hiqbal2" title="Documentation">📖</a></td>
     </tr>
     <tr>
+      <td align="center" valign="top" width="11.11%"><a href="https://github.com/hiqbal2"><img src="https://avatars3.githubusercontent.com/u/10302415?v=4?s=100" width="100px;" alt="hiqbal2"/><br /><sub><b>hiqbal2</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=hiqbal2" title="Documentation">📖</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/jesellier"><img src="https://avatars0.githubusercontent.com/u/51952076?v=4?s=100" width="100px;" alt="jesellier"/><br /><sub><b>jesellier</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=jesellier" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/jschemm"><img src="https://avatars.githubusercontent.com/u/81151346?v=4?s=100" width="100px;" alt="jschemm"/><br /><sub><b>jschemm</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=jschemm" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/kkoziara"><img src="https://avatars1.githubusercontent.com/u/4346849?v=4?s=100" width="100px;" alt="kkoziara"/><br /><sub><b>kkoziara</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=kkoziara" title="Code">💻</a> <a href="https://github.com/sktime/sktime/issues?q=author%3Akkoziara" title="Bug reports">🐛</a></td>
@@ -298,9 +299,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/pabworks"><img src="https://avatars.githubusercontent.com/u/32725127?v=4?s=100" width="100px;" alt="pabworks"/><br /><sub><b>pabworks</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=pabworks" title="Code">💻</a> <a href="https://github.com/sktime/sktime/commits?author=pabworks" title="Tests">⚠️</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/xloem"><img src="?s=100" width="100px;" alt="patiently pending world peace"/><br /><sub><b>patiently pending world peace</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=xloem" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/raishubham1"><img src="https://avatars3.githubusercontent.com/u/29356417?v=4?s=100" width="100px;" alt="raishubham1"/><br /><sub><b>raishubham1</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=raishubham1" title="Documentation">📖</a></td>
-      <td align="center" valign="top" width="11.11%"><a href="https://github.com/simone-pignotti"><img src="https://avatars1.githubusercontent.com/u/44410066?v=4?s=100" width="100px;" alt="simone-pignotti"/><br /><sub><b>simone-pignotti</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=simone-pignotti" title="Code">💻</a> <a href="https://github.com/sktime/sktime/issues?q=author%3Asimone-pignotti" title="Bug reports">🐛</a></td>
     </tr>
     <tr>
+      <td align="center" valign="top" width="11.11%"><a href="https://github.com/simone-pignotti"><img src="https://avatars1.githubusercontent.com/u/44410066?v=4?s=100" width="100px;" alt="simone-pignotti"/><br /><sub><b>simone-pignotti</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=simone-pignotti" title="Code">💻</a> <a href="https://github.com/sktime/sktime/issues?q=author%3Asimone-pignotti" title="Bug reports">🐛</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/sophijka"><img src="https://avatars2.githubusercontent.com/u/47450591?v=4?s=100" width="100px;" alt="sophijka"/><br /><sub><b>sophijka</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=sophijka" title="Documentation">📖</a> <a href="#maintenance-sophijka" title="Maintenance">🚧</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/sri1419"><img src="https://avatars2.githubusercontent.com/u/65078278?v=4?s=100" width="100px;" alt="sri1419"/><br /><sub><b>sri1419</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=sri1419" title="Code">💻</a></td>
       <td align="center" valign="top" width="11.11%"><a href="https://github.com/tensorflow-as-tf"><img src="https://avatars.githubusercontent.com/u/51345718?v=4?s=100" width="100px;" alt="tensorflow-as-tf"/><br /><sub><b>tensorflow-as-tf</b></sub></a><br /><a href="https://github.com/sktime/sktime/commits?author=tensorflow-as-tf" title="Code">💻</a></td>
diff --git a/docs/source/api_reference/param_est.rst b/docs/source/api_reference/param_est.rst
index 8369e2f6a..d8d22f427 100644
--- a/docs/source/api_reference/param_est.rst
+++ b/docs/source/api_reference/param_est.rst
@@ -32,6 +32,12 @@ Composition
 
     ParamFitterPipeline
 
+.. autosummary::
+    :toctree: auto_generated/
+    :template: class.rst
+
+    FunctionParamFitter
+
 .. currentmodule:: sktime.param_est.plugin
 
 .. autosummary::
diff --git a/examples/AA_datatypes_and_datasets.ipynb b/examples/AA_datatypes_and_datasets.ipynb
index 867016e0f..8a17bb5b9 100644
--- a/examples/AA_datatypes_and_datasets.ipynb
+++ b/examples/AA_datatypes_and_datasets.ipynb
@@ -3166,7 +3166,7 @@
     }
    ],
    "source": [
-    "df_panel = df_panel.set_index([\"timepoints\", \"level_0\"])\n",
+    "df_panel = df_panel.set_index([\"level_0\", \"timepoints\"])\n",
     "type(df_panel.index)"
    ]
   },
diff --git a/examples/mlflow.ipynb b/examples/mlflow.ipynb
index 79189d202..fd91ca73a 100644
--- a/examples/mlflow.ipynb
+++ b/examples/mlflow.ipynb
@@ -133,7 +133,6 @@
    ],
    "source": [
     "with mlflow.start_run():\n",
-    "\n",
     "    forecaster = NaiveForecaster()\n",
     "    forecaster.fit(\n",
     "        y_train,\n",
@@ -691,7 +690,6 @@
     "mlflow.set_experiment(\"Test Sktime\")\n",
     "\n",
     "with mlflow.start_run() as run:\n",
-    "\n",
     "    forecaster = NaiveForecaster()\n",
     "    forecaster.fit(y_train, X=X_train, fh=[1, 2, 3])\n",
     "    forecaster.pyfunc_predict_conf = pyfunc_predict_conf\n",
diff --git a/pyproject.toml b/pyproject.toml
index ba5713445..0d86f8947 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -116,7 +116,7 @@ all_extras_pandas2 = [
   "arch<6.3.0,>=5.6",
   "cloudpickle",
   "dash!=2.9.0",
-  "dask<2023.11.1",
+  "dask<2023.12.2",
   "dtw-python",
   'esig==0.9.7; python_version < "3.10"',
   'filterpy>=1.4.5; python_version < "3.11"',
@@ -200,7 +200,7 @@ regression = [
 transformations = [
   'esig<0.10,>=0.9.7; python_version < "3.11"',
   "filterpy<1.5,>=1.4.5",
-  "holidays>=0.29,<0.38",
+  "holidays>=0.29,<0.40",
   "mne<1.6,>=1.5",
   'numba<0.59,>=0.53; python_version < "3.12"',
   "pycatch22<0.4.4,>=0.4",
diff --git a/sktime/classification/deep_learning/cnn.py b/sktime/classification/deep_learning/cnn.py
index 042e8b2e8..9b99877bb 100644
--- a/sktime/classification/deep_learning/cnn.py
+++ b/sktime/classification/deep_learning/cnn.py
@@ -34,7 +34,6 @@ class CNNClassifier(BaseDeepClassifier):
         whether to output extra information
     loss            : string, default="mean_squared_error"
         fit parameter for the keras model
-    optimizer       : keras.optimizer, default=keras.optimizers.Adam(),
     metrics         : list of strings, default=["accuracy"],
     activation      : string or a tf callable, default="sigmoid"
         Activation function used in the output linear layer.
diff --git a/sktime/classification/interval_based/_stsf.py b/sktime/classification/interval_based/_stsf.py
index f2e08709c..55ab2503d 100644
--- a/sktime/classification/interval_based/_stsf.py
+++ b/sktime/classification/interval_based/_stsf.py
@@ -205,7 +205,7 @@ class SupervisedTimeSeriesForest(BaseClassifier):
 
         Returns
         -------
-        output : nd.array of shape = (n_instances, n_classes)
+        output : np.ndarray of shape = (n_instances, n_classes)
             Predicted probabilities
         """
         X = X.squeeze(1)
diff --git a/sktime/classification/interval_based/_tsf.py b/sktime/classification/interval_based/_tsf.py
index b2b0993fa..0b42168f2 100644
--- a/sktime/classification/interval_based/_tsf.py
+++ b/sktime/classification/interval_based/_tsf.py
@@ -170,7 +170,7 @@ class TimeSeriesForestClassifier(
 
         Returns
         -------
-        output : nd.array of shape = (n_instances, n_classes)
+        output : np.ndarray of shape = (n_instances, n_classes)
             Predicted probabilities
         """
         X = X.squeeze(1)
diff --git a/sktime/datasets/_readers_writers/arff.py b/sktime/datasets/_readers_writers/arff.py
index c18a0a1c0..e41777593 100644
--- a/sktime/datasets/_readers_writers/arff.py
+++ b/sktime/datasets/_readers_writers/arff.py
@@ -202,7 +202,7 @@ def write_tabular_transformation_to_arff(
     dirt = f"{str(path)}/{str(problem_name)}-{type(transformation).__name__}/"
     try:
         os.makedirs(dirt)
-    except os.error:
+    except OSError:
         pass  # raises os.error if path already exists
     # create arff file in the path
     file = open(
diff --git a/sktime/datasets/_readers_writers/utils.py b/sktime/datasets/_readers_writers/utils.py
index 751985b9f..f96055ce6 100644
--- a/sktime/datasets/_readers_writers/utils.py
+++ b/sktime/datasets/_readers_writers/utils.py
@@ -124,7 +124,7 @@ def _write_header(
     dirt = f"{str(path)}/{str(problem_name)}/"
     try:
         os.makedirs(dirt)
-    except os.error:
+    except OSError:
         pass  # raises os.error if path already exists
     # create ts file in the path
     file = open(f"{dirt}{str(problem_name)}{fold}.ts", "w")
@@ -210,7 +210,7 @@ def write_results_to_uea_format(
         output_path = f"{output_path}/{estimator_name}/Predictions/{dataset_name}/"
     try:
         os.makedirs(output_path)
-    except os.error:
+    except OSError:
         pass  # raises os.error if path already exists, so just ignore this
 
     if split == "TRAIN" or split == "train":
diff --git a/sktime/datatypes/_adapter/dask_to_pd.py b/sktime/datatypes/_adapter/dask_to_pd.py
index 627428c0b..7e07fa458 100644
--- a/sktime/datatypes/_adapter/dask_to_pd.py
+++ b/sktime/datatypes/_adapter/dask_to_pd.py
@@ -175,6 +175,10 @@ def check_dask_frame(
         metadata["is_empty"] = len(obj.index) < 1 or len(obj.columns) < 1
     if _req("is_univariate", return_metadata):
         metadata["is_univariate"] = len(obj.columns) == 1
+    if _req("n_features", return_metadata):
+        metadata["n_features"] = len(obj.columns)
+    if _req("feature_names", return_metadata):
+        metadata["feature_names"] = obj.columns.to_list()
 
     # check that columns are unique
     if not obj.columns.is_unique:
diff --git a/sktime/datatypes/_examples.py b/sktime/datatypes/_examples.py
index 1ea554ede..f1555c7e2 100644
--- a/sktime/datatypes/_examples.py
+++ b/sktime/datatypes/_examples.py
@@ -97,9 +97,9 @@ def get_examples(
     fixtures: dict with integer keys, elements being
         fixture - example for mtype `mtype`, scitype `as_scitype`
         if return_lossy=True, elements are pairs with fixture and
-            lossy: bool - whether the example is a lossy representation
+        lossy: bool - whether the example is a lossy representation
         if return_metadata=True, elements are triples with fixture, lossy, and
-            metadata: dict - metadata dict that would be returned by check_is_mtype
+        metadata: dict - metadata dict that would be returned by check_is_mtype
     """
     # if as_scitype is None, infer from mtype
     if as_scitype is None:
diff --git a/sktime/datatypes/_hierarchical/_check.py b/sktime/datatypes/_hierarchical/_check.py
index efe1adb04..6511f3854 100644
--- a/sktime/datatypes/_hierarchical/_check.py
+++ b/sktime/datatypes/_hierarchical/_check.py
@@ -36,6 +36,8 @@ metadata: dict - metadata about obj if valid, otherwise None
         "has_nans": bool, True iff the panel contains NaN values
         "n_instances": int, number of instances in the hierarchical panel
         "n_panels": int, number of flat panels in the hierarchical panel
+        "n_features": int, number of variables in series
+        "feature_names": list of int or object, names of variables in series
 """
 
 __author__ = ["fkiraly"]
diff --git a/sktime/datatypes/_hierarchical/_examples.py b/sktime/datatypes/_hierarchical/_examples.py
index 3833a662c..18b798b06 100644
--- a/sktime/datatypes/_hierarchical/_examples.py
+++ b/sktime/datatypes/_hierarchical/_examples.py
@@ -114,6 +114,8 @@ example_dict_metadata[("Hierarchical", 0)] = {
     "has_nans": False,
     "n_instances": 6,
     "n_panels": 2,
+    "n_features": 2,
+    "feature_names": ["var_0", "var_1"],
 }
 
 
@@ -156,4 +158,6 @@ example_dict_metadata[("Hierarchical", 1)] = {
     "has_nans": False,
     "n_instances": 6,
     "n_panels": 2,
+    "n_features": 1,
+    "feature_names": ["var_0"],
 }
diff --git a/sktime/datatypes/_panel/_check.py b/sktime/datatypes/_panel/_check.py
index eabef8e99..b00f811c7 100644
--- a/sktime/datatypes/_panel/_check.py
+++ b/sktime/datatypes/_panel/_check.py
@@ -34,6 +34,8 @@ metadata: dict - metadata about obj if valid, otherwise None
         "is_one_series": bool, True iff there is only one series in the panel
         "has_nans": bool, True iff the panel contains NaN values
         "n_instances": int, number of instances in the panel
+        "n_features": int, number of variables in series
+        "feature_names": list of int or object, names of variables in series
 """
 
 __author__ = ["fkiraly", "TonyBagnall"]
@@ -124,6 +126,10 @@ def check_dflist_panel(obj, return_metadata=False, var_name="obj"):
         metadata["is_one_panel"] = True
     if _req("n_instances", return_metadata):
         metadata["n_instances"] = n
+    if _req("n_features", return_metadata):
+        metadata["n_features"] = len(obj[0].columns)
+    if _req("feature_names", return_metadata):
+        metadata["feature_names"] = obj[0].columns.to_list()
 
     return _ret(True, None, metadata, return_metadata)
 
@@ -160,6 +166,10 @@ def check_numpy3d_panel(obj, return_metadata=False, var_name="obj"):
         metadata["n_panels"] = 1
     if _req("is_one_panel", return_metadata):
         metadata["is_one_panel"] = True
+    if _req("n_features", return_metadata):
+        metadata["n_features"] = obj.shape[1]
+    if _req("feature_names", return_metadata):
+        metadata["feature_names"] = list(range(obj.shape[1]))
 
     # check whether there any nans; only if requested
     if _req("has_nans", return_metadata):
@@ -246,6 +256,10 @@ def check_pdmultiindex_panel(obj, return_metadata=False, var_name="obj", panel=T
         metadata["is_empty"] = len(index) < 1 or len(obj.columns) < 1
     if _req("has_nans", return_metadata):
         metadata["has_nans"] = obj.isna().values.any()
+    if _req("n_features", return_metadata):
+        metadata["n_features"] = len(obj.columns)
+    if _req("feature_names", return_metadata):
+        metadata["feature_names"] = obj.columns.to_list()
 
     # check whether index is equally spaced or if there are any nans
     #   compute only if needed
@@ -419,6 +433,10 @@ def is_nested_dataframe(obj, return_metadata=False, var_name="obj"):
         metadata["has_nans"] = _nested_dataframe_has_nans(obj)
     if _req("is_equal_length", return_metadata):
         metadata["is_equal_length"] = not _nested_dataframe_has_unequal(obj)
+    if _req("n_features", return_metadata):
+        metadata["n_features"] = len(obj.columns)
+    if _req("feature_names", return_metadata):
+        metadata["feature_names"] = obj.columns.to_list()
 
     # todo: this is temporary override, proper is_empty logic needs to be added
     if _req("is_empty", return_metadata):
@@ -448,6 +466,10 @@ def check_numpyflat_Panel(obj, return_metadata=False, var_name="obj"):
         metadata["is_empty"] = len(obj) < 1 or obj.shape[1] < 1
     if _req("is_univariate", return_metadata):
         metadata["is_univariate"] = True
+    if _req("n_features", return_metadata):
+        metadata["n_features"] = 1
+    if _req("feature_names", return_metadata):
+        metadata["feature_names"] = [0]
     # np.arrays are considered equally spaced, equal length, by assumption
     if _req("is_equally_spaced", return_metadata):
         metadata["is_equally_spaced"] = True
diff --git a/sktime/datatypes/_panel/_examples.py b/sktime/datatypes/_panel/_examples.py
index d2f4ef0f3..d0b0998e3 100644
--- a/sktime/datatypes/_panel/_examples.py
+++ b/sktime/datatypes/_panel/_examples.py
@@ -39,10 +39,10 @@ X = np.array(
 )
 
 example_dict[("numpy3D", "Panel", 0)] = X
-example_dict_lossy[("numpy3D", "Panel", 0)] = False
+example_dict_lossy[("numpy3D", "Panel", 0)] = True
 
 example_dict[("numpyflat", "Panel", 0)] = None
-example_dict_lossy[("numpyflat", "Panel", 0)] = None
+example_dict_lossy[("numpyflat", "Panel", 0)] = True
 
 cols = [f"var_{i}" for i in range(2)]
 Xlist = [
@@ -101,6 +101,8 @@ example_dict_metadata[("Panel", 0)] = {
     "is_empty": False,
     "has_nans": False,
     "n_instances": 3,
+    "n_features": 2,
+    "feature_names": ["var_0", "var_1"],
 }
 
 ###
@@ -112,12 +114,12 @@ X = np.array(
 )
 
 example_dict[("numpy3D", "Panel", 1)] = X
-example_dict_lossy[("numpy3D", "Panel", 1)] = False
+example_dict_lossy[("numpy3D", "Panel", 1)] = True
 
 X = np.array([[4, 5, 6], [4, 55, 6], [42, 5, 6]], dtype=np.int64)
 
 example_dict[("numpyflat", "Panel", 1)] = X
-example_dict_lossy[("numpyflat", "Panel", 1)] = False
+example_dict_lossy[("numpyflat", "Panel", 1)] = True
 
 cols = [f"var_{i}" for i in range(1)]
 Xlist = [
@@ -173,6 +175,8 @@ example_dict_metadata[("Panel", 1)] = {
     "is_empty": False,
     "has_nans": False,
     "n_instances": 3,
+    "n_features": 1,
+    "feature_names": ["var_0"],
 }
 
 ###
@@ -184,12 +188,12 @@ X = np.array(
 )
 
 example_dict[("numpy3D", "Panel", 2)] = X
-example_dict_lossy[("numpy3D", "Panel", 2)] = False
+example_dict_lossy[("numpy3D", "Panel", 2)] = True
 
 X = np.array([[4, 5, 6]], dtype=np.int64)
 
 example_dict[("numpyflat", "Panel", 2)] = X
-example_dict_lossy[("numpyflat", "Panel", 2)] = False
+example_dict_lossy[("numpyflat", "Panel", 2)] = True
 
 cols = [f"var_{i}" for i in range(1)]
 Xlist = [
@@ -238,6 +242,8 @@ example_dict_metadata[("Panel", 2)] = {
     "is_empty": False,
     "has_nans": False,
     "n_instances": 1,
+    "n_features": 1,
+    "feature_names": ["var_0"],
 }
 
 ###
@@ -266,4 +272,6 @@ example_dict_metadata[("Panel", 3)] = {
     "is_empty": False,
     "has_nans": False,
     "n_instances": 3,
+    "n_features": 1,
+    "feature_names": ["var_0"],
 }
diff --git a/sktime/datatypes/_series/_check.py b/sktime/datatypes/_series/_check.py
index 9110c7d58..fe3a47b6e 100644
--- a/sktime/datatypes/_series/_check.py
+++ b/sktime/datatypes/_series/_check.py
@@ -31,6 +31,8 @@ metadata: dict - metadata about obj if valid, otherwise None
         "is_equally_spaced": bool, True iff series index is equally spaced
         "is_empty": bool, True iff series has no variables or no instances
         "has_nans": bool, True iff the series contains NaN values
+        "n_features": int, number of variables in series
+        "feature_names": list of int or object, names of variables in series
 """
 
 __author__ = ["fkiraly"]
@@ -67,6 +69,10 @@ def check_pddataframe_series(obj, return_metadata=False, var_name="obj"):
         metadata["is_empty"] = len(index) < 1 or len(obj.columns) < 1
     if _req("is_univariate", return_metadata):
         metadata["is_univariate"] = len(obj.columns) < 2
+    if _req("n_features", return_metadata):
+        metadata["n_features"] = len(obj.columns)
+    if _req("feature_names", return_metadata):
+        metadata["feature_names"] = obj.columns.to_list()
 
     # check that columns are unique
     if not obj.columns.is_unique:
@@ -125,6 +131,13 @@ def check_pdseries_series(obj, return_metadata=False, var_name="obj"):
         metadata["is_empty"] = len(index) < 1
     if _req("is_univariate", return_metadata):
         metadata["is_univariate"] = True
+    if _req("n_features", return_metadata):
+        metadata["n_features"] = 1
+    if _req("feature_names", return_metadata):
+        if not hasattr(obj, "name") or obj.name is None:
+            metadata["feature_names"] = [0]
+        else:
+            metadata["feature_names"] = [obj.name]
 
     # check that dtype is not object
     if "object" == obj.dtypes:
@@ -178,12 +191,20 @@ def check_numpy_series(obj, return_metadata=False, var_name="obj"):
             metadata["is_empty"] = len(obj) < 1 or obj.shape[1] < 1
         if _req("is_univariate", return_metadata):
             metadata["is_univariate"] = obj.shape[1] < 2
+        if _req("n_features", return_metadata):
+            metadata["n_features"] = obj.shape[1]
+        if _req("feature_names", return_metadata):
+            metadata["feature_names"] = list(range(obj.shape[1]))
     elif len(obj.shape) == 1:
         # we now know obj is a 1D np.ndarray
         if _req("is_empty", return_metadata):
             metadata["is_empty"] = len(obj) < 1
         if _req("is_univariate", return_metadata):
             metadata["is_univariate"] = True
+        if _req("n_features", return_metadata):
+            metadata["n_features"] = 1
+        if _req("feature_names", return_metadata):
+            metadata["feature_names"] = [0]
     else:
         msg = f"{var_name} must be 1D or 2D numpy.ndarray, but found {len(obj.shape)}D"
         return ret(False, msg, None, return_metadata)
@@ -271,6 +292,16 @@ if _check_soft_dependencies("xarray", severity="none"):
         # The second dimension is the set of columns
         if _req("is_univariate", return_metadata):
             metadata["is_univariate"] = len(obj.dims) == 1 or len(obj[obj.dims[1]]) < 2
+        if len(obj.dims) == 1:
+            if _req("n_features", return_metadata):
+                metadata["n_features"] = 1
+            if _req("feature_names", return_metadata):
+                metadata["feature_names"] = [0]
+        else:
+            if _req("n_features", return_metadata):
+                metadata["n_features"] = len(obj[obj.dims[1]])
+            if _req("feature_names", return_metadata):
+                metadata["feature_names"] = obj.indexes[obj.dims[1]].to_list()
 
         # check that columns are unique
         if not len(obj.dims) == len(set(obj.dims)):
diff --git a/sktime/datatypes/_series/_examples.py b/sktime/datatypes/_series/_examples.py
index 9cb5cd8bf..f0a042bf1 100644
--- a/sktime/datatypes/_series/_examples.py
+++ b/sktime/datatypes/_series/_examples.py
@@ -80,6 +80,8 @@ example_dict_metadata[("Series", 0)] = {
     "is_equally_spaced": True,
     "is_empty": False,
     "has_nans": False,
+    "n_features": 1,
+    "feature_names": ["a"],
 }
 
 ###
@@ -121,6 +123,8 @@ example_dict_metadata[("Series", 1)] = {
     "is_equally_spaced": True,
     "is_empty": False,
     "has_nans": False,
+    "n_features": 2,
+    "feature_names": ["a", "b"],
 }
 
 
@@ -165,6 +169,8 @@ example_dict_metadata[("Series", 2)] = {
     "is_equally_spaced": True,
     "is_empty": False,
     "has_nans": False,
+    "n_features": 2,
+    "feature_names": ["a", "b"],
 }
 
 ###
@@ -201,4 +207,6 @@ example_dict_metadata[("Series", 3)] = {
     "is_equally_spaced": True,
     "is_empty": False,
     "has_nans": False,
+    "n_features": 1,
+    "feature_names": ["a"],
 }
diff --git a/sktime/forecasting/base/_fh.py b/sktime/forecasting/base/_fh.py
index 01cbfca43..ff5341c96 100644
--- a/sktime/forecasting/base/_fh.py
+++ b/sktime/forecasting/base/_fh.py
@@ -870,6 +870,12 @@ def _to_absolute(fh: ForecastingHorizon, cutoff) -> ForecastingHorizon:
         _check_cutoff(cutoff, relative)
         is_timestamp = isinstance(cutoff, pd.DatetimeIndex)
 
+        # remember timezone to restore it later
+        if hasattr(cutoff, "tz"):
+            old_tz = cutoff.tz
+        else:
+            old_tz = None
+
         if is_timestamp:
             # coerce to pd.Period for reliable arithmetic operations and
             # computations of time deltas
@@ -884,6 +890,9 @@ def _to_absolute(fh: ForecastingHorizon, cutoff) -> ForecastingHorizon:
             # coerce back to DatetimeIndex after operation
             absolute = absolute.to_timestamp(fh.freq)
 
+        if old_tz is not None:
+            absolute = absolute.tz_localize(old_tz)
+
         return fh._new(absolute, is_relative=False, freq=fh.freq)
 
 
diff --git a/sktime/forecasting/base/adapters/_fbprophet.py b/sktime/forecasting/base/adapters/_fbprophet.py
index 79ac65bf0..acc32d7f6 100644
--- a/sktime/forecasting/base/adapters/_fbprophet.py
+++ b/sktime/forecasting/base/adapters/_fbprophet.py
@@ -116,7 +116,10 @@ class _ProphetAdapter(BaseForecaster):
             df["cap"] = self.growth_cap
             df["floor"] = self.growth_floor
 
-        fit_kwargs = self.fit_kwargs or {}
+        if hasattr(self, "fit_kwargs") and isinstance(self.fit_kwargs, dict):
+            fit_kwargs = self.fit_kwargs
+        else:
+            fit_kwargs = {}
         if self.verbose:
             self._forecaster.fit(df=df, **fit_kwargs)
         else:
diff --git a/sktime/forecasting/compose/__init__.py b/sktime/forecasting/compose/__init__.py
index 537afc455..b9f1296ee 100644
--- a/sktime/forecasting/compose/__init__.py
+++ b/sktime/forecasting/compose/__init__.py
@@ -18,6 +18,7 @@ __all__ = [
     "RecursiveTimeSeriesRegressionForecaster",
     "DirRecTabularRegressionForecaster",
     "DirRecTimeSeriesRegressionForecaster",
+    "DirectReductionForecaster",
     "StackingForecaster",
     "MultiplexForecaster",
     "make_reduction",
@@ -45,6 +46,7 @@ from sktime.forecasting.compose._pipeline import (
     TransformedTargetForecaster,
 )
 from sktime.forecasting.compose._reduce import (
+    DirectReductionForecaster,
     DirectTabularRegressionForecaster,
     DirectTimeSeriesRegressionForecaster,
     DirRecTabularRegressionForecaster,
diff --git a/sktime/forecasting/compose/_hierarchy_ensemble.py b/sktime/forecasting/compose/_hierarchy_ensemble.py
index 1eca3af03..12cb104ca 100644
--- a/sktime/forecasting/compose/_hierarchy_ensemble.py
+++ b/sktime/forecasting/compose/_hierarchy_ensemble.py
@@ -285,7 +285,8 @@ class HierarchyEnsembleForecaster(_HeterogenousEnsembleForecaster):
                 if counter == 0:
                     nodes = mi
                 else:
-                    nodes.append(mi)
+                    # For nlevels = 2, 'nodes' is pd.Index object (L286)
+                    nodes = nodes.append(mi)
             else:
                 node_l = []
                 for i in range(len(node)):
@@ -495,6 +496,7 @@ class HierarchyEnsembleForecaster(_HeterogenousEnsembleForecaster):
                     raise ValueError("Nodes cannot be empty.")
                 if z.index.nlevels == 2:
                     nodes_ix = pd.Index(nodes)
+                    nodes_t += nodes
                 else:
                     nodes_l = []
                     for i in range(len(nodes)):
diff --git a/sktime/forecasting/compose/_reduce.py b/sktime/forecasting/compose/_reduce.py
index 9f179fc26..f989bf480 100644
--- a/sktime/forecasting/compose/_reduce.py
+++ b/sktime/forecasting/compose/_reduce.py
@@ -1725,7 +1725,7 @@ class DirectReductionForecaster(BaseForecaster, _ReducerMixin):
         to obtain a prediction for `y(c+h)`, for each `h` in the forecasting horizon
     if `X_treatment = "shifted":
         applies fitted estimator's predict to
-        features = `y(c)`, `y(c-1)`, ..., `y(c-window_size)`, if provided: `X(t)`
+        features = `y(c)`, `y(c-1)`, ..., `y(c-window_size)`, if provided: `X(c)`
         to obtain prediction for `y(c+h_1)`, ..., `y(c+h_k)` for `h_j` in forec. horizon
 
     Parameters
@@ -2385,32 +2385,38 @@ class RecursiveReductionForecaster(BaseForecaster, _ReducerMixin):
 class YfromX(BaseForecaster, _ReducerMixin):
     """Simple reduction predicting endogeneous from concurrent exogeneous variables.
 
-    Tabulates all seen `X` and `y` by time index and applies
+    Tabulates all seen ``X`` and ``y`` by time index and applies
     tabular supervised regression.
 
-    In `fit`, given endogeneous time series `y` and exogeneous `X`:
-        fits `estimator` to feature-label pairs as defined as follows.
+    In ``fit``, given endogeneous time series ``y`` and exogeneous ``X``:
+    fits ``estimator`` to feature-label pairs as defined as follows.
+
+    features = :math:`y(t)`, labels: :math:`X(t)`
+    ranging over all :math:`t` where the above have been observed (are in the index)
 
-        features = :math:`y(t)`, labels: :math:`X(t)`
-        ranging over all :math:`t` where the above have been observed (are in the index)
+    In ``predict``, at a time :math:`t` in the forecasting horizon, uses ``estimator``
+    to predict :math:`y(t)`, from labels: :math:`X(t)`
 
-    In `predict`, at a time :math:`t` in the forecasting horizon, uses `estimator`
-        to predict :math:`y(t)`, from labels: :math:`X(t)`
+    If regressor is ``skpro`` probabilistic regressor, and has ``predict_interval`` etc,
+    uses ``estimator`` to predict :math:`y(t)`, from labels: :math:`X(t)`,
+    passing on the ``predict_interval`` etc arguments.
 
-    If no exogeneous data is provided, will predict the mean of `y` seen in `fit`.
+    If no exogeneous data is provided, will predict the mean of ``y`` seen in ``fit``.
 
     In order to use a fit not on the entire historical data
-    and update periodically, combine this with `UpdateRefitsEvery`.
+    and update periodically, combine this with ``UpdateRefitsEvery``.
 
-    In order to deal with missing data, combine this with `Imputer`.
+    In order to deal with missing data, combine this with ``Imputer``.
 
     To construct an custom direct reducer,
-    combine with `YtoX`, `Lag`, or `ReducerTransform`.
+    combine with ``YtoX``, ``Lag``, or ``ReducerTransform``.
 
     Parameters
     ----------
-    estimator : sklearn regressor, must be compatible with sklearn interface
+    estimator : sklearn regressor or skpro probabilistic regressor,
+        must be compatible with sklearn or skpro interface
         tabular regression algorithm used in reduction algorithm
+        if skpro regressor, resulting forecaster will have probabilistic capability
     pooling : str, one of ["local", "global", "panel"], optional, default="local"
         level on which data are pooled to fit the supervised regression model
         "local" = unit/instance level, one reduced model per lowest hierarchy level
diff --git a/sktime/param_est/compose/__init__.py b/sktime/param_est/compose/__init__.py
new file mode 100644
index 000000000..10423cfb2
--- /dev/null
+++ b/sktime/param_est/compose/__init__.py
@@ -0,0 +1,7 @@
+"""Composition involving parameter estimators."""
+# copyright: sktime developers, BSD-3-Clause License (see LICENSE file)
+from sktime.param_est.compose._func_fitter import FunctionParamFitter
+from sktime.param_est.compose._pipeline import ParamFitterPipeline
+
+__author__ = ["fkiraly", "tpvasconcelos"]
+__all__ = ["ParamFitterPipeline", "FunctionParamFitter"]
diff --git a/sktime/param_est/compose/_func_fitter.py b/sktime/param_est/compose/_func_fitter.py
new file mode 100644
index 000000000..c61e4dfe3
--- /dev/null
+++ b/sktime/param_est/compose/_func_fitter.py
@@ -0,0 +1,162 @@
+"""Implements FunctionParamFitter, a class to create custom parameter fitters."""
+# copyright: sktime developers, BSD-3-Clause License (see LICENSE file)
+from sktime.datatypes import ALL_TIME_SERIES_MTYPES
+from sktime.param_est.base import BaseParamFitter
+
+__author__ = ["tpvasconcelos"]
+__all__ = ["FunctionParamFitter"]
+
+
+class FunctionParamFitter(BaseParamFitter):
+    r"""Constructs a parameter fitter from an arbitrary callable.
+
+    A FunctionParamFitter forwards its X argument to a user-defined
+    function (or callable object) and sets the result of this function
+    to the ``param`` attribute. This can be useful for stateless
+    estimators such as simple conditional parameter selectors.
+
+    Note: If a lambda function is used as the ``func``, then the
+    resulting estimator will not be pickleable.
+
+    Parameters
+    ----------
+    param : str
+        The name of the parameter to set.
+    func : callable (X: X_type, **kwargs) -> Any
+        The callable to use for the parameter estimation. This will be
+        passed the same arguments as estimator, with args and kwargs
+        forwarded.
+    kw_args : dict, default=None
+        Dictionary of additional keyword arguments to pass to func.
+    X_type : str, one of "pd.DataFrame, pd.Series, np.ndarray", or list thereof
+        default = ["pd.DataFrame", "pd.Series", "np.ndarray"]
+        list of types that func is assumed to allow for X (see signature above)
+        if X passed to transform/inverse_transform is not on the list,
+            it will be converted to the first list element before passed to funcs
+
+    See Also
+    --------
+    sktime.param_est.plugin.PluginParamsForecaster :
+        Plugs parameters from a parameter estimator into a forecaster.
+    sktime.forecasting.compose.MultiplexForecaster :
+        MultiplexForecaster for selecting among different models.
+
+    Examples
+    --------
+    This class could be used to contruct a parameter estimator that
+    selects a forecaster based on the input data's length. The
+    selected forecaster can be stored in the ``selected_forecaster_``
+    attribute, which can be then passed down to a
+    :class:`~sktime.forecasting.compose.MultiplexForecaster` via a
+    :class:`~sktime.param_est.plugin.PluginParamsForecaster`.
+
+    >>> import numpy as np
+    >>> param_est = FunctionParamFitter(
+    ...     param="selected_forecaster",
+    ...     func=(
+    ...         lambda X, threshold: "naive-seasonal"
+    ...         if len(X) >= threshold
+    ...         else "naive-last"
+    ...     ),
+    ...     kw_args={"threshold": 7},
+    ... )
+    >>> param_est.fit(np.asarray([1, 2, 3, 4]))
+    FunctionParamFitter(...)
+    >>> param_est.get_fitted_params()
+    {'selected_forecaster': 'naive-last'}
+    >>> param_est.fit(np.asarray([1, 2, 3, 4, 5, 6, 7]))
+    FunctionParamFitter(...)
+    >>> param_est.get_fitted_params()
+    {'selected_forecaster': 'naive-seasonal'}
+
+    The full conditional forecaster selection pipeline could look
+    like this:
+
+    >>> from sktime.forecasting.compose import MultiplexForecaster
+    >>> from sktime.forecasting.naive import NaiveForecaster
+    >>> from sktime.param_est.plugin import PluginParamsForecaster
+    >>> forecaster = PluginParamsForecaster(
+    ...     param_est=param_est,
+    ...     forecaster=MultiplexForecaster(
+    ...         forecasters=[
+    ...             ("naive-last", NaiveForecaster()),
+    ...             ("naive-seasonal", NaiveForecaster(sp=7)),
+    ...         ]
+    ...     ),
+    ... )
+    >>> forecaster.fit(np.asarray([1, 2, 3, 4]))
+    PluginParamsForecaster(...)
+    >>> forecaster.predict(fh=[1,2,3])
+    array([[4.],
+           [4.],
+           [4.]])
+    >>> forecaster.fit(np.asarray([1, 2, 3, 4, 5, 6, 7]))
+    PluginParamsForecaster(...)
+    >>> forecaster.predict(fh=[1,2,3])
+    array([[1.],
+           [2.],
+           [3.]])
+    """
+
+    _tags = {
+        "X_inner_mtype": ALL_TIME_SERIES_MTYPES,
+        "scitype:X": ["Series", "Panel", "Hierarchical"],
+        "capability:missing_values": True,
+        "capability:multivariate": False,
+    }
+
+    def __init__(self, param, func, kw_args=None, X_type=None):
+        self.param = param
+        self.func = func
+        self.kw_args = kw_args
+        self.X_type = X_type
+        super().__init__()
+
+        if X_type is not None:
+            self.set_tags(X_inner_mtype=X_type)
+
+    def _fit(self, X):
+        """Fit estimator and estimate parameters.
+
+        private _fit containing the core logic, called from fit
+
+        Writes to self:
+            Sets fitted model attributes ending in "_".
+
+        Parameters
+        ----------
+        X : guaranteed to be of a type in self.get_tag("X_inner_mtype")
+            Time series to which to fit the estimator.
+
+        Returns
+        -------
+        self : reference to self
+        """
+        param = self.param.rstrip("_") + "_"
+        setattr(self, param, self.func(X, **(self.kw_args or {})))
+        return self
+
+    @classmethod
+    def get_test_params(cls, parameter_set="default"):
+        """Return testing parameter settings for the estimator.
+
+        Parameters
+        ----------
+        parameter_set : str, default="default"
+            Name of the set of test parameters to return, for use in tests. If no
+            special parameters are defined for a value, will return `"default"` set.
+            There are no reserved values for parameter estimators.
+
+        Returns
+        -------
+        params : dict or list of dict, default = {}
+            Parameters to create testing instances of the class
+            Each dict are parameters to construct an "interesting" test instance, i.e.,
+            `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
+            `create_test_instance` uses the first (or only) dictionary in `params`
+        """
+        params = [
+            {"param": "param", "func": lambda X: "foo"},
+            {"param": "param", "func": lambda X, kwarg: "foo", "kw_args": {"kwarg": 1}},
+        ]
+        return params
diff --git a/sktime/param_est/compose.py b/sktime/param_est/compose/_pipeline.py
similarity index 99%
rename from sktime/param_est/compose.py
rename to sktime/param_est/compose/_pipeline.py
index 7c4d3d8e4..bb1bff045 100644
--- a/sktime/param_est/compose.py
+++ b/sktime/param_est/compose/_pipeline.py
@@ -1,4 +1,7 @@
-"""Composition involving parameter estimators."""
+"""Implements ParamFitterPipeline.
+
+A class to create a pipeline of transformers and a parameter estimator.
+"""
 # copyright: sktime developers, BSD-3-Clause License (see LICENSE file)
 from sktime.base import _HeterogenousMetaEstimator
 from sktime.param_est.base import BaseParamFitter
@@ -8,7 +11,6 @@ from sktime.transformations.compose import TransformerPipeline
 __author__ = ["fkiraly"]
 __all__ = ["ParamFitterPipeline"]
 
-
 # we ensure that internally we convert to pandas for now
 SUPPORTED_MTYPES = ["pd.DataFrame", "pd.Series", "pd-multiindex", "pd_multiindex_hier"]
 
diff --git a/sktime/param_est/stationarity/_arch.py b/sktime/param_est/stationarity/_arch.py
index 0b6f4ff53..8944f3cda 100644
--- a/sktime/param_est/stationarity/_arch.py
+++ b/sktime/param_est/stationarity/_arch.py
@@ -83,7 +83,7 @@ class StationarityADFArch(BaseParamFitter):
     """
 
     _tags = {
-        "X_inner_mtype": ["pd.Series", "nd.array"],
+        "X_inner_mtype": ["pd.Series", "np.array"],
         "scitype:X": "Series",
         "python_dependencies": "arch",
     }
@@ -233,7 +233,7 @@ class StationarityDFGLS(BaseParamFitter):
     """
 
     _tags = {
-        "X_inner_mtype": ["pd.Series", "nd.array"],
+        "X_inner_mtype": ["pd.Series", "np.array"],
         "scitype:X": "Series",
         "python_dependencies": "arch",
     }
@@ -374,7 +374,7 @@ class StationarityPhillipsPerron(BaseParamFitter):
     """
 
     _tags = {
-        "X_inner_mtype": ["pd.Series", "nd.array"],
+        "X_inner_mtype": ["pd.Series", "np.array"],
         "scitype:X": "Series",
         "python_dependencies": "arch",
     }
@@ -507,7 +507,7 @@ class StationarityKPSSArch(BaseParamFitter):
     """
 
     _tags = {
-        "X_inner_mtype": ["pd.Series", "nd.array"],
+        "X_inner_mtype": ["pd.Series", "np.array"],
         "scitype:X": "Series",
         "python_dependencies": "arch",
     }
@@ -647,7 +647,7 @@ class StationarityZivotAndrews(BaseParamFitter):
     """
 
     _tags = {
-        "X_inner_mtype": ["pd.Series", "nd.array"],
+        "X_inner_mtype": ["pd.Series", "np.array"],
         "scitype:X": "Series",
         "python_dependencies": "arch",
     }
@@ -794,7 +794,7 @@ class StationarityVarianceRatio(BaseParamFitter):
     """
 
     _tags = {
-        "X_inner_mtype": ["pd.Series", "nd.array"],
+        "X_inner_mtype": ["pd.Series", "np.array"],
         "scitype:X": "Series",
         "python_dependencies": "arch",
     }
diff --git a/sktime/registry/_scitype.py b/sktime/registry/_scitype.py
index 054dcd4ba..8ef58e6ac 100644
--- a/sktime/registry/_scitype.py
+++ b/sktime/registry/_scitype.py
@@ -40,7 +40,10 @@ def scitype(obj, force_single_scitype=True, coerce_to_list=False):
         else:
             tag_type = obj.get_tag("object_type", None, raise_error=False)
         if tag_type is not None:
-            return tag_type
+            if coerce_to_list and not isinstance(tag_type, list):
+                return [tag_type]
+            else:
+                return tag_type
 
     # if the tag is not present, determine scitype from legacy base class logic
     if isclass(obj):
diff --git a/sktime/transformations/compose/_pipeline.py b/sktime/transformations/compose/_pipeline.py
index 2da71e4d1..8ad5011bd 100644
--- a/sktime/transformations/compose/_pipeline.py
+++ b/sktime/transformations/compose/_pipeline.py
@@ -145,7 +145,6 @@ class TransformerPipeline(_HeterogenousMetaEstimator, BaseTransformer):
         # abbreviate for readability
         ests = self.steps_
         first_trafo = ests[0][1]
-        last_trafo = ests[-1][1]
 
         # input mtype and input type are as of the first estimator
         self.clone_tags(first_trafo, ["scitype:transform-input"])
@@ -153,7 +152,7 @@ class TransformerPipeline(_HeterogenousMetaEstimator, BaseTransformer):
         # if "Primitives" occur in the middle, then output is set to that too
         # this is in a case where "Series-to-Series" is applied to primitive df
         #   e.g., in a case of pipelining with scikit-learn transformers
-        last_out = last_trafo.get_tag("scitype:transform-output")
+        last_out = self._trafo_out()
         self._anytagis_then_set(
             "scitype:transform-output", "Primitives", last_out, ests
         )
@@ -397,3 +396,63 @@ class TransformerPipeline(_HeterogenousMetaEstimator, BaseTransformer):
         params3 = {"steps": [("foo", t1), ("foo", t2), ("foo_1", t3)]}
 
         return [params1, params2, params3]
+
+    def _to_dim(self, x):
+        """Translate scitype:transform-input or output tag to data dimension.
+
+        Parameters
+        ----------
+        x : str, one of "Series", "Panel", "Hierarchical"
+            scitype:transform-input or output tag
+
+        Returns
+        -------
+        int
+            data dimension corresponding to x
+        """
+        if x == "Series":
+            return 1
+        elif x == "Panel":
+            return 2
+        else:
+            return 3
+
+    def _dim_diff(self, obj):
+        """Compute difference between input and output dimension."""
+        inp = obj.get_tag("scitype:transform-input")
+        out = obj.get_tag("scitype:transform-output")
+        return self._to_dim(out) - self._to_dim(inp)
+
+    def _dim_to_sci(self, d):
+        """Translate data dimension to scitype:transform-output tag.
+
+        Parameters
+        ----------
+        d : int
+            data dimension
+
+        Returns
+        -------
+        str
+            scitype:transform-output tag corresponding to data dimension
+        """
+        if d <= 1:
+            return "Series"
+        elif d == 2:
+            return "Panel"
+        else:
+            return "Hierarchical"
+
+    def _trafo_out(self):
+        """Infer scitype:transform-output tag.
+
+        Uses the self.steps_ attribute, assumes it is initialized already.
+        """
+        ests = self.steps_
+        est_list = [x[1] for x in ests]
+        inp_dim = self._to_dim(est_list[0].get_tag("scitype:transform-input"))
+        out_dim = inp_dim
+        for est in est_list:
+            dim_diff = self._dim_diff(est)
+            out_dim = out_dim + dim_diff
+        return self._dim_to_sci(out_dim)
diff --git a/sktime/transformations/panel/catch22.py b/sktime/transformations/panel/catch22.py
index 53ac4e290..c12814ea8 100644
--- a/sktime/transformations/panel/catch22.py
+++ b/sktime/transformations/panel/catch22.py
@@ -403,9 +403,7 @@ class Catch22(BaseTransformer):
         return c22
 
     def _transform_single_feature(self, X, feature, case_id=None):
-        if isinstance(feature, (int, np.integer)) or isinstance(
-            feature, (float, float)
-        ):
+        if isinstance(feature, (int, np.integer)) or isinstance(feature, float):
             if feature > 21 or feature < 0:
                 raise ValueError("Invalid catch22 feature ID")
         elif isinstance(feature, str):
diff --git a/sktime/transformations/panel/rocket/_minirocket.py b/sktime/transformations/panel/rocket/_minirocket.py
index 125440374..d44465d76 100644
--- a/sktime/transformations/panel/rocket/_minirocket.py
+++ b/sktime/transformations/panel/rocket/_minirocket.py
@@ -20,6 +20,14 @@ class MiniRocket(BaseTransformer):
     MiniRocket is for unviariate time series only.  Use class MiniRocketMultivariate
     for multivariate time series.
 
+    This transformer fits one set of paramereters per individual series,
+    and applies them to series of the same number in the test set.
+
+    To fit and transform at the same time,
+    without an identification of fit/transform instances,
+    wrap this transformer in ``FitInTransform``,
+    from ``sktime.transformations.compose``.
+
     Parameters
     ----------
     num_kernels : int, default=10,000
diff --git a/sktime/transformations/panel/rocket/_minirocket_multivariate.py b/sktime/transformations/panel/rocket/_minirocket_multivariate.py
index 74569fd37..17f1e14d8 100644
--- a/sktime/transformations/panel/rocket/_minirocket_multivariate.py
+++ b/sktime/transformations/panel/rocket/_minirocket_multivariate.py
@@ -19,6 +19,15 @@ class MiniRocketMultivariate(BaseTransformer):
     convolutions with six of one weight, three of the second weight to seed dilations.
     MiniRocketMultivariate works with univariate and multivariate time series.
 
+    This transformer fits one set of paramereters per individual series,
+    and applies the transform with fitted parameter i to the i-th series in transform.
+    Vanilla use requies same number of series in fit and transform.
+
+    To fit and transform series at the same time,
+    without an identification of fit/transform instances,
+    wrap this transformer in ``FitInTransform``,
+    from ``sktime.transformations.compose``.
+
     Parameters
     ----------
     num_kernels : int, default=10,000
diff --git a/sktime/transformations/panel/rocket/_minirocket_multivariate_variable.py b/sktime/transformations/panel/rocket/_minirocket_multivariate_variable.py
index ada0429d2..0717394b0 100644
--- a/sktime/transformations/panel/rocket/_minirocket_multivariate_variable.py
+++ b/sktime/transformations/panel/rocket/_minirocket_multivariate_variable.py
@@ -25,6 +25,15 @@ class MiniRocketMultivariateVariable(BaseTransformer):
     performance, use the sktime class MiniRocket for univariate input,
     and MiniRocketMultivariate to equal length multivariate input.
 
+    This transformer fits one set of paramereters per individual series,
+    and applies the transform with fitted parameter i to the i-th series in transform.
+    Vanilla use requies same number of series in fit and transform.
+
+    To fit and transform series at the same time,
+    without an identification of fit/transform instances,
+    wrap this transformer in ``FitInTransform``,
+    from ``sktime.transformations.compose``.
+
     Parameters
     ----------
     num_kernels : int, default=10,000
diff --git a/sktime/transformations/panel/rocket/_multirocket.py b/sktime/transformations/panel/rocket/_multirocket.py
index dbb904c44..6240cf592 100644
--- a/sktime/transformations/panel/rocket/_multirocket.py
+++ b/sktime/transformations/panel/rocket/_multirocket.py
@@ -20,6 +20,15 @@ class MultiRocket(BaseTransformer):
     Positive Values (LSPV). This version is for univariate time series only. Use class
     MultiRocketMultivariate for multivariate input.
 
+    This transformer fits one set of paramereters per individual series,
+    and applies the transform with fitted parameter i to the i-th series in transform.
+    Vanilla use requies same number of series in fit and transform.
+
+    To fit and transform series at the same time,
+    without an identification of fit/transform instances,
+    wrap this transformer in ``FitInTransform``,
+    from ``sktime.transformations.compose``.
+
     Parameters
     ----------
     num_kernels : int, default = 6,250
@@ -45,7 +54,6 @@ class MultiRocket(BaseTransformer):
         parameter (dilations, num_features_per_dilation, biases) for
         transformation of input X1 = np.diff(X, 1)
 
-
     See Also
     --------
     MultiRocketMultivariate, MiniRocket, MiniRocketMultivariate, Rocket
diff --git a/sktime/transformations/panel/rocket/_multirocket_multivariate.py b/sktime/transformations/panel/rocket/_multirocket_multivariate.py
index 7ebe0f7a6..936651875 100644
--- a/sktime/transformations/panel/rocket/_multirocket_multivariate.py
+++ b/sktime/transformations/panel/rocket/_multirocket_multivariate.py
@@ -16,6 +16,15 @@ class MultiRocketMultivariate(BaseTransformer):
     Values (MPV); Mean of Indices of Positive Values (MIPV); and Longest Stretch of
     Positive Values (LSPV). This version is the multivariate version.
 
+    This transformer fits one set of paramereters per individual series,
+    and applies the transform with fitted parameter i to the i-th series in transform.
+    Vanilla use requies same number of series in fit and transform.
+
+    To fit and transform series at the same time,
+    without an identification of fit/transform instances,
+    wrap this transformer in ``FitInTransform``,
+    from ``sktime.transformations.compose``.
+
     Parameters
     ----------
     num_kernels : int, default=6,250
@@ -41,7 +50,6 @@ class MultiRocketMultivariate(BaseTransformer):
         parameter (dilations, num_features_per_dilation, biases) for
         transformation of input X1 = np.diff(X, 1)
 
-
     See Also
     --------
     MultiRocketMultivariate, MiniRocket, MiniRocketMultivariate, Rocket
diff --git a/sktime/transformations/panel/rocket/_rocket.py b/sktime/transformations/panel/rocket/_rocket.py
index 8218b26e4..c206e69fa 100755
--- a/sktime/transformations/panel/rocket/_rocket.py
+++ b/sktime/transformations/panel/rocket/_rocket.py
@@ -18,6 +18,14 @@ class Rocket(BaseTransformer):
     dilation. It transforms the time series with two features per kernel. The first
     feature is global max pooling and the second is proportion of positive values.
 
+    This transformer fits one set of paramereters per individual series,
+    and applies the transform with fitted parameter i to the i-th series in transform.
+    Vanilla use requies same number of series in fit and transform.
+
+    To fit and transform series at the same time,
+    without an identification of fit/transform instances,
+    wrap this transformer in ``FitInTransform``,
+    from ``sktime.transformations.compose``.
 
     Parameters
     ----------
diff --git a/sktime/transformations/panel/tsfresh.py b/sktime/transformations/panel/tsfresh.py
index 48ce7661c..7378fe1bb 100644
--- a/sktime/transformations/panel/tsfresh.py
+++ b/sktime/transformations/panel/tsfresh.py
@@ -98,6 +98,12 @@ class _TSFreshFeatureExtractor(BaseTransformer):
                 if value is not None:
                     extraction_params[name] = value
 
+            # Fixes key mismatch between tsfresh and sktime
+            # tsfresh uses "profile" while sktime uses "profiling"
+            # This fix keeps compatibility
+            if name == "profile":
+                extraction_params[name] = self.profiling
+
         self.n_jobs = n_jobs
 
         # Convert convenience string arguments to tsfresh parameters classes
@@ -708,4 +714,9 @@ class TSFreshRelevantFeatureExtractor(_TSFreshFeatureExtractor):
             "show_warnings": False,
             "fdr_level": 0.01,
         }
-        return params
+        params2 = {
+            "default_fc_parameters": "minimal",
+            "disable_progressbar": True,
+            "show_warnings": False,
+        }
+        return [params, params2]
diff --git a/sktime/transformations/series/func_transform.py b/sktime/transformations/series/func_transform.py
index 0d853141d..d8ed52324 100644
--- a/sktime/transformations/series/func_transform.py
+++ b/sktime/transformations/series/func_transform.py
@@ -18,12 +18,12 @@ class FunctionTransformer(BaseTransformer):
     r"""Constructs a transformer from an arbitrary callable.
 
     A FunctionTransformer forwards its y (and optionally X) arguments to a
-    user-defined function or function object and returns the result of this
+    user-defined function (or callable object) and returns the result of this
     function. This is useful for stateless transformations such as taking the
     log of frequencies, doing custom scaling, etc.
 
-    Note: If a lambda is used as the function, then the resulting
-    transformer will not be pickleable.
+    Note: If a lambda function is used as the ``func``, then the
+    resulting transformer will not be pickleable.
 
     Parameters
     ----------
diff --git a/sktime/utils/plotting.py b/sktime/utils/plotting.py
index b23f9cce3..8312c8bda 100644
--- a/sktime/utils/plotting.py
+++ b/sktime/utils/plotting.py
@@ -449,7 +449,7 @@ def plot_windows(cv, y, title="", ax=None):
 
     # create figure if no ax provided for plotting
     if _ax_kwarg_is_none:
-        fig, ax = plt.subplots(1, figsize=plt.figaspect(0.25))
+        fig, ax = plt.subplots(figsize=plt.figaspect(0.3))
 
     train_windows, test_windows = _get_windows(cv, y)
 
@@ -463,8 +463,6 @@ def plot_windows(cv, y, title="", ax=None):
 
     train_color, test_color = sns.color_palette("colorblind")[:2]
 
-    fig, ax = plt.subplots(figsize=plt.figaspect(0.3))
-
     for i in range(n_splits):
         train = train_windows[i]
         test = test_windows[i]


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 119f4bbb7..282bb2233 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -13,7 +13,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
-      - uses: actions/setup-python@v4
+      - uses: actions/setup-python@v5
       - id: file_changes
         uses: trilom/file-changes-action@v1.2.4
         with:
@@ -33,7 +33,7 @@ jobs:
     steps:
       - uses: actions/checkout@v4
       - name: Set up Python
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@v5
         with:
           python-version: 3.9
       - name: Install dependencies
@@ -50,7 +50,7 @@ jobs:
     steps:
       - uses: actions/checkout@v4
       - name: Set up Python
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@v5
         with:
           python-version: 3.9
       - name: Display Python version
@@ -70,7 +70,7 @@ jobs:
     steps:
       - uses: actions/checkout@v4
       - name: Set up Python
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@v5
         with:
           python-version: 3.9
       - name: Display Python version
@@ -97,7 +97,7 @@ jobs:
       - run: git fetch --depth 1
 
       - name: Set up Python
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@v5
         with:
           python-version: 3.9
 
@@ -120,7 +120,7 @@ jobs:
     steps:
       - uses: actions/checkout@v4
       - name: Set up Python
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@v5
         with:
           python-version: 3.9
       - name: Display Python version
@@ -147,7 +147,7 @@ jobs:
       - run: git fetch --depth 1
 
       - name: Set up Python
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@v5
         with:
           python-version: 3.9
       - name: Display Python version
@@ -185,7 +185,7 @@ jobs:
       - run: git fetch --depth 1
 
       - name: Set up Python
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@v5
         with:
           python-version: ${{ matrix.python-version }}
 
@@ -223,7 +223,7 @@ jobs:
       - run: git fetch --depth 1
 
       - name: Set up Python
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@v5
         with:
           python-version: ${{ matrix.python-version }}
 
diff --git a/.github/workflows/test_all.yml b/.github/workflows/test_all.yml
index e00cd1265..334502190 100644
--- a/.github/workflows/test_all.yml
+++ b/.github/workflows/test_all.yml
@@ -11,7 +11,7 @@ jobs:
       - name: repository checkout step
         uses: actions/checkout@v4
       - name: python environment step
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@v5
         with:
           python-version: "3.8"
       - name: install pre-commit
@@ -109,7 +109,7 @@ jobs:
       - name: update local shallow clone
         run: git fetch --depth 1
       - name: create python virtual environment
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@v5
         with:
           python-version: ${{ matrix.python-version }}
       - name: install core, test and all soft dependencies
diff --git a/.github/workflows/test_code_quality.yml b/.github/workflows/test_code_quality.yml
index 8e2b60ee1..48e90042a 100644
--- a/.github/workflows/test_code_quality.yml
+++ b/.github/workflows/test_code_quality.yml
@@ -11,7 +11,7 @@ jobs:
       - name: repository checkout step
         uses: actions/checkout@v4
       - name: python environment step
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@v5
         with:
           python-version: "3.8"
       - name: install pre-commit
diff --git a/.github/workflows/test_components.yml b/.github/workflows/test_components.yml
index 417c34f03..33523721f 100644
--- a/.github/workflows/test_components.yml
+++ b/.github/workflows/test_components.yml
@@ -122,7 +122,7 @@ jobs:
       - name: update local shallow clone
         run: git fetch --depth 1
       - name: create python virtual environment
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@v5
         with:
           python-version: ${{ matrix.python-version }}
       - name: install core, test and all soft dependencies
diff --git a/sktime/datatypes/tests/test_check.py b/sktime/datatypes/tests/test_check.py
index 99d77fb91..85d93be4e 100644
--- a/sktime/datatypes/tests/test_check.py
+++ b/sktime/datatypes/tests/test_check.py
@@ -217,7 +217,7 @@ def test_check_metadata_inference(scitype, mtype, fixture_index):
     error if check itself raises an error
     """
     # retrieve fixture for checking
-    fixture, _, expected_metadata = get_examples(
+    fixture, lossy, expected_metadata = get_examples(
         mtype=mtype, as_scitype=scitype, return_metadata=True
     ).get(fixture_index)
 
@@ -230,6 +230,15 @@ def test_check_metadata_inference(scitype, mtype, fixture_index):
     # is_equal_index is not fully supported yet in inference
     EXCLUDE_KEYS = ["is_equal_index"]
 
+    # metadata keys to ignore if mtype is lossy
+    EXCLUDE_IF_LOSSY = [
+        "feature_names",  # lossy mtypes do not have feature names
+    ]
+
+    # if mtype is in the list, add mtype specific keys to exclude
+    if lossy:
+        EXCLUDE_KEYS += EXCLUDE_IF_LOSSY
+
     if metadata_provided:
         expected_metadata = expected_metadata.copy()
         subset_keys = set(expected_metadata.keys()).difference(EXCLUDE_KEYS)
@@ -248,6 +257,11 @@ def test_check_metadata_inference(scitype, mtype, fixture_index):
         if "scitype" in metadata:
             del metadata["scitype"]
 
+        # remove keys that are not checked
+        for key in EXCLUDE_KEYS:
+            if key in metadata:
+                del metadata[key]
+
         # currently we do not check this field in metadata inference
 
         msg = (
diff --git a/sktime/forecasting/base/tests/test_fh.py b/sktime/forecasting/base/tests/test_fh.py
index 5e5ddd9ef..c2e1d8ade 100644
--- a/sktime/forecasting/base/tests/test_fh.py
+++ b/sktime/forecasting/base/tests/test_fh.py
@@ -774,3 +774,14 @@ def test_fh_expected_pred():
         [("a", 3, 6), ("a", 3, 7), ("b", 5, 7), ("a", 3, 8), ("b", 5, 8), ("b", 5, 9)]
     )
     assert y_pred_idx.equals(y_pred_idx_expected)
+
+
+def test_tz_preserved():
+    """Test that time zone information is preserved in to_absolute.
+
+    Failure case in issue #5584.
+    """
+    cutoff = pd.Timestamp("2020-01-01", tz="utc")
+    fh_absolute = ForecastingHorizon(range(100), freq="h").to_absolute(cutoff)
+
+    assert fh_absolute[0].tz == cutoff.tz
diff --git a/sktime/forecasting/compose/tests/test_hierarchy_ensemble.py b/sktime/forecasting/compose/tests/test_hierarchy_ensemble.py
index b3a655fb4..e56194314 100644
--- a/sktime/forecasting/compose/tests/test_hierarchy_ensemble.py
+++ b/sktime/forecasting/compose/tests/test_hierarchy_ensemble.py
@@ -7,10 +7,11 @@ __author__ = ["VyomkeshVyas"]
 import numpy as np
 import pytest
 
+from sktime.base._meta import flatten
 from sktime.datatypes._utilities import get_window
 from sktime.forecasting.compose import HierarchyEnsembleForecaster
 from sktime.forecasting.naive import NaiveForecaster
-from sktime.forecasting.trend import PolynomialTrendForecaster
+from sktime.forecasting.trend import PolynomialTrendForecaster, TrendForecaster
 from sktime.transformations.hierarchical.aggregate import Aggregator
 from sktime.utils._testing.hierarchical import _bottom_hier_datagen, _make_hierarchical
 from sktime.utils.validation._dependencies import _check_soft_dependencies
@@ -145,3 +146,57 @@ def test_hierarchy_ensemble_exog(forecasters):
     estimator_instance.fit(y=y_train, X=X_train, fh=[1, 2, 3])
     estimator_instance.predict(X=X_test)
     estimator_instance.update(y=y_test, X=X_test)
+
+
+@pytest.mark.parametrize(
+    "forecasters",
+    [
+        [
+            ("trend", TrendForecaster(), ["l1_node01"]),
+            ("polytrend", PolynomialTrendForecaster(), ["l1_node02", "l1_node03"]),
+            ("naive", NaiveForecaster(), ["__total"]),
+        ],
+        [
+            (
+                "trend",
+                TrendForecaster(),
+                [("__total"), ("l1_node01"), ("l1_node02"), ("l1_node03")],
+            ),
+        ],
+    ],
+)
+@pytest.mark.parametrize("default", [NaiveForecaster(), None])
+def test_level_one_data(forecasters, default):
+    "Check for data with one level of hierarchy (excluding timepoints level)."
+    agg = Aggregator()
+
+    y = _bottom_hier_datagen(
+        no_bottom_nodes=3,
+        no_levels=1,
+        random_seed=123,
+    )
+
+    forecaster = HierarchyEnsembleForecaster(forecasters, by="node", default=default)
+
+    forecaster.fit(y, fh=[1, 2, 3])
+    actual_pred = forecaster.predict()
+
+    y = agg.fit_transform(y)
+
+    for i in range(len(forecasters)):
+        test_frcstr = forecasters[i][1].clone()
+        df = y[y.index.droplevel(-1).isin(forecaster.fitted_list[i][1])]
+        test_frcstr.fit(df, fh=[1, 2, 3])
+        test_pred = test_frcstr.predict()
+        msg = "Node predictions do not match"
+        assert np.all(actual_pred.loc[test_pred.index] == test_pred), msg
+
+    _, _, nodes = zip(*forecasters)
+    nodes = set(flatten(nodes))
+    if default is not None and len(nodes) != len(y.index.droplevel(-1).unique()):
+        def_frcstr = default
+        df = y[y.index.droplevel(-1).isin(forecaster.fitted_list[-1][1])]
+        def_frcstr.fit(df, fh=[1, 2, 3])
+        def_pred = def_frcstr.predict()
+        msg = "Node default predictions do not match"
+        assert np.all(actual_pred.loc[def_pred.index] == def_pred), msg
diff --git a/sktime/registry/tests/test_scitype.py b/sktime/registry/tests/test_scitype.py
new file mode 100644
index 000000000..00aa58979
--- /dev/null
+++ b/sktime/registry/tests/test_scitype.py
@@ -0,0 +1,37 @@
+"""Tests for scitype typipng function."""
+
+import pytest
+
+from sktime.registry._scitype import scitype
+
+
+@pytest.mark.parametrize("coerce_to_list", [True, False])
+def test_scitype(coerce_to_list):
+    """Test that the scitype function recovers the correct scitype(s)."""
+    from sktime.forecasting.arima import ARIMA
+    from sktime.forecasting.naive import NaiveForecaster
+    from sktime.transformations.series.exponent import ExponentTransformer
+
+    # test that scitype works for classes with soft dependencies
+    result_arima = scitype(ARIMA, coerce_to_list=coerce_to_list)
+    if coerce_to_list:
+        assert isinstance(result_arima, list)
+        assert "forecaster" == result_arima[0]
+    else:
+        assert "forecaster" == result_arima
+
+    # test that scitype works for instances
+    result_naive = scitype(NaiveForecaster(), coerce_to_list=coerce_to_list)
+    if coerce_to_list:
+        assert isinstance(result_naive, list)
+        assert "forecaster" == result_naive[0]
+    else:
+        assert "forecaster" == result_naive
+
+    # test transformer object
+    result_transformer = scitype(ExponentTransformer, coerce_to_list=coerce_to_list)
+    if coerce_to_list:
+        assert isinstance(result_transformer, list)
+        assert "transformer" == result_transformer[0]
+    else:
+        assert "transformer" == result_transformer
diff --git a/sktime/registry/tests/test_tags.py b/sktime/registry/tests/test_tags.py
index e3e738163..74064d286 100644
--- a/sktime/registry/tests/test_tags.py
+++ b/sktime/registry/tests/test_tags.py
@@ -1,4 +1,4 @@
-"""Tests for tag register an tag functionality."""
+"""Tests for tag register and tag functionality."""
 
 from sktime.registry._tags import ESTIMATOR_TAG_REGISTER
 
diff --git a/sktime/transformations/tests/test_compose.py b/sktime/transformations/tests/test_compose.py
index 92cb635b5..353375c27 100644
--- a/sktime/transformations/tests/test_compose.py
+++ b/sktime/transformations/tests/test_compose.py
@@ -5,6 +5,7 @@ __author__ = ["fkiraly"]
 __all__ = []
 
 import pandas as pd
+import pytest
 from sklearn.preprocessing import StandardScaler
 
 from sktime.datasets import load_airline
@@ -24,6 +25,7 @@ from sktime.transformations.series.summarize import SummaryTransformer
 from sktime.transformations.series.theta import ThetaLinesTransformer
 from sktime.utils._testing.deep_equals import deep_equals
 from sktime.utils._testing.estimator_checks import _assert_array_almost_equal
+from sktime.utils.validation._dependencies import _check_soft_dependencies
 
 
 def test_dunder_mul():
@@ -262,3 +264,27 @@ def test_dunder_neg():
     assert isinstance(tp.get_params()["transformer"], ExponentTransformer)
 
     _assert_array_almost_equal(tp.fit_transform(X), X)
+
+
+@pytest.mark.skipif(
+    not _check_soft_dependencies("statsmodel", severity="none"),
+    reason="skip test if required soft dependency for statsmodels not available",
+)
+def test_input_output_series_panel_chain():
+    """Test that series-to-panel can be chained with series-to-series trafos.
+
+    Failure case of #5624.
+    """
+    from sktime.datasets import load_airline
+    from sktime.transformations.bootstrap import STLBootstrapTransformer
+    from sktime.transformations.series.impute import Imputer
+
+    X = load_airline()
+    bootstrap_trafo = STLBootstrapTransformer(4, sp=4) * Imputer(method="nearest")
+
+    assert bootstrap_trafo.get_tags()["scitype:transform-input"] == "Series"
+    assert bootstrap_trafo.get_tags()["scitype:transform-output"] == "Panel"
+
+    Xt = bootstrap_trafo.fit_transform(X)
+    assert isinstance(Xt, pd.DataFrame)
+    assert isinstance(Xt.index, pd.MultiIndex)
diff --git a/sktime/utils/tests/test_mlflow_sktime_model_export.py b/sktime/utils/tests/test_mlflow_sktime_model_export.py
index 6bed6a1ee..5a8531fee 100644
--- a/sktime/utils/tests/test_mlflow_sktime_model_export.py
+++ b/sktime/utils/tests/test_mlflow_sktime_model_export.py
@@ -95,7 +95,7 @@ def auto_arima_model(test_data_airline):
 
 @pytest.mark.skipif(
     not _check_soft_dependencies("tensorflow", severity="none"),
-    reaons="skip test if required soft dependency is not available.",
+    reason="skip test if required soft dependency is not available.",
 )
 @pytest.fixture(scope="module")
 def cnn_model(test_data_arrow_head):