Repository: aeon-toolkit/aeon
Pull request #1363 (2024-04-02 15:35:03)

[ENH,REF] Refactor `univariate-only` tag
<!--
Thanks for contributing a pull request! Please ensure you have taken a look at our
contribution guide: https://www.aeon-toolkit.org/en/stable/contributing.html

Feel free to delete sections of this template if they do not apply to your PR,
avoid submitting a blank template or empty sections.
-->

#### Reference Issues/PRs
Fixes: #812 
<!--
Example: Fixes #1234. See also #3456.
Please use keywords (e.g., Fixes) to create link to the issues or pull requests
you resolved, so that they will automatically be closed when your pull request
is merged. See https://github.com/blog/1506-closing-issues-via-pull-requests
-->

#### What does this implement/fix? Explain your changes.

<!--
A clear and concise description of what you have implemented.
-->
Refactor `univariate-only` tag to `capability:multivariate` and accordingly update the tests.

aeon-actions-bot[bot]: 
## Thank you for contributing to `aeon`

I did not find any labels to add based on the title. Please add the [ENH], [MNT], [BUG], [DOC], [REF], [DEP] and/or [GOV] tags to your pull requests titles. For now you can add the labels manually.
I have added the following labels to this PR based on the changes made: **[ $\\color{#41A8F6}{\\textsf{transformations}}$ ]**. Feel free to change these if they do not properly represent the PR.

The [Checks](https://github.com/aeon-toolkit/aeon/pull/1363/checks) tab will show the status of our automated tests. You can click on individual test runs in the tab or "Details" in the panel below to see more information if there is a failure.

If our `pre-commit` code quality check fails, any trivial fixes will automatically be pushed to your PR unless it is a draft.

Don't hesitate to ask questions on the `aeon` [Slack](
https://join.slack.com/t/aeon-toolkit/shared_invite/zt-22vwvut29-HDpCu~7VBUozyfL_8j3dLA) channel if you have any.
    
itsdivya1309: Hi @TonyBagnall! 
I was wondering if I should update the [tags registry](https://github.com/aeon-toolkit/aeon/blob/v0.8.0/aeon/registry/_tags.py) as well? Once this PR is merged, the `univariate-only` lag will only be used in forecasting.
TonyBagnall: > Hi @TonyBagnall! I was wondering if I should update the [tags registry](https://github.com/aeon-toolkit/aeon/blob/v0.8.0/aeon/registry/_tags.py) as well? Once this PR is merged, the `univariate-only` lag will only be used in forecasting.

not sure atm, but I think get this in first, forecasting is a little bit there be dragons ....I'll leave it until friday for further comments

Patch:
diff --git a/.all-contributorsrc b/.all-contributorsrc
index abc24f43..ce7d77cd 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -168,11 +168,17 @@
         "doc",
         "design",
         "example",
+        "ideas",
         "infra",
         "maintenance",
+        "mentoring",
+        "promotion",
+        "question",
+        "research",
+        "review",
         "test",
         "tutorial",
-        "review"
+        "talk"
       ]
     },
     {
@@ -2372,6 +2378,15 @@
       "contributions": [
         "bug"
       ]
+    },
+    {
+      "login": "wayneadams",
+      "name": "Wayne Adams",
+      "avatar_url": "https://avatars.githubusercontent.com/u/15034841?v=4",
+      "profile": "https://github.com/wayneadams",
+      "contributions": [
+        "doc"
+      ]
     }
   ],
   "commitType": "docs"
diff --git a/.github/workflows/github_maintenace.yml b/.github/workflows/github_maintenace.yml
new file mode 100644
index 00000000..3a483385
--- /dev/null
+++ b/.github/workflows/github_maintenace.yml
@@ -0,0 +1,35 @@
+name: GitHub Maintenance
+
+on:
+  schedule:
+    # every Monday at 01:00 AM UTC
+    - cron:  "0 1 * * 1"
+  workflow_dispatch:
+
+permissions:
+  issues: write
+  contents: write
+
+jobs:
+  stale_branches:
+    runs-on: ubuntu-20.04
+
+    steps:
+      - name: Create app token
+        uses: actions/create-github-app-token@v1
+        id: app-token
+        with:
+          app-id: ${{ vars.PR_APP_ID }}
+          private-key: ${{ secrets.PR_APP_KEY }}
+
+      - name: Stale Branches
+        uses: crs-k/stale-branches@v4.1.0
+        with:
+          repo-token: ${{ steps.app-token.outputs.token }}
+          days-before-stale: 140
+          days-before-delete: 9999  # todo take down to 175
+          comment-updates: true
+          tag-committer: true
+          stale-branch-label: "stale"
+          compare-branches: 'info'
+          pr-check: true
diff --git a/.github/workflows/pr_examples.yml b/.github/workflows/pr_examples.yml
index 53367fb8..905553c4 100644
--- a/.github/workflows/pr_examples.yml
+++ b/.github/workflows/pr_examples.yml
@@ -45,5 +45,5 @@ jobs:
           command: python -m pip install .[all_extras,binder,dev]
 
       - name: Run example notebooks
-        run: build_tools/run_examples.sh
+        run: build_tools/run_examples.sh ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'full examples run') }}
         shell: bash
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 4ec569f7..c7ff4fc1 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -1,7 +1,7 @@
 # Contributors
 
 <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
