Repository: aeon-toolkit/aeon
Pull request #842 (2023-10-16 14:03:24)

[DOC] Update documentation to add workgroups
Updates links for the new governance and CoC documents.

The deleted gov file was a duplicate introduced during the front page merge, oops!

sweep-ai[bot]: ## Apply [Sweep Rules](https://docs.sweep.dev/usage/config#rules) to your PR?
- [ ] **Apply:**  There should not be large chunks of code that are just commented out. Docstrings and explanations in code are okay though.
- [ ] **Apply:**  Update the relevant API page in 'docs/api_reference/' when new public functions and classes are added and not included in the API documentation. Only add functions and classes which are not already in the relevant API documentation and avoid duplicate entries.

Patch:
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 6e3bb867..37132856 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -9,5 +9,5 @@ contact_links:
     url: https://mailxto.com/jbp3ave49x
     about: Send an email to the aeon developers at contact@aeon-toolkit.org. Checked periodically.
   - name: "\u2709\uFE0F CoC Report"
-    url: https://www.aeon-toolkit.org/en/latest/get_involved/code_of_conduct.html#incident-reporting-guidelines
-    about: Report an incident to the Code of Conduct Committee.
+    url: https://www.aeon-toolkit.org/en/latest/code_of_conduct.html
+    about: Report an incident to the Code of Conduct Workgroup.
diff --git a/.github/workflows/pr_examples.yml b/.github/workflows/pr_examples.yml
index 0d888ab9..e36538fa 100644
--- a/.github/workflows/pr_examples.yml
+++ b/.github/workflows/pr_examples.yml
@@ -1,4 +1,4 @@
-name: PR examples
+name: PR Examples
 
 on:
   push:
diff --git a/.github/workflows/pr_opened.yml b/.github/workflows/pr_opened.yml
new file mode 100644
index 00000000..be07f7b6
--- /dev/null
+++ b/.github/workflows/pr_opened.yml
@@ -0,0 +1,37 @@
+name: PR Opened
+on:
+  pull_request_target:
+    types: [opened]
+
+permissions:
+  contents: read
+  pull-requests: write
+  issues: write
+
+jobs:
+  # based on the scikit-learn 1.3.1 PR labelers
+  labeler:
+    runs-on: ubuntu-22.04
+
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          sparse-checkout: build_tools
+
+      - uses: actions/setup-python@v4
+        with:
+          python-version: "3.10"
+
+      - name: Install PyGithub
+        run: pip install -Uq PyGithub
+
+      - name: Label pull request
+        id: label-pr
+        run: python build_tools/pr_labeler.py
+        env:
+          CONTEXT_GITHUB: ${{ toJson(github) }}
+
+      - name: Write pull request comment
+        run: python build_tools/pr_open_commenter.py ${{ steps.label-pr.outputs.title-labels }} ${{ steps.label-pr.outputs.title-labels-new }} ${{ steps.label-pr.outputs.content-labels }} ${{ steps.label-pr.outputs.content-labels-status }}
+        env:
+          CONTEXT_GITHUB: ${{ toJson(github) }}
diff --git a/.github/workflows/pr_pre_commit.yml b/.github/workflows/pr_precommit.yml
similarity index 66%
rename from .github/workflows/pr_pre_commit.yml
rename to .github/workflows/pr_precommit.yml
index 8aadc4bb..4902bb84 100644
--- a/.github/workflows/pr_pre_commit.yml
+++ b/.github/workflows/pr_precommit.yml
@@ -29,7 +29,14 @@ jobs:
       - name: List changed files
         run: echo '${{ steps.changed-files.outputs.all_changed_files }}'
 
-      - uses: pre-commit/action@v3.0.0
+      - if: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'full pre-commit') }}
+        name: Full pre-commit
+        uses: pre-commit/action@v3.0.0
+        with:
+          extra_args: --all-files
+      - if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'full pre-commit') }}
+        name: Local pre-commit
+        uses: pre-commit/action@v3.0.0
         with:
           extra_args: --files ${{ steps.changed-files.outputs.all_changed_files }}
 
diff --git a/.github/workflows/precommit_autoupdate.yml b/.github/workflows/precommit_autoupdate.yml
new file mode 100644
index 00000000..4d1a74a3
--- /dev/null
+++ b/.github/workflows/precommit_autoupdate.yml
@@ -0,0 +1,27 @@
+name: Update pre-commit Hooks
+
+on:
+  schedule:
+    # every Monday at 12:30 AM UTC
+    - cron:  "30 0 * * 1"
+
+jobs:
+  pre-commit-auto-update:
+    runs-on: ubuntu-22.04
+
+    steps:
+      - uses: actions/checkout@v3
+
+      - uses: actions/setup-python@v4
+        with:
+          python-version: "3.10"
+
+      - uses: browniebroke/pre-commit-autoupdate-action@v1.0.0
+
+      - uses: peter-evans/create-pull-request@v5
+        with:
+          commit-message: "Automated `pre-commit` hook update"
+          branch: pre-commit-hooks-update
+          title: "[MNT] Automated `pre-commit` hook update"
+          body: "Automated weekly update to `.pre-commit-config.yaml` hook versions."
+          labels: maintenance, no changelog
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1ab62906..97781414 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -6,7 +6,7 @@ on:
       - published
 
 jobs:
-  check_manifest:
+  check-manifest:
     runs-on: ubuntu-22.04
 
     steps:
@@ -20,7 +20,7 @@ jobs:
         with:
           extra_args: check-manifest --hook-stage manual
 
-  build_project:
+  build-project:
     needs: check_manifest
     runs-on: ubuntu-22.04
 