-[![All Contributors](https://img.shields.io/badge/all_contributors-230-orange.svg)](#contributors)
+[![All Contributors](https://img.shields.io/badge/all_contributors-231-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!
@@ -166,7 +166,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
     <tr>
       <td align="center" valign="top" width="12.5%"><a href="https://www.linkedin.com/in/martin-walter-1a33b3114/"><img src="https://avatars0.githubusercontent.com/u/29627036?v=4?s=80" width="80px;" alt="Martin Walter"/><br /><sub><b>Martin Walter</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=aiwalter" title="Code">💻</a> <a href="https://github.com/aeon-toolkit/aeon/issues?q=author%3Aaiwalter" title="Bug reports">🐛</a> <a href="#projectManagement-aiwalter" title="Project Management">📆</a> <a href="#fundingFinding-aiwalter" title="Funding Finding">🔍</a> <a href="#mentoring-aiwalter" title="Mentoring">🧑‍🏫</a> <a href="#ideas-aiwalter" title="Ideas, Planning, & Feedback">🤔</a> <a href="#design-aiwalter" title="Design">🎨</a> <a href="https://github.com/aeon-toolkit/aeon/pulls?q=is%3Apr+reviewed-by%3Aaiwalter" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=aiwalter" title="Documentation">📖</a> <a href="#talk-aiwalter" title="Talks">📢</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/martinagvilas"><img src="https://avatars2.githubusercontent.com/u/37339384?v=4?s=80" width="80px;" alt="Martina G. Vilas"/><br /><sub><b>Martina G. Vilas</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/pulls?q=is%3Apr+reviewed-by%3Amartinagvilas" title="Reviewed Pull Requests">👀</a> <a href="#ideas-martinagvilas" title="Ideas, Planning, & Feedback">🤔</a></td>
-      <td align="center" valign="top" width="12.5%"><a href="https://github.com/MatthewMiddlehurst"><img src="https://avatars0.githubusercontent.com/u/25731235?v=4?s=80" width="80px;" alt="Matthew Middlehurst"/><br /><sub><b>Matthew Middlehurst</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/issues?q=author%3AMatthewMiddlehurst" title="Bug reports">🐛</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=MatthewMiddlehurst" title="Code">💻</a> <a href="#data-MatthewMiddlehurst" title="Data">🔣</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=MatthewMiddlehurst" title="Documentation">📖</a> <a href="#design-MatthewMiddlehurst" title="Design">🎨</a> <a href="#example-MatthewMiddlehurst" title="Examples">💡</a> <a href="#infra-MatthewMiddlehurst" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#maintenance-MatthewMiddlehurst" title="Maintenance">🚧</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=MatthewMiddlehurst" title="Tests">⚠️</a> <a href="#tutorial-MatthewMiddlehurst" title="Tutorials">✅</a> <a href="https://github.com/aeon-toolkit/aeon/pulls?q=is%3Apr+reviewed-by%3AMatthewMiddlehurst" title="Reviewed Pull Requests">👀</a></td>
+      <td align="center" valign="top" width="12.5%"><a href="https://github.com/MatthewMiddlehurst"><img src="https://avatars0.githubusercontent.com/u/25731235?v=4?s=80" width="80px;" alt="Matthew Middlehurst"/><br /><sub><b>Matthew Middlehurst</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/issues?q=author%3AMatthewMiddlehurst" title="Bug reports">🐛</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=MatthewMiddlehurst" title="Code">💻</a> <a href="#data-MatthewMiddlehurst" title="Data">🔣</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=MatthewMiddlehurst" title="Documentation">📖</a> <a href="#design-MatthewMiddlehurst" title="Design">🎨</a> <a href="#example-MatthewMiddlehurst" title="Examples">💡</a> <a href="#ideas-MatthewMiddlehurst" title="Ideas, Planning, & Feedback">🤔</a> <a href="#infra-MatthewMiddlehurst" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#maintenance-MatthewMiddlehurst" title="Maintenance">🚧</a> <a href="#mentoring-MatthewMiddlehurst" title="Mentoring">🧑‍🏫</a> <a href="#promotion-MatthewMiddlehurst" title="Promotion">📣</a> <a href="#question-MatthewMiddlehurst" title="Answering Questions">💬</a> <a href="#research-MatthewMiddlehurst" title="Research">🔬</a> <a href="https://github.com/aeon-toolkit/aeon/pulls?q=is%3Apr+reviewed-by%3AMatthewMiddlehurst" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=MatthewMiddlehurst" title="Tests">⚠️</a> <a href="#tutorial-MatthewMiddlehurst" title="Tutorials">✅</a> <a href="#talk-MatthewMiddlehurst" title="Talks">📢</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/solen0id"><img src="https://avatars.githubusercontent.com/u/20767606?v=4?s=80" width="80px;" alt="Max Patzelt"/><br /><sub><b>Max Patzelt</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=solen0id" title="Code">💻</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/Hephaest"><img src="https://avatars2.githubusercontent.com/u/37981444?v=4?s=80" width="80px;" alt="Miao Cai"/><br /><sub><b>Miao Cai</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/issues?q=author%3AHephaest" title="Bug reports">🐛</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=Hephaest" title="Code">💻</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/frankl1"><img src="https://avatars.githubusercontent.com/u/23366578?s=96&v=4?s=80" width="80px;" alt="Michael F. Mbouopda"/><br /><sub><b>Michael F. Mbouopda</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=frankl1" title="Code">💻</a> <a href="https://github.com/aeon-toolkit/aeon/issues?q=author%3Afrankl1" title="Bug reports">🐛</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=frankl1" title="Documentation">📖</a></td>
@@ -251,9 +251,10 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/victordremov"><img src="https://avatars.githubusercontent.com/u/32140716?s=80" width="80px;" alt="Viktor Dremov"/><br /><sub><b>Viktor Dremov</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=victordremov" title="Code">💻</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/ViktorKaz"><img src="https://avatars0.githubusercontent.com/u/33499138?v=4?s=80" width="80px;" alt="ViktorKaz"/><br /><sub><b>ViktorKaz</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=ViktorKaz" title="Code">💻</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=ViktorKaz" title="Documentation">📖</a> <a href="#design-ViktorKaz" title="Design">🎨</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/VyomkeshVyas"><img src="https://avatars.githubusercontent.com/u/110027694?v=4?s=80" width="80px;" alt="Vyomkesh Vyas"/><br /><sub><b>Vyomkesh Vyas</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=VyomkeshVyas" title="Code">💻</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=VyomkeshVyas" title="Documentation">📖</a> <a href="#example-VyomkeshVyas" title="Examples">💡</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=VyomkeshVyas" title="Tests">⚠️</a></td>
-      <td align="center" valign="top" width="12.5%"><a href="https://www.linkedin.com/in/templierw/"><img src="https://github.com/templierw.png?s=80" width="80px;" alt="William Templier"/><br /><sub><b>William Templier</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=templierw" title="Documentation">📖</a></td>
+      <td align="center" valign="top" width="12.5%"><a href="https://github.com/wayneadams"><img src="https://avatars.githubusercontent.com/u/15034841?v=4?s=80" width="80px;" alt="Wayne Adams"/><br /><sub><b>Wayne Adams</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=wayneadams" title="Documentation">📖</a></td>
     </tr>
     <tr>
+      <td align="center" valign="top" width="12.5%"><a href="https://www.linkedin.com/in/templierw/"><img src="https://github.com/templierw.png?s=80" width="80px;" alt="William Templier"/><br /><sub><b>William Templier</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=templierw" title="Documentation">📖</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/wwzeng1"><img src="https://avatars.githubusercontent.com/u/44910023?v=4?s=80" width="80px;" alt="William Zeng"/><br /><sub><b>William Zeng</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/issues?q=author%3Awwzeng1" title="Bug reports">🐛</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/magittan"><img src="https://avatars0.githubusercontent.com/u/14024202?v=4?s=80" width="80px;" alt="William Zheng"/><br /><sub><b>William Zheng</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=magittan" title="Code">💻</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=magittan" title="Tests">⚠️</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/yairbeer"><img src="https://avatars.githubusercontent.com/yairbeer?s=80" width="80px;" alt="Yair Beer"/><br /><sub><b>Yair Beer</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=yairbeer" title="Code">💻</a></td>
@@ -261,9 +262,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/xuyxu"><img src="https://avatars2.githubusercontent.com/u/22359569?v=4?s=80" width="80px;" alt="Yi-Xuan Xu"/><br /><sub><b>Yi-Xuan Xu</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=xuyxu" title="Code">💻</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=xuyxu" title="Tests">⚠️</a> <a href="#maintenance-xuyxu" title="Maintenance">🚧</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=xuyxu" title="Documentation">📖</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/ZiyaoWei"><img src="https://avatars.githubusercontent.com/u/940823?v=4?s=80" width="80px;" alt="Ziyao Wei"/><br /><sub><b>Ziyao Wei</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=ZiyaoWei" title="Code">💻</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/aa25desh"><img src="https://avatars1.githubusercontent.com/u/29518290?v=4?s=80" width="80px;" alt="aa25desh"/><br /><sub><b>aa25desh</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=aa25desh" title="Code">💻</a> <a href="https://github.com/aeon-toolkit/aeon/issues?q=author%3Aaa25desh" title="Bug reports">🐛</a></td>
-      <td align="center" valign="top" width="12.5%"><a href="https://github.com/abandus"><img src="https://avatars2.githubusercontent.com/u/46486474?v=4?s=80" width="80px;" alt="abandus"/><br /><sub><b>abandus</b></sub></a><br /><a href="#ideas-abandus" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=abandus" title="Code">💻</a></td>
     </tr>
     <tr>
+      <td align="center" valign="top" width="12.5%"><a href="https://github.com/abandus"><img src="https://avatars2.githubusercontent.com/u/46486474?v=4?s=80" width="80px;" alt="abandus"/><br /><sub><b>abandus</b></sub></a><br /><a href="#ideas-abandus" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=abandus" title="Code">💻</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/adoherty21"><img src="https://avatars.githubusercontent.com/u/52799751?s=400&v=4?s=80" width="80px;" alt="adoherty21"/><br /><sub><b>adoherty21</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/issues?q=author%3Aadoherty21" title="Bug reports">🐛</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/bethrice44"><img src="https://avatars.githubusercontent.com/u/11226988?v=4?s=80" width="80px;" alt="bethrice44"/><br /><sub><b>bethrice44</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/issues?q=author%3Abethrice44" title="Bug reports">🐛</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=bethrice44" title="Code">💻</a> <a href="https://github.com/aeon-toolkit/aeon/pulls?q=is%3Apr+reviewed-by%3Abethrice44" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=bethrice44" title="Tests">⚠️</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/big-o"><img src="https://avatars1.githubusercontent.com/u/1134151?v=4?s=80" width="80px;" alt="big-o"/><br /><sub><b>big-o</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=big-o" title="Code">💻</a> <a href="https://github.com/aeon-toolkit/aeon/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/aeon-toolkit/aeon/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>
@@ -271,9 +272,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/brettkoonce"><img src="https://avatars2.githubusercontent.com/u/11281814?v=4?s=80" width="80px;" alt="brett koonce"/><br /><sub><b>brett koonce</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=brettkoonce" title="Documentation">📖</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/btrtts"><img src="https://avatars3.githubusercontent.com/u/66252156?v=4?s=80" width="80px;" alt="btrtts"/><br /><sub><b>btrtts</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=btrtts" title="Documentation">📖</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/chizzi25"><img src="https://avatars3.githubusercontent.com/u/67911243?v=4?s=80" width="80px;" alt="chizzi25"/><br /><sub><b>chizzi25</b></sub></a><br /><a href="#blog-chizzi25" title="Blogposts">📝</a></td>
-      <td align="center" valign="top" width="12.5%"><a href="https://github.com/chrisholder"><img src="https://avatars.githubusercontent.com/u/4674372?v=4?s=80" width="80px;" alt="chrisholder"/><br /><sub><b>chrisholder</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=chrisholder" title="Code">💻</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=chrisholder" title="Tests">⚠️</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=chrisholder" title="Documentation">📖</a> <a href="#design-chrisholder" title="Design">🎨</a> <a href="#example-chrisholder" title="Examples">💡</a> <a href="https://github.com/aeon-toolkit/aeon/issues?q=author%3Achrisholder" title="Bug reports">🐛</a></td>
     </tr>
     <tr>
+      <td align="center" valign="top" width="12.5%"><a href="https://github.com/chrisholder"><img src="https://avatars.githubusercontent.com/u/4674372?v=4?s=80" width="80px;" alt="chrisholder"/><br /><sub><b>chrisholder</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=chrisholder" title="Code">💻</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=chrisholder" title="Tests">⚠️</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=chrisholder" title="Documentation">📖</a> <a href="#design-chrisholder" title="Design">🎨</a> <a href="#example-chrisholder" title="Examples">💡</a> <a href="https://github.com/aeon-toolkit/aeon/issues?q=author%3Achrisholder" title="Bug reports">🐛</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/danbartl"><img src="https://avatars.githubusercontent.com/u/19947407?v=4?s=80" width="80px;" alt="danbartl"/><br /><sub><b>danbartl</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/issues?q=author%3Adanbartl" title="Bug reports">🐛</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=danbartl" title="Code">💻</a> <a href="https://github.com/aeon-toolkit/aeon/pulls?q=is%3Apr+reviewed-by%3Adanbartl" title="Reviewed Pull Requests">👀</a> <a href="#talk-danbartl" title="Talks">📢</a> <a href="https://github.com/aeon-toolkit/aeon/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="12.5%"><a href="https://github.com/hamzahiqb"><img src="https://avatars3.githubusercontent.com/u/10302415?v=4?s=80" width="80px;" 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="12.5%"><a href="https://github.com/hiqbal2"><img src="https://avatars3.githubusercontent.com/u/10302415?v=4?s=80" width="80px;" alt="hiqbal2"/><br /><sub><b>hiqbal2</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=hiqbal2" title="Documentation">📖</a></td>
@@ -281,9 +282,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/jschemm"><img src="https://avatars.githubusercontent.com/u/81151346?v=4?s=80" width="80px;" alt="jschemm"/><br /><sub><b>jschemm</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=jschemm" title="Code">💻</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/julu98"><img src="https://avatars.githubusercontent.com/u/133243896?v=4?s=80" width="80px;" alt="julu98"/><br /><sub><b>julu98</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/issues?q=author%3Ajulu98" title="Bug reports">🐛</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/kkoziara"><img src="https://avatars1.githubusercontent.com/u/4346849?v=4?s=80" width="80px;" alt="kkoziara"/><br /><sub><b>kkoziara</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=kkoziara" title="Code">💻</a> <a href="https://github.com/aeon-toolkit/aeon/issues?q=author%3Akkoziara" title="Bug reports">🐛</a></td>
-      <td align="center" valign="top" width="12.5%"><a href="https://github.com/matteogales"><img src="https://avatars0.githubusercontent.com/u/9269326?v=4?s=80" width="80px;" alt="matteogales"/><br /><sub><b>matteogales</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=matteogales" title="Code">💻</a> <a href="#design-matteogales" title="Design">🎨</a> <a href="#ideas-matteogales" title="Ideas, Planning, & Feedback">🤔</a></td>
     </tr>
     <tr>
+      <td align="center" valign="top" width="12.5%"><a href="https://github.com/matteogales"><img src="https://avatars0.githubusercontent.com/u/9269326?v=4?s=80" width="80px;" alt="matteogales"/><br /><sub><b>matteogales</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=matteogales" title="Code">💻</a> <a href="#design-matteogales" title="Design">🎨</a> <a href="#ideas-matteogales" title="Ideas, Planning, & Feedback">🤔</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/oleskiewicz"><img src="https://avatars1.githubusercontent.com/u/5682158?v=4?s=80" width="80px;" alt="oleskiewicz"/><br /><sub><b>oleskiewicz</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=oleskiewicz" title="Code">💻</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=oleskiewicz" title="Documentation">📖</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=oleskiewicz" title="Tests">⚠️</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/pabworks"><img src="https://avatars.githubusercontent.com/u/32725127?v=4?s=80" width="80px;" alt="pabworks"/><br /><sub><b>pabworks</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=pabworks" title="Code">💻</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=pabworks" title="Tests">⚠️</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/xloem"><img src="https://avatars.githubusercontent.com/u/279585?v=4?s=80" width="80px;" alt="patiently pending world peace"/><br /><sub><b>patiently pending world peace</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=xloem" title="Code">💻</a></td>
@@ -291,9 +292,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/simone-pignotti"><img src="https://avatars1.githubusercontent.com/u/44410066?v=4?s=80" width="80px;" alt="simone-pignotti"/><br /><sub><b>simone-pignotti</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=simone-pignotti" title="Code">💻</a> <a href="https://github.com/aeon-toolkit/aeon/issues?q=author%3Asimone-pignotti" title="Bug reports">🐛</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/sophijka"><img src="https://avatars2.githubusercontent.com/u/47450591?v=4?s=80" width="80px;" alt="sophijka"/><br /><sub><b>sophijka</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=sophijka" title="Documentation">📖</a> <a href="#maintenance-sophijka" title="Maintenance">🚧</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/sri1419"><img src="https://avatars2.githubusercontent.com/u/65078278?v=4?s=80" width="80px;" alt="sri1419"/><br /><sub><b>sri1419</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=sri1419" title="Code">💻</a></td>
-      <td align="center" valign="top" width="12.5%"><a href="https://github.com/tensorflow-as-tf"><img src="https://avatars.githubusercontent.com/u/51345718?v=4?s=80" width="80px;" alt="tensorflow-as-tf"/><br /><sub><b>tensorflow-as-tf</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=tensorflow-as-tf" title="Code">💻</a></td>
     </tr>
     <tr>
+      <td align="center" valign="top" width="12.5%"><a href="https://github.com/tensorflow-as-tf"><img src="https://avatars.githubusercontent.com/u/51345718?v=4?s=80" width="80px;" alt="tensorflow-as-tf"/><br /><sub><b>tensorflow-as-tf</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=tensorflow-as-tf" title="Code">💻</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/vNtzYy"><img src="https://avatars.githubusercontent.com/u/111287448?v=4?s=80" width="80px;" alt="vNtzYy"/><br /><sub><b>vNtzYy</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/issues?q=author%3AvNtzYy" title="Bug reports">🐛</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/vedazeren"><img src="https://avatars3.githubusercontent.com/u/63582874?v=4?s=80" width="80px;" alt="vedazeren"/><br /><sub><b>vedazeren</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=vedazeren" title="Code">💻</a> <a href="https://github.com/aeon-toolkit/aeon/commits?author=vedazeren" title="Tests">⚠️</a></td>
       <td align="center" valign="top" width="12.5%"><a href="https://github.com/vincent-nich12"><img src="https://avatars3.githubusercontent.com/u/36476633?v=4?s=80" width="80px;" alt="vincent-nich12"/><br /><sub><b>vincent-nich12</b></sub></a><br /><a href="https://github.com/aeon-toolkit/aeon/commits?author=vincent-nich12" title="Code">💻</a></td>
diff --git a/aeon/base/_base_series.py b/aeon/base/_base_series.py
index ba10a63b..79433a7b 100644
--- a/aeon/base/_base_series.py
+++ b/aeon/base/_base_series.py
@@ -195,7 +195,7 @@ class BaseSeriesEstimator(BaseEstimator):
                 X = pd.DataFrame(X)
             else:
                 tag = self.get_tag("X_inner_type")
-                raise ValueError(f"Unknown inner type {inner} derived from {tag}")
+                raise ValueError(f"Unsupported inner type {inner} derived from {tag}")
         if axis > 1 or axis < 0:
             raise ValueError("Axis should be 0 or 1")
         if not self.get_tag("capability:multivariate"):
@@ -235,6 +235,16 @@ class BaseSeriesEstimator(BaseEstimator):
             self.metadata_ = meta
         return self._convert_X(X, axis)
 
+    def _postprocess_series(self, X, axis=None):
+        """Postprocess data X to revert to original shape."""
+        # If a univariate only transformer, return a univariate series
+        if not self.get_tag("capability:multivariate"):
+            return X.squeeze()
+        # If passed an axis, return with that axis
+        if axis is None or axis == self.axis:
+            return X
+        return X.T
+
     @classmethod
     def get_test_params(cls, parameter_set="default"):
         """
diff --git a/aeon/forecasting/base/_base.py b/aeon/forecasting/base/_base.py
index 2942f3e8..af0f0c6a 100644
--- a/aeon/forecasting/base/_base.py
+++ b/aeon/forecasting/base/_base.py
@@ -396,7 +396,8 @@ class BaseForecaster(BaseEstimator):
             # otherwise we call the vectorized version of predict
             y_pred = self._vectorize("predict", X=X_inner, fh=fh)
 
-        # convert to output mtype, identical with last y mtype seen
+        # convert to output type, identical with last y type seen
+
         y_out = convert_to(
             y_pred,
             self._y_mtype_last_seen,
@@ -1411,6 +1412,7 @@ class BaseForecaster(BaseEstimator):
 
         if not requires_vectorization:
             # converts y, skips conversion if already of right type
+
             y_inner = convert_to(
                 y,
                 to_type=y_inner_type,
diff --git a/aeon/forecasting/conformal.py b/aeon/forecasting/conformal.py
index 63fbcd85..63f559eb 100644
--- a/aeon/forecasting/conformal.py
+++ b/aeon/forecasting/conformal.py
@@ -14,8 +14,8 @@ import pandas as pd
 from joblib import Parallel, delayed
 from sklearn.base import clone
 
-from aeon.datatypes import convert, convert_to
 from aeon.forecasting.base import BaseForecaster
+from aeon.utils.conversion import convert_series
 from aeon.utils.index_functions import get_slice
 
 
@@ -249,7 +249,7 @@ class ConformalIntervals(BaseForecaster):
             pred_int.loc[fh_ind] = pred_int_row
 
         y_pred = self.predict(fh=fh, X=X)
-        y_pred = convert(y_pred, from_type=self._y_mtype_last_seen, to_type="pd.Series")
+        y_pred = convert_series(y_pred, output_type="pd.Series")
         y_pred.index = fh_absolute.to_pandas()
 
         for col in cols:
@@ -362,7 +362,7 @@ class ConformalIntervals(BaseForecaster):
             if sample_frac is passed this will have NaN values for 1 - sample_frac
             fraction of the matrix
         """
-        y = convert_to(y, "pd.Series")
+        y = convert_series(y, "pd.Series")
 
         n_initial_window = self._parse_initial_window(y, initial_window=initial_window)
 
diff --git a/aeon/forecasting/model_evaluation/_functions.py b/aeon/forecasting/model_evaluation/_functions.py
index 81b90e96..ddb73904 100644
--- a/aeon/forecasting/model_evaluation/_functions.py
+++ b/aeon/forecasting/model_evaluation/_functions.py
@@ -10,10 +10,15 @@ from typing import List, Optional, Union
 import numpy as np
 import pandas as pd
 
-from aeon.datatypes import convert_to
 from aeon.exceptions import FitFailedWarning
 from aeon.forecasting.base import ForecastingHorizon
-from aeon.utils.validation import is_collection, is_hierarchical, is_single_series
+from aeon.utils.conversion import convert_collection, convert_series
+from aeon.utils.validation import (
+    is_collection,
+    is_hierarchical,
+    is_single_series,
+    validate_input,
+)
 from aeon.utils.validation._dependencies import _check_soft_dependencies
 from aeon.utils.validation.forecasting import check_cv, check_scoring
 
@@ -229,9 +234,9 @@ def evaluate(
         aeon forecaster (concrete BaseForecaster descendant)
     cv : aeon BaseSplitter descendant
         Splitter of how to split the data into test data and train data
-    y : aeon time series container
+    y : aeon time series data structure
         Target (endogeneous) time series used in the evaluation experiment
-    X : aeon time series container, of same mtype as y
+    X : aeon time series data structure, of same type as y
         Exogenous time series used in the evaluation experiment
     strategy : {"refit", "update", "no-update_params"}, optional, default="refit"
         defines the ingestion mode when the forecaster sees new data when window expands
@@ -331,13 +336,20 @@ def evaluate(
     else:
         scoring = check_scoring(scoring)
 
-    if not (is_single_series(y) or is_hierarchical(y) or is_collection(y)):
+    valid, y_metadata = validate_input(y)
+    if not valid:
         raise TypeError(
             f"Expected a series, collection or hierarchy. Got {type(y)} instead."
         )
-
-    y = convert_to(y, to_type=PANDAS_MTYPES)
-
+    if isinstance(y, np.ndarray):
+        if y_metadata["scitype"] == "Series":
+            if y_metadata["is_univariate"]:
+                y = convert_series(y, output_type="pd.Series")
+            else:
+                y = convert_series(y, output_type="pd.DataFrame")
+        elif y_metadata["scitype"] == "Panel":
+            y = convert_collection(y, output_type="pd-multiindex")
+        # If hierarchical, we don't need to convert
     freq = None
     try:
         if y.index.nlevels == 1:
@@ -348,11 +360,22 @@ def evaluate(
         pass
 
     if X is not None:
-        if not (is_single_series(X) or is_hierarchical(X) or is_collection(X)):
+        series = is_single_series(X)
+        hier = is_hierarchical(X)
+        collection = is_collection(X)
+        if not (series or hier or collection):
             raise TypeError(
                 f"Expected a series, collection or hierarchy. Got {type(y)} instead."
             )
-        X = convert_to(X, to_type=PANDAS_MTYPES)
+        _, x_metadata = validate_input(X)
+        if isinstance(X, np.ndarray):
+            if series:
+                if x_metadata["is_univariate"]:
+                    X = convert_series(X, output_type="pd.Series")
+                else:
+                    X = convert_series(X, output_type="pd.DataFrame")
+            elif collection:
+                X = convert_collection(X, output_type="pd-multiindex")
     score_name = (
         f"test_{scoring.__name__}"
         if not isinstance(scoring, List)
diff --git a/aeon/forecasting/model_selection/_split.py b/aeon/forecasting/model_selection/_split.py
index c950a738..a0ed79c4 100644
--- a/aeon/forecasting/model_selection/_split.py
+++ b/aeon/forecasting/model_selection/_split.py
@@ -16,9 +16,9 @@ import pandas as pd
 from sklearn.model_selection import train_test_split as _train_test_split
 
 from aeon.base import BaseObject
-from aeon.datatypes import convert_to
 from aeon.forecasting.base import ForecastingHorizon
 from aeon.forecasting.base._fh import VALID_FORECASTING_HORIZON_TYPES
+from aeon.utils.conversion import convert_collection, convert_series
 from aeon.utils.index_functions import get_index_for_series, get_time_index, get_window
 from aeon.utils.validation import (
     ACCEPTED_WINDOW_LENGTH_TYPES,
@@ -28,11 +28,8 @@ from aeon.utils.validation import (
     array_is_datetime64,
     array_is_int,
     check_window_length,
-    is_collection,
     is_datetime,
-    is_hierarchical,
     is_int,
-    is_single_series,
     is_timedelta,
     is_timedelta_or_date_offset,
     validate_input,
@@ -469,8 +466,6 @@ class BaseSplitter(BaseObject):
         for train, test in self.split(y.index):
             y_train = y.iloc[train]
             y_test = y.iloc[test]
-            y_train = convert_to(y_train, y_orig_mtype)
-            y_test = convert_to(y_test, y_orig_mtype)
             yield y_train, y_test
 
     def _coerce_to_index(self, y: ACCEPTED_Y_TYPES) -> pd.Index:
@@ -499,12 +494,12 @@ class BaseSplitter(BaseObject):
 
         Parameters
         ----------
-        y : pd.Series, pd.DataFrame, or np.ndarray (1D or 2D), optional (default=None)
+        y : pd.Series, pd.DataFrame, or np.ndarray (1D or 2D)
             Time series to check, must conform with one of the aeon type conventions.
 
         Returns
         -------
-        y_inner : time series y coerced to one of the aeon pandas based mtypes:
+        y_inner : time series y as pd.Series or pd.DataFrame
             pd.DataFrame, pd.Series, pd-multiindex, pd_multiindex_hier
             returns pd.Series only if y was pd.Series, otherwise a pandas.DataFrame
         y_mtype : original mtype of y
@@ -513,7 +508,8 @@ class BaseSplitter(BaseObject):
         ------
         TypeError if y is not one of the permissible mtypes
         """
-        if not (is_hierarchical(y) or is_collection(y) or is_single_series(y)):
+        valid, y_metadata = validate_input(y)
+        if not valid:
             raise TypeError(
                 "y must be in an aeon compatible format, "
                 "of scitype Series, Panel or Hierarchical, "
@@ -524,10 +520,16 @@ class BaseSplitter(BaseObject):
                 "run aeon.datatypes.check_raise(y, mtype) to diagnose the error, "
                 "where mtype is the string of the type specification you want for y. "
             )
-        _, y_metadata = validate_input(y)
-
-        y_inner = convert_to(y, to_type=PANDAS_MTYPES)
-
+        y_inner = y
+        if isinstance(y, np.ndarray):
+            if y_metadata["scitype"] == "Series":
+                if y_metadata["is_univariate"]:
+                    y_inner = convert_series(y, output_type="pd.Series")
+                else:
+                    y_inner = convert_series(y, output_type="pd.DataFrame")
+            elif y_metadata["scitype"] == "Panel":
+                y_inner = convert_collection(y, output_type="pd-multiindex")
+            # If hierarchical, it is already in the correct format
         mtype = y_metadata["mtype"]
 
         return y_inner, mtype
diff --git a/aeon/forecasting/naive.py b/aeon/forecasting/naive.py
index 016fa4b3..d0edd218 100644
--- a/aeon/forecasting/naive.py
+++ b/aeon/forecasting/naive.py
@@ -11,10 +11,10 @@ import numpy as np
 import pandas as pd
 from scipy.stats import norm
 
-from aeon.datatypes._convert import convert, convert_to
 from aeon.forecasting.base import ForecastingHorizon
 from aeon.forecasting.base._aeon import _BaseWindowForecaster
 from aeon.forecasting.base._base import DEFAULT_ALPHA, BaseForecaster
+from aeon.utils.conversion import convert_series
 from aeon.utils.index_functions import get_slice
 from aeon.utils.validation import check_window_length
 from aeon.utils.validation.forecasting import check_sp
@@ -362,7 +362,8 @@ class NaiveForecaster(_BaseWindowForecaster):
                 at quantile probability in second-level col index, for each row index.
         """
         y_pred = self.predict(fh)
-        y_pred = convert(y_pred, from_type=self._y_mtype_last_seen, to_type="pd.Series")
+
+        y_pred = convert_series(y_pred, output_type="pd.Series")
 
         pred_var = self.predict_var(fh)
         z_scores = norm.ppf(alpha)
@@ -410,7 +411,7 @@ class NaiveForecaster(_BaseWindowForecaster):
         .. [1] https://otexts.com/fpp3/prediction-intervals.html#benchmark-methods
         """
         y = self._y
-        y = convert_to(y, "pd.Series")
+        y = convert_series(y, "pd.Series")
         T = len(y)
         sp = self.sp
 
@@ -650,7 +651,7 @@ class NaiveVariance(BaseForecaster):
                 at quantile probability in second-level col index, for each row index.
         """
         y_pred = self.predict(fh, X)
-        y_pred = convert(y_pred, from_type=self._y_mtype_last_seen, to_type="pd.Series")
+        y_pred = convert_series(y_pred, output_type="pd.Series")
         pred_var = self.predict_var(fh, X)
         pred_var = pred_var[pred_var.columns[0]]
         pred_var.index = y_pred.index
@@ -753,7 +754,7 @@ class NaiveVariance(BaseForecaster):
             [i,j]-th entry is signed residual of forecasting y.loc[j] from y.loc[:i],
             using a clone of the forecaster passed through the forecaster arg
         """
-        y = convert_to(y, "pd.Series")
+        y = convert_series(y, "pd.Series")
 
         y_index = y.index[initial_window:]
         residuals_matrix = pd.DataFrame(columns=y_index, index=y_index, dtype="float")
diff --git a/aeon/forecasting/squaring_residuals.py b/aeon/forecasting/squaring_residuals.py
index 2adcfdc8..6e3cb492 100644
--- a/aeon/forecasting/squaring_residuals.py
+++ b/aeon/forecasting/squaring_residuals.py
@@ -7,10 +7,10 @@ from warnings import warn
 
 import pandas as pd
 
-from aeon.datatypes._convert import convert_to
 from aeon.forecasting.base import BaseForecaster, ForecastingHorizon
 from aeon.forecasting.model_selection import ExpandingWindowSplitter
 from aeon.forecasting.naive import NaiveForecaster
+from aeon.utils.conversion import convert_series
 
 
 class SquaringResiduals(BaseForecaster):
@@ -154,7 +154,7 @@ class SquaringResiduals(BaseForecaster):
         self._residual_forecaster_ = self.residual_forecaster.clone()
         self._forecaster_ = self.forecaster.clone()
 
-        y = convert_to(y, "pd.Series")
+        y = convert_series(y, "pd.Series")
         cv = ExpandingWindowSplitter(initial_window=self.initial_window, fh=fh_rel)
         self._forecaster_.fit(y=y.iloc[: self.initial_window], X=X)
         y_pred = self._forecaster_.update_predict(y=y, cv=cv, X=X, update_params=True)
diff --git a/aeon/regression/compose/_pipeline.py b/aeon/regression/compose/_pipeline.py
index 2676bae3..4ede972d 100644
--- a/aeon/regression/compose/_pipeline.py
+++ b/aeon/regression/compose/_pipeline.py
@@ -227,7 +227,7 @@ class SklearnRegressorPipeline(_HeterogenousMetaEstimator, BaseRegressor):
 
         # can handle multivariate iff all transformers can
         # sklearn transformers always support multivariate
-        multivariate = not self.transformers_.get_tag("univariate-only", True)
+        multivariate = not self.transformers_.get_tag("capability:multivariate", False)
         # can handle missing values iff transformer chain removes missing data
         # sklearn regressors might be able to handle missing data (but no tag there)
         # so better set the tag liberally
diff --git a/aeon/transformations/acf.py b/aeon/transformations/acf.py
index 754262ab..e33b261c 100644
--- a/aeon/transformations/acf.py
+++ b/aeon/transformations/acf.py
@@ -71,7 +71,7 @@ class AutoCorrelationTransformer(BaseTransformer):
         "instancewise": True,  # is this an instance-wise transform?
         "X_inner_type": "pd.Series",
         "y_inner_type": "None",
-        "univariate-only": True,
+        "capability:multivariate": False,
         "fit_is_empty": True,
         "python_dependencies": "statsmodels",
     }
@@ -203,7 +203,7 @@ class PartialAutoCorrelationTransformer(BaseTransformer):
         "instancewise": True,
         "X_inner_type": "pd.Series",
         "y_inner_type": "None",
-        "univariate-only": True,
+        "capability:multivariate": False,
         "fit_is_empty": True,
         "python_dependencies": "statsmodels",
     }
diff --git a/aeon/transformations/adapt.py b/aeon/transformations/adapt.py
index d9ff3607..40339d68 100644
--- a/aeon/transformations/adapt.py
+++ b/aeon/transformations/adapt.py
@@ -92,7 +92,7 @@ class TabularToSeriesAdaptor(BaseTransformer):
         "instancewise": True,  # is this an instance-wise transform?
         "X_inner_type": "np.ndarray",
         "y_inner_type": "None",
-        "univariate-only": False,
+        "capability:multivariate": True,
         "transform-returns-same-time-index": True,
         "fit_is_empty": False,
     }
@@ -267,7 +267,7 @@ class PandasTransformAdaptor(BaseTransformer):
         "instancewise": True,
         "X_inner_type": "pd.DataFrame",
         "y_inner_type": "None",
-        "univariate-only": False,
+        "capability:multivariate": True,
         "transform-returns-same-time-index": False,
         "fit_is_empty": False,
         "capability:inverse_transform": False,
diff --git a/aeon/transformations/base.py b/aeon/transformations/base.py
index 0a6fe7e8..e1b8f03d 100644
--- a/aeon/transformations/base.py
+++ b/aeon/transformations/base.py
@@ -95,7 +95,7 @@ class BaseTransformer(BaseEstimator):
         "output_data_type": "Series",
         "transform_labels": "None",
         "instancewise": True,
-        "univariate-only": False,  # can the transformer handle multivariate X?
+        "capability:multivariate": True,  # can the transformer handle multivariate X?
         "X_inner_type": "pd.DataFrame",
         # this can be a Panel mtype even if transform-input is Series, vectorized
         "y_inner_type": "None",
@@ -765,8 +765,8 @@ class BaseTransformer(BaseEstimator):
         ------
         TypeError if X is None
         TypeError if X or y is not one of the permissible Series mtypes
-        TypeError if X is not compatible with self.get_tag("univariate_only")
-            if tag value is "True", X must be univariate
+        TypeError if X is compatible with self.get_tag("capability:multivariate")
+            if tag value is "False", X must be univariate
         ValueError if self.get_tag("requires_y")=True but y is None
         """
         if X is None:
@@ -833,7 +833,7 @@ class BaseTransformer(BaseEstimator):
         metadata["_convert_case"] = case
 
         # checking X vs tags
-        inner_univariate = self.get_tag("univariate-only")
+        inner_univariate = not self.get_tag("capability:multivariate")
         # we remember whether we need to vectorize over columns, and at all
         req_vec_because_cols = inner_univariate and not X_metadata["is_univariate"]
         requires_vectorization = req_vec_because_rows or req_vec_because_cols
diff --git a/aeon/transformations/binning.py b/aeon/transformations/binning.py
index b56d16af..ef85dcec 100644
--- a/aeon/transformations/binning.py
+++ b/aeon/transformations/binning.py
@@ -44,7 +44,7 @@ class TimeBinAggregate(BaseTransformer):
 
     _tags = {
         "fit_is_empty": True,
-        "univariate-only": False,
+        "capability:multivariate": True,
         "input_data_type": "Series",
         # what is the abstract type of X: Series, or Panel
         "output_data_type": "Series",
diff --git a/aeon/transformations/bkfilter.py b/aeon/transformations/bkfilter.py
index 4a607242..f2282eac 100644
--- a/aeon/transformations/bkfilter.py
+++ b/aeon/transformations/bkfilter.py
@@ -70,7 +70,7 @@ class BKFilter(BaseTransformer):
         "output_data_type": "Series",
         # what abstract type is returned: Primitives, Series, Panel
         "instancewise": True,
-        "univariate-only": False,
+        "capability:multivariate": True,
         "X_inner_type": "np.ndarray",
         "y_inner_type": "None",
         "enforce_index_type": [pd.RangeIndex],
diff --git a/aeon/transformations/bootstrap/_mbb.py b/aeon/transformations/bootstrap/_mbb.py
index edeb9622..8bb2929c 100644
--- a/aeon/transformations/bootstrap/_mbb.py
+++ b/aeon/transformations/bootstrap/_mbb.py
@@ -172,7 +172,7 @@ class STLBootstrapTransformer(BaseTransformer):
         "y_inner_type": "None",
         "capability:inverse_transform": False,
         "skip-inverse-transform": True,
-        "univariate-only": True,
+        "capability:multivariate": False,
         "capability:missing_values": False,
         "X-y-must-have-same-index": False,
         "enforce_index_type": None,
@@ -482,7 +482,7 @@ class MovingBlockBootstrapTransformer(BaseTransformer):
         "y_inner_type": "None",
         "capability:inverse_transform": False,
         "skip-inverse-transform": True,  # is inverse-transform skipped when called?
-        "univariate-only": True,  # can the transformer handle multivariate X?
+        "capability:multivariate": False,  # can the transformer handle multivariate X?
         "capability:missing_values": False,  # can estimator handle missing data?
         "X-y-must-have-same-index": False,  # can estimator handle different X/y index?
         "enforce_index_type": None,  # index type that needs to be enforced in X/y
diff --git a/aeon/transformations/boxcox.py b/aeon/transformations/boxcox.py
index d9131c98..62b18080 100644
--- a/aeon/transformations/boxcox.py
+++ b/aeon/transformations/boxcox.py
@@ -125,7 +125,7 @@ class BoxCoxTransformer(BaseTransformer):
         "y_inner_type": "None",
         "transform-returns-same-time-index": True,
         "fit_is_empty": False,
-        "univariate-only": True,
+        "capability:multivariate": False,
         "capability:inverse_transform": True,
     }
 
@@ -256,7 +256,7 @@ class LogTransformer(BaseTransformer):
         "y_inner_type": "None",
         "transform-returns-same-time-index": True,
         "fit_is_empty": True,
-        "univariate-only": False,
+        "capability:multivariate": True,
         "capability:inverse_transform": True,
     }
 
diff --git a/aeon/transformations/clasp.py b/aeon/transformations/clasp.py
index 2e491b1b..9a4adf6b 100644
--- a/aeon/transformations/clasp.py
+++ b/aeon/transformations/clasp.py
@@ -398,7 +398,7 @@ class ClaSPTransformer(BaseTransformer):
         "instancewise": True,
         "X_inner_type": "np.ndarray",
         "y_inner_type": "None",
-        "univariate-only": True,
+        "capability:multivariate": False,
         "fit_is_empty": True,
     }
 
diff --git a/aeon/transformations/clear_sky.py b/aeon/transformations/clear_sky.py
index 4a27efe2..5cc9cdbf 100644
--- a/aeon/transformations/clear_sky.py
+++ b/aeon/transformations/clear_sky.py
@@ -75,7 +75,7 @@ class ClearSky(BaseTransformer):
         "transform_labels": "None",
         "instancewise": True,  # is this an instance-wise transform?
         "capability:inverse_transform": True,  # can the transformer inverse transform?
-        "univariate-only": True,  # can the transformer handle multivariate X?
+        "capability:multivariate": False,  # can the transformer handle multivariate X?
         "X_inner_type": [
             "pd.Series",
         ],
diff --git a/aeon/transformations/compose.py b/aeon/transformations/compose.py
index 2346ccc3..9e978f71 100644
--- a/aeon/transformations/compose.py
+++ b/aeon/transformations/compose.py
@@ -140,7 +140,7 @@ class TransformerPipeline(_HeterogenousMetaEstimator, BaseTransformer):
     _tags = {
         # we let all X inputs through to be handled by first transformer
         "X_inner_type": ALL_TIME_SERIES_TYPES,
-        "univariate-only": False,
+        "capability:multivariate": True,
     }
 
     # no further default tag values - these are set dynamically below
@@ -441,7 +441,7 @@ class FeatureUnion(_HeterogenousMetaEstimator, BaseTransformer):
         "output_data_type": "Series",
         "transform_labels": "None",
         "instancewise": False,  # depends on components
-        "univariate-only": False,  # depends on components
+        "capability:multivariate": True,  # depends on components
         "capability:missing_values": False,  # depends on components
         "X_inner_type": ["pd.DataFrame", "pd-multiindex", "pd_multiindex_hier"],
         "y_inner_type": "None",
@@ -497,7 +497,7 @@ class FeatureUnion(_HeterogenousMetaEstimator, BaseTransformer):
         self._anytagis_then_set("skip-inverse-transform", True, False, ests)
         # self._anytagis_then_set("capability:inverse_transform", False, True, ests)
         self._anytagis_then_set("capability:missing_values", False, True, ests)
-        self._anytagis_then_set("univariate-only", True, False, ests)
+        self._anytagis_then_set("capability:multivariate", False, True, ests)
 
     @property
     def _transformer_list(self):
@@ -857,7 +857,7 @@ class MultiplexTransformer(_HeterogenousMetaEstimator, _DelegatedTransformer):
     # tags will largely be copied from selected_transformer
     _tags = {
         "fit_is_empty": False,
-        "univariate-only": False,
+        "capability:multivariate": True,
         "X_inner_type": [
             "dask_panel",
             "pd-multiindex",
@@ -1067,7 +1067,7 @@ class InvertTransform(_DelegatedTransformer):
         "instancewise": True,  # is this an instance-wise transform?
         "X_inner_type": ["pd.DataFrame", "pd.Series"],
         "y_inner_type": "None",
-        "univariate-only": False,
+        "capability:multivariate": True,
         "fit_is_empty": False,
         "capability:inverse_transform": True,
     }
@@ -1192,7 +1192,7 @@ class Id(BaseTransformer):
 
     _tags = {
         "capability:inverse_transform": True,  # can the transformer inverse transform?
-        "univariate-only": False,  # can the transformer handle multivariate X?
+        "capability:multivariate": True,  # can the transformer handle multivariate X?
         "X_inner_type": ALL_TIME_SERIES_TYPES,
         "y_inner_type": "None",
         "fit_is_empty": True,  # is fit empty and can be skipped? Yes = True
@@ -1321,7 +1321,7 @@ class OptionalPassthrough(_DelegatedTransformer):
         "instancewise": True,
         "X_inner_type": ALL_TIME_SERIES_TYPES,
         "y_inner_type": "None",
-        "univariate-only": False,
+        "capability:multivariate": True,
         "fit_is_empty": False,
         "capability:inverse_transform": True,
     }
@@ -1426,7 +1426,7 @@ class ColumnwiseTransformer(BaseTransformer):
         "instancewise": True,  # is this an instance-wise transform?
         "X_inner_type": "pd.DataFrame",
         "y_inner_type": "None",
-        "univariate-only": False,
+        "capability:multivariate": True,
         "fit_is_empty": False,
     }
 
@@ -1685,7 +1685,7 @@ class YtoX(BaseTransformer):
     _tags = {
         "transform-returns-same-time-index": True,
         "skip-inverse-transform": False,
-        "univariate-only": False,
+        "capability:multivariate": True,
         "X_inner_type": ["pd.DataFrame", "pd-multiindex", "pd_multiindex_hier"],
         "y_inner_type": ["pd.DataFrame", "pd-multiindex", "pd_multiindex_hier"],
         "y_input_type": "both",
diff --git a/aeon/transformations/cos.py b/aeon/transformations/cos.py
index ec551c0a..b3265c8b 100644
--- a/aeon/transformations/cos.py
+++ b/aeon/transformations/cos.py
@@ -34,7 +34,7 @@ class CosineTransformer(BaseTransformer):
         "instancewise": True,
         "X_inner_type": "np.ndarray",
         "y_inner_type": "None",
-        "univariate-only": False,
+        "capability:multivariate": True,
         "fit_is_empty": True,
         "transform-returns-same-time-index": True,
         "capability:inverse_transform": False,
diff --git a/aeon/transformations/date.py b/aeon/transformations/date.py
index 36ef8387..d44f926c 100644
--- a/aeon/transformations/date.py
+++ b/aeon/transformations/date.py
@@ -122,7 +122,7 @@ class DateTimeFeatures(BaseTransformer):
             "pd_multiindex_hier",
         ],
         "y_inner_type": "None",
-        "univariate-only": False,
+        "capability:multivariate": True,
         "fit_is_empty": True,
         "transform-returns-same-time-index": True,
         "enforce_index_type": [pd.DatetimeIndex, pd.PeriodIndex],
diff --git a/aeon/transformations/detrend/_deseasonalize.py b/aeon/transformations/detrend/_deseasonalize.py
index 72a86c36..f5a0d946 100644
--- a/aeon/transformations/detrend/_deseasonalize.py
+++ b/aeon/transformations/detrend/_deseasonalize.py
@@ -124,7 +124,7 @@ class Deseasonalizer(BaseTransformer):
         "fit_is_empty": False,
         "capability:inverse_transform": True,
         "transform-returns-same-time-index": True,
-        "univariate-only": True,
+        "capability:multivariate": False,
         "python_dependencies": "statsmodels",
     }
 
@@ -509,7 +509,7 @@ class STLTransformer(BaseTransformer):
         "X_inner_type": "pd.Series",
         "y_inner_type": "pd.Series",
         "transform-returns-same-time-index": True,
-        "univariate-only": True,
+        "capability:multivariate": False,
         "fit_is_empty": False,
         "python_dependencies": "statsmodels",
     }
diff --git a/aeon/transformations/detrend/_detrend.py b/aeon/transformations/detrend/_detrend.py
index 4c2184da..c03e463b 100644
--- a/aeon/transformations/detrend/_detrend.py
+++ b/aeon/transformations/detrend/_detrend.py
@@ -73,7 +73,7 @@ class Detrender(BaseTransformer):
         "instancewise": True,  # is this an instance-wise transform?
         "X_inner_type": ["pd.DataFrame", "pd-multiindex", "pd_multiindex_hier"],
         "y_inner_type": ["pd.DataFrame", "pd-multiindex", "pd_multiindex_hier"],
-        "univariate-only": False,
+        "capability:multivariate": True,
         "fit_is_empty": False,
         "capability:inverse_transform": True,
         "transform-returns-same-time-index": True,
diff --git a/aeon/transformations/difference.py b/aeon/transformations/difference.py
index 26f52b49..b86b3ffc 100644
--- a/aeon/transformations/difference.py
+++ b/aeon/transformations/difference.py
@@ -226,7 +226,7 @@ class Differencer(BaseTransformer):
         "y_inner_type": "None",
         "fit_is_empty": False,
         "transform-returns-same-time-index": False,
-        "univariate-only": False,
+        "capability:multivariate": True,
         "capability:inverse_transform": True,
     }
 
diff --git a/aeon/transformations/exponent.py b/aeon/transformations/exponent.py
index a0b8891f..2bcb451c 100644
--- a/aeon/transformations/exponent.py
+++ b/aeon/transformations/exponent.py
@@ -78,7 +78,7 @@ class ExponentTransformer(BaseTransformer):
         "y_inner_type": "None",
         "fit_is_empty": True,
         "transform-returns-same-time-index": True,
-        "univariate-only": False,
+        "capability:multivariate": True,
         "capability:inverse_transform": True,
     }
 
diff --git a/aeon/transformations/feature_selection.py b/aeon/transformations/feature_selection.py
index 12c7b38d..d19ccb11 100644
--- a/aeon/transformations/feature_selection.py
+++ b/aeon/transformations/feature_selection.py
@@ -79,7 +79,7 @@ class FeatureSelection(BaseTransformer):
         "fit_is_empty": False,
         "transform-returns-same-time-index": True,
         "skip-inverse-transform": True,
-        "univariate-only": False,
+        "capability:multivariate": True,
     }
 
     def __init__(
diff --git a/aeon/transformations/fourier.py b/aeon/transformations/fourier.py
index 39b60459..2b074878 100644
--- a/aeon/transformations/fourier.py
+++ b/aeon/transformations/fourier.py
@@ -76,7 +76,7 @@ class FourierFeatures(BaseTransformer):
         # what is the abstract type of y: None (not needed), Primitives, Series, Panel
         "instancewise": True,
         "capability:inverse_transform": False,
-        "univariate-only": False,
+        "capability:multivariate": True,
         "X_inner_type": "pd.DataFrame",
         "y_inner_type": "None",
         "requires_y": False,  # does y need to be passed in fit?
diff --git a/aeon/transformations/hierarchical/aggregate.py b/aeon/transformations/hierarchical/aggregate.py
index e82104be..5f0b9151 100644
--- a/aeon/transformations/hierarchical/aggregate.py
+++ b/aeon/transformations/hierarchical/aggregate.py
@@ -60,7 +60,7 @@ class Aggregator(BaseTransformer):
         "y_inner_type": "None",
         "capability:inverse_transform": False,  # does transformer have inverse
         "skip-inverse-transform": True,  # is inverse-transform skipped when called?
-        "univariate-only": False,  # can the transformer handle multivariate X?
+        "capability:multivariate": True,  # can the transformer handle multivariate X?
         "capability:missing_values": False,  # can estimator handle missing data?
         "X-y-must-have-same-index": False,  # can estimator handle different X/y index?
         "fit_is_empty": True,  # is fit empty and can be skipped? Yes = True
diff --git a/aeon/transformations/hierarchical/reconcile.py b/aeon/transformations/hierarchical/reconcile.py
index 015022be..af4594ab 100644
--- a/aeon/transformations/hierarchical/reconcile.py
+++ b/aeon/transformations/hierarchical/reconcile.py
@@ -84,7 +84,7 @@ class Reconciler(BaseTransformer):
         "y_inner_type": "None",
         "capability:inverse_transform": False,
         "skip-inverse-transform": True,  # is inverse-transform skipped when called?
-        "univariate-only": True,  # can the transformer handle multivariate X?
+        "capability:multivariate": False,  # can the transformer handle multivariate X?
         "capability:missing_values": False,  # can estimator handle missing data?
         "X-y-must-have-same-index": False,  # can estimator handle different X/y index?
         "fit_is_empty": False,  # is fit empty and can be skipped? Yes = True
diff --git a/aeon/transformations/impute.py b/aeon/transformations/impute.py
index 9e0df2c5..87125b00 100644
--- a/aeon/transformations/impute.py
+++ b/aeon/transformations/impute.py
@@ -90,7 +90,7 @@ class Imputer(BaseTransformer):
         "capability:missing_values": True,
         "skip-inverse-transform": True,
         "capability:inverse_transform": True,
-        "univariate-only": False,
+        "capability:multivariate": True,
         "capability:missing_values:removes": True,
         "remember_data": False,
     }
diff --git a/aeon/transformations/lag.py b/aeon/transformations/lag.py
index 85a3863b..0d0346c1 100644
--- a/aeon/transformations/lag.py
+++ b/aeon/transformations/lag.py
@@ -114,7 +114,7 @@ class Lag(BaseTransformer):
         # what abstract type is returned: Primitives, Series, Panel
         "instancewise": True,  # is this an instance-wise transform?
         "capability:inverse_transform": False,  # can the transformer inverse transform?
-        "univariate-only": False,  # can the transformer handle multivariate X?
+        "capability:multivariate": True,  # can the transformer handle multivariate X?
         "X_inner_type": "pd.DataFrame",
         "y_inner_type": "None",
         "fit_is_empty": False,  # is fit empty and can be skipped? Yes = True
@@ -391,7 +391,7 @@ class ReducerTransform(BaseTransformer):
         "output_data_type": "Series",
         "instancewise": True,  # is this an instance-wise transform?
         "capability:inverse_transform": False,  # can the transformer inverse transform?
-        "univariate-only": False,  # can the transformer handle multivariate X?
+        "capability:multivariate": True,  # can the transformer handle multivariate X?
         "X_inner_type": "pd.DataFrame",
         "y_inner_type": "pd.DataFrame",
         "fit_is_empty": False,  # is fit empty and can be skipped? Yes = True
diff --git a/aeon/transformations/outlier_detection.py b/aeon/transformations/outlier_detection.py
index e5a75ce9..6a51a4c3 100644
--- a/aeon/transformations/outlier_detection.py
+++ b/aeon/transformations/outlier_detection.py
@@ -60,7 +60,7 @@ class HampelFilter(BaseTransformer):
         "fit_is_empty": True,
         "capability:missing_values": True,
         "skip-inverse-transform": True,
-        "univariate-only": False,
+        "capability:multivariate": True,
     }
 
     def __init__(self, window_length=10, n_sigma=3, k=1.4826, return_bool=False):
diff --git a/aeon/transformations/pca.py b/aeon/transformations/pca.py
index 5f29e117..7b061966 100644
--- a/aeon/transformations/pca.py
+++ b/aeon/transformations/pca.py
@@ -111,7 +111,7 @@ class PCATransformer(BaseTransformer):
         "instancewise": False,  # is this an instance-wise transform?
         "X_inner_type": "pd.DataFrame",
         "y_inner_type": "None",
-        "univariate-only": False,
+        "capability:multivariate": True,
         "fit_is_empty": False,
         "capability:missing_values": False,
         "python_version": ">=3.8",
diff --git a/aeon/transformations/scaledlogit.py b/aeon/transformations/scaledlogit.py
index 41c11853..8f959449 100644
--- a/aeon/transformations/scaledlogit.py
+++ b/aeon/transformations/scaledlogit.py
@@ -98,7 +98,7 @@ class ScaledLogitTransformer(BaseTransformer):
         "y_inner_type": "None",
         "transform-returns-same-time-index": True,
         "fit_is_empty": True,
-        "univariate-only": False,
+        "capability:multivariate": True,
         "capability:inverse_transform": True,
         "skip-inverse-transform": False,
     }
diff --git a/aeon/transformations/series/base.py b/aeon/transformations/series/base.py
index 297abb40..878dc2c6 100644
--- a/aeon/transformations/series/base.py
+++ b/aeon/transformations/series/base.py
@@ -101,7 +101,8 @@ class BaseSeriesTransformer(BaseSeriesEstimator, BaseTransformer, metaclass=ABCM
 
         Returns
         -------
-        transformed version of X
+        transformed version of X with the same axis as passed by the user, if axis
+        not None.
         """
         # check whether is fitted
         self.check_is_fitted()
@@ -109,7 +110,8 @@ class BaseSeriesTransformer(BaseSeriesEstimator, BaseTransformer, metaclass=ABCM
         if axis is None:
             axis = self.axis
         X = self._preprocess_series(X, axis=axis)
-        return self._transform(X)
+        Xt = self._transform(X)
+        return self._postprocess_series(Xt, axis=axis)
 
     @final
     def fit_transform(self, X, y=None, axis=None):
@@ -138,14 +140,15 @@ class BaseSeriesTransformer(BaseSeriesEstimator, BaseTransformer, metaclass=ABCM
 
         Returns
         -------
-        transformed version of X
+        transformed version of X with the same axis as passed by the user, if axis
+        not None.
         """
         # input checks and datatype conversion, to avoid doing in both fit and transform
         self.reset()
         X = self._preprocess_series(X, axis=axis)
         Xt = self._fit_transform(X=X, y=y)
         self._is_fitted = True
-        return Xt
+        return self._postprocess_series(Xt, axis=axis)
 
     @final
     def inverse_transform(self, X, y=None, axis=None):
@@ -177,7 +180,8 @@ class BaseSeriesTransformer(BaseSeriesEstimator, BaseTransformer, metaclass=ABCM
         # check whether is fitted
         self.check_is_fitted()
         X = self._preprocess_series(X, axis=axis)
-        return self._inverse_transform(X=X, y=y)
+        Xt = self._inverse_transform(X=X, y=y)
+        return self._postprocess_series(Xt, axis=axis)
 
     @final
     def update(self, X, y=None, update_params=True, axis=None):
diff --git a/aeon/transformations/subset.py b/aeon/transformations/subset.py
index 4a5fdf74..57073dea 100644
--- a/aeon/transformations/subset.py
+++ b/aeon/transformations/subset.py
@@ -15,8 +15,8 @@ class IndexSubset(BaseTransformer):
     If `y` is None, returns `X` without subsetting.
     numpy-based `X` are interpreted as having a RangeIndex starting at n,
     where n is the number of numpy rows seen so far through `fit` and `update`.
-    Non-pandas types are interpreted as having index as after conversion to pandas,
-    via `datatypes.convert_to`, to the `"pd.DataFrame"` aeon type.
+    Non-pandas types are interpreted as having index as after conversion to the
+    `"pd.DataFrame"` aeon type.
 
     Parameters
     ----------
@@ -45,7 +45,7 @@ class IndexSubset(BaseTransformer):
         "y_inner_type": ["pd.DataFrame", "pd.Series"],
         "transform-returns-same-time-index": False,
         "fit_is_empty": False,
-        "univariate-only": False,
+        "capability:multivariate": True,
         "capability:inverse_transform": False,
         "remember_data": True,  # remember all data seen as _X
     }
@@ -158,7 +158,7 @@ class ColumnSelect(BaseTransformer):
         "y_inner_type": "None",
         "transform-returns-same-time-index": True,
         "fit_is_empty": True,
-        "univariate-only": False,
+        "capability:multivariate": True,
         "capability:inverse_transform": False,
     }
 
diff --git a/aeon/transformations/summarize.py b/aeon/transformations/summarize.py
index 6f122c25..0c4c8302 100644
--- a/aeon/transformations/summarize.py
+++ b/aeon/transformations/summarize.py
@@ -188,7 +188,7 @@ class WindowSummarizer(BaseTransformer):
             "pd_multiindex_hier",
         ],
         "skip-inverse-transform": True,  # is inverse-transform skipped when called?
-        "univariate-only": False,  # can the transformer handle multivariate X?
+        "capability:multivariate": True,  # can the transformer handle multivariate X?
         "capability:missing_values": True,  # can estimator handle missing data?
         "X-y-must-have-same-index": False,  # can estimator handle different X/y index?
         "enforce_index_type": None,  # index type that needs to be enforced in X/y
@@ -748,7 +748,7 @@ class PlateauFinder(BaseTransformer):
 
     _tags = {
         "fit_is_empty": True,
-        "univariate-only": True,
+        "capability:multivariate": False,
         "output_data_type": "Series",
         "instancewise": False,
         "X_inner_type": "numpy3D",
@@ -838,7 +838,7 @@ class FittedParamExtractor(BaseTransformer):
 
     _tags = {
         "fit_is_empty": True,
-        "univariate-only": True,
+        "capability:multivariate": False,
         "input_data_type": "Series",
         # what is the abstract type of X: Series, or Panel
         "output_data_type": "Primitives",
diff --git a/aeon/transformations/theta.py b/aeon/transformations/theta.py
index b9f89840..92da676c 100644
--- a/aeon/transformations/theta.py
+++ b/aeon/transformations/theta.py
@@ -66,7 +66,7 @@ class ThetaLinesTransformer(BaseTransformer):
         "X_inner_type": ["pd.DataFrame", "pd.Series"],
         "y_inner_type": "None",
         "transform-returns-same-time-index": True,
-        "univariate-only": True,
+        "capability:multivariate": False,
         "fit_is_empty": True,
     }
 
diff --git a/aeon/utils/conversion/__init__.py b/aeon/utils/conversion/__init__.py
index 49842067..cccb33c9 100644
--- a/aeon/utils/conversion/__init__.py
+++ b/aeon/utils/conversion/__init__.py
@@ -4,6 +4,7 @@ __all__ = [
     "resolve_equal_length_inner_type",
     "resolve_unequal_length_inner_type",
     "convert_collection",
+    "convert_series",
 ]
 
 from aeon.utils.conversion._convert_collection import (
@@ -11,3 +12,4 @@ from aeon.utils.conversion._convert_collection import (
     resolve_equal_length_inner_type,
     resolve_unequal_length_inner_type,
 )
+from aeon.utils.conversion._convert_series import convert_series
diff --git a/aeon/utils/conversion/_convert_series.py b/aeon/utils/conversion/_convert_series.py
new file mode 100644
index 00000000..9271244b
--- /dev/null
+++ b/aeon/utils/conversion/_convert_series.py
@@ -0,0 +1,72 @@
+"""Series data converters.
+
+This contains all functions to convert supported collection data types.
+
+String identifier meanings (from aeon.utils.conversion import COLLECTIONS_DATA_TYPES) :
+np.ndarray : 1 or 2D numpy array of time series
+pd.Series:
+pd.DataFrame:
+"""
+
+import numpy as np
+import pandas as pd
+
+
+def convert_series(y, output_type):
+    """Convert series y to the specified output_type.
+
+    y, a single series of type pd.Series, pd.DataFrame or np.ndarray.
+
+    Parameters
+    ----------
+    y : np.ndarray, pd.Series, pd.DataFrame
+        Time series to be converted
+    output_type : string
+        The type to convert y to
+
+    Returns
+    -------
+    converted version of y
+    """
+    if output_type == "np.ndarray":
+        if isinstance(y, (pd.Series, pd.DataFrame)):
+            return y.to_numpy()
+        elif isinstance(y, np.ndarray):
+            return y
+        else:
+            raise ValueError(f"Cannot convert: {type(y)} to np.ndarray")
+    elif output_type == "pd.Series":
+        if isinstance(y, pd.Series):
+            return y
+        elif isinstance(y, pd.DataFrame):
+            if y.shape[1] > 1:
+                raise ValueError(
+                    "DataFrame more than one column, cannot convert to Series"
+                )
+            y = y.iloc[:, 0]
+            return y
+        elif isinstance(y, np.ndarray):
+            if y.ndim == 1:
+                # Convert 1D numpy array to pandas Series
+                return pd.Series(y)
+            else:
+                raise ValueError(
+                    "Cannot convert array with more than one dimension to a pd.Series"
+                )
+    elif output_type == "pd.DataFrame":
+        if isinstance(y, pd.DataFrame):
+            return y
+        elif isinstance(y, pd.Series):
+            return y.to_frame()
+        elif isinstance(y, np.ndarray):
+            return pd.DataFrame(y)
+        else:
+            raise ValueError(
+                f"Unknown output_type: {output_type} must be one of "
+                f"pd.Series, pd.DataFrame or np.ndarray"
+            )
+    else:
+        raise ValueError(
+            f"Unknown output_type: {output_type} must be one of "
+            f"pd.Series, pd.DataFrame or np.ndarray"
+        )
diff --git a/build_tools/run_examples.sh b/build_tools/run_examples.sh
index 9a57fba1..d03c4fc2 100755
--- a/build_tools/run_examples.sh
+++ b/build_tools/run_examples.sh
@@ -5,12 +5,15 @@ set -euxo pipefail
 
 CMD="jupyter nbconvert --to notebook --inplace --execute --ExecutePreprocessor.timeout=600"
 
-excluded=(
-  "examples/datasets/load_data_from_web.ipynb"
-  "examples/benchmarking/reference_results.ipynb"
-  "examples/benchmarking/bakeoff_results.ipynb"
-  "examples/benchmarking/regression.ipynb"
-)
+excluded=()
+if [ "$1" = true ]; then
+  excluded+=(
+    "examples/datasets/load_data_from_web.ipynb"
+    "examples/benchmarking/reference_results.ipynb"
+    "examples/benchmarking/bakeoff_results.ipynb"
+    "examples/benchmarking/regression.ipynb"
+  )
+fi
 
 shopt -s lastpipe
 notebooks=()
diff --git a/docs/api_reference/classification.rst b/docs/api_reference/classification.rst
index 12f2586c..51291c6f 100644
--- a/docs/api_reference/classification.rst
+++ b/docs/api_reference/classification.rst
@@ -20,6 +20,8 @@ Convolution-based
 
     Arsenal
     RocketClassifier
+    HydraClassifier
+    MultiRocketHydraClassifier
 
 Deep learning
 -------------
diff --git a/docs/api_reference/regression.rst b/docs/api_reference/regression.rst
index 0d09a049..fa6a23e8 100644
--- a/docs/api_reference/regression.rst
+++ b/docs/api_reference/regression.rst
@@ -37,6 +37,8 @@ Deep learning
     InceptionTimeRegressor
     IndividualInceptionRegressor
     ResNetRegressor
+    LITETimeRegressor
+    IndividualLITERegressor
 
 Distance-based
 --------------
diff --git a/docs/developer_guide/deprecation.md b/docs/developer_guide/deprecation.md
index 84f5d553..7e5f27e3 100644
--- a/docs/developer_guide/deprecation.md
+++ b/docs/developer_guide/deprecation.md
@@ -18,8 +18,9 @@ Note that the deprecation policy does not necessarily apply to modules we class
 - `benchmarking`
 - `segmentation`
 - `similarity_search`
-- `visualisation`
 - `testing`
+- `transformations/series`
+- `visualisation`
 
 When we introduce a new module, we may classify it as experimental until the API is stable. We will try to not make drastic changes to experimental modules, but we need to retain the freedom to be more agile with the design in these cases.
 
diff --git a/docs/mentoring.md b/docs/mentoring.md
index 99e72879..d9bcaf4d 100644
--- a/docs/mentoring.md
+++ b/docs/mentoring.md
@@ -112,10 +112,8 @@ shapelets.
 
 ##### Expected Outcomes
 
-We expect the mentee engage with the aeon community and produce a performance games
-for the
-We Based on the benchmark of the different implementations, we will evaluate the
-performance gains of the new shapelet transform and the success of this project.
+We expect the mentee to engage with the aeon community and produce a more performant
+implementation for the shapelet transform that gets accepted into the toolkit.
 
 ##### References
 


Test patch:
diff --git a/.github/workflows/periodic_tests.yml b/.github/workflows/periodic_tests.yml
index bf19ded2..2a8922bc 100644
--- a/.github/workflows/periodic_tests.yml
+++ b/.github/workflows/periodic_tests.yml
@@ -73,7 +73,7 @@ jobs:
           command: python -m pip install .[all_extras,binder,dev]
 
       - name: Run example notebooks
-        run: build_tools/run_examples.sh
+        run: build_tools/run_examples.sh false
         shell: bash
 
       - name: Save new cache
diff --git a/aeon/base/tests/test_base_series.py b/aeon/base/tests/test_base_series.py
index 05d62fe2..bbe816f8 100644
--- a/aeon/base/tests/test_base_series.py
+++ b/aeon/base/tests/test_base_series.py
@@ -25,6 +25,11 @@ def test__check_X():
         dummy1._check_X(multi_np)
     with pytest.raises(ValueError, match=r"Multivariate data not supported"):
         dummy1._check_X(multi_pd)
+    missing = np.random.rand(4, 10)
+    missing[3][8] = np.NAN
+    with pytest.raises(ValueError, match=r"Missing values not supported"):
+        dummy1._check_X(missing)
+
     dummy2 = BaseSeriesEstimator()
     all_tags = {
         "capability:multivariate": True,
@@ -58,6 +63,9 @@ def test__check_X():
         dummy2._check_X(uni_np)
     with pytest.raises(ValueError, match=r"Univariate data not supported"):
         dummy2._check_X(uni_pd)
+    wrong_size = np.random.rand(10, 2, 20)
+    with pytest.raises(ValueError, match=r"Should be 1D or 2D"):
+        dummy2._check_X(wrong_size)
 
 
 UNIVARIATE = {
@@ -91,6 +99,14 @@ def test_univariate_convert_X():
             assert type(X2).__name__ == st.split(".")[1]
             X2 = dummy1._convert_X(X, axis=1)
             assert len(X) == len(X2)
+    with pytest.raises(ValueError, match="Axis should be 0 or 1"):
+        dummy1._convert_X(X, axis=2)
+    d = BaseSeriesEstimator.get_test_params()
+    assert d["axis"] == 0
+    all_tags = {"X_inner_type": "numpy3D"}
+    dummy1.set_tags(**all_tags)
+    with pytest.raises(ValueError, match="Unsupported inner"):
+        dummy1._convert_X(X, axis=0)
 
 
 def test_multivariate_convert_X():
@@ -158,3 +174,27 @@ def test_check_y_wrong(y_wrong):
     """Test the _check_y method with incorrect input."""
     with pytest.raises(ValueError, match="Error in input type for y"):
         BaseSeriesEstimator._check_y(None, y_wrong)
+
+
+@pytest.mark.parametrize("returned_type", VALID_TYPES)
+def test__postprocess_series(returned_type):
+    """Test data reformatted correctly."""
+    x = UNIVARIATE[returned_type]
+    dummy1 = BaseSeriesEstimator()
+    tags = {"capability:multivariate": False}
+    dummy1.set_tags(**tags)
+    y = dummy1._postprocess_series(x)
+    y2 = dummy1._postprocess_series(x, axis=0)
+    y3 = dummy1._postprocess_series(x, axis=1)
+    assert y.ndim == 1
+    assert y2.shape == y3.shape
+    tags = {"capability:multivariate": True}
+    dummy1.set_tags(**tags)
+    x = MULTIVARIATE[returned_type]
+    y = dummy1._postprocess_series(x)
+    y2 = dummy1._postprocess_series(x, axis=0)
+    y3 = dummy1._postprocess_series(x, axis=1)
+    assert x.shape == y.shape
+    assert y.shape == y2.shape
+    if returned_type != "pd.Series":
+        assert y.shape[0] == y3.shape[1] and y.shape[1] == y3.shape[0]
diff --git a/aeon/testing/mock_estimators/__init__.py b/aeon/testing/mock_estimators/__init__.py
index 5d51236d..3f51f085 100644
--- a/aeon/testing/mock_estimators/__init__.py
+++ b/aeon/testing/mock_estimators/__init__.py
@@ -12,6 +12,9 @@ __all__ = [
     "SupervisedMockSegmenter",
     "MockHandlesAllInput",
     "MockRegressor",
+    "MockMultivariateSeriesTransformer",
+    "MockSeriesTransformerNoFit",
+    "MockUnivariateSeriesTransformer",
 ]
 
 from aeon.testing.mock_estimators._mock_classifiers import (
@@ -33,3 +36,8 @@ from aeon.testing.mock_estimators._mock_segmenters import (
     MockSegmenter,
     SupervisedMockSegmenter,
 )
+from aeon.testing.mock_estimators._mock_series_transformers import (
+    MockMultivariateSeriesTransformer,
+    MockSeriesTransformerNoFit,
+    MockUnivariateSeriesTransformer,
+)
diff --git a/aeon/testing/mock_estimators/_mock_series_transformers.py b/aeon/testing/mock_estimators/_mock_series_transformers.py
new file mode 100644
index 00000000..c3bea3d3
--- /dev/null
+++ b/aeon/testing/mock_estimators/_mock_series_transformers.py
@@ -0,0 +1,269 @@
+"""Mock series transformers."""
+
+import numpy as np
+
+from aeon.transformations.series import BaseSeriesTransformer
+
+
+class MockUnivariateSeriesTransformer(BaseSeriesTransformer):
+    """
+    MockSeriesTransformer adds a random value and a constant to the input series.
+
+    Parameters
+    ----------
+    constant : int, default=0
+        The constant to be added to each element of the input time series.
+    random_state : int or None, default=None
+        Seed for random number generation.
+    """
+
+    _tags = {
+        "capability:univariate": True,
+        "capability:multivariate": False,
+        "capability:inverse_transform": True,
+    }
+
+    def __init__(self, constant: int = 0, random_state=None) -> None:
+        self.constant = constant
+        self.random_state = random_state
+        super().__init__(axis=1)
+
+    def _fit(self, X: np.ndarray, y=None):
+        """Simulate a fit on X by generating a random value to be added to inputs.
+
+        Parameters
+        ----------
+        X : np.ndarray, shape = (n_channels, n_timepoints)
+            2D time series to be transformed
+        y : ignored argument for interface compatibility
+
+        Returns
+        -------
+        self
+        """
+        self.n_features_ = X.shape[0]
+        rng = np.random.RandomState(seed=self.random_state)
+        self.random_values_ = rng.random(self.n_features_)
+        return self
+
+    def _transform(self, X: np.ndarray, y=None) -> np.ndarray:
+        """Transform X by adding the constant and random value set during fit.
+
+        Parameters
+        ----------
+        X : np.ndarray, shape = (n_channels, n_timepoints)
+            2D time series to be transformed
+        y : ignored argument for interface compatibility
+
+        Returns
+        -------
+        np.ndarray, shape = (n_channels, n_timepoints)
+            2D transformed version of X
+        """
+        X_new = np.zeros_like(X)
+        for i in range(self.n_features_):
+            X_new[i] = X[i] + (self.constant + self.random_values_[i])
+        return X_new
+
+    def _inverse_transform(self, X: np.ndarray, y=None) -> np.ndarray:
+        """Inverse transform X by substracting the constant and random value.
+
+        Parameters
+        ----------
+        X : np.ndarray, shape = (n_channels, n_timepoints)
+            2D time series to be transformed
+        y : ignored argument for interface compatibility
+
+        Returns
+        -------
+        np.ndarray, shape = (n_channels, n_timepoints)
+            2D inverse transformed version of X
+        """
+        X_new = np.zeros_like(X)
+        for i in range(self.n_features_):
+            X_new[i] = X[i] - (self.constant + self.random_values_[i])
+        return X_new
+
+    @classmethod
+    def get_test_params(cls, parameter_set="default"):
+        """
+        Return testing parameter settings for the estimator.
+
+        Parameters
+        ----------
+        parameter_set : str, default="default"
+
+        Returns
+        -------
+        params : dict or list of dict, default = {}
+            Parameters to create testing instances of the class.
+        """
+        return {}
+
+
+class MockMultivariateSeriesTransformer(BaseSeriesTransformer):
+    """
+    MockSeriesTransformer adds a random value and a constant to the input series.
+
+    Parameters
+    ----------
+    constant : int, default=0
+        The constant to be added to each element of the input time series.
+    random_state : int or None, default=None
+        Seed for random number generation.
+    """
+
+    _tags = {
+        "capability:multivariate": True,
+        "capability:inverse_transform": True,
+    }
+
+    def __init__(self, constant: int = 0, random_state=None) -> None:
+        self.constant = constant
+        self.random_state = random_state
+        super().__init__(axis=1)
+
+    def _fit(self, X: np.ndarray, y=None):
+        """Simulate a fit on X by generating a random value to be added to inputs.
+
+        Parameters
+        ----------
+        X : np.ndarray, shape = (n_channels, n_timepoints)
+            2D time series to be transformed
+        y : ignored argument for interface compatibility
+
+        Returns
+        -------
+        self
+        """
+        self.n_features_ = X.shape[0]
+        rng = np.random.RandomState(seed=self.random_state)
+        self.random_values_ = rng.random(self.n_features_)
+        return self
+
+    def _transform(self, X: np.ndarray, y=None) -> np.ndarray:
+        """Transform X by adding the constant and random value set during fit.
+
+        Parameters
+        ----------
+        X : np.ndarray, shape = (n_channels, n_timepoints)
+            2D time series to be transformed
+        y : ignored argument for interface compatibility
+
+        Returns
+        -------
+        np.ndarray, shape = (n_channels, n_timepoints)
+            2D transformed version of X
+        """
+        X_new = np.zeros_like(X)
+        for i in range(self.n_features_):
+            X_new[i] = X[i] + (self.constant + self.random_values_[i])
+        return X_new
+
+    def _inverse_transform(self, X: np.ndarray, y=None) -> np.ndarray:
+        """Inverse transform X by substracting the constant and random value.
+
+        Parameters
+        ----------
+        X : np.ndarray, shape = (n_channels, n_timepoints)
+            2D time series to be transformed
+        y : ignored argument for interface compatibility
+
+        Returns
+        -------
+        np.ndarray, shape = (n_channels, n_timepoints)
+            2D inverse transformed version of X
+        """
+        X_new = np.zeros_like(X)
+        for i in range(self.n_features_):
+            X_new[i] = X[i] - (self.constant + self.random_values_[i])
+        return X_new
+
+    @classmethod
+    def get_test_params(cls, parameter_set="default"):
+        """
+        Return testing parameter settings for the estimator.
+
+        Parameters
+        ----------
+        parameter_set : str, default="default"
+
+        Returns
+        -------
+        params : dict or list of dict, default = {}
+            Parameters to create testing instances of the class.
+        """
+        return {}
+
+
+class MockSeriesTransformerNoFit(BaseSeriesTransformer):
+    """
+    MockSeriesTransformerNoFit adds a value to all elements the input series.
+
+    Parameters
+    ----------
+    constant : int, default=0
+        The constant to be added to each element of the input time series.
+    """
+
+    _tags = {
+        "capability:multivariate": True,
+        "capability:inverse_transform": True,
+        "fit_is_empty": True,
+    }
+
+    def __init__(self, constant: int = 0) -> None:
+        self.constant = constant
+        super().__init__(axis=1)
+
+    def _transform(self, X: np.ndarray, y=None) -> np.ndarray:
+        """Transform X by adding the constant value given in init.
+
+        Parameters
+        ----------
+        X : np.ndarray, shape = (n_channels, n_timepoints)
+            2D time series to be transformed
+        y : ignored argument for interface compatibility
+
+        Returns
+        -------
+        np.ndarray, shape = (n_channels, n_timepoints)
+            2D transformed version of X
+        """
+        X_new = np.zeros_like(X)
+        X_new = X + self.constant
+        return X_new
+
+    def _inverse_transform(self, X: np.ndarray, y=None) -> np.ndarray:
+        """Inverse transform X by substracting the constant.
+
+        Parameters
+        ----------
+        X : np.ndarray, shape = (n_channels, n_timepoints)
+            2D time series to be transformed
+        y : ignored argument for interface compatibility
+
+        Returns
+        -------
+        np.ndarray, shape = (n_channels, n_timepoints)
+            2D inverse transformed version of X
+        """
+        X_new = np.zeros_like(X)
+        X_new = X - self.constant
+        return X_new
+
+    @classmethod
+    def get_test_params(cls, parameter_set="default"):
+        """
+        Return testing parameter settings for the estimator.
+
+        Parameters
+        ----------
+        parameter_set : str, default="default"
+
+        Returns
+        -------
+        params : dict or list of dict, default = {}
+            Parameters to create testing instances of the class.
+        """
+        return {}
diff --git a/aeon/transformations/series/tests/test_base.py b/aeon/transformations/series/tests/test_base.py
index 6db14d43..3320beba 100644
--- a/aeon/transformations/series/tests/test_base.py
+++ b/aeon/transformations/series/tests/test_base.py
@@ -1,1 +1,50 @@
-""""Test the base series transformer."""
+"""Test the base series transformer."""
+
+import numpy as np
+import pytest
+
+from aeon.testing.mock_estimators import (
+    MockMultivariateSeriesTransformer,
+    MockSeriesTransformerNoFit,
+    MockUnivariateSeriesTransformer,
+)
+
+ALL_TRANSFORMERS = [MockMultivariateSeriesTransformer(), MockSeriesTransformerNoFit()]
+MULTIVARIATE_SERIES = [
+    np.array([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]),
+    np.array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]),
+    np.array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]),
+]
+
+
+@pytest.mark.parametrize("transformer", ALL_TRANSFORMERS)
+@pytest.mark.parametrize("series", MULTIVARIATE_SERIES)
+def test_fit_transform_multivariate(transformer, series):
+    """Test fit then transform equivalent to fit_transform with setting axis."""
+    transformer.fit(series)
+    x2 = transformer.transform(series)
+    x3 = transformer.fit_transform(series)
+    np.testing.assert_array_almost_equal(x2, x3)
+    assert series.shape == x2.shape
+
+
+@pytest.mark.parametrize("transformer", ALL_TRANSFORMERS)
+@pytest.mark.parametrize("series", MULTIVARIATE_SERIES)
+def test_axis(transformer, series):
+    """Test axis."""
+    transformer.fit(series, axis=1)
+    x2 = transformer.transform(series, axis=1)
+    assert series.shape == x2.shape
+
+
+def test_fit_transform_univariate():
+    """Test fit transform for univariate."""
+    x1 = np.array([10, 9, 8, 7, 6, 5, 4, 3, 2, 1])
+    transformer = MockUnivariateSeriesTransformer()
+    x2 = transformer.fit_transform(x1)
+    assert x1.shape == x2.shape
+    x3 = np.array([[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]])
+    x4 = transformer.fit_transform(x3)
+    assert isinstance(x4, np.ndarray) and x4.ndim == 1
+    with pytest.raises(ValueError, match="Multivariate data not supported"):
+        transformer.fit_transform(np.random.rand(2, 10))