@@ -41,8 +41,9 @@ jobs:
         with:
           name: dist
           path: dist/*
+          retention-days: 5
 
-  test_wheels:
+  test-wheels:
     needs: build_project
     runs-on: ${{ matrix.os }}
 
@@ -81,7 +82,7 @@ jobs:
       - name: Tests
         run: python -m pytest
 
-  upload_wheels:
+  upload-wheels:
     needs: test_wheels
     runs-on: ubuntu-22.04
 
diff --git a/.github/workflows/update_contributors.yml b/.github/workflows/update_contributors.yml
index 7af9db04..9fe5fa53 100644
--- a/.github/workflows/update_contributors.yml
+++ b/.github/workflows/update_contributors.yml
@@ -9,7 +9,8 @@ on:
 
 jobs:
   generate-markdown-and-commit:
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-22.04
+
     steps:
       - uses: actions/checkout@v3
 
@@ -26,8 +27,8 @@ jobs:
 
       - uses: peter-evans/create-pull-request@v5
         with:
-          commit-message: "Automated CONTRIBUTORS.md update"
+          commit-message: "Automated `CONTRIBUTORS.md` update"
           branch: update_contributors
-          title: "[MNT] Automated CONTRIBUTORS.md update"
-          body: "Automated update to CONTRIBUTORS.md caused by an update to the .all-contributorsrc file."
+          title: "[MNT] Automated `CONTRIBUTORS.md` update"
+          body: "Automated update to CONTRIBUTORS.md caused by an update to the `.all-contributorsrc` file."
           labels: maintenance, no changelog
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 8072b037..d4063972 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -8,5 +8,5 @@ the [Python Software Foundation Code of Conduct](https://www.python.org/psf/code
 
 If you want to make a CoC report, contact the general `aeon` email
 [contact@aeon-toolkit.org](mailto:contact@aeon-toolkit.org) or message a member of the
-[Code of Conduct Workgroup (COCW)](https://www.aeon-toolkit.org/en/latest/governance.html#code-of-conduct-workgroup)
+[Code of Conduct Workgroup (COCW)](https://www.aeon-toolkit.org/en/latest/about.html#code-of-conduct-workgroup)
 directly on [Slack](https://join.slack.com/t/aeon-toolkit/shared_invite/zt-22vwvut29-HDpCu~7VBUozyfL_8j3dLA).
diff --git a/aeon/classification/deep_learning/inception_time.py b/aeon/classification/deep_learning/inception_time.py
index c4196d8d..56d30bf9 100644
--- a/aeon/classification/deep_learning/inception_time.py
+++ b/aeon/classification/deep_learning/inception_time.py
@@ -24,103 +24,101 @@ class InceptionTimeClassifier(BaseClassifier):
 
     Parameters
     ----------
-        n_classifiers       : int, default = 5,
-            the number of Inception models used for the
-            Ensemble in order to create
-            InceptionTime.
-        depth               : int, default = 6,
+    n_classifiers       : int, default = 5,
+        the number of Inception models used for the
+        Ensemble in order to create
+        InceptionTime.
+    depth               : int, default = 6,
             the number of inception modules used
-        nb_filters          : int or list of int32, default = 32,
-            the number of filters used in one inception
-            module, if not a list,
-            the same number of filters is used in
-            all inception modules
-        nb_conv_per_layer   : int or list of int, default = 3,
-            the number of convolution layers in each inception
-            module, if not a list,
-            the same number of convolution layers is used
-            in all inception modules
-        kernel_size         : int or list of int, default = 40,
-            the head kernel size used for each inception
-            module, if not a list,
-            the same is used in all inception modules
-        use_max_pooling     : bool or list of bool, default = True,
-            conditioning whether or not to use max pooling layer
-            in inception modules,if not a list,
-            the same is used in all inception modules
-        max_pool_size       : int or list of int, default = 3,
-            the size of the max pooling layer, if not a list,
-            the same is used in all inception modules
-        strides             : int or list of int, default = 1,
-            the strides of kernels in convolution layers for each
-            inception module, if not a list,
-            the same is used in all inception modules
-        dilation_rate       : int or list of int, default = 1,
-            the dilation rate of convolutions in each inception
-            module, if not a list,
-            the same is used in all inception modules
-        padding             : str or list of str, default = "same",
-            the type of padding used for convoltuon for each
-            inception module, if not a list,
-            the same is used in all inception modules
-        activation          : str or list of str, default = "relu",
-            the activation function used in each inception
-            module, if not a list,
-            the same is used in all inception modules
-        use_bias            : bool or list of bool, default = False,
-            conditioning whether or not convolutions should
-            use bias values in each inception
-            module, if not a list,
-            the same is used in all inception modules
-        use_residual : bool, default = True,
-            condition whether or not to use residual
-            connections all over Inception
-        use_bottleneck : bool, default = True,
-            condition whether or not to use bottlenecks
-            all over Inception
-        bottleneck_size : int, default = 32,
-            the bottleneck size in case use_bottleneck = True
-        use_custom_filters : bool, default = True,
-            condition on whether or not to use custom
-            filters in the first inception module
-        batch_size : int, default = 64
-            the number of samples per gradient update.
-        use_mini_batch_size : bool, default = False
-            condition on using the mini batch size
-            formula Wang et al.
-        n_epochs : int, default = 1500
-            the number of epochs to train the model.
-        callbacks : callable or None, default
-        ReduceOnPlateau and ModelCheckpoint
-            list of tf.keras.callbacks.Callback objects.
-        file_path : str, default = "./"
-            file_path when saving model_Checkpoint callback
-        save_best_model : bool, default = False
-            Whether or not to save the best model, if the
-            modelcheckpoint callback is used by default,
-            this condition, if True, will prevent the
-            automatic deletion of the best saved model from
-            file and the user can choose the file name
-        save_last_model     : bool, default = False
-            Whether or not to save the last model, last
-            epoch trained, using the base class method
-            save_last_model_to_file
-        best_file_name      : str, default = "best_model"
-            The name of the file of the best model, if
-            save_best_model is set to False, this parameter
-            is discarded
-        last_file_name      : str, default = "last_model"
-            The name of the file of the last model, if
-            save_last_model is set to False, this parameter
-            is discarded
-        random_state        : int, default = 0
-            seed to any needed random actions.
-        verbose             : boolean, default = False
-            whether to output extra information
-        optimizer           : keras optimizer, default = Adam
-        loss                : keras loss,
-                              default = categorical_crossentropy
-        metrics             : keras metrics, default = None,
+    nb_filters          : int or list of int32, default = 32,
+        the number of filters used in one inception
+        module, if not a list,
+        the same number of filters is used in
+        all inception modules
+    nb_conv_per_layer   : int or list of int, default = 3,
+        the number of convolution layers in each inception
+        module, if not a list,
+        the same number of convolution layers is used
+        in all inception modules
+    kernel_size         : int or list of int, default = 40,
+        the head kernel size used for each inception
+        module, if not a list,
+        the same is used in all inception modules
+    use_max_pooling     : bool or list of bool, default = True,
+        conditioning whether or not to use max pooling layer
+        in inception modules,if not a list,
+        the same is used in all inception modules
+    max_pool_size       : int or list of int, default = 3,
+        the size of the max pooling layer, if not a list,
+        the same is used in all inception modules
+    strides             : int or list of int, default = 1,
+        the strides of kernels in convolution layers for each
+        inception module, if not a list,
+        the same is used in all inception modules
+    dilation_rate       : int or list of int, default = 1,
+        the dilation rate of convolutions in each inception
+        module, if not a list,
+        the same is used in all inception modules
+    padding             : str or list of str, default = "same",
+        the type of padding used for convoltuon for each
+        inception module, if not a list,
+        the same is used in all inception modules
+    activation          : str or list of str, default = "relu",
+        the activation function used in each inception
+        module, if not a list,
+        the same is used in all inception modules
+    use_bias            : bool or list of bool, default = False,
+        conditioning whether or not convolutions should
+        use bias values in each inception
+        module, if not a list,
+        the same is used in all inception modules
+    use_residual : bool, default = True,
+        condition whether or not to use residual
+        connections all over Inception
+    use_bottleneck : bool, default = True,
+        condition whether or not to use bottlenecks
+        all over Inception
+    bottleneck_size : int, default = 32,
+        the bottleneck size in case use_bottleneck = True
+    use_custom_filters : bool, default = True,
+        condition on whether or not to use custom
+        filters in the first inception module
+    batch_size : int, default = 64
+        the number of samples per gradient update.
+    use_mini_batch_size : bool, default = False
+        condition on using the mini batch size
+        formula Wang et al.
+    n_epochs : int, default = 1500
+        the number of epochs to train the model.
+    callbacks : callable or None, default = ReduceOnPlateau and ModelCheckpoint
+        list of tf.keras.callbacks.Callback objects.
+    file_path : str, default = "./"
+        file_path when saving model_Checkpoint callback
+    save_best_model : bool, default = False
+        Whether or not to save the best model, if the
+        modelcheckpoint callback is used by default,
+        this condition, if True, will prevent the
+        automatic deletion of the best saved model from
+        file and the user can choose the file name
+    save_last_model     : bool, default = False
+        Whether or not to save the last model, last
+        epoch trained, using the base class method
+        save_last_model_to_file
+    best_file_name      : str, default = "best_model"
+        The name of the file of the best model, if
+        save_best_model is set to False, this parameter
+        is discarded
+    last_file_name      : str, default = "last_model"
+        The name of the file of the last model, if
+        save_last_model is set to False, this parameter
+        is discarded
+    random_state        : int, default = 0
+        seed to any needed random actions.
+    verbose             : boolean, default = False
+        whether to output extra information
+    optimizer           : keras optimizer, default = Adam
+    loss                : keras loss, default = categorical_crossentropy
+    metrics             : keras metrics, default = None,
         will be set to accuracy as default if None
 
     Notes
diff --git a/aeon/classification/shapelet_based/_rdst.py b/aeon/classification/shapelet_based/_rdst.py
index 05629f16..f57ab249 100644
--- a/aeon/classification/shapelet_based/_rdst.py
+++ b/aeon/classification/shapelet_based/_rdst.py
@@ -15,7 +15,9 @@ from sklearn.preprocessing import StandardScaler
 
 from aeon.base._base import _clone_estimator
 from aeon.classification.base import BaseClassifier
-from aeon.transformations.collection import RandomDilatedShapeletTransform
+from aeon.transformations.collection.shapelet_based import (
+    RandomDilatedShapeletTransform,
+)
 
 
 class RDSTClassifier(BaseClassifier):
diff --git a/aeon/registry/_tags.py b/aeon/registry/_tags.py
index 0f3e6254..df05eaf9 100644
--- a/aeon/registry/_tags.py
+++ b/aeon/registry/_tags.py
@@ -153,7 +153,7 @@ ESTIMATOR_TAG_REGISTER = [
                 "pd-multiindex",
                 "numpy3D",
                 "df-list",
-                "numpy-list",
+                "np-list",
             ],
         ),
         "which data structure is the internal _fit/_predict able to deal with?",
@@ -161,14 +161,20 @@ ESTIMATOR_TAG_REGISTER = [
     (
         "input_data_type",
         "transformer",
-        ("list", ["Series", "Panel", "Collection"]),
-        "The abstract data type of the transformer input X",
+        ("list", ["Series", "Collection", "Panel"]),
+        "The input abstract data type of the transformer, input X. Series "
+        "indicates a single series input, Collection indicates a collection of "
+        "time series. Panel is a legacy term and equivalent to Collection.",
     ),
     (
         "output_data_type",
         "transformer",
-        ("list", ["Series", "Primitives", "Panel"]),
-        "The input abstract data type of the transformer output, the transformed X",
+        ("list", ["Tabular", "Series", "Collection", "Primitives", "Panel"]),
+        "The output abstract data type of the transformer output, the transformed X. "
+        "Tabular indicates 2D output where rows are cases and unordered attributes are "
+        "columns. Series indicates a single series output and collection indicates "
+        "output is a collection of time series.  Primitives is a legacy term for "
+        "Tabular and Panel for Collection.",
     ),
     (
         "instancewise",
diff --git a/aeon/transformations/base.py b/aeon/transformations/base.py
index b36703ae..868f5c2f 100644
--- a/aeon/transformations/base.py
+++ b/aeon/transformations/base.py
@@ -96,13 +96,9 @@ class BaseTransformer(BaseEstimator):
     # default tag values - these typically make the "safest" assumption
     _tags = {
         "input_data_type": "Series",
-        # The type of X: Series, or Panel
         "output_data_type": "Series",
-        # The type is returned: Primitives, Series, Panel
         "transform_labels": "None",
-        # what is the scitype of y: None (not needed), Primitives, Series, Panel
-        "instancewise": True,  # is this an instance-wise transform?
-        "capability:inverse_transform": False,  # can the transformer inverse transform?
+        "instancewise": True,
         "univariate-only": False,  # can the transformer handle multivariate X?
         "X_inner_mtype": "pd.DataFrame",  # which mtypes do _fit/_predict support for X?
         # this can be a Panel mtype even if transform-input is Series, vectorized
@@ -114,10 +110,9 @@ class BaseTransformer(BaseEstimator):
         "transform-returns-same-time-index": False,
         # does transform return have the same time index as input X
         "skip-inverse-transform": False,  # is inverse-transform skipped when called?
+        "capability:inverse_transform": False,  # can the transformer inverse transform?
         "capability:unequal_length": True,
-        # can the transformer handle unequal length time series (if passed Panel)?
         "capability:unequal_length:removes": False,
-        # is transform result always guaranteed to be equal length (and series)?
         "capability:missing_values": False,  # can estimator handle missing data?
         "capability:missing_values:removes": False,
         # is transform result always guaranteed to contain no missing values?
diff --git a/aeon/transformations/bootstrap/_mbb.py b/aeon/transformations/bootstrap/_mbb.py
index 772a51c4..2190a539 100644
--- a/aeon/transformations/bootstrap/_mbb.py
+++ b/aeon/transformations/bootstrap/_mbb.py
@@ -164,11 +164,8 @@ class STLBootstrapTransformer(BaseTransformer):
     """
 
     _tags = {
-        # todo: what is the scitype of X: Series, or Panel
         "input_data_type": "Series",
-        # todo: what scitype is returned: Primitives, Series, Panel
         "output_data_type": "Panel",
-        # todo: what is the scitype of y: None (not needed), Primitives, Series, Panel
         "transform_labels": "None",
         "instancewise": True,  # is this an instance-wise transform?
         "X_inner_mtype": "pd.DataFrame",  # which mtypes do _fit/_predict support for X?
@@ -478,11 +475,8 @@ class MovingBlockBootstrapTransformer(BaseTransformer):
     """
 
     _tags = {
-        # todo: what is the scitype of X: Series, or Panel
         "input_data_type": "Series",
-        # todo: what scitype is returned: Primitives, Series, Panel
         "output_data_type": "Panel",
-        # todo: what is the scitype of y: None (not needed), Primitives, Series, Panel
         "transform_labels": "None",
         "instancewise": True,  # is this an instance-wise transform?
         "X_inner_mtype": "pd.DataFrame",  # which mtypes do _fit/_predict support for X?
diff --git a/aeon/transformations/collection/__init__.py b/aeon/transformations/collection/__init__.py
index 5aeef919..89d87aed 100644
--- a/aeon/transformations/collection/__init__.py
+++ b/aeon/transformations/collection/__init__.py
@@ -20,10 +20,8 @@ __all__ = [
     "Tabularizer",
     "TimeBinner",
     "IntervalSegmenter",
-    "SlidingWindowSegmenter",
     "RandomIntervalSegmenter",
-    "RandomShapeletTransform",
-    "RandomDilatedShapeletTransform",
+    "SlidingWindowSegmenter",
     "SlopeTransformer",
     "SevenNumberSummaryTransformer",
     "SupervisedIntervals",
@@ -36,7 +34,6 @@ __all__ = [
 from aeon.transformations.collection._collection_wrapper import (
     CollectionToSeriesWrapper,
 )
-from aeon.transformations.collection._scaler import TimeSeriesScaler
 from aeon.transformations.collection.acf import AutocorrelationFunctionTransformer
 from aeon.transformations.collection.ar_coefficient import ARCoefficientTransformer
 from aeon.transformations.collection.base import BaseCollectionTransformer
@@ -53,15 +50,12 @@ from aeon.transformations.collection.pad import PaddingTransformer
 from aeon.transformations.collection.periodogram import PeriodogramTransformer
 from aeon.transformations.collection.random_intervals import RandomIntervals
 from aeon.transformations.collection.reduce import Tabularizer, TimeBinner
+from aeon.transformations.collection.scaler import TimeSeriesScaler
 from aeon.transformations.collection.segment import (
     IntervalSegmenter,
     RandomIntervalSegmenter,
     SlidingWindowSegmenter,
 )
-from aeon.transformations.collection.shapelet_based import (
-    RandomDilatedShapeletTransform,
-    RandomShapeletTransform,
-)
 from aeon.transformations.collection.slope import SlopeTransformer
 from aeon.transformations.collection.summary import SevenNumberSummaryTransformer
 from aeon.transformations.collection.supervised_intervals import SupervisedIntervals
diff --git a/aeon/transformations/collection/acf.py b/aeon/transformations/collection/acf.py
index 76c5f0db..bbf41a13 100644
--- a/aeon/transformations/collection/acf.py
+++ b/aeon/transformations/collection/acf.py
@@ -48,6 +48,7 @@ class AutocorrelationFunctionTransformer(BaseCollectionTransformer):
     """
 
     _tags = {
+        "fit_is_empty": True,
         "capability:multivariate": True,
     }
 
diff --git a/aeon/transformations/collection/base.py b/aeon/transformations/collection/base.py
index 4b050517..2180047c 100644
--- a/aeon/transformations/collection/base.py
+++ b/aeon/transformations/collection/base.py
@@ -1,7 +1,7 @@
 """
-Base class template for panel transformers.
+Base class template for Collection transformers.
 
-    class name: BasePanelTransformer
+    class name: BaseCollectionTransformer
 
 Defining methods:
     fitting         - fit(self, X, y=None)
@@ -25,6 +25,7 @@ __all__ = [
 ]
 
 from abc import ABCMeta, abstractmethod
+from typing import final
 
 import numpy as np
 import pandas as pd
@@ -37,18 +38,21 @@ from aeon.transformations.base import BaseTransformer
 class BaseCollectionTransformer(
     BaseCollectionEstimator, BaseTransformer, metaclass=ABCMeta
 ):
-    """Transformer base class."""
+    """Transformer base class for collections."""
 
-    # Relevant tag values inherited from BaseTransformer
+    # tag values specific to CollectionTransformers
     _tags = {
-        "input_data_type": "Panel",
-        "output_data_type": "Panel",
+        "input_data_type": "Collection",
+        "output_data_type": "Collection",
         "fit_is_empty": False,
+        "requires_y": False,
+        "capability:inverse_transform": False,
     }
 
     def __init__(self):
         super(BaseCollectionTransformer, self).__init__()
 
+    @final
     def fit(self, X, y=None):
         """Fit transformer to X, optionally using y if supervised.
 
@@ -70,6 +74,9 @@ class BaseCollectionTransformer(
         -------
         self : a fitted instance of the estimator
         """
+        if self.get_tag("requires_y"):
+            if y is None:
+                raise ValueError("Tag requires_y is true, but fit called with y=None")
         # skip the rest if fit_is_empty is True
         if self.get_tag("fit_is_empty"):
             self._is_fitted = True
@@ -85,6 +92,7 @@ class BaseCollectionTransformer(
 
         return self
 
+    @final
     def transform(self, X, y=None):
         """Transform X and return a transformed version.
 
@@ -118,6 +126,7 @@ class BaseCollectionTransformer(
 
         return Xt
 
+    @final
     def fit_transform(self, X, y=None):
         """
         Fit to data, then transform it.
@@ -156,27 +165,28 @@ class BaseCollectionTransformer(
 
         return Xt
 
+    @final
     def inverse_transform(self, X, y=None):
         """Inverse transform X and return an inverse transformed version.
 
         Currently it is assumed that only transformers with tags
-            "input_data_type"="Series", "output_data_type"="Series",
-        have an inverse_transform.
+             "input_data_type"="Series", "output_data_type"="Series",
+        can have an inverse_transform.
 
         State required:
-            Requires state to be "fitted".
+             Requires state to be "fitted".
 
         Accesses in self:
-        _is_fitted : must be True
-        _X : optionally accessed, only available if remember_data tag is True
-        fitted model attributes (ending in "_") : accessed by _inverse_transform
+         _is_fitted : must be True
+         _X : optionally accessed, only available if remember_data tag is True
+         fitted model attributes (ending in "_") : accessed by _inverse_transform
 
         Parameters
         ----------
         X : Input data
             Data to fit transform to, of valid collection type.
         y : Target variable, default=None
-            Additional data, e.g., labels for transformation
+             Additional data, e.g., labels for transformation
 
         Returns
         -------
@@ -202,6 +212,7 @@ class BaseCollectionTransformer(
 
         return Xt
 
+    @final
     def update(self, X, y=None, update_params=True):
         """Update transformer with X, optionally y.
 
@@ -243,7 +254,7 @@ class BaseCollectionTransformer(
             raise ValueError(f"{self.__class__.__name__} requires `y` in `update`.")
 
         # check and convert X/y
-        X_inner = self.preprocess_collection(X)
+        X_inner = self._preprocess_collection(X)
         y_inner = y
 
         # update memory of X, if remember_data tag is set to True
diff --git a/aeon/transformations/collection/catch22.py b/aeon/transformations/collection/catch22.py
index 3505e2e0..d74dda3e 100644
--- a/aeon/transformations/collection/catch22.py
+++ b/aeon/transformations/collection/catch22.py
@@ -46,7 +46,7 @@ feature_names = [
 class Catch22(BaseCollectionTransformer):
     """Canonical Time-series Characteristics (Catch22).
 
-    Overview: Input n series with d dimensions of length m
+    Overview: Input n series with d dimensions of length m.
     Transforms series into the 22 Catch22 [1]_ features extracted from the hctsa [2]_
     toolbox.
 
@@ -131,7 +131,7 @@ class Catch22(BaseCollectionTransformer):
     """
 
     _tags = {
-        "output_data_type": "Primitives",
+        "output_data_type": "Tabular",
         "X_inner_mtype": ["np-list", "numpy3D"],
         "capability:unequal_length": True,
         "capability:multivariate": True,
diff --git a/aeon/transformations/collection/channel_selection.py b/aeon/transformations/collection/channel_selection.py
index 27fdb0a0..d8b6ebb7 100644
--- a/aeon/transformations/collection/channel_selection.py
+++ b/aeon/transformations/collection/channel_selection.py
@@ -422,12 +422,8 @@ class ElbowClassPairwise(BaseCollectionTransformer):
     """
 
     _tags = {
-        "requires_y": True,  # does y need to be passed in fit?
-        "fit_is_empty": False,  # is fit empty and can be skipped? Yes = True
-        "skip-inverse-transform": True,  # is inverse-transform skipped when called?
-        "capability:unequal_length": False,
+        "requires_y": True,
         "capability:multivariate": True,
-        "y_inner_type": "numpy1D",
     }
 
     def __init__(
@@ -449,7 +445,7 @@ class ElbowClassPairwise(BaseCollectionTransformer):
 
         Parameters
         ----------
-        X: pandas DataFrame or np.ndarray
+        X: np.ndarray
             The training input samples.
         y: array-like or list
             The class values for X.
diff --git a/aeon/transformations/collection/convolution_based/_minirocket.py b/aeon/transformations/collection/convolution_based/_minirocket.py
index 039db9a6..2eeb7ecf 100644
--- a/aeon/transformations/collection/convolution_based/_minirocket.py
+++ b/aeon/transformations/collection/convolution_based/_minirocket.py
@@ -58,7 +58,7 @@ class MiniRocket(BaseCollectionTransformer):
     """
 
     _tags = {
-        "output_data_type": "Primitives",
+        "output_data_type": "Tabular",
     }
 
     def __init__(
diff --git a/aeon/transformations/collection/convolution_based/_minirocket_multivariate.py b/aeon/transformations/collection/convolution_based/_minirocket_multivariate.py
index 7513d002..744008d0 100644
--- a/aeon/transformations/collection/convolution_based/_minirocket_multivariate.py
+++ b/aeon/transformations/collection/convolution_based/_minirocket_multivariate.py
@@ -59,7 +59,7 @@ class MiniRocketMultivariate(BaseCollectionTransformer):
     """
 
     _tags = {
-        "output_data_type": "Primitives",
+        "output_data_type": "Tabular",
         "capability:multivariate": True,
     }
 
diff --git a/aeon/transformations/collection/convolution_based/_minirocket_multivariate_variable.py b/aeon/transformations/collection/convolution_based/_minirocket_multivariate_variable.py
index 85bc5e52..0d9bd97c 100644
--- a/aeon/transformations/collection/convolution_based/_minirocket_multivariate_variable.py
+++ b/aeon/transformations/collection/convolution_based/_minirocket_multivariate_variable.py
@@ -82,7 +82,7 @@ class MiniRocketMultivariateVariable(BaseCollectionTransformer):
     """
 
     _tags = {
-        "output_data_type": "Primitives",
+        "output_data_type": "Tabular",
         "capability:multivariate": True,
         "capability:unequal_length": True,
         "X_inner_mtype": "df-list",
@@ -129,12 +129,12 @@ class MiniRocketMultivariateVariable(BaseCollectionTransformer):
 
         super(MiniRocketMultivariateVariable, self).__init__()
 
-    def _fit(self, X: List[np.ndarray], y=None):
+    def _fit(self, X, y=None):
         """Fits dilations and biases to input time series.
 
         Parameters
         ----------
-        X : List of 2D np.ndarray
+        X : List of 2D dataframes
         y : ignored argument for interface compatibility
 
         Returns
@@ -275,14 +275,14 @@ def _nested_dataframe_to_transposed2D_array_and_len_list(
         )
 
     vec = []
-    lenghts = []
+    lengths = []
 
     for _x in X:
         _x_shape = _x.shape
         if _x_shape[0] < 9:
             if pad is not None:
                 # emergency: pad with zeros up to 9.
-                lenghts.append(9)
+                lengths.append(9)
                 vec.append(
                     np.vstack(
                         [_x.values, np.full([9 - _x_shape[0], _x_shape[1]], float(pad))]
@@ -295,11 +295,11 @@ def _nested_dataframe_to_transposed2D_array_and_len_list(
                     " padding, discard, or setting a pad_value_short_series value"
                 )
         else:
-            lenghts.append(_x_shape[0])
+            lengths.append(_x_shape[0])
             vec.append(_x.values)
 
     X_2d_t = np.vstack(vec).T.astype(dtype=np.float32)
-    lengths = np.array(lenghts, dtype=np.int32)
+    lengths = np.array(lengths, dtype=np.int32)
 
     if not lengths.sum() == X_2d_t.shape[1]:
         raise ValueError("X_new and lengths do not match. check input dimension")
diff --git a/aeon/transformations/collection/convolution_based/_multirocket.py b/aeon/transformations/collection/convolution_based/_multirocket.py
index bbcde9dd..d025d050 100644
--- a/aeon/transformations/collection/convolution_based/_multirocket.py
+++ b/aeon/transformations/collection/convolution_based/_multirocket.py
@@ -4,7 +4,6 @@ import numpy as np
 import pandas as pd
 from numba import get_num_threads, njit, prange, set_num_threads
 
-from aeon.datatypes import convert
 from aeon.transformations.collection import BaseCollectionTransformer
 
 
@@ -72,7 +71,7 @@ class MultiRocket(BaseCollectionTransformer):
     """
 
     _tags = {
-        "output_data_type": "Primitives",
+        "output_data_type": "Tabular",
     }
 
     def __init__(
@@ -112,11 +111,11 @@ class MultiRocket(BaseCollectionTransformer):
         self
         """
         X = X.astype(np.float64)
-        X = convert(X, from_type="numpy3D", to_type="numpyflat", as_scitype="Panel")
         if self.normalise:
             X = (X - X.mean(axis=-1, keepdims=True)) / (
                 X.std(axis=-1, keepdims=True) + 1e-8
             )
+        X = X.reshape((X.shape[0], X.shape[1] * X.shape[2]))
 
         self.parameter = self._get_parameter(X)
 
@@ -139,7 +138,7 @@ class MultiRocket(BaseCollectionTransformer):
         pandas DataFrame, transformed features
         """
         X = X.astype(np.float64)
-        X = convert(X, from_type="numpy3D", to_type="numpyflat", as_scitype="Panel")
+        X = X.reshape((X.shape[0], X.shape[1] * X.shape[2]))
         if self.normalise:
             X = (X - X.mean(axis=-1, keepdims=True)) / (
                 X.std(axis=-1, keepdims=True) + 1e-8
diff --git a/aeon/transformations/collection/convolution_based/_multirocket_multivariate.py b/aeon/transformations/collection/convolution_based/_multirocket_multivariate.py
index 6512409a..c51067dc 100644
--- a/aeon/transformations/collection/convolution_based/_multirocket_multivariate.py
+++ b/aeon/transformations/collection/convolution_based/_multirocket_multivariate.py
@@ -71,7 +71,7 @@ class MultiRocketMultivariate(BaseCollectionTransformer):
     """
 
     _tags = {
-        "output_data_type": "Primitives",
+        "output_data_type": "Tabular",
         "capability:multivariate": True,
     }
 
diff --git a/aeon/transformations/collection/convolution_based/_rocket.py b/aeon/transformations/collection/convolution_based/_rocket.py
index add46a44..eaf7d0d4 100644
--- a/aeon/transformations/collection/convolution_based/_rocket.py
+++ b/aeon/transformations/collection/convolution_based/_rocket.py
@@ -62,7 +62,7 @@ class Rocket(BaseCollectionTransformer):
     """
 
     _tags = {
-        "output_data_type": "Primitives",
+        "output_data_type": "Tabular",
         "capability:multivariate": True,
     }
 
@@ -81,7 +81,7 @@ class Rocket(BaseCollectionTransformer):
 
         Parameters
         ----------
-        X : 3D np.ndarray of shape = [n_instances, n_channels, series_length]
+        X : 3D np.ndarray of shape = (n_instances, n_channels, n_timepoints)
             collection of time series to transform
         y : ignored argument for interface compatibility
 
diff --git a/aeon/transformations/collection/dictionary_based/_sfa.py b/aeon/transformations/collection/dictionary_based/_sfa.py
index 26eb7c7d..7f8bd476 100644
--- a/aeon/transformations/collection/dictionary_based/_sfa.py
+++ b/aeon/transformations/collection/dictionary_based/_sfa.py
@@ -102,11 +102,7 @@ class SFA(BaseCollectionTransformer):
     """
 
     _tags = {
-        "univariate-only": True,
-        "instancewise": False,
-        "fit_is_empty": False,
         "requires_y": True,
-        "y_inner_type": "numpy1D",
     }
 
     def __init__(
diff --git a/aeon/transformations/collection/dictionary_based/_sfa_fast.py b/aeon/transformations/collection/dictionary_based/_sfa_fast.py
index c675b5cb..65abc4c5 100644
--- a/aeon/transformations/collection/dictionary_based/_sfa_fast.py
+++ b/aeon/transformations/collection/dictionary_based/_sfa_fast.py
@@ -151,11 +151,7 @@ class SFAFast(BaseCollectionTransformer):
     """
 
     _tags = {
-        "univariate-only": True,
-        "instancewise": False,
-        "fit_is_empty": False,
         "requires_y": True,
-        "y_inner_type": "numpy1D",
     }
 
     def __init__(
diff --git a/aeon/transformations/collection/dwt.py b/aeon/transformations/collection/dwt.py
index 29bf1d3d..41a9bec2 100644
--- a/aeon/transformations/collection/dwt.py
+++ b/aeon/transformations/collection/dwt.py
@@ -1,14 +1,15 @@
 """Discrete wavelet transform."""
+
+__author__ = "Vincent Nicholson"
+
 import math
 
 import numpy as np
 
-from aeon.transformations.base import BaseTransformer
-
-__author__ = "Vincent Nicholson"
+from aeon.transformations.collection import BaseCollectionTransformer
 
 
-class DWTTransformer(BaseTransformer):
+class DWTTransformer(BaseCollectionTransformer):
     """Discrete Wavelet Transform Transformer.
 
     Performs the Haar wavelet transformation on a time series.
@@ -29,11 +30,8 @@ class DWTTransformer(BaseTransformer):
     """
 
     _tags = {
-        "output_data_type": "Series",
-        "instancewise": False,
-        "X_inner_mtype": "numpy3D",
-        "y_inner_type": "None",
         "fit_is_empty": True,
+        "capability:multivariate": True,
     }
 
     def __init__(self, n_levels=3):
diff --git a/aeon/transformations/collection/hog1d.py b/aeon/transformations/collection/hog1d.py
index e1c9322e..9e8d172b 100644
--- a/aeon/transformations/collection/hog1d.py
+++ b/aeon/transformations/collection/hog1d.py
@@ -1,18 +1,19 @@
 """HOG1D transform."""
+
 import math
 import numbers
 
 import numpy as np
 
 from aeon.transformations._split import SplitsTimeSeries
-from aeon.transformations.base import BaseTransformer
+from aeon.transformations.collection import BaseCollectionTransformer
 
 
-class HOG1DTransformer(BaseTransformer, SplitsTimeSeries):
+class HOG1DTransformer(BaseCollectionTransformer, SplitsTimeSeries):
     """HOG1D transform.
 
-    This transformer calculates the HOG1D transform [1] of a collection of time seriess.
-    HOG1D splits each time series num_intervals times, and finds a histogram of
+    This transformer calculates the HOG1D transform [1] of a collection of time series.
+    HOG1D splits each time series n_intervals times, and finds a histogram of
     gradients within each interval.
 
     Parameters
@@ -33,19 +34,14 @@ class HOG1DTransformer(BaseTransformer, SplitsTimeSeries):
     """
 
     _tags = {
-        "output_data_type": "Series",
-        "instancewise": True,
-        "X_inner_mtype": "numpy3D",
-        "y_inner_type": "None",
         "fit_is_empty": True,
-        "univariate-only": True,
     }
 
     def __init__(self, n_intervals=2, n_bins=8, scaling_factor=0.1):
         self.n_intervals = n_intervals
         self.n_bins = n_bins
         self.scaling_factor = scaling_factor
-        super(HOG1DTransformer, self).__init__(_output_convert=False)
+        super(HOG1DTransformer, self).__init__()
 
     def _transform(self, X, y=None):
         """Transform X and return a transformed version.
diff --git a/aeon/transformations/collection/interpolate.py b/aeon/transformations/collection/interpolate.py
index 58ed511b..10e61dd2 100644
--- a/aeon/transformations/collection/interpolate.py
+++ b/aeon/transformations/collection/interpolate.py
@@ -1,13 +1,14 @@
 """Time series interpolator/re-sampler."""
-import numpy as np
-
-from aeon.transformations.base import BaseTransformer
 
 __all__ = ["TSInterpolator"]
 __author__ = ["mloning", "TonyBagnall"]
 
+import numpy as np
+
+from aeon.transformations.collection import BaseCollectionTransformer
+
 
-class TSInterpolator(BaseTransformer):
+class TSInterpolator(BaseCollectionTransformer):
     """Time series interpolator/re-sampler.
 
     Transformer that rescales series for another number of points.
@@ -37,10 +38,7 @@ class TSInterpolator(BaseTransformer):
     """
 
     _tags = {
-        "output_data_type": "Series",
-        "instancewise": False,
         "X_inner_mtype": ["np-list", "numpy3D"],
-        "y_inner_type": "None",
         "capability:multivariate": True,
         "capability:unequal_length": True,
         "fit_is_empty": True,
@@ -57,7 +55,7 @@ class TSInterpolator(BaseTransformer):
             raise ValueError("resizing length must be integer and > 0")
 
         self.length = length
-        super(TSInterpolator, self).__init__(_output_convert=False)
+        super(TSInterpolator, self).__init__()
 
     def _transform(self, X, y=None):
         """Take series in each cell, train linear interpolation and samples n.
diff --git a/aeon/transformations/collection/matrix_profile.py b/aeon/transformations/collection/matrix_profile.py
index c5e5892f..884331a8 100644
--- a/aeon/transformations/collection/matrix_profile.py
+++ b/aeon/transformations/collection/matrix_profile.py
@@ -3,9 +3,8 @@
 __author__ = ["Claudia Rincon Sanchez"]
 
 import numpy as np
-import pandas as pd
 
-from aeon.transformations.base import BaseTransformer
+from aeon.transformations.collection import BaseCollectionTransformer
 
 
 def _sliding_dot_products(q, t, q_len, t_len):
@@ -193,7 +192,7 @@ def _stomp_self(ts, m):
     return mp
 
 
-class MatrixProfile(BaseTransformer):
+class MatrixProfile(BaseCollectionTransformer):
     """Return the matrix profile and index profile for each time series of a dataset.
 
     Example of use:
@@ -206,15 +205,7 @@ class MatrixProfile(BaseTransformer):
     """
 
     _tags = {
-        "univariate-only": True,
         "fit_is_empty": True,
-        "input_data_type": "Series",
-        # what is the scitype of X: Series, or Panel
-        "output_data_type": "Primitives",
-        # what is the scitype of y: None (not needed), Primitives, Series, Panel
-        "instancewise": False,  # is this an instance-wise transform?
-        "X_inner_mtype": "numpy3D",  # which mtypes do _fit/_predict support for X?
-        "y_inner_type": "None",  # which mtypes do _fit/_predict support for X?
     }
 
     def __init__(self, m=10):
@@ -226,18 +217,17 @@ class MatrixProfile(BaseTransformer):
 
         Parameters
         ----------
-        X : 3D np.ndarray of shape = [n_instances, n_dimensions, series_length]
+        X : 3D np.ndarray of shape = [n_instances, n_channels, series_length]
             panel of time series to transform
         y : ignored argument for interface compatibility
 
         Returns
         -------
-        Xt : pandas.DataFrame in nested_univ mtype format
-            Dataframe with the n_instances rows as the input.
-            The number of columns equals the number of subsequences
-            of the desired length in each time series.
+        Xt : Matrix profile series
         """
         # Input checks
-        n_instances = X.shape[0]
+        import pandas as pd
+
+        n_instances = len(X)
         Xt = pd.DataFrame([_stomp_self(X[i], self.m) for i in range(n_instances)])
         return Xt
diff --git a/aeon/transformations/collection/pad.py b/aeon/transformations/collection/pad.py
index 0790e088..0ce9ddae 100644
--- a/aeon/transformations/collection/pad.py
+++ b/aeon/transformations/collection/pad.py
@@ -1,11 +1,12 @@
 """Padding transformer, pad unequal length time series to max length or fixed length."""
-import numpy as np
-
-from aeon.transformations.base import BaseTransformer
 
 __all__ = ["PaddingTransformer"]
 __author__ = ["abostrom", "TonyBagnall"]
 
+import numpy as np
+
+from aeon.transformations.collection import BaseCollectionTransformer
+
 
 def _get_max_length(X):
     max_length = X[0].shape[1]
@@ -16,7 +17,7 @@ def _get_max_length(X):
     return max_length
 
 
-class PaddingTransformer(BaseTransformer):
+class PaddingTransformer(BaseCollectionTransformer):
     """Pad unequal length time series to equal, fixed length.
 
     Pads the input dataset to either fixed length (at least as long as the longest
@@ -46,11 +47,9 @@ class PaddingTransformer(BaseTransformer):
     """
 
     _tags = {
-        "output_data_type": "Series",
-        "instancewise": False,
         "X_inner_mtype": ["np-list", "numpy3D"],
-        "y_inner_type": "None",
         "fit_is_empty": False,
+        "capability:multivariate": True,
         "capability:unequal_length": True,
         "capability:unequal_length:removes": True,
     }
@@ -58,7 +57,7 @@ class PaddingTransformer(BaseTransformer):
     def __init__(self, pad_length=None, fill_value=0):
         self.pad_length = pad_length
         self.fill_value = fill_value
-        super(PaddingTransformer, self).__init__(_output_convert=False)
+        super(PaddingTransformer, self).__init__()
 
     def _fit(self, X, y=None):
         """Fit padding transformer to X and y.
diff --git a/aeon/transformations/collection/random_intervals.py b/aeon/transformations/collection/random_intervals.py
index 55be07eb..1ab632f8 100644
--- a/aeon/transformations/collection/random_intervals.py
+++ b/aeon/transformations/collection/random_intervals.py
@@ -108,7 +108,7 @@ class RandomIntervals(BaseCollectionTransformer):
     """
 
     _tags = {
-        "output_data_type": "Primitives",
+        "output_data_type": "Tabular",
         "capability:multivariate": True,
         "fit_is_empty": False,
     }
diff --git a/aeon/transformations/collection/reduce.py b/aeon/transformations/collection/reduce.py
index aa0a232a..8480853f 100644
--- a/aeon/transformations/collection/reduce.py
+++ b/aeon/transformations/collection/reduce.py
@@ -1,7 +1,7 @@
 """Tabularizer transform, for pipelining."""
 
 __author__ = ["mloning", "fkiraly", "kcc-lion"]
-__all__ = ["Tabularizer"]
+__all__ = ["Tabularizer", "TimeBinner"]
 
 import warnings
 
@@ -9,32 +9,25 @@ import numpy as np
 import pandas as pd
 
 from aeon.datatypes import convert, convert_to
-from aeon.transformations.base import BaseTransformer
+from aeon.transformations.collection import BaseCollectionTransformer
 
 
-class Tabularizer(BaseTransformer):
+class Tabularizer(BaseCollectionTransformer):
     """
     A transformer that turns time series/panel data into tabular data.
 
     This estimator converts nested pandas dataframe containing
     time-series/panel data with numpy arrays or pandas Series in
-    dataframe cells into a tabular pandas dataframe with only primitives in
-    cells. This is useful for transforming
+    dataframe cells into a tabular numpy array. This is useful for transforming
     time-series/panel data into a format that is accepted by standard
     validation learning algorithms (as in sklearn).
     """
 
     _tags = {
         "fit_is_empty": True,
-        "univariate-only": False,
-        "input_data_type": "Series",
-        # what is the scitype of X: Series, or Panel
-        "output_data_type": "Primitives",
-        # what is the scitype of y: None (not needed), Primitives, Series, Panel
-        "instancewise": True,  # is this an instance-wise transform?
+        "output_data_type": "Tabular",
         "X_inner_mtype": ["nested_univ", "numpy3D"],
-        # which mtypes do _fit/_predict support for X?
-        "y_inner_type": "None",  # and for y?
+        "capability:multivariate": True,
     }
 
     def _transform(self, X, y=None):
@@ -72,14 +65,14 @@ class Tabularizer(BaseTransformer):
         return Xt
 
 
-class TimeBinner(BaseTransformer):
+class TimeBinner(BaseCollectionTransformer):
     """
     Turns time series/panel data into tabular data based on intervals.
 
     This estimator converts nested pandas dataframe containing
     time-series/panel data with numpy arrays or pandas Series in
-    dataframe cells into a tabular pandas dataframe with only primitives in
-    cells. The primitives are calculated based on Intervals defined
+    dataframe cells into a tabular numpy array.
+    The primitives are calculated based on Intervals defined
     by the IntervalIndex and aggregated by aggfunc.
 
     This is useful for transforming time-series/panel data
@@ -98,15 +91,11 @@ class TimeBinner(BaseTransformer):
 
     _tags = {
         "fit_is_empty": True,
-        "univariate-only": False,
-        "input_data_type": "Series",
-        # what is the scitype of X: Series, or Panel
-        "output_data_type": "Primitives",
-        # what is the scitype of y: None (not needed), Primitives, Series, Panel
-        "instancewise": True,  # is this an instance-wise transform?
+        "output_data_type": "Tabular",
+        "instancewise": True,
         "X_inner_mtype": ["nested_univ"],
-        # which mtypes do _fit/_predict support for X?
-        "y_inner_type": "None",  # and for y?
+        "y_inner_type": "None",
+        "capability:multivariate": True,
     }
 
     def __init__(self, idx, aggfunc=None):
diff --git a/aeon/transformations/collection/_scaler.py b/aeon/transformations/collection/scaler.py
similarity index 100%
rename from aeon/transformations/collection/_scaler.py
rename to aeon/transformations/collection/scaler.py
diff --git a/aeon/transformations/collection/segment.py b/aeon/transformations/collection/segment.py
index 66b18226..1b9ab4e8 100644
--- a/aeon/transformations/collection/segment.py
+++ b/aeon/transformations/collection/segment.py
@@ -6,11 +6,11 @@ import pandas as pd
 from sklearn.utils import check_random_state
 
 from aeon.datatypes._panel._convert import _concat_nested_arrays, _get_time_index
-from aeon.transformations.base import BaseTransformer
+from aeon.transformations.collection import BaseCollectionTransformer
 from aeon.utils.validation import check_window_length
 
 
-class IntervalSegmenter(BaseTransformer):
+class IntervalSegmenter(BaseCollectionTransformer):
     """Interval segmentation transformer.
 
     Parameters
@@ -25,22 +25,14 @@ class IntervalSegmenter(BaseTransformer):
     """
 
     _tags = {
-        "univariate-only": True,
-        "input_data_type": "Series",
-        "output_data_type": "Series",
-        "instancewise": True,
-        "X_inner_mtype": "numpy3D",
-        "y_inner_type": "None",
-        "fit_is_empty": False,
         "capability:unequal_length:removes": True,
-        "capability:multivariate": False,
     }
 
     def __init__(self, intervals=10):
         self.intervals = intervals
         self._time_index = []
         self.input_shape_ = ()
-        super(IntervalSegmenter, self).__init__(_output_convert=False)
+        super(IntervalSegmenter, self).__init__()
 
     def _fit(self, X, y=None):
         """
@@ -183,9 +175,7 @@ class RandomIntervalSegmenter(IntervalSegmenter):
     """
 
     _tags = {
-        "X_inner_mtype": "numpy3D",  # which mtypes do _fit/_predict support for X?
-        "y_inner_type": "pd_Series_Table",
-        # which mtypes do _fit/_predict support for y?
+        "X_inner_mtype": "numpy3D",
     }
 
     def __init__(
@@ -347,7 +337,7 @@ def _rand_intervals_fixed_n(
     return np.column_stack([starts, ends])
 
 
-class SlidingWindowSegmenter(BaseTransformer):
+class SlidingWindowSegmenter(BaseCollectionTransformer):
     """Sliding window segmenter transformer.
 
     This class is to transform a univariate series into a multivariate one by
@@ -388,19 +378,14 @@ class SlidingWindowSegmenter(BaseTransformer):
     """
 
     _tags = {
-        "univariate-only": True,
         "fit_is_empty": True,
-        "input_data_type": "Series",
-        # what is the scitype of X: Series, or Panel
-        "output_data_type": "Series",
         "instancewise": False,
-        "X_inner_mtype": "numpy3D",
         "y_inner_type": "None",
     }
 
     def __init__(self, window_length=5):
         self.window_length = window_length
-        super(SlidingWindowSegmenter, self).__init__(_output_convert=False)
+        super(SlidingWindowSegmenter, self).__init__()
 
     def _transform(self, X, y=None):
         """Transform time series.
diff --git a/aeon/transformations/collection/shapelet_based/_dilated_shapelet_transform.py b/aeon/transformations/collection/shapelet_based/_dilated_shapelet_transform.py
index a1bc9e21..db9b3cd5 100644
--- a/aeon/transformations/collection/shapelet_based/_dilated_shapelet_transform.py
+++ b/aeon/transformations/collection/shapelet_based/_dilated_shapelet_transform.py
@@ -14,7 +14,7 @@ from numba import njit, prange, set_num_threads
 from sklearn.preprocessing import LabelEncoder
 
 from aeon.distances import manhattan_distance
-from aeon.transformations.base import BaseTransformer
+from aeon.transformations.collection import BaseCollectionTransformer
 from aeon.utils.numba.general import (
     choice_log,
     combinations_1d,
@@ -27,7 +27,7 @@ from aeon.utils.numba.stats import prime_up_to
 from aeon.utils.validation import check_n_jobs
 
 
-class RandomDilatedShapeletTransform(BaseTransformer):
+class RandomDilatedShapeletTransform(BaseCollectionTransformer):
     """Random Dilated Shapelet Transform (RDST) as described in [1]_[2]_.
 
     Overview: The input is n series with d channels of length m. First step is to
@@ -130,14 +130,8 @@ class RandomDilatedShapeletTransform(BaseTransformer):
     """
 
     _tags = {
-        "output_data_type": "Primitives",
-        "fit_is_empty": False,
-        "univariate-only": False,
-        "X_inner_mtype": "numpy3D",
-        "y_inner_type": "numpy1D",
-        "requires_y": False,
-        "capability:inverse_transform": False,
-        "capability:missing_values": False,
+        "output_data_type": "Tabular",
+        "capability:multivariate": True,
     }
 
     def __init__(
diff --git a/aeon/transformations/collection/shapelet_based/_shapelet_transform.py b/aeon/transformations/collection/shapelet_based/_shapelet_transform.py
index dcffba80..0a012f20 100644
--- a/aeon/transformations/collection/shapelet_based/_shapelet_transform.py
+++ b/aeon/transformations/collection/shapelet_based/_shapelet_transform.py
@@ -138,10 +138,8 @@ class RandomShapeletTransform(BaseCollectionTransformer):
     """
 
     _tags = {
-        "output_data_type": "Primitives",
+        "output_data_type": "Tabular",
         "capability:multivariate": True,
-        "y_inner_type": "numpy1D",
-        "fit_is_empty": False,
         "requires_y": True,
     }
 
diff --git a/aeon/transformations/collection/signature_based/_compute.py b/aeon/transformations/collection/signature_based/_compute.py
index 9e0d4b55..621017e3 100644
--- a/aeon/transformations/collection/signature_based/_compute.py
+++ b/aeon/transformations/collection/signature_based/_compute.py
@@ -26,7 +26,7 @@ class _WindowSignatureTransform(BaseCollectionTransformer):
     # default tag values for "Series-to-Primitives"
     _tags = {
         "fit_is_empty": True,
-        "output_data_type": "Primitives",
+        "output_data_type": "Tabular",
         "capability:multivariate": True,
         "python_dependencies": "esig",
         "python_version": "<3.11",
diff --git a/aeon/transformations/collection/signature_based/_signature_method.py b/aeon/transformations/collection/signature_based/_signature_method.py
index 82faf942..db55fc07 100644
--- a/aeon/transformations/collection/signature_based/_signature_method.py
+++ b/aeon/transformations/collection/signature_based/_signature_method.py
@@ -40,9 +40,8 @@ class SignatureTransformer(BaseCollectionTransformer):
     """
 
     _tags = {
-        "output_data_type": "Primitives",
+        "output_data_type": "Tabular",
         "capability:multivariate": True,
-        "fit_is_empty": False,
         "python_dependencies": "esig",
         "python_version": "<3.11",
     }
diff --git a/aeon/transformations/collection/slope.py b/aeon/transformations/collection/slope.py
index 895a11f4..7dd5391e 100644
--- a/aeon/transformations/collection/slope.py
+++ b/aeon/transformations/collection/slope.py
@@ -1,16 +1,17 @@
 """Slope transformer."""
+
+__all__ = ["SlopeTransformer"]
+__author__ = ["mloning"]
+
 import math
 
 import numpy as np
 
 from aeon.transformations._split import SplitsTimeSeries
-from aeon.transformations.base import BaseTransformer
-
-__all__ = ["SlopeTransformer"]
-__author__ = ["mloning"]
+from aeon.transformations.collection import BaseCollectionTransformer
 
 
-class SlopeTransformer(BaseTransformer, SplitsTimeSeries):
+class SlopeTransformer(BaseCollectionTransformer, SplitsTimeSeries):
     """Piecewise slope transformation.
 
     Class to perform a slope transformation on a collection of time series.
@@ -35,16 +36,13 @@ class SlopeTransformer(BaseTransformer, SplitsTimeSeries):
     """
 
     _tags = {
-        "output_data_type": "Series",
-        "instancewise": False,
-        "X_inner_mtype": "numpy3D",
-        "y_inner_type": "None",
+        "capability:multivariate": True,
         "fit_is_empty": True,
     }
 
     def __init__(self, n_intervals=8):
         self.n_intervals = n_intervals
-        super(SlopeTransformer, self).__init__(_output_convert=False)
+        super(SlopeTransformer, self).__init__()
 
     def _transform(self, X, y=None):
         """Transform X and return a transformed version.
diff --git a/aeon/transformations/collection/supervised_intervals.py b/aeon/transformations/collection/supervised_intervals.py
index 0b4e43ba..35f66b19 100644
--- a/aeon/transformations/collection/supervised_intervals.py
+++ b/aeon/transformations/collection/supervised_intervals.py
@@ -134,8 +134,7 @@ class SupervisedIntervals(BaseCollectionTransformer):
     """
 
     _tags = {
-        "output_data_type": "Primitives",
-        "y_inner_type": "numpy1D",
+        "output_data_type": "Tabular",
         "capability:multivariate": True,
         "requires_y": True,
     }
diff --git a/aeon/transformations/collection/truncate.py b/aeon/transformations/collection/truncate.py
index 69c3bd9b..128e4ebc 100644
--- a/aeon/transformations/collection/truncate.py
+++ b/aeon/transformations/collection/truncate.py
@@ -1,13 +1,14 @@
 """Truncation transformer - truncate unequal length panels to lower/upper bounds."""
-import numpy as np
-
-from aeon.transformations.base import BaseTransformer
 
 __all__ = ["TruncationTransformer"]
 __author__ = ["abostrom", "TonyBagnall"]
 
+import numpy as np
+
+from aeon.transformations.collection import BaseCollectionTransformer
+
 
-class TruncationTransformer(BaseTransformer):
+class TruncationTransformer(BaseCollectionTransformer):
     """Truncate unequal length time series to a lower bounds.
 
     Truncates all series in panel between lower/upper range bounds. This transformer
@@ -34,18 +35,15 @@ class TruncationTransformer(BaseTransformer):
     """
 
     _tags = {
-        "output_data_type": "Series",
-        "instancewise": False,
         "X_inner_mtype": ["np-list", "numpy3D"],
-        "y_inner_type": "None",
-        "fit_is_empty": False,
+        "capability:multivariate": True,
         "capability:unequal_length": True,
         "capability:unequal_length:removes": True,
     }
 
     def __init__(self, truncated_length=None):
         self.truncated_length = truncated_length
-        super(TruncationTransformer, self).__init__(_output_convert=False)
+        super(TruncationTransformer, self).__init__()
 
     @staticmethod
     def _get_min_length(X):
diff --git a/aeon/transformations/collection/tsfresh.py b/aeon/transformations/collection/tsfresh.py
index 57a033ae..85d6bad1 100644
--- a/aeon/transformations/collection/tsfresh.py
+++ b/aeon/transformations/collection/tsfresh.py
@@ -12,10 +12,9 @@ class _TSFreshFeatureExtractor(BaseCollectionTransformer):
     """Base adapter class for tsfresh transformations."""
 
     _tags = {
-        "output_data_type": "Primitives",
+        "output_data_type": "Tabular",
         "capability:multivariate": True,
         "fit_is_empty": True,
-        "y_inner_type": "None",
         "python_dependencies": "tsfresh",
     }
 
diff --git a/build_tools/generate_developer_tables.py b/build_tools/generate_developer_tables.py
index 7a1db05a..b73b3c52 100644
--- a/build_tools/generate_developer_tables.py
+++ b/build_tools/generate_developer_tables.py
@@ -41,45 +41,63 @@ def get(url, auth):
 
 def get_contributors(auth):
     """Get the list of contributor profiles. Require admin rights."""
-    cc = []
-    cc_slug = "aeon-community-council"
-    cocc = []
-    cocc_slug = "aeon-code-of-conduct-committee"
-    core_devs = []
-    core_devs_slug = "aeon-core-developers"
+    cocw = []
+    cocw_slug = "aeon-code-of-conduct-workgroup"
+    cw = []
+    cw_slug = "aeon-communications-workgroup"
+    cd = []
+    cd_slug = "aeon-core-developers"
+    fw = []
+    fw_slug = "aeon-finance-workgroup"
+    iw = []
+    iw_slug = "aeon-infrastructure-workgroup"
+    rmw = []
+    rmw_slug = "aeon-release-management-workgroup"
 
     entry_point = "https://api.github.com/orgs/aeon-toolkit/"
 
     for team_slug, lst in zip(
-        (cc_slug, cocc_slug, core_devs_slug),
-        (cc, cocc, core_devs),
+        (cocw_slug, cw_slug, cd_slug, fw_slug, iw_slug, rmw_slug),
+        (cocw, cw, cd, fw, iw, rmw),
     ):
         for page in range(5):  # 5 pages, 30 per page
             reply = get(f"{entry_point}teams/{team_slug}/members?page={page}", auth)
             lst.extend(reply.json())
 
     # keep only the logins
-    cc = set(c["login"] for c in cc)
-    cocc = set(c["login"] for c in cocc)
-    core_devs = set(c["login"] for c in core_devs)
+    cocw = set(c["login"] for c in cocw)
+    cw = set(c["login"] for c in cw)
+    cd = set(c["login"] for c in cd)
+    fw = set(c["login"] for c in fw)
+    iw = set(c["login"] for c in iw)
+    rmw = set(c["login"] for c in rmw)
 
     # add missing contributors with GitHub accounts
-    cocc |= {"KatieBuc"}
+    cocw |= {"KatieBuc"}
 
     # get profiles from GitHub
-    cc = [get_profile(login, auth) for login in cc]
-    cocc = [get_profile(login, auth) for login in cocc]
-    core_devs = [get_profile(login, auth) for login in core_devs]
+    cocw = [get_profile(login, auth) for login in cocw]
+    cw = [get_profile(login, auth) for login in cw]
+    cd = [get_profile(login, auth) for login in cd]
+    fw = [get_profile(login, auth) for login in fw]
+    iw = [get_profile(login, auth) for login in iw]
+    rmw = [get_profile(login, auth) for login in rmw]
 
     # sort by last name
-    cc = sorted(cc, key=key)
-    cocc = sorted(cocc, key=key)
-    core_devs = sorted(core_devs, key=key)
+    cocw = sorted(cocw, key=key)
+    cw = sorted(cw, key=key)
+    cd = sorted(cd, key=key)
+    fw = sorted(fw, key=key)
+    iw = sorted(iw, key=key)
+    rmw = sorted(rmw, key=key)
 
     return (
-        cc,
-        cocc,
-        core_devs,
+        cocw,
+        cw,
+        cd,
+        fw,
+        iw,
+        rmw,
     )
 
 
@@ -138,22 +156,48 @@ if __name__ == "__main__":
     auth = ("user", token)
 
     (
-        cc,
-        cocc,
-        core_devs,
+        cocw,
+        cw,
+        cd,
+        fw,
+        iw,
+        rmw,
     ) = get_contributors(auth)
 
     with open(
-        REPO_FOLDER / "docs" / "about" / "community_council.rst", "w+", encoding="utf-8"
+        REPO_FOLDER / "docs" / "about" / "code_of_conduct_workgroup.rst",
+        "w+",
+        encoding="utf-8",
     ) as rst_file:
-        rst_file.write(generate_table(cc))
+        rst_file.write(generate_table(cocw))
 
     with open(
-        REPO_FOLDER / "docs" / "about" / "coc_committee.rst", "w+", encoding="utf-8"
+        REPO_FOLDER / "docs" / "about" / "communications_workgroup.rst",
+        "w+",
+        encoding="utf-8",
     ) as rst_file:
-        rst_file.write(generate_table(cocc))
+        rst_file.write(generate_table(cw))
 
     with open(
         REPO_FOLDER / "docs" / "about" / "core_developers.rst", "w+", encoding="utf-8"
     ) as rst_file:
-        rst_file.write(generate_table(core_devs))
+        rst_file.write(generate_table(cd))
+
+    with open(
+        REPO_FOLDER / "docs" / "about" / "finance_workgroup.rst", "w+", encoding="utf-8"
+    ) as rst_file:
+        rst_file.write(generate_table(fw))
+
+    with open(
+        REPO_FOLDER / "docs" / "about" / "infrastructure_workgroup.rst",
+        "w+",
+        encoding="utf-8",
+    ) as rst_file:
+        rst_file.write(generate_table(iw))
+
+    with open(
+        REPO_FOLDER / "docs" / "about" / "release_management_workgroup.rst",
+        "w+",
+        encoding="utf-8",
+    ) as rst_file:
+        rst_file.write(generate_table(rmw))
diff --git a/build_tools/pr_labeler.py b/build_tools/pr_labeler.py
new file mode 100644
index 00000000..b0bf57d2
--- /dev/null
+++ b/build_tools/pr_labeler.py
@@ -0,0 +1,91 @@
+"""Labels PRs based on title and change list.
+
+Must be run in a github action with the pull_request_target event.
+
+Based on the scikit-learn v1.3.1 label_title_regex.py script.
+"""
+
+import json
+import os
+import re
+
+from github import Github
+
+context_dict = json.loads(os.getenv("CONTEXT_GITHUB"))
+
+repo = context_dict["repository"]
+g = Github(context_dict["token"])
+repo = g.get_repo(repo)
+pr_number = context_dict["event"]["number"]
+pr = repo.get_pull(number=pr_number)
+labels = [label.name for label in pr.get_labels()]
+
+# title labels
+title = pr.title
+
+title_regex_to_labels = [
+    (r"\bENH\b", "enhancement"),
+    (r"\bMNT\b", "maintenance"),
+    (r"\bBUG\b", "bug"),
+    (r"\bDOC\b", "documentation"),
+    (r"\bGOV\b", "governance"),
+]
+
+title_labels = [
+    label for regex, label in title_regex_to_labels if re.search(regex, title)
+]
+title_labels_to_add = list(set(title_labels) - set(labels))
+
+# content labels
+paths = [file.filename for file in pr.get_files()]
+
+content_paths_to_labels = [
+    ("aeon/anomaly_detection/", "anomaly detection"),
+    ("aeon/benchmarking/", "benchmarking"),
+    ("aeon/classification/", "classification"),
+    ("aeon/clustering/", "clustering"),
+    ("aeon/datasets/", "datasets"),
+    ("aeon/datatypes/", "datatypes"),
+    ("aeon/distances/", "distances"),
+    ("examples/", "examples"),
+    ("aeon/forecasting/", "forecasting"),
+    ("aeon/networks/", "networks"),
+    ("aeon/regression/", "regression"),
+    ("aeon/segmentation/", "segmentation"),
+    ("aeon/similarity_search/", "similarity search"),
+    ("aeon/transformations/", "transformations"),
+]
+
+present_content_labels = [
+    label for _, label in content_paths_to_labels if label in labels
+]
+
+content_labels = [
+    label
+    for package, label in content_paths_to_labels
+    if any([package in path for path in paths])
+]
+content_labels = list(set(content_labels))
+
+content_labels_to_add = content_labels
+content_labels_status = "used"
+if len(present_content_labels) > 0:
+    content_labels_to_add = []
+    content_labels_status = "ignored"
+if len(content_labels) > 3:
+    content_labels_to_add = []
+    content_labels_status = (
+        "large" if content_labels_status != "ignored" else "ignored+large"
+    )
+
+# add to PR
+if title_labels_to_add or content_labels_to_add:
+    pr.add_to_labels(*title_labels_to_add + content_labels_to_add)
+
+with open(os.environ["GITHUB_OUTPUT"], "a") as fh:
+    print(f"title-labels={title_labels}".replace(" ", ""), file=fh)  # noqa: T201
+    print(  # noqa: T201
+        f"title-labels-new={title_labels_to_add}".replace(" ", ""), file=fh
+    )
+    print(f"content-labels={content_labels}".replace(" ", ""), file=fh)  # noqa: T201
+    print(f"content-labels-status={content_labels_status}", file=fh)  # noqa: T201
diff --git a/build_tools/pr_open_commenter.py b/build_tools/pr_open_commenter.py
new file mode 100644
index 00000000..0a791e74
--- /dev/null
+++ b/build_tools/pr_open_commenter.py
@@ -0,0 +1,104 @@
+"""Writes a comment on PR opening.
+
+Includes output from the labeler action.
+"""
+
+import json
+import os
+import sys
+
+from github import Github
+
+context_dict = json.loads(os.getenv("CONTEXT_GITHUB"))
+
+repo = context_dict["repository"]
+g = Github(context_dict["token"])
+repo = g.get_repo(repo)
+pr_number = context_dict["event"]["number"]
+pr = repo.get_pull(number=pr_number)
+
+print(sys.argv)  # noqa
+title_labels = sys.argv[1][1:-1].split(",")
+title_labels_new = sys.argv[2][1:-1].split(",")
+content_labels = sys.argv[3][1:-1].split(",")
+content_labels_status = sys.argv[4]
+
+labels = [(label.name, label.color) for label in repo.get_labels()]
+title_labels = [
+    "$\\color{#%s}{\\textsf{%s}}$" % (color, label)
+    for label, color in labels
+    if label in title_labels
+]
+title_labels_new = [
+    "$\\color{#%s}{\\textsf{%s}}$" % (color, label)
+    for label, color in labels
+    if label in title_labels_new
+]
+content_labels = [
+    "$\\color{#%s}{\\textsf{%s}}$" % (color, label)
+    for label, color in labels
+    if label in content_labels
+]
+
+title_labels_str = ""
+if len(title_labels) == 0:
+    title_labels_str = (
+        "I did not find any labels to add based on the title. Please "
+        "add the ENH, MNT, BUG, DOC and/or GOV tag to your pull "
+        "requests titles. For now you can add the labels manually."
+    )
+elif len(title_labels_new) != 0:
+    arr_str = str(title_labels_new).strip("[]").replace("'", "")
+    title_labels_str = (
+        "I have added the following labels to this PR based on the title: "
+        f"**[ {arr_str} ]**."
+    )
+    if len(title_labels) != len(title_labels_new):
+        arr_str = (
+            str(set(title_labels) - set(title_labels_new)).strip("[]").replace("'", "")
+        )
+        title_labels_str += (
+            f" The following labels were already present: **[ {arr_str} ]**"
+        )
+
+content_labels_str = ""
+if len(content_labels) != 0:
+    if content_labels_status == "used":
+        arr_str = str(content_labels).strip("[]").replace("'", "")
+        content_labels_str = (
+            "I have added the following labels to this PR based on "
+            f"the changes made: **[ {arr_str} ]**. Feel free "
+            "to change these if they do not properly represent the PR."
+        )
+    elif content_labels_status == "ignored":
+        arr_str = str(content_labels).strip("[]").replace("'", "")
+        content_labels_str = (
+            "I would have added the following labels to this PR "
+            f"based on the changes made: **[ {arr_str} ]**, "
+            "however some package labels are already present."
+        )
+    elif content_labels_status == "large":
+        content_labels_str = (
+            "This PR changes too many different packages (>3) for "
+            "automatic addition of labels, please manually add package "
+            "labels if relevant."
+        )
+elif title_labels_str == "":
+    content_labels_str = (
+        "I did not find any labels to add that did not already "
+        "exist. If the content of your PR changes, make sure to "
+        "update the labels accordingly."
+    )
+
+pr.create_issue_comment(
+    f"""
+## Thank you for contributing to `aeon`!
+
+{title_labels_str}
+{content_labels_str}
+
+The "Checks" tab above or the panel below will show the status of our automated tests. From the panel below you can click on "Details" to the right of the check 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.
+
+Don't hesitate to ask questions on the `aeon` Slack channel if you have any!
+    """  # noqa
+)
diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css
index 1fe360ae..346bbb51 100644
--- a/docs/_static/css/custom.css
+++ b/docs/_static/css/custom.css
@@ -9,7 +9,7 @@
 .aeon-teams-container > div {
   width: 80px;
   margin: 5px;
-  font-size: 0.9rem;
+  font-size: 0.8rem;
 }
 
 img.avatar {
diff --git a/docs/about.md b/docs/about.md
index 99786ed0..a665f6ce 100644
--- a/docs/about.md
+++ b/docs/about.md
@@ -1,26 +1,50 @@
-# About us
+# About Us
 
 ## Roles
 
-The roles are described in aeon's [governance](governance.md) document.
+The roles are described in `aeon`'s [governance](governance.md) document.
 A list of all contributors can be found [here](contributors.md).
 
-### Community Council
+### Code of Conduct Workgroup
 
-The community council currently has no sitting members while we discuss governance for
-the project.
+The code of conduct workgroup maintains the code of conduct and handles any
+issues related that arise.
 
 ```{eval-rst}
-.. include:: about/community_council.rst
+.. include:: about/code_of_conduct_workgroup.rst
 ```
 
-### Code of Conduct Committee
+### Communications Workgroup
 
-The code of conduct committee maintains the code of conduct and handles any issues
-related that arise.
+The communications workgroup manages the project's communication channels.
 
 ```{eval-rst}
-.. include:: about/coc_committee.rst
+.. include:: about/communications_workgroup.rst
+```
+
+### Finance Workgroup
+
+The finance workgroup is responsible for managing the project's finances.
+
+```{eval-rst}
+.. include:: about/finance_workgroup.rst
+```
+
+### Infrastructure Workgroup
+
+The infrastructure workgroup is responsible for managing the project's infrastructure
+and CI.
+
+```{eval-rst}
+.. include:: about/infrastructure_workgroup.rst
+```
+
+### Release Management Workgroup
+
+The release management workgroup is responsible for managing the project's releases.
+
+```{eval-rst}
+.. include:: about/release_management_workgroup.rst
 ```
 
 ### Core Developers
@@ -66,8 +90,8 @@ point prior to our split from the project.
 ## History
 
 `aeon` was started in January 2023 as a fork of the
-[sktime project](https://pypi.org/project/sktime/) by 8 core developers using v0.16.0
-as a base.
+[sktime project](https://pypi.org/project/sktime/0.16.0/) by 8 core developers using
+v0.16.0 as a base.
 
 ## Artwork
 
diff --git a/docs/about/coc_committee.rst b/docs/about/code_of_conduct_workgroup.rst
similarity index 64%
rename from docs/about/coc_committee.rst
rename to docs/about/code_of_conduct_workgroup.rst
index 875db82c..9f3b54bd 100644
--- a/docs/about/coc_committee.rst
+++ b/docs/about/code_of_conduct_workgroup.rst
@@ -7,15 +7,19 @@
     <p><a href='https://github.com/KatieBuc'>Katie Buchhorn</a></p>
     </div>
     <div>
-    <a href='https://github.com/GuzalBulatova'><img src='https://avatars.githubusercontent.com/u/73598322?v=4' class='avatar' /></a> <br />
-    <p><a href='https://github.com/GuzalBulatova'>Guzal</a></p>
+    <a href='https://github.com/hadifawaz1999'><img src='https://avatars.githubusercontent.com/u/54309336?v=4' class='avatar' /></a> <br />
+    <p><a href='https://github.com/hadifawaz1999'>Ali El Hadi ISMAIL FAWAZ</a></p>
     </div>
     <div>
-    <a href='https://github.com/MatthewMiddlehurst'><img src='https://avatars.githubusercontent.com/u/25731235?v=4' class='avatar' /></a> <br />
-    <p><a href='https://github.com/MatthewMiddlehurst'>Matthew Middlehurst</a></p>
+    <a href='https://github.com/GuzalBulatova'><img src='https://avatars.githubusercontent.com/u/73598322?v=4' class='avatar' /></a> <br />
+    <p><a href='https://github.com/GuzalBulatova'>Guzal</a></p>
     </div>
     <div>
     <a href='https://github.com/patrickzib'><img src='https://avatars.githubusercontent.com/u/7783034?v=4' class='avatar' /></a> <br />
     <p><a href='https://github.com/patrickzib'>Patrick Schäfer</a></p>
     </div>
+    <div>
+    <a href='https://github.com/ltsaprounis'><img src='https://avatars.githubusercontent.com/u/64217214?v=4' class='avatar' /></a> <br />
+    <p><a href='https://github.com/ltsaprounis'>Leonidas Tsaprounis</a></p>
+    </div>
     </div>
diff --git a/docs/about/communications_workgroup.rst b/docs/about/communications_workgroup.rst
new file mode 100644
index 00000000..0d7b8990
--- /dev/null
+++ b/docs/about/communications_workgroup.rst
@@ -0,0 +1,13 @@
+.. raw :: html
+
+    <!-- Generated by generate_developer_table.py -->
+    <div class="aeon-teams-container">
+    <div>
+    <a href='https://github.com/TonyBagnall'><img src='https://avatars.githubusercontent.com/u/9594042?v=4' class='avatar' /></a> <br />
+    <p><a href='https://github.com/TonyBagnall'>Tony Bagnall</a></p>
+    </div>
+    <div>
+    <a href='https://github.com/baraline'><img src='https://avatars.githubusercontent.com/u/10759117?v=4' class='avatar' /></a> <br />
+    <p><a href='https://github.com/baraline'>Antoine Guillaume</a></p>
+    </div>
+    </div>
diff --git a/docs/about/community_council.rst b/docs/about/community_council.rst
deleted file mode 100644
index d1a41c90..00000000
--- a/docs/about/community_council.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-.. raw :: html
-
-    <!-- Generated by generate_developer_table.py -->
-    <div class="aeon-teams-container">
-    </div>
diff --git a/docs/about/finance_workgroup.rst b/docs/about/finance_workgroup.rst
new file mode 100644
index 00000000..f6612c22
--- /dev/null
+++ b/docs/about/finance_workgroup.rst
@@ -0,0 +1,13 @@
+.. raw :: html
+
+    <!-- Generated by generate_developer_table.py -->
+    <div class="aeon-teams-container">
+    <div>
+    <a href='https://github.com/TonyBagnall'><img src='https://avatars.githubusercontent.com/u/9594042?v=4' class='avatar' /></a> <br />
+    <p><a href='https://github.com/TonyBagnall'>Tony Bagnall</a></p>
+    </div>
+    <div>
+    <a href='https://github.com/dguijo'><img src='https://avatars.githubusercontent.com/u/47889499?v=4' class='avatar' /></a> <br />
+    <p><a href='https://github.com/dguijo'>David Guijo Rubio</a></p>
+    </div>
+    </div>
diff --git a/docs/about/infrastructure_workgroup.rst b/docs/about/infrastructure_workgroup.rst
new file mode 100644
index 00000000..cc6c56f9
--- /dev/null
+++ b/docs/about/infrastructure_workgroup.rst
@@ -0,0 +1,21 @@
+.. raw :: html
+
+    <!-- Generated by generate_developer_table.py -->
+    <div class="aeon-teams-container">
+    <div>
+    <a href='https://github.com/TonyBagnall'><img src='https://avatars.githubusercontent.com/u/9594042?v=4' class='avatar' /></a> <br />
+    <p><a href='https://github.com/TonyBagnall'>Tony Bagnall</a></p>
+    </div>
+    <div>
+    <a href='https://github.com/MatthewMiddlehurst'><img src='https://avatars.githubusercontent.com/u/25731235?v=4' class='avatar' /></a> <br />
+    <p><a href='https://github.com/MatthewMiddlehurst'>Matthew Middlehurst</a></p>
+    </div>
+    <div>
+    <a href='https://github.com/ltsaprounis'><img src='https://avatars.githubusercontent.com/u/64217214?v=4' class='avatar' /></a> <br />
+    <p><a href='https://github.com/ltsaprounis'>Leonidas Tsaprounis</a></p>
+    </div>
+    <div>
+    <a href='https://github.com/aiwalter'><img src='https://avatars.githubusercontent.com/u/29627036?v=4' class='avatar' /></a> <br />
+    <p><a href='https://github.com/aiwalter'>Martin Walter</a></p>
+    </div>
+    </div>
diff --git a/docs/about/release_management_workgroup.rst b/docs/about/release_management_workgroup.rst
new file mode 100644
index 00000000..e09d7b5e
--- /dev/null
+++ b/docs/about/release_management_workgroup.rst
@@ -0,0 +1,9 @@
+.. raw :: html
+
+    <!-- Generated by generate_developer_table.py -->
+    <div class="aeon-teams-container">
+    <div>
+    <a href='https://github.com/MatthewMiddlehurst'><img src='https://avatars.githubusercontent.com/u/25731235?v=4' class='avatar' /></a> <br />
+    <p><a href='https://github.com/MatthewMiddlehurst'>Matthew Middlehurst</a></p>
+    </div>
+    </div>
diff --git a/docs/api_reference/transformations.rst b/docs/api_reference/transformations.rst
index 1b9f90a9..bc29661d 100644
--- a/docs/api_reference/transformations.rst
+++ b/docs/api_reference/transformations.rst
@@ -116,7 +116,6 @@ These transformers extract simple summary features.
 
     SummaryTransformer
     WindowSummarizer
-    PlateauFinder
     FittedParamExtractor
 
 
@@ -142,6 +141,8 @@ Shapelets, wavelets, and convolution
     MiniRocket
     MiniRocketMultivariate
     MiniRocketMultivariateVariable
+    MultiRocket
+    MultiRocketMultivariate
 
 .. currentmodule:: aeon.transformations.collection.dwt
 
diff --git a/docs/code_of_conduct.md b/docs/code_of_conduct.md
index 31999d6b..b2ebb3e6 100644
--- a/docs/code_of_conduct.md
+++ b/docs/code_of_conduct.md
@@ -2,10 +2,10 @@
 
 We value the participation of every member in our community and want to
 ensure that every contributor has an enjoyable experience. Accordingly, those who
-participate in the aeon project are expected to show respect and courtesy to other
+participate in the `aeon` project are expected to show respect and courtesy to other
 community members at all times.
 
-Everyone participating in activities associated with the aeon project (whether
+Everyone participating in activities associated with the `aeon` project (whether
 in-person or remote) is required to conform to this Code of Conduct (CoC) regardless
 of platform.
 
@@ -14,7 +14,7 @@ Software Foundation Code of Conduct](https://www.python.org/psf/codeofconduct/)
 help maintain an open, considerate and respectful environment.
 
 Unacceptable behavior and code of conduct violations are determined and dealt with
-at the discretion of the aeon [Code of Conduct Workgroup (COCW)](./governance.md#code-of-conduct-workgroup).
+at the discretion of the `aeon` [Code of Conduct Workgroup (COCW)](./governance.md#code-of-conduct-workgroup).
 Possible actions taken include warnings, mediated meeting and/or apologies, and
 expulsion from the project depending on the severity of the breach. The COCW can edit
 and remove comments and other contributions that are not aligned to this Code of
@@ -35,8 +35,9 @@ immediately.
 
 If you believe someone is violating the CoC, we ask that you report it by
 contacting the COCW. All reports will be kept confidential. You can make a report by
-emailing the general aeon contact address [contact@aeon-toolkit.org](mailto:contact@aeon-toolkit.org),
-or by messaging members of the COCW directly on [Slack](https://join.slack.com/t/aeon-toolkit/shared_invite/zt-22vwvut29-HDpCu~7VBUozyfL_8j3dLA).
+emailing the general `aeon` contact address [contact@aeon-toolkit.org](mailto:contact@aeon-toolkit.org),
+or by messaging [members of the COCW](./about.md#code-of-conduct-workgroup)
+directly on [Slack](https://join.slack.com/t/aeon-toolkit/shared_invite/zt-22vwvut29-HDpCu~7VBUozyfL_8j3dLA).
 
 Thank you for reading and helping make this a welcoming, friendly community for
 everyone.
diff --git a/docs/get_involved/governance.md b/docs/get_involved/governance.md
deleted file mode 100644
index ffde585a..00000000
--- a/docs/get_involved/governance.md
+++ /dev/null
@@ -1,165 +0,0 @@
-# Governance and decision-making
-
-The purpose of this document is to formalize the governance process used by the `aeon`
-project, to clarify how decisions are made and how the various elements of our community
-interact. Our goal is to ensure a transparent, democratic, and inclusive decision-making
-process that empowers all community members to contribute to the project.
-
-`aeon` is a community-owned and community-run project. Anyone with an interest in the
-project can join the community, contribute to the project design and participate in the
-decision-making process.
-
-This document establishes the decision-making structure used by `aeon` which strives to
-find consensus from the community, while avoiding any deadlocks. Our decision-making
-process involves different roles, each with specific responsibilities.
-
-## Roles
-
-### Contributors
-
-Contributors are community members who have contributed in concrete ways to the project.
-Anyone can become a contributor, and contributions can take many forms – not only code –
-as detailed in the contributing guide. Contributors play a crucial role in shaping the
-project through participating in discussions and influencing the decision-making
-process.
-
-### Core Developers
-
-Core developers are community members that have made significant contributions and are
-trusted to continue the development of the project and engage with its community.
-Core developers are granted write access to the `aeon` repository and voting rights
-on all project decisions. They are expected to review code contributions
-and engage with topics or code they are knowledgeable about.
-
-New core developers are nominated by existing core developers and are subject to a
-two-thirds majority vote of existing core developers. Core developers are expected to
-maintain a reasonable amount of engagement with the project. Developing code,
-interacting with contributions and engaging with the broader community are all valid
-contributions for core developers.
-
-Core developers who no longer want or are able to engage with the project are expected
-to resign. Core developers may lose their role after a year of inactivity with no
-contributions (see above) and engagement with fellow developers. If this lack of
-engagement has been demonstrated, another core developer can suggest removal and create
-a pull request on the `aeon` repository. The removal will complete if this pull request
-is successfully merged.
-
-### Workgroups
-
-Extra responsibilities are delegated to workgroups, which are groups of contributors
-with a core developer lead. Workgroups are responsible for specific areas of the project
-and are expected to manage and make decisions in their area of responsibility.
-Workgroups are expected to be transparent about their activities and decisions, and to
-engage with the community where possible.
-
-Membership of workgroups and the leadership position of a workgroup is subject to a
-two-thirds majority vote of core developers. Workgroup members unable to fulfill their
-responsibilities are expected to resign from the workgroup. It is the responsibility of
-the workgroup lead to safeguard access to relevant project resources and accounts.
-
-#### Infrastructure Workgroup
-
-The infrastructure workgroup maintains the infrastructure of `aeon` to ensure the
-smooth running of the project. This includes ensuring the website remains online, that
-CI remains functional and other related tasks.
-
-The infrastructure workgroup maintains ownership of the `aeon` GitHub organisation and
-ReadTheDocs account.
-
-#### Release Management Workgroup
-
-The release management workgroup manages `aeon` releases. This includes deciding on
-release schedules, managing release candidates and publishing releases.
-
-The release management workgroup maintains ownership of the `aeon` PyPi project and
-conda feedstock.
-
-#### Finance Workgroup
-
-The finance workgroup is responsible for managing the project finances. This includes
-approving any project expenses and managing any finance related accounts.
-
-#### Communications Workgroup
-
-The role of the communications workgroups is to interact with the broader community
-through the `aeon` social network accounts and discussion forums. It is the
-responsibility of the communications workgroup to manage and maintain the `aeon`
-Twitter, LinkedIn, Slack and other relevant communications accounts.
-
-The communications team maintains access to social networking accounts and is
-responsible for managing access to the `aeon` email address. To help manage
-GitHub discussions, the communication workgroup is given triage access to the `aeon`
-GitHub repository.
-
-#### Code of Conduct Workgroup
-
-The Code of Conduct Workgroup (CoCW) consists of contributors tasked with making sure
-`aeon` remains a welcoming and inclusive community. CoCW responsibilities include
-maintaining the `aeon` Code of Conduct (CoC) and managing reports of breaking the CoC.
-CoCW members are expected to review reports of CoC violations, contact and discuss with
-involved individuals and make recommendations on actions to take.
-
-Any CoCW members involved in a CoC report or CoCW members which have a conflict of
-interest regarding the report are expected to recuse themselves from the process. The
-CoCW is given triage access to the `aeon` GitHub repository to moderate discussions if
-necessary.
-
-## Decision-Making Process
-
-Decisions about the future of the project are announced publicly to allow discussion
-with all members of the community. The whole process from proposal to implementation
-is fully visible, apart from topics considered sensitive. All non-sensitive project
-management discussion takes place on the contributors channel on the project
-Slack and/or the issue tracker. Occasionally, sensitive discussion and votes such as
-appointments will occur in private Slack channels.
-
-For most decisions a consensus seeking process of all interested contributors is used.
-Contributors try to find a resolution that has no open objections among core developers.
-If a reasonable amount of time (at least 7-days for non-trivial changes) has passed
-since the last change to a proposed contribution, the proposal has at least one approval
-(+1) and no rejections (-1) from core developers, it can be approved by lazy consensus.
-If a change is rejected, it is expected that an explanation and description of
-conditions (if any) to withdraw the rejection is provided.
-
-At any point during the discussion, any core developer in favour of a change can call
-for a vote, which will conclude two weeks from the call for the vote. Any vote to
-bypass a rejection from a core developer must be backed by an AEP (see the following
-section). For major contributions (such as a new module or major framework redesigns)
-an AEP may be requested without a rejection or vote. In the event a vote is called,
-the proposal must receive a two-thirds majority of core developers to be approved.
-
-All changes to the `aeon` code or documentation should be done via Pull Request.
-By default, push rights to the `main` GitHub branch are restricted for all core developers.
-In emergencies, the infrastructure workgroup may temporarily revoke the branch
-protection for group members and make direct commits, but this should happen only in
-emergencies where harm will come to the project unless timely action is taken.
-
-### Enhancement Proposals
-
-For contentious decision-making votes (not including appointment votes), a proposal
-must have been made public for discussion before the vote. It is recommended that this
-proposal is made as a consolidated document, in the form of an “aeon Enhancement
-Proposal” (AEP). The AEP template is available
-[here](https://github.com/aeon-toolkit/aeon-admin/blob/main/aep/aep_template.md), but
-the use of said template is not a requirement. A detailed issue or pull request can
-substitute an AEP if all parties believe it is sufficient, but a more formal proposal
-can be requested by any core developer.
-
-Having a rejection on a pull request does not necessitate the creation of an AEP and
-further discussion to find consensus can be held, but one must be created prior to any
-vote to bypass a rejection.
-
-A vote does not have to be called for an AEP to be submitted. If a contributor wants to
-make a more formal proposal or believes a change will be controversial, an AEP can be
-submitted to the community for discussion and comment.
-
-## Acknowledgements
-
-Substantial portions of this document were adapted from or inspired by the following
-projects governance documents:
-
-- [Scikit-learn](https://scikit-learn.org/stable/governance.html)
-- [SciPy](https://docs.scipy.org/doc/scipy/dev/governance.html)
-- [NumPy](https://numpy.org/doc/stable/dev/governance/governance.html#governance)
-- [Jupyter](https://jupyter.org/governance/overview.html)
-- [Pandas](https://pandas.pydata.org/about/governance.html)
diff --git a/pyproject.toml b/pyproject.toml
index a1b6b8c4..199712cf 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -90,7 +90,7 @@ dl = [
     "tensorflow-probability<0.21.0",
 ]
 unstable_extras = [
-    "pycatch22",  # known to fail installation on some setups
+    "pycatch22<=4.3",  # known to fail installation on some setups
     "mrsqm>=0.0.1,<0.1.0 ; platform_system == 'Darwin'",  # requires gcc and fftw to be installed for Windows and some other OS (see http://www.fftw.org/index.html)
 ]
 dev = [


Test patch:
diff --git a/.github/workflows/periodic_tests.yml b/.github/workflows/periodic_tests.yml
index 1ea730c7..d615ca79 100644
--- a/.github/workflows/periodic_tests.yml
+++ b/.github/workflows/periodic_tests.yml
@@ -10,6 +10,38 @@ concurrency:
   cancel-in-progress: true
 
 jobs:
+  check-manifest:
+    runs-on: ubuntu-22.04
+
+    steps:
+      - uses: actions/checkout@v3
+
+      - uses: actions/setup-python@v4
+        with:
+          python-version: "3.10"
+
+      - uses: pre-commit/action@v3.0.0
+        with:
+          extra_args: check-manifest --hook-stage manual
+
+  pre-commit:
+    runs-on: ubuntu-22.04
+
+    steps:
+      - uses: actions/checkout@v3
+
+      - uses: actions/setup-python@v4
+        with:
+          python-version: "3.10"
+
+      - uses: pre-commit/action@v3.0.0
+        with:
+          extra_args: --all-files
+
+      - name: Check for missing init files
+        run: build_tools/fail_on_missing_init_files.sh
+        shell: bash
+
   run-notebook-examples:
     runs-on: ubuntu-22.04
 
diff --git a/.github/workflows/pr_pytest.yml b/.github/workflows/pr_pytest.yml
index 96f150e6..41836f62 100644
--- a/.github/workflows/pr_pytest.yml
+++ b/.github/workflows/pr_pytest.yml
@@ -88,7 +88,7 @@ jobs:
 
   codecov:
     # run the code coverage job if a PR has the 'codecov actions' label
-    if: github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'codecov actions')
+    if: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'codecov actions') }}
 
     needs: test-nosoftdeps
     runs-on: ubuntu-22.04
diff --git a/aeon/transformations/collection/tests/test_base.py b/aeon/transformations/collection/tests/test_base.py
index 6573798a..162db555 100644
--- a/aeon/transformations/collection/tests/test_base.py
+++ b/aeon/transformations/collection/tests/test_base.py
@@ -38,11 +38,7 @@ def test_collection_transformer_valid_input(data_gen):
 @pytest.mark.parametrize("dtype", ["pd.Series"])
 def test_collection_transformer_invalid_input(dtype):
     """Test that BaseCollectionTransformer fails with series input."""
-    y = convert_to(
-        _make_series(),
-        to_type=dtype,
-    )
-
+    y = (_make_series(),)
     t = _Dummy()
     with pytest.raises(TypeError):
         t.fit_transform(y)
diff --git a/aeon/transformations/collection/tests/test_segment.py b/aeon/transformations/collection/tests/test_segment.py
index 071e7cc0..4c4f4771 100644
--- a/aeon/transformations/collection/tests/test_segment.py
+++ b/aeon/transformations/collection/tests/test_segment.py
@@ -9,7 +9,6 @@ from aeon.transformations.collection.segment import (
     _rand_intervals_fixed_n,
     _rand_intervals_rand_n,
 )
-from aeon.utils._testing.collection import _make_nested_from_array
 
 N_ITER = 10
 
@@ -50,7 +49,7 @@ def test_output_format_dim(n_timepoints, n_instances, n_intervals):
 @pytest.mark.parametrize("bad_interval", [0, -0, "str", 1.2, -1.2, -1])
 def test_bad_input_args(bad_interval):
     """Check that exception is raised for bad input args."""
-    X = _make_nested_from_array(np.ones(10), n_instances=10, n_columns=2)
+    X = np.random.random(size=(10, 1, 20))
     with pytest.raises(ValueError):
         RandomIntervalSegmenter(n_intervals=bad_interval).fit(X)
 
diff --git a/aeon/transformations/collection/tests/test_timebinner.py b/aeon/transformations/collection/tests/test_timebinner.py
index c1dba6b2..328beea0 100644
--- a/aeon/transformations/collection/tests/test_timebinner.py
+++ b/aeon/transformations/collection/tests/test_timebinner.py
@@ -18,7 +18,7 @@ def test_timebinner():
     tb.fit(X)
     row = 1
     Xtb = tb.transform(X)
-    assert np.isclose(np.sum(X.iloc[row, 0][freq : (2 * freq)]), Xtb.iloc[row, 1])
+    assert np.isclose(np.sum(X.iloc[row, 0][freq : (2 * freq)]), Xtb[row, 1])
 
 
 def test_timebinner2():
@@ -32,9 +32,7 @@ def test_timebinner2():
     tb.fit(X)
     row = 3
     Xtb = tb.transform(X)
-    assert np.isclose(
-        np.max(X.iloc[row, 5][8 * 10 + 1 : 9 * 10 + 1]), Xtb.iloc[row, 58]
-    )
+    assert np.isclose(np.max(X.iloc[row, 5][8 * 10 + 1 : 9 * 10 + 1]), Xtb[row, 58])
 
 
 def test_timebinner3():
@@ -53,5 +51,5 @@ def test_timebinner3():
     Xtb = tb.transform(X)
     assert np.isclose(
         np.quantile(X.iloc[row, 0][col * freq + 1 : ((col + 1) * freq) + 1], q=0.25),
-        Xtb.iloc[row, col],
+        Xtb[row, col],
     )