Repository: aeon-toolkit/aeon
Pull request #606 (2023-07-27 19:50:52)

[DOCS] pre-processing notebook and collections validations
1. correct some things on classification notebook
2. Adds a first go at a preprocessing notebook to show how to deal with missing values and unequal length. More to come of this at a later date
3. Adds a util/validation file called collection.py to contain simple ways of checking for unequal and missing (related to #576  with associated tests
4. Fixes minor bug on distances notebook 
5. Removes big picture from front of docs, changes first sentence (from slack)


step (3) was necesssary to write the notebook, but morphed into something a bit more, sorry. The file collection.py and the full test suite associated it is a precursor to #551 and #576 

after these two PRs, I'll introduce a new base class between BaseEstimator and BaseClassifier so these changes can be easily extended to BaseRegressor, BaseClusterer and BaseCollectionTransformer.




review-notebook-app[bot]: Check out this pull request on&nbsp; <a href="https://app.reviewnb.com/aeon-toolkit/aeon/pull/606"><img align="absmiddle"  alt="ReviewNB" height="28" class="BotMessageButtonImage" src="https://raw.githubusercontent.com/ReviewNB/support/master/images/button_reviewnb.png"/></a> 

 See visual diffs & provide feedback on Jupyter Notebooks. 

---

 <i>Powered by <a href='https://www.reviewnb.com/?utm_source=gh'>ReviewNB</a></i>

Patch:
diff --git a/.github/release.yml b/.github/release.yml
deleted file mode 100644
index 12fc604f..00000000
--- a/.github/release.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-changelog:
-  categories:
-    - title: '🚀 Features'
-      labels:
-        - 'enhancement'
-        - 'implementing algorithms'
-        - 'implementing framework'
-        - 'interfacing algorithms'
-    - title: '🐛 Bug Fixes'
-      labels:
-        - 'bug'
-    - title: '🧰 Maintenance'
-      label:
-        - 'maintenance'
-        - 'refactor'
-        - 'testing'
-    - title: '📖 Documentation'
-      label:
-        - 'documentation'
-    - title: Other Changes
-      labels:
-        - "*"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 2a285694..a4684651 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -20,7 +20,7 @@ jobs:
         with:
           extra_args: check-manifest --hook-stage manual
 
-  build_wheels:
+  build_project:
     needs: check_manifest
     runs-on: ubuntu-22.04
 
@@ -29,7 +29,7 @@ jobs:
 
       - uses: actions/setup-python@v4
         with:
-          python-version: '3.10'
+          python-version: "3.10"
 
       - name: Build project
         run: |
@@ -42,15 +42,15 @@ jobs:
           name: dist
           path: dist/*
 
-  test_unix_wheels:
-    needs: build_wheels
-    name: Test wheels on ${{ matrix.os }} with ${{ matrix.python-version }}
+  test_wheels:
+    needs: build_project
     runs-on: ${{ matrix.os }}
 
     strategy:
+      fail-fast: false
       matrix:
-        os: [ubuntu-22.04, macOS-12]
-        python-version: ['3.8', '3.9', '3.10', '3.11']
+        os: [ ubuntu-22.04, macOS-12, windows-2022 ]
+        python-version: [ "3.8", "3.9", "3.10", "3.11" ]
 
     steps:
       - uses: actions/checkout@v3
@@ -64,89 +64,26 @@ jobs:
           name: dist
           path: dist
 
-      - name: Get wheel filename
-        run: echo "WHEELNAME=$(ls ./dist/aeon-*none-any.whl)" >> $GITHUB_ENV
-
-      - name: Install wheel and extras
-        run: python -m pip install "${{ env.WHEELNAME }}[all_extras,dev]"
-
-      - name: Run tests
-        run: make test
-
-  test_windows_wheels:
-    needs: build_wheels
-    name: Test wheels on ${{ matrix.os }} with ${{ matrix.python-version }}
-    runs-on: windows-2022
-    strategy:
-      matrix:
-        include:
-          # Window 64 bit
-          - os: windows-2022
-            python: 38
-            python-version: '3.8'
-            bitness: 64
-            platform_id: win_amd64
-          - os: windows-2022
-            python: 39
-            python-version: '3.9'
-            bitness: 64
-            platform_id: win_amd64
-          - os: windows-2022
-            python: 310
-            python-version: '3.10'
-            bitness: 64
-            platform_id: win_amd64
-          - os: windows-2022
-            python: 311
-            python-version: '3.11'
-            bitness: 64
-            platform_id: win_amd64
-
-    steps:
-      - uses: actions/checkout@v3
-      - uses: conda-incubator/setup-miniconda@v2
-        with:
-          activate-environment: test
-          auto-update-conda: true
-          python-version: ${{ matrix.python-version }}
-          channels: anaconda, conda-forge,
-
-      - run: conda --version
-      - run: which python
-
-      - uses: actions/download-artifact@v3
-        with:
-          name: dist
-          path: dist
-
-      - name: Install conda libpython
-        run: conda install -c anaconda -n test -y libpython
-
-      - name: Display downloaded artifacts
-        run: ls -l dist
+      - if: matrix.os == 'windows-2022'
+        name: Windows wheel filename
+        run: echo "WHEELNAME=$(ls ./dist/aeon-*-none-any.whl)" >> $env:GITHUB_ENV
+      - if: matrix.os != 'windows-2022'
+        name: Unix wheel filename
+        run: echo "WHEELNAME=$(ls ./dist/aeon-*-none-any.whl)" >> $GITHUB_ENV
 
-      - name: Get wheel filename
-        run: echo "WHEELNAME=$(ls ./dist/aeon-*none-any.whl)" >> $env:GITHUB_ENV
-
-      - name: Activate conda env
-        run: conda activate test
-
-      - name: Install wheel and extras
+      - if: matrix.os == 'windows-2022'
+        name: Windows install
         run: python -m pip install "${env:WHEELNAME}[all_extras,dev]"
+      - if: matrix.os != 'windows-2022'
+        name: Unix install
+        run: python -m pip install "${{ env.WHEELNAME }}[all_extras,dev]"
 
-      - name: Show conda packages
-        run: conda list -n test
-
-      - name: Run tests
-        run: |
-          mkdir -p testdir/
-          cp .coveragerc testdir/
-          cp setup.cfg testdir/
-          python -m pytest
+      - name: Tests
+        run: python -m pytest
 
   upload_wheels:
-    runs-on: ubuntu-latest
-    needs: [build_wheels,test_unix_wheels,test_windows_wheels]
+    needs: test_wheels
+    runs-on: ubuntu-22.04
 
     steps:
       - uses: actions/download-artifact@v3
diff --git a/MANIFEST.in b/MANIFEST.in
index 08d17e81..fb9fe8c4 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,10 +1,30 @@
+recursive-include aeon *.py
+recursive-include aeon/benchmarking/example_results *.csv
+recursive-include aeon/datasets *.csv *.arff *.txt *.ts *.tsv *.tsf
 recursive-include docs *
 recursive-include examples *
-recursive-include aeon *.py
-recursive-include aeon *.c *.h *.pyx *.pxd *.pxi *.tp
-recursive-include aeon/datasets *.csv *.csv.gz *.arff *.arff.gz *.txt *.ts *.tsv *.tsf
-include *.rst
-include *.md
-include LICENSE
-include conftest.py
+recursive-include extension_templates *.py
 include aeon/registry/README.md
+include .coveragerc
+include conftest.py
+include LICENSE
+include MANIFEST.in
+include pyproject.toml
+include README.md
+include setup.cfg
+
+recursive-exclude .binder *
+recursive-exclude .github *
+recursive-exclude build_tools *
+exclude .all-contributorsrc
+exclude .codecov.yml
+exclude .gitattributes
+exclude .gitignore
+exclude .pre-commit-config.yaml
+exclude .readthedocs.yml
+exclude CHANGELOG.md
+exclude CODE_OF_CONDUCT.md
+exclude CODEOWNERS
+exclude CONTRIBUTING.md
+exclude CONTRIBUTORS.md
+exclude GOVERNANCE.md
diff --git a/README.md b/README.md
index 05b10c52..118c7153 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
     <a href="https://aeon-toolkit.org"><img src="https://github.com/aeon-toolkit/aeon/blob/main/docs/images/logo/aeon-logo-blue-compact.png" width="50%" alt="aeon logo" /></a>
 </p>
 
-# ⌛ Welcome to **aeon**
+# ⌛ Welcome to aeon
 
 `aeon` is an open source toolkit for learning from time series. It is compatible with
 [scikit-learn](https://scikit-learn.org) and provides access to the very latest
@@ -13,7 +13,7 @@ We strive to provide a broad library of time series algorithms including the
 latest advances, offer efficient implementations using numba, and interfaces with other
 time series packages to provide a single framework for algorithm comparison.
 
-The latest `aeon` release is `v0.3.0`. You can view the full changelog
+The latest `aeon` release is `v0.4.0`. You can view the full changelog
 [here](https://www.aeon-toolkit.org/en/latest/changelog.html).
 
 ```diff
@@ -23,11 +23,11 @@ The latest `aeon` release is `v0.3.0`. You can view the full changelog
 
 Our webpage and documentation is available at https://aeon-toolkit.org.
 
-| Overview      |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
-|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| **CI/CD**     | [![github-actions-release](https://img.shields.io/github/actions/workflow/status/aeon-toolkit/aeon/release.yml?logo=github&label=build%20%28release%29)](https://github.com/aeon-toolkit/aeon/actions/workflows/release.yml) [![github-actions-main](https://img.shields.io/github/actions/workflow/status/aeon-toolkit/aeon/test.yml?logo=github&branch=main&label=build%20%28main%29)](https://github.com/aeon-toolkit/aeon/actions/workflows/test.yml) [![docs-main](https://img.shields.io/readthedocs/aeon-toolkit/stable?logo=readthedocs&label=docs%20%28stable%29)](https://www.aeon-toolkit.org/en/stable/) [![docs-main](https://img.shields.io/readthedocs/aeon-toolkit/latest?logo=readthedocs&label=docs%20%28latest%29)](https://www.aeon-toolkit.org/en/latest/) [![!codecov](https://img.shields.io/codecov/c/github/aeon-toolkit/aeon?label=codecov&logo=codecov)](https://codecov.io/gh/aeon-toolkit/aeon) |
-| **Code**      | [![!pypi](https://img.shields.io/pypi/v/aeon?logo=pypi&color=blue)](https://pypi.org/project/aeon/) [![!python-versions](https://img.shields.io/pypi/pyversions/aeon?logo=python)](https://www.python.org/) [![!black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![license](https://img.shields.io/badge/license-BSD%203--Clause-green?logo=style)](https://github.com/aeon-toolkit/aeon/blob/main/LICENSE) [![binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/aeon-toolkit/aeon/main?filepath=examples)                                                                                                                                                                                                                                                                                                                                          |
-| **Community** | [![!slack](https://img.shields.io/static/v1?logo=slack&label=Slack&message=chat&color=lightgreen)](https://join.slack.com/t/aeon-toolkit/shared_invite/zt-1plkevy4x-vAg1dAUXcuoR38FjY9nxzg) [![!linkedin](https://img.shields.io/static/v1?logo=linkedin&label=LinkedIn&message=news&color=lightblue)](https://www.linkedin.com/company/aeon-toolkit/) [![!twitter](https://img.shields.io/static/v1?logo=twitter&label=Twitter&message=news&color=lightblue)](https://twitter.com/aeon_toolkit)                                                                                                                                                                                                                                                                                                                                                                                                                             |
+| Overview      |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
+|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **CI/CD**     | [![github-actions-release](https://img.shields.io/github/actions/workflow/status/aeon-toolkit/aeon/release.yml?logo=github&label=build%20%28release%29)](https://github.com/aeon-toolkit/aeon/actions/workflows/release.yml) [![github-actions-main](https://img.shields.io/github/actions/workflow/status/aeon-toolkit/aeon/pr_tests.yml?logo=github&branch=main&label=build%20%28main%29)](https://github.com/aeon-toolkit/aeon/actions/workflows/pr_tests.yml) [![github-actions-nightly](https://img.shields.io/github/actions/workflow/status/aeon-toolkit/aeon/periodic_tests.yml?logo=github&label=build%20%28nightly%29)](https://github.com/aeon-toolkit/aeon/actions/workflows/periodic_tests.yml) [![docs-main](https://img.shields.io/readthedocs/aeon-toolkit/stable?logo=readthedocs&label=docs%20%28stable%29)](https://www.aeon-toolkit.org/en/stable/) [![docs-main](https://img.shields.io/readthedocs/aeon-toolkit/latest?logo=readthedocs&label=docs%20%28latest%29)](https://www.aeon-toolkit.org/en/latest/) [![!codecov](https://img.shields.io/codecov/c/github/aeon-toolkit/aeon?label=codecov&logo=codecov)](https://codecov.io/gh/aeon-toolkit/aeon) |
+| **Code**      | [![!pypi](https://img.shields.io/pypi/v/aeon?logo=pypi&color=blue)](https://pypi.org/project/aeon/) [![!python-versions](https://img.shields.io/pypi/pyversions/aeon?logo=python)](https://www.python.org/) [![!black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![license](https://img.shields.io/badge/license-BSD%203--Clause-green?logo=style)](https://github.com/aeon-toolkit/aeon/blob/main/LICENSE) [![binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/aeon-toolkit/aeon/main?filepath=examples)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
+| **Community** | [![!slack](https://img.shields.io/static/v1?logo=slack&label=Slack&message=chat&color=lightgreen)](https://join.slack.com/t/aeon-toolkit/shared_invite/zt-1plkevy4x-vAg1dAUXcuoR38FjY9nxzg) [![!linkedin](https://img.shields.io/static/v1?logo=linkedin&label=LinkedIn&message=news&color=lightblue)](https://www.linkedin.com/company/aeon-toolkit/) [![!twitter](https://img.shields.io/static/v1?logo=twitter&label=Twitter&message=news&color=lightblue)](https://twitter.com/aeon_toolkit)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
 
 ## ⚙️ Installation
 
diff --git a/aeon/__init__.py b/aeon/__init__.py
index e4bd8450..4734ce0c 100644
--- a/aeon/__init__.py
+++ b/aeon/__init__.py
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 """aeon."""
 
-__version__ = "0.3.0"
+__version__ = "0.4.0"
 
 __all__ = ["show_versions"]
 
diff --git a/aeon/registry/_lookup.py b/aeon/registry/_lookup.py
index 629f290b..59a7a24a 100644
--- a/aeon/registry/_lookup.py
+++ b/aeon/registry/_lookup.py
@@ -221,6 +221,7 @@ def all_estimators(
         warnings.filterwarnings(
             "ignore", category=UserWarning, message=".*has been moved to.*"
         )
+        warnings.filterwarnings("ignore", category=Warning)
         for module_name in _walk(root=ROOT, exclude=MODULES_TO_IGNORE, prefix="aeon."):
             # Filter modules
             if _is_private_module(module_name):
diff --git a/aeon/utils/validation/__init__.py b/aeon/utils/validation/__init__.py
index 47484403..0ffa9b67 100644
--- a/aeon/utils/validation/__init__.py
+++ b/aeon/utils/validation/__init__.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python3 -u
 # -*- coding: utf-8 -*-
 # copyright: aeon developers, BSD-3-Clause License (see LICENSE file)
-"""Validation functions."""
+"""Validation and checking functions for time series."""
 
 __all__ = [
     "is_int",
@@ -11,8 +11,14 @@ __all__ = [
     "is_timedelta_or_date_offset",
     "check_n_jobs",
     "check_window_length",
+    "get_n_cases",
+    "get_type",
+    "equal_length",
+    "is_equal_length",
+    "has_missing",
+    "is_univariate",
 ]
-__author__ = ["mloning", "Taiwo Owoseni", "khrapovs"]
+__author__ = ["mloning", "Taiwo Owoseni", "khrapovs", "TonyBagnall"]
 
 import os
 from datetime import timedelta
@@ -21,6 +27,15 @@ from typing import Union
 import numpy as np
 import pandas as pd
 
+from aeon.utils.validation.collection import (
+    equal_length,
+    get_n_cases,
+    get_type,
+    has_missing,
+    is_equal_length,
+    is_univariate,
+)
+
 ACCEPTED_DATETIME_TYPES = np.datetime64, pd.Timestamp
 ACCEPTED_TIMEDELTA_TYPES = pd.Timedelta, timedelta, np.timedelta64
 ACCEPTED_DATEOFFSET_TYPES = pd.DateOffset
diff --git a/aeon/utils/validation/collection.py b/aeon/utils/validation/collection.py
new file mode 100644
index 00000000..6a5a3836
--- /dev/null
+++ b/aeon/utils/validation/collection.py
@@ -0,0 +1,359 @@
+# -*- coding: utf-8 -*-
+
+# -*- coding: utf-8 -*-
+"""Validation and checking functions for collections of time series."""
+
+__author__ = ["TonyBagnall"]
+
+import numpy as np
+import pandas as pd
+
+COLLECTIONS_DATA_TYPES = [
+    "numpy3D",  # 3D np.ndarray of format (n_cases, n_channels, n_timepoints)
+    "np-list",  # python list of 2D numpy array of length [n_cases],
+    # each of shape (n_channels, n_timepoints_i)
+    "df-list",  # python list of 2D pd.DataFrames of length [n_cases], each a of
+    # shape (n_channels, n_timepoints_i)
+    "numpyflat",  # 2D np.ndarray of shape (n_cases, n_channels*n_timepoints)
+    "pd-wide",  # 2D pd.DataFrame of shape (n_cases, n_channels*n_timepoints)
+    "nested_univ",  # pd.DataFrame (n_cases, n_channels) with each cell a pd.Series,
+    "pd-multiindex",  # d.DataFrame with multi-index,
+    # To add "dask_panel": but not currently used anywhere
+]
+
+
+def resolve_equal_length_inner_type(inner_type):
+    """Hierarchy of preference for internal supported types for equal length."""
+    if "numpy3D" in inner_type:
+        return "numpy3D"
+    if "np-list" in inner_type:
+        return "np-list"
+    if "numpyflat" in inner_type:
+        return "numpyflat"
+    if "pd-multiindex" in inner_type:
+        return "pd-multiindex"
+    if "df-list" in inner_type:
+        return "df-list"
+    if "pd-wide" in inner_type:
+        return "pd-multiindex"
+    if "nested_univ" in inner_type:
+        return "nested_univ"
+    raise ValueError(
+        f"Error, no valid inner types in {inner_type} must be one of "
+        f"{COLLECTIONS_DATA_TYPES}"
+    )
+
+
+def resolve_unequal_length_inner_type(inner_type):
+    """Hierarchy of preference for internal supported types for unequal length."""
+    if "np-list" in inner_type:
+        return "np-list"
+    if "df-list" in inner_type:
+        return "df-list"
+    if "pd-multiindex" in inner_type:
+        return "pd-multiindex"
+    if "nested_univ" in inner_type:
+        return "nested_univ"
+    raise ValueError(
+        f"Error, no valid inner types for unequal series in {inner_type} "
+        f"must be one of np-list, df-list, pd-multiindex or nested_univ"
+    )
+
+
+def convertX(X, output_type):
+    """Convert from one of collections compatible data structure to another.
+
+    See aeon.utils.validation.collections.COLLECTIONS_DATA_TYPE for the list.
+
+    Parameters
+    ----------
+    X : data structure.
+    output_type : string, one of COLLECTIONS_DATA_TYPES
+
+    Returns
+    -------
+    Data structure conforming to "to_type"
+
+    Raises
+    ------
+    ValueError if
+        X pd.ndarray but wrong dimension
+        X is list but not of np.ndarray or p.DataFrame.
+        X is a pd.DataFrame of non float primitives.
+
+    Example
+    -------
+    >>> from aeon.utils.validation.collection import convertX, get_type
+    >>> X=convertX(np.zeros(shape=(10, 3, 20)), "np-list")
+    >>> type(X)
+    <class 'list'>
+    >>> get_type(X)
+    'np-list'
+    """
+    # Temporarily retain the current conversion
+    from aeon.datatypes import convert_to
+
+    input_type = get_type(X)
+    if output_type not in COLLECTIONS_DATA_TYPES:
+        raise ValueError(
+            f"Error with convertX, trying to convert to {output_type} "
+            f"which is not a valid collection type: {COLLECTIONS_DATA_TYPES}"
+        )
+    # Temporary fix because numpyflat does not work with old conversions
+    if input_type == "numpyflat":
+        X = X.reshape(X.shape[0], 1, X.shape[1])
+    else:
+        X = convert_to(
+            X,
+            to_type=output_type,
+            as_scitype="Panel",
+        )
+    return X
+
+
+def get_n_cases(X):
+    """Return the number of cases in a collectiom.
+
+    Handle the single exception of multi index DataFrame.
+
+    Parameters
+    ----------
+    X : valid collection data structure
+
+    Returns
+    -------
+    int : number of cases
+    """
+    if isinstance(X, pd.DataFrame) and isinstance(X.index, pd.MultiIndex):
+        return len(X.index.get_level_values(0).unique())
+    return len(X)
+
+
+def get_type(X):
+    """Get the string identifier associated with different data structures.
+
+    Parameters
+    ----------
+    X : data structure.
+
+    Returns
+    -------
+    input_type : string, one of COLLECTIONS_DATA_TYPES
+
+    Raises
+    ------
+    ValueError if
+        X pd.ndarray but wrong dimension
+        X is list but not of np.ndarray or p.DataFrame.
+        X is a pd.DataFrame of non float primitives.
+
+    Example
+    -------
+    >>> from aeon.utils.validation.collection import get_type
+    >>> get_type( np.zeros(shape=(10, 3, 20)))
+    'numpy3D'
+    """
+    if isinstance(X, np.ndarray):  # “numpy3D” or numpyflat
+        if X.ndim == 3:
+            return "numpy3D"
+        elif X.ndim == 2:
+            return "numpyflat"
+        else:
+            raise ValueError("ERROR np.ndarray must be either 2D or 3D")
+    elif isinstance(X, list):  # np-list or df-list
+        if isinstance(X[0], np.ndarray):  # if one a numpy they must all be 2D numpy
+            for a in X:
+                if not (isinstance(a, np.ndarray) and a.ndim == 2):
+                    raise ValueError("ERROR np-list np.ndarray must be either 2D or 3D")
+            return "np-list"
+        elif isinstance(X[0], pd.DataFrame):
+            for a in X:
+                if not isinstance(a, pd.DataFrame):
+                    raise ValueError("ERROR df-list must only contain pd.DataFrame")
+            return "df-list"
+    elif isinstance(X, pd.DataFrame):  # Nested univariate, hierachical or pd-wide
+        if _is_nested_univ_dataframe(X):
+            return "nested_univ"
+        if isinstance(X.index, pd.MultiIndex):
+            return "pd-multiindex"
+        elif _is_pd_wide(X):
+            return "pd-wide"
+        raise ValueError(
+            "ERROR unknown pd.DataFrame, contains non float values, "
+            "not hierarchical nor is it nested pd.Series"
+        )
+    #    if isinstance(X, dask.dataframe.core.DataFrame):
+    #        return "dask_panel"
+    raise ValueError(f"ERROR unknown input type {type(X)}")
+
+
+def is_equal_length(X):
+    """Test if X contains equal length time series.
+
+    Assumes input_type is a valid type (COLLECTIONS_DATA_TYPES).
+
+    Parameters
+    ----------
+    X : data structure.
+
+    Returns
+    -------
+    boolean: True if all series in X are equal length, False otherwise
+
+    Raises
+    ------
+    ValueError if input_type equals "dask_panel" or not in COLLECTIONS_DATA_TYPES.
+
+    Example
+    -------
+    >>> is_equal_length( np.zeros(shape=(10, 3, 20)))
+    True
+    """
+    return equal_length(X, get_type(X))
+
+
+def equal_length(X, input_type):
+    """Test if X contains equal length time series.
+
+    Assumes input_type is a valid type (COLLECTIONS_DATA_TYPES).
+
+    Parameters
+    ----------
+    X : data structure.
+    input_type : string, one of COLLECTIONS_DATA_TYPES
+
+    Returns
+    -------
+    boolean: True if all series in X are equal length, False otherwise
+
+    Raises
+    ------
+    ValueError if input_type equals "dask_panel" or not in COLLECTIONS_DATA_TYPES.
+
+    Example
+    -------
+    >>> equal_length( np.zeros(shape=(10, 3, 20)), "numpy3D")
+    True
+    """
+    always_equal = {"numpy3D", "numpyflat", "pd-wide"}
+    if input_type in always_equal:
+        return True
+    if input_type == "np-list":
+        first = X[0].shape[1]
+        for i in range(1, len(X)):
+            if X[i].shape[1] != first:
+                return False
+        return True
+    if input_type == "df-list":
+        first = X[0].shape[1]
+        for i in range(1, len(X)):
+            if X[i].shape[1] != first:
+                return False
+        return True
+    if input_type == "nested_univ":  # Nested univariate or hierachical
+        return _nested_univ_is_equal(X)
+    if input_type == "pd-multiindex":
+        # TEMPORARY: WORK OUT HOW TO TEST THESE
+        return True
+    #        raise ValueError(" Multi index not supported here ")
+    if input_type == "dask_panel":
+        raise ValueError(" DASK panel not supported here ")
+    raise ValueError(f" unknown input type {input_type}")
+    return False
+
+
+def has_missing(X):
+    """Check if X has missing values.
+
+    Parameters
+    ----------
+    X : data structure.
+    input_type : string, one of COLLECTIONS_DATA_TYPES
+
+    Returns
+    -------
+    boolean: True if there are any missing values, False otherwise
+
+    Raises
+    ------
+    ValueError if input_type equals "dask_panel" or not in COLLECTIONS_DATA_TYPES.
+
+    Example
+    -------
+    >>> from aeon.utils.validation.collection import has_missing
+    >>> has_missing( np.zeros(shape=(10, 3, 20)))
+    False
+    """
+    type = get_type(X)
+    if type == "numpy3D" or type == "numpyflat":
+        return np.any(np.isnan(np.min(X)))
+    if type == "np-list":
+        for x in X:
+            if np.any(np.isnan(np.min(x))):
+                return True
+        return False
+    if type == "df-list":
+        for x in X:
+            if x.isnull().any().any():
+                return True
+        return False
+    if type == "pd-wide":
+        return X.isnull().any().any()
+    if type == "nested_univ":
+        for i in range(len(X)):
+            for j in range(X.shape[1]):
+                if X.iloc[i, j].hasnans:
+                    return True
+        return False
+    if type == "pd-multiindex":
+        if X.isna().values.any():
+            return True
+        return False
+
+
+def is_univariate(X):
+    """Check if X is multivariate."""
+    type = get_type(X)
+    if type == "numpyflat" or type == "pd-wide":
+        return True
+    if type == "numpy3D" or type == "nested_univ":
+        return X.shape[1] == 1
+    if type == "df-list" or type == "np-list":
+        return X[0].shape[0] == 1
+    if type == "pd-multiindex":
+        return X.columns.shape[0] == 1
+
+
+def _nested_univ_is_equal(X):
+    """Check whether series are unequal length."""
+    length = X.iloc[0, 0].size
+    for series in X.iloc[0]:
+        if series.size != length:
+            return False
+    return True
+
+
+def _is_nested_univ_dataframe(X):
+    """Check if X is nested dataframe."""
+    # Otherwise check all entries are pd.Series
+    if not isinstance(X, pd.DataFrame):
+        return False
+    for _, series in X.items():
+        for cell in series:
+            if not isinstance(cell, pd.Series):
+                return False
+    return True
+
+
+def _is_pd_wide(X):
+    """Check whether the input nested DataFrame is "pd-wide" type."""
+    # only test is if all values are float. This from chatgpt seems stupid
+    if isinstance(X, pd.DataFrame) and not isinstance(X.index, pd.MultiIndex):
+        if _is_nested_univ_dataframe(X):
+            return False
+        float_cols = X.select_dtypes(include=[float]).columns
+        for col in float_cols:
+            if not np.issubdtype(X[col].dtype, np.floating):
+                return False
+        return True
+    return False
diff --git a/docs/changelog.md b/docs/changelog.md
index 297373a0..07966167 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -13,6 +13,7 @@ For upcoming changes and next releases, see our
 [milestones](https://github.com/aeon-toolkit/aeon/milestones). For our long-term plan,
 see our [roadmap](roadmap).
 
+- [Version 0.4.0](changelogs/v0.4.md)
 - [Version 0.3.0](changelogs/v0.3.md)
 - [Version 0.2.0](changelogs/v0.2.md)
 - [Version 0.1.0](changelogs/v0.1.md)
diff --git a/docs/changelogs/v0.4.md b/docs/changelogs/v0.4.md
new file mode 100644
index 00000000..718ea4f2
--- /dev/null
+++ b/docs/changelogs/v0.4.md
@@ -0,0 +1,234 @@
+# v0.4.0
+
+July 2023
+
+Following this release the deprecation policy remains suspended. Future releases may have breaking changes, so it may be wise to set an upper bound on the package version.
+
+## Highlights
+
+- Data downloading functions for popular classification, regression and forecasting archives are now available
+- Implementations for the RSTSF interval-based and the RDST shapelet-based classifiers have been added to the classification module
+- Time series adaptations for the PAM, CLARA, CLARANS clustering algorithms have been added to the clustering module
+- The interval-based forests in the classification module have been reworked to use a new base class, including speed-ups and also allowing the implementation of regression versions (RISERegressor, CIFRegressor and DrCIFRegressor)
+- ResNet and FCN deep learning regressors are now available
+- Large portions of the website documentation have been overhauled, including the examples page and introduction notebooks for data types and data loading
+
+## Annotation
+
+### Documentation
+
+- [DOC] Restructure annotation and transformation notebooks ({pr}`531`) {user}`TonyBagnall`
+
+## Benchmarking
+
+### Enhancements
+
+- [ENH] Result loader from tsc.com ({pr}`452`) {user}`TonyBagnall`
+- [ENH] Data downloaders for classification/regression and forecasting ({pr}`478`) {user}`TonyBagnall`
+- [ENH] Full classifier results ({pr}`571`) {user}`TonyBagnall`
+
+## Classification
+
+### Bug Fixes
+
+- [BUG] HIVECOTEV1 cross-validation bugfix ({pr}`518`) {user}`MatthewMiddlehurst`
+- [BUG] RotationForest bugfix and doc improvements ({pr}`517`) {user}`MatthewMiddlehurst`
+- [ENH] Updates to `Catch22` and fixes to collection converters ({pr}`509`) {user}`MatthewMiddlehurst`
+- [BUG] Add `non-deterministic` tag to RDSTClassifier ({pr}`577`) {user}`MatthewMiddlehurst`
+
+### Documentation
+
+- [BUG] RotationForest bugfix and doc improvements ({pr}`517`) {user}`MatthewMiddlehurst`
+- [DOC] Add correct images for classification ({pr}`514`) {user}`TonyBagnall`
+
+### Enhancements
+
+- [ENH] Updates to `Catch22` and fixes to collection converters ({pr}`509`) {user}`MatthewMiddlehurst`
+- [ENH] Update interval extraction transformers and pipelines ({pr}`511`) {user}`MatthewMiddlehurst`
+- [ENH] Implement RSTSF classifier ({pr}`544`) {user}`MatthewMiddlehurst`
+- [ENH] Add deep networks/classifiers/regressors to init ({pr}`554`) {user}`TonyBagnall`
+- [ENH] Implement configurable interval base class with classifier and regressor ({pr}`545`) {user}`MatthewMiddlehurst`
+- [ENH] RDSTClassifier and transformer tests ({pr}`529`) {user}`baraline`
+- [ENH] Replace TSF estimators with base class version and remove `series_as_features` package ({pr}`575`) {user}`MatthewMiddlehurst`
+- [ENH] Full classifier results ({pr}`571`) {user}`TonyBagnall`
+- [ENH] Move CIF and DrCIF to use the interval base class ({pr}`573`) {user}`MatthewMiddlehurst`
+- [ENH] Updates the RISE classifier to use new interval base class ({pr}`584`) {user}`MatthewMiddlehurst`
+- [ENH] Updates the STSF classifier to use new interval base class ({pr}`585`) {user}`MatthewMiddlehurst`
+
+### Maintenance
+
+- [MNT] Avoid CI fail on deep test by generating at least 2 classes in random data ({pr}`485`) {user}`hadifawaz1999`
+
+### Refactored
+
+- [REF] change single class use case ({pr}`497`) {user}`TonyBagnall`
+
+## Clustering
+
+### Bug Fixes
+
+- [BUG] DBA numba error ({pr}`541`) {user}`chrisholder`
+
+### Documentation
+
+- [DOC] clustering and regression notebooks ({pr}`516`) {user}`TonyBagnall`
+
+### Enhancements
+
+- [ENH] Kmedoids refactor and speed up ({pr}`498`) {user}`chrisholder`
+- [ENH] Pam kmedoids clusterer ({pr}`500`) {user}`chrisholder`
+- [ENH] CLARA clusterer ({pr}`501`) {user}`chrisholder`
+- [ENH] CLARANS clusterer ({pr}`503`) {user}`chrisholder`
+
+### Refactored
+
+- [ENH] Kmedoids refactor and speed up ({pr}`498`) {user}`chrisholder`
+- [ENH] Pam kmedoids clusterer ({pr}`500`) {user}`chrisholder`
+
+## Distances
+
+### Bug Fixes
+
+- [BUG] RDST bugfixes ({pr}`483`) {user}`baraline`
+- [BUG] DBA numba error ({pr}`541`) {user}`chrisholder`
+
+### Documentation
+
+- [DOC] Update distances notebook ({pr}`491`) {user}`TonyBagnall`
+- [DOC] Tidy up the datasets and distances examples ({pr}`513`) {user}`TonyBagnall`
+- [DOCS] Add docs for function transformer with distances, and tests for distances with sklearn ({pr}`581`) {user}`TonyBagnall`
+- [DOC] Distance function template ({pr}`583`) {user}`TonyBagnall`
+
+## Forecasting
+
+### Documentation
+
+- [DOC] Move forecasting notebooks ({pr}`536`) {user}`TonyBagnall`
+
+### Maintenance
+
+- [MNT] BATS parameter test ({pr}`568`) {user}`TonyBagnall`
+- [MNT] Add `statsforecast` back to all_extras ({pr}`552`) {user}`MatthewMiddlehurst`
+
+## Regression
+
+### Bug Fixes
+
+- [BUG] RotationForest bugfix and doc improvements ({pr}`517`) {user}`MatthewMiddlehurst`
+- [ENH] Updates to `Catch22` and fixes to collection converters ({pr}`509`) {user}`MatthewMiddlehurst`
+- [BUG] Regression base class score function ({pr}`549`) {user}`TonyBagnall`
+
+### Documentation
+
+- [BUG] RotationForest bugfix and doc improvements ({pr}`517`) {user}`MatthewMiddlehurst`
+- [DOC] clustering and regression notebooks ({pr}`516`) {user}`TonyBagnall`
+
+### Enhancements
+
+- [ENH] Updates to `Catch22` and fixes to collection converters ({pr}`509`) {user}`MatthewMiddlehurst`
+- [ENH] Update interval extraction transformers and pipelines ({pr}`511`) {user}`MatthewMiddlehurst`
+- [ENH] Add deep networks/classifiers/regressors to init ({pr}`554`) {user}`TonyBagnall`
+- [ENH] Add ResNet deep learning regressor ({pr}`558`) {user}`hadifawaz1999`
+- [ENH] Add FCN deep learning regressor ({pr}`560`) {user}`hadifawaz1999`
+- [ENH] Implement configurable interval base class with classifier and regressor ({pr}`545`) {user}`MatthewMiddlehurst`
+- [MNT-ENH] Fixed stuff in CNN deep learning regressor ({pr}`559`) {user}`hadifawaz1999`
+- [ENH] Replace TSF estimators with base class version and remove `series_as_features` package ({pr}`575`) {user}`MatthewMiddlehurst`
+- [ENH] Move CIF and DrCIF to use the interval base class ({pr}`573`) {user}`MatthewMiddlehurst`
+- [ENH] Updates the RISE classifier to use new interval base class ({pr}`584`) {user}`MatthewMiddlehurst`
+
+### Maintenance
+
+- [MNT] Avoid CI fail on deep test by generating at least 2 classes in random data ({pr}`485`) {user}`hadifawaz1999`
+- [MNT-ENH] Fixed stuff in CNN deep learning regressor ({pr}`559`) {user}`hadifawaz1999`
+
+### Other
+
+- [ENH] Removes the delegate structure from regression ({pr}`580`) {user}`TonyBagnall`
+
+## Transformations
+
+### Bug Fixes
+
+- [BUG] RDST bugfixes ({pr}`483`) {user}`baraline`
+- [BUG] reduce test size for MultiRocket ({pr}`520`) {user}`TonyBagnall`
+- [ENH] Updates to `Catch22` and fixes to collection converters ({pr}`509`) {user}`MatthewMiddlehurst`
+
+### Documentation
+
+- [DOC] Restructure annotation and transformation notebooks ({pr}`531`) {user}`TonyBagnall`
+
+### Enhancements
+
+- [ENH] Collection transformer wrapper and basic base class testing ({pr}`467`) {user}`MatthewMiddlehurst`
+- [ENH] Updates to `Catch22` and fixes to collection converters ({pr}`509`) {user}`MatthewMiddlehurst`
+- [ENH] Add some simple collection transformers ({pr}`508`) {user}`MatthewMiddlehurst`
+- [ENH] Update interval extraction transformers and pipelines ({pr}`511`) {user}`MatthewMiddlehurst`
+- [ENH] RDSTClassifier and transformer tests ({pr}`529`) {user}`baraline`
+
+### Maintenance
+
+- [MNT] Add test for transformer numpy input ({pr}`555`) {user}`MatthewMiddlehurst`
+
+### Refactored
+
+- [ENH] Refactoring PAA ({pr}`489`) {user}`hadifawaz1999`
+
+## Other
+
+### Bug Fixes
+
+- [MNT] tidy up resnet and fix failures ({pr}`486`) {user}`hadifawaz1999`
+- [BUG] Update gunpoint arff ({pr}`496`) {user}`TonyBagnall`
+- [BUG] Doctest formatting bug 546 ({pr}`548`) {user}`TonyBagnall`
+- [BUG] Scenarios string fix ({pr}`594`) {user}`TonyBagnall`
+
+### Documentation
+
+- [DOC] Change README logo link ({pr}`482`) {user}`MatthewMiddlehurst`
+- [DOC] InceptionTime docstrings ({pr}`494`) {user}`TonyBagnall`
+- [DOCS] Update the API ({pr}`495`) {user}`TonyBagnall`
+- [DOC] Add transformations to getting started page and add opengraph sphinx package ({pr}`522`) {user}`MatthewMiddlehurst`
+- [DOC] Added Deep Learning notebook and images ({pr}`493`) {user}`hadifawaz1999`
+- [DOC] Remove the tutorials and users webpages ({pr}`525`) {user}`MatthewMiddlehurst`
+- [DOC] Remove stray user_guide dir and _mission webpage ({pr}`526`) {user}`MatthewMiddlehurst`
+- [DOC] fixed path of image ({pr}`535`) {user}`hadifawaz1999`
+- [BUG] Doctest formatting bug 546 ({pr}`548`) {user}`TonyBagnall`
+- [DOC] Rewrite current user installation guide and move dev installation guide to its own file ({pr}`524`) {user}`MatthewMiddlehurst`
+- [DOC] Remove mentoring and estimator overview webpages ({pr}`527`) {user}`MatthewMiddlehurst`
+- [DOC] Auto generate `about` page teams ({pr}`532`) {user}`MatthewMiddlehurst`
+- [DOC] Utils notebook directory ({pr}`561`) {user}`TonyBagnall`
+- [DOCS] attempt to minimise docstring warnings ({pr}`572`) {user}`TonyBagnall`
+- [DOC] Datatypes docs ({pr}`538`) {user}`TonyBagnall`
+- [DOC] purge AA_datatypes_and_datasets.ipynb ({pr}`540`) {user}`TonyBagnall`
+- [DOC] Tidy up the docs ({pr}`591`) {user}`TonyBagnall`
+- [DOC] Full examples debug ({pr}`598`) {user}`TonyBagnall`
+
+### Enhancements
+
+- [ENH] Add function to make example 3D numpy problems ({pr}`510`) {user}`MatthewMiddlehurst`
+- [ENH] Tidy up test exclusions ({pr}`473`) {user}`TonyBagnall`
+
+### Maintenance
+
+- [MNT] Loosen upper bound for `esig` and update Python version for notebook tests. ({pr}`487`) {user}`MatthewMiddlehurst`
+- [MNT] Revert `holidays` and `dash` dependency changes ({pr}`445`) {user}`MatthewMiddlehurst`
+- [MNT] Add `non-deterministic` and `cant-pickle` tag for estimators ({pr}`371`) {user}`hadifawaz1999`
+- [MNT] Replace `handles-missing-data` tag with `capability:missing_values` ({pr}`505`) {user}`MatthewMiddlehurst`
+- [MNT] Set upper bound for `tslearn` to fix CI ({pr}`534`) {user}`MatthewMiddlehurst`
+- [MNT] Change `esig` dependency version requirements to fix CI ({pr}`539`) {user}`MatthewMiddlehurst`
+- [MNT] Setting more upper bounds to fix CI ({pr}`542`) {user}`MatthewMiddlehurst`
+- [MNT] Exclude results loading notebook ({pr}`562`) {user}`MatthewMiddlehurst`
+- [MNT] estimator checks deprecation ({pr}`556`) {user}`TonyBagnall`
+- [MNT] Add periodic test workflow and remove Makefile ({pr}`553`) {user}`MatthewMiddlehurst`
+- [MNT] Update codeowners ({pr}`547`) {user}`TonyBagnall`
+- [MNT] DL OOM test failure fix and remove Makefile ({pr}`593`) {user}`MatthewMiddlehurst`
+
+## Contributors
+
+The following have contributed to this release through a collective 79 GitHub Pull Requests:
+
+{user}`baraline`,
+{user}`chrisholder`,
+{user}`hadifawaz1999`,
+{user}`MatthewMiddlehurst`,
+{user}`TonyBagnall`
diff --git a/docs/developer_guide/release.md b/docs/developer_guide/release.md
new file mode 100644
index 00000000..55d9ce35
--- /dev/null
+++ b/docs/developer_guide/release.md
@@ -0,0 +1,63 @@
+# Releases
+
+This page is to help core developers in releasing a new version of `aeon`.
+
+Core developers making a release should have write access to the repository.
+
+`aeon` aims for a release every one or two months currently, based on the amount of
+new content available for the release.
+
+## Preparing the release version
+
+The release process is as follows, on high-level:
+
+1. __Ensure deprecation actions are carried out.__
+  Deprecation actions for a version should be marked by "version number" annotated
+  comments in the code. E.g., for the release 0.10.0, search for the string 0.10.0 in
+  the code and carry out described deprecation actions. Collect list of deprecation
+  actions, as they should go in the release notes.
+
+2. __Create a "release" pull request.__
+  Create a branch from main and PR named after the release version. This should make
+  changes to the version numbers (root `__init__.py`, `README.md` and `pyproject.toml`)
+  and have complete release notes in the changelog webpage.
+
+3. __Merge the "release" pull request.__
+  This PR should ideally be the final PR made before the release with the exception of
+  any necessary troubleshooting PRs. The PR and release notes should optimally be
+  reviewed by the core developers, then merged once tests pass.
+
+4. __Create the GitHub release.__
+  This release should create a new tag following the syntax v[MAJOR].[MINOR].[PATCH],
+  e.g., the string `v0.10.0` for version 0.10.0. The release name should similarly be
+  `aeon v0.10.0`.  The GitHub release notes should contain only "hightlights",
+  "new contributors" and "all contributors" sections, and otherwise link to the release
+  notes in the changelog, following the pattern of current GitHub release notes. The
+  full GitHub commit log between releases can also be included.
+
+## ``pypi`` release and release validation
+
+Creation of the GitHub release trigger the `pypi` release workflow.
+
+5. __Wait for the ``pypi`` release CI/CD to finish.__
+  If tests fail due to sporadic unrelated failure, restart. If tests fail genuinely,
+  something went wrong in the above steps, investigate, fix, and repeat.
+
+6. __Release workflow completion tasks.__
+  Once the release workflow has passed, check `aeon` version on `pypi`, this should be
+  the new version. A validatory installation of `aeon` in a new Python environment
+  should be carried out according to the installation instructions. If the installation
+  does not succeed or wheels have not been uploaded, action to diagnose and remedy must
+  be taken.
+
+## Release notes
+
+Release notes can be generated using the `build_tools/changelog.py` script, and should
+be placed at the top of the relevant [MINOR] versions webpage. e.g.,
+`docs/chanelogs/v0.10.md` for version 0.10.0. Generally, release notes should follow the
+general pattern of previous release notes, with sections:
+
+- Highlights
+- Dependency changes, if any
+- Deprecations/removals, if any.
+- Auto generated PR and contributions sections.
diff --git a/docs/developer_guide/release.rst b/docs/developer_guide/release.rst
deleted file mode 100644
index dfdcdc29..00000000
--- a/docs/developer_guide/release.rst
+++ /dev/null
@@ -1,133 +0,0 @@
-.. _release:
-
-Releases
-========
-
-This section is for core developers releasing a new version of ``aeon``.
-
-Core developers making a release should be release managers (appointed by CC) and have write access to the repository.
-
-
-Summary of release process
---------------------------
-
-The release process includes, in sequence:
-
-* release cycle management
-* release version preparation
-* the release on ``pypi``
-* the release on ``conda``
-* troubeleshooting and accident remedial, if applicable
-
-Details follow below.
-
-Release cycle process
-^^^^^^^^^^^^^^^^^^^^^
-
-``aeon`` aims for a release every two weeks.
-
-The release cycle process is as follows:
-
-1. 1 week before release date, update the release board.
-2. for major releases or substantial features, optionally extend the release cycle
-3. feature freeze 1 day before release date. Only release managers should merge at this point.
-  The feature freeze should be announced on the core dev channel (on slack) 1 week before, and 1 day before it comes into action.
-  Any delays and extensions to the feature freeze should also be announced.
-4. if "must have" are not merged by planned release date: either delay release date and extend freeze period, or deprioritize.
-
-Preparing the release version
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-The release process is as follows, on high-level:
-
-1. ensure deprecation actions are carried out.
-  Deprecation actions for a version should be marked by "version number" annotated comments in the code.
-  E.g., for the release 0.12.0, search for the string 0.12.0 in the code and carry out described deprecation actions.
-  Collect list of deprecation actions in an issue, as they will have to go in the release notes.
-  Deprecation actions should be merged only by release managers.
-
-2. create a "release" pull request (ideally from a branch following the naming pattern ``release/v0.x.y``). This should make changes to the version numbers and have complete release notes.
-  See below for version numbers and release notes.
-
-3. The PR and release notes should optimally be reviewed by the other core developers, then merged once tests pass.
-
-4. create a GitHub draft release with a new tag following the syntax v[MAJOR].[MINOR].[PATCH],
-   e.g., the string ``v0.12.0`` for version 0.12.0.
-   The GitHub release notes should contain only "new contributors" and "all contributors" section,
-   and otherwise link to the release notes in the changelog,
-   following the pattern of current GitHub release notes.
-
-``pypi`` release and release validation
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-5. publish the GitHub draft release. Creation of the new tag will trigger the ``pypi`` release CI/CD.
-
-6. wait for the ``pypi`` release CI/CD to finish. If tests fail due to sporadic unrelated failure, restart.
-  If tests fail genuinely, something went wrong in the above steps, investigate, fix, and repeat.
-  Common possibilities are core devs not respecting the feature freeze period,
-  new releases of dependencies that happen in the ca one day period between release PR and release.
-
-7. once release CI/CD has passed, check ``aeon`` version on ``pypi``, this should be the new version.
-  It should be checked that all wheels have been uploaded, `here <https://pypi.org/simple/aeon/>`__.
-  A validatory install of ``aeon`` in a new ``python`` environment should be carried out (one arbitrary version/OS),
-  according to the install instructions.
-  If the install does not succeed or wheels have not been uploaded, urgent action to diagnose and remedy must be taken.
-  All core developers should be urgently informed of such a situation through mail-all in the core developer channel on slack.
-  In the most common case, the install instructions need to be updated.
-  If wheel upload has failed, the tag in 5. needs to be deleted and recreated.
-  The tag can be deleted using the ``git`` command ``git push --delete origin tagname`` from a local repo.
-
-``conda`` release and release validation
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-8. If the release on ``pypi`` has succeeded, there should be an automated release PR created
-  against the aeon conda-forge repo: https://github.com/conda-forge/aeon-feedstock.
-
-  .. note:: Manual creation of release pull request
-     In cases where the release PR is not created automatically it can be created and submitted manually. For general
-     guidelines related to maintaining conda feedstcok packages see `conda-forge package<https://conda-forge.org/docs/maintainer/updating_pkgs.html>`_.
-
-     After forking and cloning the repo, edit the ``meta.yml`` file and
-
-     - increment the version in the line that contains ``{% set version = "0.X.Y" %}``
-     - paste the sha256 sum of the source archive from github in the ``source/sha256`` section
-     - submit PR and ask for review
-
-9. The conda release PR need to be reviewed and in dependencies should be checked against any changes in the main aeon repo.
-  In case the dependencies (or python version support) have changes, the ``meta.yml`` file in the conda recipe need to updated to reflect those changes.
-
-10. Once reviewed, the conda release PR should merged, and it will automatically trigger a release of the conda package.
-
-11. After 1h, it should be checked whether the package has been released on conda.
-  Once the package is available on ``conda``, a test install should be carried out to validate the release.
-  Should either of these fail, the same urgent action as in 7. should be taken.
-
-
-Version number locations
-------------------------
-
-Version numbers need to be updated in:
-
-* root ``__init__.py``
-* ``README.md``
-* ``pyproject.toml``
-
-
-Release notes
--------------
-
-Release notes can be generated using the ``build_tools.changelog.py`` script, and should be placed at the top of the ``changelog.rst``.
-Generally, release notes should follow the general pattern of previous release notes, with sections:
-
-* highlights
-* dependency changes, if any
-* deprecations and removals, if any.
-  In PATCH versions, there are no deprecation actions, but there can be new deprecations.
-  Deprecation action usually happen with the MINOR release cycle.
-* core interface changes, if any. This means, changes to the base class interfaces.
-  Only MINOR or MAJOR releases should have core interface changes that are not downwards compatible.
-* enhancements, by module/area
-* documentation
-* maintenance
-* bugfixes
-* all contributor credits
diff --git a/docs/index.md b/docs/index.md
index 28bc6462..0573cdc3 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -2,17 +2,13 @@
 hide-toc: true
 ---
 
-```{image} images/logo/aeon-logo-blue-compact.png
-:align: center
-:alt: aeon logo
-:width: 80%
-```
-# Welcome to the documentation for **aeon**
+# <p style="text-align: center;">Welcome to **aeon**</p>
 
 <br>
+<p>
+aeon is a scikit learn compatible toolkit for time series tasks such as
+  forecasting, classification and clustering.</p>
 
-- scikit learn compatible Framework for time series tasks such as forecasting and
-classification.
 - Provides a broad library of time series algorithms, including the latest advances.
 - Efficient implementation of time series algorithms using numba.
 - Interfaces with other time series packages to provide a single framework for algorithm
@@ -47,8 +43,6 @@ Forecasting
 :text-align: center
 
 
-
-
 Get started with time series classification.
 
 +++
@@ -140,73 +134,74 @@ Distances
 :::
 
 :::{grid-item-card}
-:img-top: examples/annotation/img/clasp.png
+:img-top: /examples/networks/img/Inception.png
 :text-align: center
 
-
-Annotation
+Deep learning networks.
 
 +++
 
-```{button-ref} /examples/annotation/annotation.ipynb
+```{button-ref} /examples/networks/deep_learning.ipynb
 :color: primary
 :click-parent:
 :expand:
 
-Annotation
+Networks
 ```
 
 :::
 
 :::{grid-item-card}
+:img-top: examples/annotation/img/clasp.png
 :text-align: center
 
 
-Data structures and containers used in aeon
+Annotation
 
 +++
 
-```{button-ref} /examples/datasets/datasets.ipynb
+```{button-ref} /examples/annotation/annotation.ipynb
 :color: primary
 :click-parent:
 :expand:
 
-Data
+Annotation
 ```
 
 :::
 
 :::{grid-item-card}
-:img-top: /examples/benchmarking/img/benchmarking.png
+:img-top: <img src="/examples/datasets/img/data.png" style="max-width:100px; margin:auto;">
 :text-align: center
 
-How to benchmark algorithms.
+Data structures and containers used in aeon
 
 +++
 
-```{button-ref} /examples/benchmarking/benchmarking.ipynb
+```{button-ref} /examples/datasets/datasets.ipynb
 :color: primary
 :click-parent:
 :expand:
 
-Benchmarking
+Data
 ```
+
 :::
 
 :::{grid-item-card}
-:img-top: /examples/networks/img/Inception.png
+:img-top: /examples/benchmarking/img/benchmarking.png
 :text-align: center
 
-Deep learning networks.
+How to benchmark algorithms.
 
 +++
 
-```{button-ref} /examples/networks/deep_learning.ipynb
+```{button-ref} /examples/benchmarking/benchmarking.ipynb
 :color: primary
 :click-parent:
 :expand:
 
-Networks
+Benchmarking
 ```
 
 :::
diff --git a/examples/classification/classification.ipynb b/examples/classification/classification.ipynb
index e7369841..bf72000d 100644
--- a/examples/classification/classification.ipynb
+++ b/examples/classification/classification.ipynb
@@ -49,8 +49,9 @@
     "sensor is a univariate series, but a motion trace of from a smart watch would be\n",
     "multivariate, with at least three dimensions (x,y,z co-ordinates). The image above is\n",
     " a univariate problem: each series has its own label. The dimension of the time\n",
-    " series instance is also often called the channel. We recommend storing time series in 3D numpy array of shape (instance,\n",
-    " dimension, time point) and where possible our single problem loaders will return a\n",
+    " series instance is also often called the channel. We recommend storing time series\n",
+    " in 3D numpy array of shape `(n_instances, n_channels, n_timepoints)` and\n",
+    " where  possible our single problem loaders will return a\n",
     " 3D numpy. Unequal length classification problems are stored in a list of 2D numpy\n",
     " arrays."
    ],
@@ -97,7 +98,7 @@
     " with shape (instance, time point), currently some transformers do not work correctly\n",
     "  with 2D arrays. If your series are unequal length, have missing values or are\n",
     "  sampled at irregular time intervals, you should read the note book\n",
-    "  datatypes_and_datasets (link).\n",
+    "  on [data preprocessing](../utils/preprocessing.ipynb).\n",
     "\n",
     "The UCR/UEA [TSC dataset archive](https://timeseriesclassification.com/) contains a\n",
     "large number of example TSC problems that have been used thousands of times in the\n",
@@ -105,15 +106,13 @@
     "influence what data structure we use to store them in memory.\n",
     "\n",
     "Most datasets in the archive contain time series all the same length. For example,\n",
-    "the [ArrowHead dataset](https://timeseriesclassification.com/description\n",
-    ".php?Dataset=ArrowHead) we have just loaded consists of outlines of the images of\n",
+    "the [ArrowHead dataset](https://timeseriesclassification.com/description.php?Dataset=ArrowHead) we have just loaded consists of outlines of the images of\n",
     "arrow heads. The classification of projectile points is an important topic in anthropology.\n",
     "\n",
     "<img src=\"../img/arrow-heads.png\" width=\"600\" alt=\"arrow heads\">\n",
     "\n",
     "The shapes of the projectile points are converted into a sequence using the\n",
-    "angle-based method as described in this [blog post](https://izbicki\n",
-    ".me/blog/converting-images-into-time-series-for-data-mining.html) about converting images into time series for data mining.\n",
+    "angle-based method as described in this [blog post](https://izbicki.me/blog/converting-images-into-time-series-for-data-mining.html) about converting images into time series for data mining.\n",
     "\n",
     "<img src=\"img/from-shapes-to-time-series.png\" width=\"600\" alt=\"from shapes to time series\">\n",
     "\n",
@@ -204,9 +203,11 @@
     "\n",
     "To apply `sklearn` classifiers directly, the data needs to be reshaped into a 2D\n",
     "numpy array. We also offer the ability to load univariate TSC problems directly in 2D\n",
-    " arrays. Please note that currently, due to poor legacy design decisions, Transformers\n",
+    " arrays. Please note that currently,\n",
+    " some Transformers treat a single multivariate time series in a numpy array as shape\n",
+    " `(n_timepoints, n_channels)` rather than `(n_channels,n_timepoints)`\n",
     "  do not work correctly  with 2D numpy classification problems, so we recommend using\n",
-    "   3D numpy."
+    "   3D numpyof shape `(n_channels, 1, n_timepoints)` for univariate series."
    ]
   },
   {
@@ -248,14 +249,14 @@
     "`aeon` contains the state of the art in time series classifiers in the package\n",
     "`classification`. These are grouped based on the data representation used to find\n",
     "discriminatory features. We provide a separate notebook for each of type:\n",
-    "[convolution based](https://www.aeon-toolkit.org/en/latest/examples/classification/convolution_based.html), [deep learning](https://www.aeon-toolkit.org/en/latest/examples/classification/deep_learning.html), [distance based](https://www.aeon-toolkit.org/en/latest/examples/classification/distance_based.html), [dictionary based](https://www.aeon-toolkit.org/en/latest/examples/classification/dictionary_based.html),\n",
-    "[feature_based](https://www.aeon-toolkit.org/en/latest/examples/classification/feature_based.html), [hybrid](https://www.aeon-toolkit.org/en/latest/examples/classification/hybrid.html), [interval based](https://www.aeon-toolkit.org/en/latest/examples/classification/interval_based.html), and [shapelet based](https://www.aeon-toolkit.org/en/latest/examples/classification/shapelet_based.html). We also\n",
+    "[convolution based](convolution_based.ipynb), [deep learning](deep_learning.ipynb), [distance based](distance_based.ipynb), [dictionary based](dictionary_based.ipynb),\n",
+    "[feature_based](feature_based.ipynb), [hybrid](hybrid.ipynb), [interval based](interval_based.ipynb), and [shapelet based](shapelet_based.ipynb). We also\n",
     "provide some\n",
     "standard classifiers not available in scikit learn in the sklearn package.\n",
     "We show the simplest use cases for classifiers and demonstrate how to build bespoke\n",
     "pipelines for time series classification. An accurate and relatively\n",
     "fast classifier is the [ROCKET](https://link.springer.com/article/10.1007/s10618-020-00701-z) classifier. ROCKET is a convolution based algorithm\n",
-    "described in detail in the [convolution based](https://www.aeon-toolkit.org/en/latest/examples/classification/convolution_based.html) note book."
+    "described in detail in the [convolution based](convolution_based.ipynb) note book."
    ]
   },
   {
@@ -292,8 +293,8 @@
    "source": [
     "Another accurate classifier for time series classification is version 2 of the\n",
     "[HIVE-COTE](https://link.springer.com/article/10.1007/s10994-021-06057-9) algorithm.\n",
-    "(HC2) is described in the [hybrid](https://www.aeon-toolkit\n",
-    ".org/en/latest/examples/classification/hybrid.html) notebook. HC2 is relatively slow\n",
+    "(HC2) is described in the [hybrid notebook](hybrid.ipynb) notebook. HC2 is relatively\n",
+    "slow\n",
     "on small problems like these examples. However, it can be\n",
     "configured with an approximate maximum run time as follows (it may take a bit longer\n",
     "than 12 seconds to run this cell, very short times are approximate since there is a\n",
@@ -431,7 +432,9 @@
     "from aeon.registry import all_estimators\n",
     "\n",
     "all_estimators(\n",
-    "    filter_tags={\"capability:multivariate\": True}, estimator_types=\"classifier\"\n",
+    "    filter_tags={\"capability:multivariate\": True},\n",
+    "    estimator_types=\"classifier\",\n",
+    "    as_dataframe=True,\n",
     ")"
    ],
    "metadata": {
diff --git a/examples/distances/distances.ipynb b/examples/distances/distances.ipynb
index c042b156..da004000 100644
--- a/examples/distances/distances.ipynb
+++ b/examples/distances/distances.ipynb
@@ -699,19 +699,24 @@
     "[1] Chris Holder, Matthew Middlehurst and Anthony Bagnall, A Review and Evaluation of\n",
     " Elastic Distance Functions for Time Series Clustering,\n",
     " [ArXiv](https://arxiv.org/abs/2205.15181)\n",
+    "\n",
     "[2] Keogh, Eamonn & Pazzani, Michael. Derivative Dynamic Time Warping.\n",
     "    First SIAM International Conference on Data Mining, 2002.\n",
+    "\n",
     "[3] Young-Seon Jeong, Myong K. Jeong, Olufemi A. Omitaomu, Weighted dynamic time\n",
     "    warping for time series classification, Pattern Recognition, Volume 44, Issue 9,\n",
     "    2011.\n",
+    "\n",
     "[4] Chen L, Ozsu MT, Oria V: Robust and fast similarity search for moving\n",
     "object trajectories. In: Proceedings of the ACM SIGMOD International Conference\n",
     "on Management of Data, 2005.\n",
+    "\n",
     "[5] Stefan A., Athitsos V., Das G.: The Move-Split-Merge metric for time\n",
     "series. IEEE Transactions on Knowledge and Data Engineering 25(6), 2013.\n",
+    "\n",
     "[6] Marteau, P.; F. Time Warp Edit Distance with Stiffness Adjustment\n",
     "    for Time Series Matching. IEEE Transactions on Pattern Analysis and Machine\n",
-    "    Intelligence. 31 (2), 2009/"
+    "    Intelligence. 31 (2), 2009"
    ],
    "metadata": {
     "collapsed": false
diff --git a/examples/utils/preprocessing.ipynb b/examples/utils/preprocessing.ipynb
new file mode 100644
index 00000000..70f8f2cb
--- /dev/null
+++ b/examples/utils/preprocessing.ipynb
@@ -0,0 +1,369 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "source": [
+    "# Preprocessing data prior to machine learning\n",
+    "\n",
+    "For machine learning, we assume that data is in shape `(n_cases, n_channels,\n",
+    "n_timepoints)` for equal length series or a python list with `len` of `[n_cases]` if\n",
+    "the series are unequal length. However, in reality, there are often many steps to get\n",
+    " your data into this format. We introduce some common uses cases that may be handled\n",
+    " with preprocessing, and give some suggestions about how to handle them.\n"
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 23,
+   "outputs": [],
+   "source": [
+    "import numpy as np\n",
+    "\n",
+    "from aeon.classification.distance_based import KNeighborsTimeSeriesClassifier\n",
+    "from aeon.datasets import load_japanese_vowels, load_plaid\n",
+    "from aeon.registry import all_estimators\n",
+    "from aeon.utils.validation import has_missing, is_equal_length, is_univariate"
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "## Missing values\n",
+    "\n",
+    "Missing values are indicated by `NaN` in numpy array. You can test whether any `aeon`\n",
+    " data structure contains missing values using the utility function\n",
+    "\n"
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 26,
+   "outputs": [
+    {
+     "data": {
+      "text/plain": "False"
+     },
+     "execution_count": 26,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "X = np.random.random(size=(10, 2, 200))\n",
+    "has_missing(X)"
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 27,
+   "outputs": [
+    {
+     "data": {
+      "text/plain": "True"
+     },
+     "execution_count": 27,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "X[5][0][55] = np.NAN\n",
+    "has_missing(X)"
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "There are a range of strategies for handling missing values. These include:\n",
+    "\n",
+    "1. Use an estimator that internally handles missing values. It is fairly easy for\n",
+    "some algorithms (such as decision trees) to internally deal with missing values,\n",
+    "usually be using it as a distinct series value after discretisation.\n",
+    "2. Removing series with missing: this is often desirable if the train set size is\n",
+    "large, the number of series with missing is small and the proportion of missing\n",
+    "values for these series is high.\n",
+    "3. Interpolating: estimating the missing values from the other series values. This is\n",
+    " often desirable if the train set size is small and the proportion of missing values\n",
+    " is low.\n",
+    "\n",
+    "Some forecasters can internally handle missing values, but there is very little\n",
+    "support as yet for internally dealing with missing."
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 29,
+   "outputs": [
+    {
+     "data": {
+      "text/plain": "                                         name  \\\n0                                       ARIMA   \n1                                   AutoARIMA   \n2                                     AutoETS   \n3           DirRecTabularRegressionForecaster   \n4        DirRecTimeSeriesRegressionForecaster   \n5           DirectTabularRegressionForecaster   \n6        DirectTimeSeriesRegressionForecaster   \n7                             DummyClassifier   \n8                              DummyRegressor   \n9                               DynamicFactor   \n10                            ForecastByLevel   \n11                        ForecastingPipeline   \n12     MultioutputTabularRegressionForecaster   \n13  MultioutputTimeSeriesRegressionForecaster   \n14                            NaiveForecaster   \n15                                    Permute   \n16       RecursiveTabularRegressionForecaster   \n17    RecursiveTimeSeriesRegressionForecaster   \n18                   SklearnRegressorPipeline   \n19                         StackingForecaster   \n20                TransformedTargetForecaster   \n\n                                            estimator  \n0              <class 'aeon.forecasting.arima.ARIMA'>  \n1          <class 'aeon.forecasting.arima.AutoARIMA'>  \n2              <class 'aeon.forecasting.ets.AutoETS'>  \n3   <class 'aeon.forecasting.compose._reduce.DirRe...  \n4   <class 'aeon.forecasting.compose._reduce.DirRe...  \n5   <class 'aeon.forecasting.compose._reduce.Direc...  \n6   <class 'aeon.forecasting.compose._reduce.Direc...  \n7   <class 'aeon.classification._dummy.DummyClassi...  \n8     <class 'aeon.regression._dummy.DummyRegressor'>  \n9   <class 'aeon.forecasting.dynamic_factor.Dynami...  \n10  <class 'aeon.forecasting.compose._grouped.Fore...  \n11  <class 'aeon.forecasting.compose._pipeline.For...  \n12  <class 'aeon.forecasting.compose._reduce.Multi...  \n13  <class 'aeon.forecasting.compose._reduce.Multi...  \n14   <class 'aeon.forecasting.naive.NaiveForecaster'>  \n15  <class 'aeon.forecasting.compose._pipeline.Per...  \n16  <class 'aeon.forecasting.compose._reduce.Recur...  \n17  <class 'aeon.forecasting.compose._reduce.Recur...  \n18  <class 'aeon.regression.compose._pipeline.Skle...  \n19  <class 'aeon.forecasting.compose._stack.Stacki...  \n20  <class 'aeon.forecasting.compose._pipeline.Tra...  ",
+      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>name</th>\n      <th>estimator</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>0</th>\n      <td>ARIMA</td>\n      <td>&lt;class 'aeon.forecasting.arima.ARIMA'&gt;</td>\n    </tr>\n    <tr>\n      <th>1</th>\n      <td>AutoARIMA</td>\n      <td>&lt;class 'aeon.forecasting.arima.AutoARIMA'&gt;</td>\n    </tr>\n    <tr>\n      <th>2</th>\n      <td>AutoETS</td>\n      <td>&lt;class 'aeon.forecasting.ets.AutoETS'&gt;</td>\n    </tr>\n    <tr>\n      <th>3</th>\n      <td>DirRecTabularRegressionForecaster</td>\n      <td>&lt;class 'aeon.forecasting.compose._reduce.DirRe...</td>\n    </tr>\n    <tr>\n      <th>4</th>\n      <td>DirRecTimeSeriesRegressionForecaster</td>\n      <td>&lt;class 'aeon.forecasting.compose._reduce.DirRe...</td>\n    </tr>\n    <tr>\n      <th>5</th>\n      <td>DirectTabularRegressionForecaster</td>\n      <td>&lt;class 'aeon.forecasting.compose._reduce.Direc...</td>\n    </tr>\n    <tr>\n      <th>6</th>\n      <td>DirectTimeSeriesRegressionForecaster</td>\n      <td>&lt;class 'aeon.forecasting.compose._reduce.Direc...</td>\n    </tr>\n    <tr>\n      <th>7</th>\n      <td>DummyClassifier</td>\n      <td>&lt;class 'aeon.classification._dummy.DummyClassi...</td>\n    </tr>\n    <tr>\n      <th>8</th>\n      <td>DummyRegressor</td>\n      <td>&lt;class 'aeon.regression._dummy.DummyRegressor'&gt;</td>\n    </tr>\n    <tr>\n      <th>9</th>\n      <td>DynamicFactor</td>\n      <td>&lt;class 'aeon.forecasting.dynamic_factor.Dynami...</td>\n    </tr>\n    <tr>\n      <th>10</th>\n      <td>ForecastByLevel</td>\n      <td>&lt;class 'aeon.forecasting.compose._grouped.Fore...</td>\n    </tr>\n    <tr>\n      <th>11</th>\n      <td>ForecastingPipeline</td>\n      <td>&lt;class 'aeon.forecasting.compose._pipeline.For...</td>\n    </tr>\n    <tr>\n      <th>12</th>\n      <td>MultioutputTabularRegressionForecaster</td>\n      <td>&lt;class 'aeon.forecasting.compose._reduce.Multi...</td>\n    </tr>\n    <tr>\n      <th>13</th>\n      <td>MultioutputTimeSeriesRegressionForecaster</td>\n      <td>&lt;class 'aeon.forecasting.compose._reduce.Multi...</td>\n    </tr>\n    <tr>\n      <th>14</th>\n      <td>NaiveForecaster</td>\n      <td>&lt;class 'aeon.forecasting.naive.NaiveForecaster'&gt;</td>\n    </tr>\n    <tr>\n      <th>15</th>\n      <td>Permute</td>\n      <td>&lt;class 'aeon.forecasting.compose._pipeline.Per...</td>\n    </tr>\n    <tr>\n      <th>16</th>\n      <td>RecursiveTabularRegressionForecaster</td>\n      <td>&lt;class 'aeon.forecasting.compose._reduce.Recur...</td>\n    </tr>\n    <tr>\n      <th>17</th>\n      <td>RecursiveTimeSeriesRegressionForecaster</td>\n      <td>&lt;class 'aeon.forecasting.compose._reduce.Recur...</td>\n    </tr>\n    <tr>\n      <th>18</th>\n      <td>SklearnRegressorPipeline</td>\n      <td>&lt;class 'aeon.regression.compose._pipeline.Skle...</td>\n    </tr>\n    <tr>\n      <th>19</th>\n      <td>StackingForecaster</td>\n      <td>&lt;class 'aeon.forecasting.compose._stack.Stacki...</td>\n    </tr>\n    <tr>\n      <th>20</th>\n      <td>TransformedTargetForecaster</td>\n      <td>&lt;class 'aeon.forecasting.compose._pipeline.Tra...</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
+     },
+     "execution_count": 29,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "all_estimators(\n",
+    "    estimator_types=[\"classifier\", \"regressor\", \"clusterer\", \"forecaster\"],\n",
+    "    filter_tags={\"capability:missing_values\": True},\n",
+    "    as_dataframe=True,\n",
+    ")"
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "Removing series with missing and interpolation is currently best done by you: there\n",
+    "are no transformers to deal with at at the moment. It is on the wish list."
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "## Unequal length series\n",
+    "\n",
+    "Learning from unequal length series is very common. aeon provided two baked in\n",
+    "unequal length collections: the univariate PLAID dataset, and the multivariate\n",
+    "JapaneseVowels dataset:\n"
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "PLAID is univariate = True has missing = False is equal length = False\n",
+      "JapaneseVowels is univariate = False has missing = False is equal length = False\n"
+     ]
+    }
+   ],
+   "source": [
+    "plaid_X, plaid_y = load_plaid(split=\"train\")\n",
+    "print(\n",
+    "    f\"PLAID is univariate = {is_univariate(plaid_X)} has missing =\"\n",
+    "    f\"{has_missing(plaid_X)} is equal length = {is_equal_length(plaid_X)}\"\n",
+    ")\n",
+    "vowels_X, vowels_y = load_japanese_vowels(split=\"train\")\n",
+    "print(\n",
+    "    f\"JapaneseVowels is univariate = {is_univariate(vowels_X)} \"\n",
+    "    f\"has missing = {has_missing(vowels_X)} is \"\n",
+    "    f\"equal length = {is_equal_length(vowels_X)}\"\n",
+    ")"
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "### Handling unequal length\n",
+    "\n",
+    "There are two basic strategies for unequal length problems\n",
+    "1. Use an estimator that can internally handle missing values\n",
+    "2. Transform the data to be equal length by, for example, truncating or padding series\n",
+    "\n",
+    "At the time of writing, functionality for handling unequal length series is limited.\n",
+    "Estimators with the tag `\"capability:unequal_length\": True` have the capability to\n",
+    "handle unequal length series. For classification, regression and\n",
+    "clusterign, the\n",
+    "current list is"
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "outputs": [
+    {
+     "data": {
+      "text/plain": "                             name  \\\n0               Catch22Classifier   \n1                Catch22Regressor   \n2                 DummyClassifier   \n3                 ElasticEnsemble   \n4  KNeighborsTimeSeriesClassifier   \n5   KNeighborsTimeSeriesRegressor   \n\n                                           estimator  \n0  <class 'aeon.classification.feature_based._cat...  \n1  <class 'aeon.regression.feature_based._catch22...  \n2  <class 'aeon.classification._dummy.DummyClassi...  \n3  <class 'aeon.classification.distance_based._el...  \n4  <class 'aeon.classification.distance_based._ti...  \n5  <class 'aeon.regression.distance_based._time_s...  ",
+      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>name</th>\n      <th>estimator</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>0</th>\n      <td>Catch22Classifier</td>\n      <td>&lt;class 'aeon.classification.feature_based._cat...</td>\n    </tr>\n    <tr>\n      <th>1</th>\n      <td>Catch22Regressor</td>\n      <td>&lt;class 'aeon.regression.feature_based._catch22...</td>\n    </tr>\n    <tr>\n      <th>2</th>\n      <td>DummyClassifier</td>\n      <td>&lt;class 'aeon.classification._dummy.DummyClassi...</td>\n    </tr>\n    <tr>\n      <th>3</th>\n      <td>ElasticEnsemble</td>\n      <td>&lt;class 'aeon.classification.distance_based._el...</td>\n    </tr>\n    <tr>\n      <th>4</th>\n      <td>KNeighborsTimeSeriesClassifier</td>\n      <td>&lt;class 'aeon.classification.distance_based._ti...</td>\n    </tr>\n    <tr>\n      <th>5</th>\n      <td>KNeighborsTimeSeriesRegressor</td>\n      <td>&lt;class 'aeon.regression.distance_based._time_s...</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
+     },
+     "execution_count": 14,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "all_estimators(\n",
+    "    estimator_types=[\"classifier\", \"regressor\", \"clusterer\"],\n",
+    "    filter_tags={\"capability:unequal_length\": True},\n",
+    "    as_dataframe=True,\n",
+    ")"
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "You can pass these estimators unequal length series and they will work as expected."
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 15,
+   "outputs": [
+    {
+     "data": {
+      "text/plain": "KNeighborsTimeSeriesClassifier()",
+      "text/html": "<style>#sk-container-id-2 {color: black;background-color: white;}#sk-container-id-2 pre{padding: 0;}#sk-container-id-2 div.sk-toggleable {background-color: white;}#sk-container-id-2 label.sk-toggleable__label {cursor: pointer;display: block;width: 100%;margin-bottom: 0;padding: 0.3em;box-sizing: border-box;text-align: center;}#sk-container-id-2 label.sk-toggleable__label-arrow:before {content: \"▸\";float: left;margin-right: 0.25em;color: #696969;}#sk-container-id-2 label.sk-toggleable__label-arrow:hover:before {color: black;}#sk-container-id-2 div.sk-estimator:hover label.sk-toggleable__label-arrow:before {color: black;}#sk-container-id-2 div.sk-toggleable__content {max-height: 0;max-width: 0;overflow: hidden;text-align: left;background-color: #f0f8ff;}#sk-container-id-2 div.sk-toggleable__content pre {margin: 0.2em;color: black;border-radius: 0.25em;background-color: #f0f8ff;}#sk-container-id-2 input.sk-toggleable__control:checked~div.sk-toggleable__content {max-height: 200px;max-width: 100%;overflow: auto;}#sk-container-id-2 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {content: \"▾\";}#sk-container-id-2 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {background-color: #d4ebff;}#sk-container-id-2 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {background-color: #d4ebff;}#sk-container-id-2 input.sk-hidden--visually {border: 0;clip: rect(1px 1px 1px 1px);clip: rect(1px, 1px, 1px, 1px);height: 1px;margin: -1px;overflow: hidden;padding: 0;position: absolute;width: 1px;}#sk-container-id-2 div.sk-estimator {font-family: monospace;background-color: #f0f8ff;border: 1px dotted black;border-radius: 0.25em;box-sizing: border-box;margin-bottom: 0.5em;}#sk-container-id-2 div.sk-estimator:hover {background-color: #d4ebff;}#sk-container-id-2 div.sk-parallel-item::after {content: \"\";width: 100%;border-bottom: 1px solid gray;flex-grow: 1;}#sk-container-id-2 div.sk-label:hover label.sk-toggleable__label {background-color: #d4ebff;}#sk-container-id-2 div.sk-serial::before {content: \"\";position: absolute;border-left: 1px solid gray;box-sizing: border-box;top: 0;bottom: 0;left: 50%;z-index: 0;}#sk-container-id-2 div.sk-serial {display: flex;flex-direction: column;align-items: center;background-color: white;padding-right: 0.2em;padding-left: 0.2em;position: relative;}#sk-container-id-2 div.sk-item {position: relative;z-index: 1;}#sk-container-id-2 div.sk-parallel {display: flex;align-items: stretch;justify-content: center;background-color: white;position: relative;}#sk-container-id-2 div.sk-item::before, #sk-container-id-2 div.sk-parallel-item::before {content: \"\";position: absolute;border-left: 1px solid gray;box-sizing: border-box;top: 0;bottom: 0;left: 50%;z-index: -1;}#sk-container-id-2 div.sk-parallel-item {display: flex;flex-direction: column;z-index: 1;position: relative;background-color: white;}#sk-container-id-2 div.sk-parallel-item:first-child::after {align-self: flex-end;width: 50%;}#sk-container-id-2 div.sk-parallel-item:last-child::after {align-self: flex-start;width: 50%;}#sk-container-id-2 div.sk-parallel-item:only-child::after {width: 0;}#sk-container-id-2 div.sk-dashed-wrapped {border: 1px dashed gray;margin: 0 0.4em 0.5em 0.4em;box-sizing: border-box;padding-bottom: 0.4em;background-color: white;}#sk-container-id-2 div.sk-label label {font-family: monospace;font-weight: bold;display: inline-block;line-height: 1.2em;}#sk-container-id-2 div.sk-label-container {text-align: center;}#sk-container-id-2 div.sk-container {/* jupyter's `normalize.less` sets `[hidden] { display: none; }` but bootstrap.min.css set `[hidden] { display: none !important; }` so we also need the `!important` here to be able to override the default hidden behavior on the sphinx rendered scikit-learn.org. See: https://github.com/scikit-learn/scikit-learn/issues/21755 */display: inline-block !important;position: relative;}#sk-container-id-2 div.sk-text-repr-fallback {display: none;}</style><div id=\"sk-container-id-2\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>KNeighborsTimeSeriesClassifier()</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-2\" type=\"checkbox\" checked><label for=\"sk-estimator-id-2\" class=\"sk-toggleable__label sk-toggleable__label-arrow\">KNeighborsTimeSeriesClassifier</label><div class=\"sk-toggleable__content\"><pre>KNeighborsTimeSeriesClassifier()</pre></div></div></div></div></div>"
+     },
+     "execution_count": 15,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "knn = KNeighborsTimeSeriesClassifier()\n",
+    "knn.fit(plaid_X, plaid_y)"
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "The alternative is to transform your data so that it becomes equal length, and can\n",
+    "then be used with any time series estimator. Two simple examples are tpo pad the\n",
+    "series to the longest series length, or to truncate to the shortest series length. By\n",
+    " default, padding pads with zeros. Be careful if your data is not normalised, because\n",
+    "  this could then effect the classifier."
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 17,
+   "outputs": [
+    {
+     "data": {
+      "text/plain": "(537, 1, 1344)"
+     },
+     "execution_count": 17,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "from aeon.transformations.collection import PaddingTransformer\n",
+    "\n",
+    "pt = PaddingTransformer()\n",
+    "plaid_equal = pt.fit_transform(plaid_X)\n",
+    "plaid_equal.shape"
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 21,
+   "outputs": [
+    {
+     "data": {
+      "text/plain": "[<matplotlib.lines.Line2D at 0x235ecdc37f0>]"
+     },
+     "execution_count": 21,
+     "metadata": {},
+     "output_type": "execute_result"
+    },
+    {
+     "data": {
+      "text/plain": "<Figure size 640x480 with 1 Axes>",
+      "image/png": "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"
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "import matplotlib.pyplot as plt\n",
+    "\n",
+    "plt.title(\"Before and after padding: PLAID first case (shifted up for unpadded)\")\n",
+    "plt.plot(plaid_X[0][0] + 10)\n",
+    "plt.plot(plaid_equal[0][0])"
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "## Coming soon\n",
+    "\n",
+    "Unequally spaced samples\n",
+    "Streaming series: windowing and segmenting\n",
+    "Channel selection for multivariate series\n",
+    "Channel selection for multivariate series"
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 2
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython2",
+   "version": "2.7.6"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
diff --git a/pyproject.toml b/pyproject.toml
index f227508c..a9f594a6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "aeon"
-version = "0.3.0"
+version = "0.4.0"
 description = "A unified framework for machine learning with time series"
 authors = [
     {name = "aeon developers", email = "contact@aeon-toolkit.org"},
diff --git a/setup.cfg b/setup.cfg
index 1561a1c8..ab4b2e27 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -34,16 +34,11 @@ long_description_content_type = text/markdown
 
 [check-manifest]
 ignore =
-    .binder/**
-    build_tools/**
-    docs/**
-    examples/**
-    .all-contributorsrc
-    .coveragerc
-    CODEOWNERS
-    CONTRIBUTING.md
-    *.yaml
-    *.yml
+    # Ignore virtual environments in local builds
+    venv/**
+    # Ignore the docs examples symlink to examples and its contents
+    docs/examples
+    docs/examples/**
 
 [isort]
 profile = black


Test patch:
diff --git a/aeon/utils/validation/tests/test_collection.py b/aeon/utils/validation/tests/test_collection.py
new file mode 100644
index 00000000..8c984223
--- /dev/null
+++ b/aeon/utils/validation/tests/test_collection.py
@@ -0,0 +1,219 @@
+# -*- coding: utf-8 -*-
+"""Unit tests for aeon.utils.validation.collection check/convert functions."""
+import numpy as np
+import pandas as pd
+import pytest
+
+from aeon.datasets import make_example_multi_index_dataframe
+
+# from aeon.datasets._data_generators import make_example_multi_index_dataframe
+from aeon.utils._testing.tests.test_collection import make_nested_dataframe_data
+from aeon.utils.validation.collection import (  # _nested_uni_is_equal,; ,
+    COLLECTIONS_DATA_TYPES,
+    _is_nested_univ_dataframe,
+    _is_pd_wide,
+    convertX,
+    equal_length,
+    get_n_cases,
+    get_type,
+    has_missing,
+    is_equal_length,
+    is_univariate,
+    resolve_equal_length_inner_type,
+    resolve_unequal_length_inner_type,
+)
+
+np_list = []
+for _ in range(10):
+    np_list.append(np.random.random(size=(1, 20)))
+df_list = []
+for _ in range(10):
+    df_list.append(pd.DataFrame(np.random.random(size=(1, 20))))
+nested, _ = make_nested_dataframe_data(n_cases=10)
+multiindex = make_example_multi_index_dataframe(
+    n_instances=10, n_channels=1, n_timepoints=20
+)
+
+EQUAL_LENGTH_UNIVARIATE = {
+    "numpy3D": np.random.random(size=(10, 1, 20)),
+    "np-list": np_list,
+    "df-list": df_list,
+    "numpyflat": np.zeros(shape=(10, 20)),
+    "pd-wide": pd.DataFrame(np.zeros(shape=(10, 20))),
+    "nested_univ": nested,
+    "pd-multiindex": multiindex,
+}
+np_list_uneq = []
+for i in range(10):
+    np_list_uneq.append(np.random.random(size=(1, 20 + i)))
+df_list_uneq = []
+for i in range(10):
+    df_list_uneq.append(pd.DataFrame(np.random.random(size=(1, 20 + i))))
+
+nested_univ_uneq = pd.DataFrame(dtype=float)
+instance_list = []
+for i in range(0, 10):
+    instance_list.append(pd.Series(np.random.randn(20 + i)))
+nested_univ_uneq["channel0"] = instance_list
+
+UNEQUAL_LENGTH_DATA_EXAMPLES = {
+    "np-list": np_list_uneq,
+    "df-list": df_list_uneq,
+    "nested_univ": nested_univ_uneq,
+}
+np_list_multi = []
+for _ in range(10):
+    np_list_multi.append(np.random.random(size=(2, 20)))
+df_list_multi = []
+for _ in range(10):
+    df_list_multi.append(pd.DataFrame(np.random.random(size=(2, 20))))
+multi = make_example_multi_index_dataframe(
+    n_instances=10, n_channels=2, n_timepoints=20
+)
+
+nested_univ_multi = pd.DataFrame(dtype=float)
+instance_list = []
+for i in range(0, 10):
+    instance_list.append(pd.Series(np.random.randn(20 + i)))
+nested_univ_multi["channel0"] = instance_list
+instance_list = []
+for i in range(0, 10):
+    instance_list.append(pd.Series(np.random.randn(20 + i)))
+nested_univ_multi["channel1"] = instance_list
+
+
+EQUAL_LENGTH_MULTIVARIATE = {
+    "numpy3D": np.random.random(size=(10, 2, 20)),
+    "np-list": np_list_multi,
+    "df-list": df_list_multi,
+    "nested_univ": nested_univ_multi,
+    "pd-multiindex": multi,
+}
+# NotImplementedError: no conversion defined from type np-list to numpy3D under the
+# datatypes definitions. SOON TO CHANGE!
+
+NOT_IMPLEMENTED_CONVERSIONS = [
+    ("np-list", "numpy3D"),
+    ("pd-wide", "numpy3D"),
+    ("numpyflat", "np-list"),
+    ("pd-wide", "np-list"),
+    ("numpyflat", "df-list"),
+    ("pd-wide", "df-list"),
+    ("np-list", "numpyflat"),
+    ("numpyflat", "numpyflat"),
+    ("pd-wide", "numpyflat"),
+    ("numpy3D", "pd-wide"),
+    ("pd-wide", "pd-wide"),
+    ("pd-multiindex", "pd-wide"),
+    ("np-list", "pd-wide"),
+    ("df-list", "pd-wide"),
+    ("numpyflat", "pd-wide"),
+    ("numpyflat", "nested_univ"),
+    ("pd-wide", "nested_univ"),
+    ("numpyflat", "pd-multiindex"),
+    ("pd-wide", "pd-multiindex"),
+]
+
+
+@pytest.mark.parametrize("input_data", COLLECTIONS_DATA_TYPES)
+@pytest.mark.parametrize("output_data", COLLECTIONS_DATA_TYPES)
+def test_convertX(input_data, output_data):
+    """Test all valid and invalid conversions."""
+    # This should be possible for all, but under the current the following are not
+    # defined
+    if (input_data, output_data) not in NOT_IMPLEMENTED_CONVERSIONS:
+        X = convertX(EQUAL_LENGTH_UNIVARIATE[input_data], output_data)
+        assert get_type(X) == output_data
+    # TODO: expand this test when conversions finished.
+
+
+def test_resolve_equal_length_inner_type():
+    test = ["numpy3D"]
+    X = resolve_equal_length_inner_type(test)
+    assert X == "numpy3D"
+    test = ["np-list", "numpy3D", "FOOBAR"]
+    X = resolve_equal_length_inner_type(test)
+    assert X == "numpy3D"
+    test = ["nested_univ", "np-list"]
+    X = resolve_equal_length_inner_type(test)
+    assert X == "np-list"
+
+
+def test_resolve_unequal_length_inner_type():
+    test = ["np-list"]
+    X = resolve_unequal_length_inner_type(test)
+    assert X == "np-list"
+    test = ["np-list", "numpy3D"]
+    X = resolve_unequal_length_inner_type(test)
+    assert X == "np-list"
+    test = ["nested_univ", "FOOBAR"]
+    X = resolve_unequal_length_inner_type(test)
+    assert X == "nested_univ"
+
+
+@pytest.mark.parametrize("data", COLLECTIONS_DATA_TYPES)
+def test_get_n_cases(data):
+    """Test getting the number of cases."""
+    assert get_n_cases(EQUAL_LENGTH_UNIVARIATE[data]) == 10
+
+
+@pytest.mark.parametrize("data", COLLECTIONS_DATA_TYPES)
+def test_get_type(data):
+    """Test getting the type."""
+    assert get_type(EQUAL_LENGTH_UNIVARIATE[data]) == data
+
+
+@pytest.mark.parametrize("data", COLLECTIONS_DATA_TYPES)
+def test_equal_length(data):
+    """Test if equal length series correctly identified."""
+    assert equal_length(EQUAL_LENGTH_UNIVARIATE[data], data)
+
+
+@pytest.mark.parametrize("data", COLLECTIONS_DATA_TYPES)
+def test_is_equal_length(data):
+    """Test if equal length series correctly identified."""
+    assert is_equal_length(EQUAL_LENGTH_UNIVARIATE[data])
+
+
+@pytest.mark.parametrize("data", ["df-list", "np-list"])
+def test_unequal_length(data):
+    """Test if unequal length series correctly identified."""
+    assert not equal_length(UNEQUAL_LENGTH_DATA_EXAMPLES[data], data)
+
+
+@pytest.mark.parametrize("data", ["df-list", "np-list"])
+def test_is_unequal_length(data):
+    """Test if unequal length series correctly identified."""
+    assert not is_equal_length(UNEQUAL_LENGTH_DATA_EXAMPLES[data])
+
+
+@pytest.mark.parametrize("data", COLLECTIONS_DATA_TYPES)
+def test_has_missing(data):
+    assert not has_missing(EQUAL_LENGTH_UNIVARIATE[data])
+    X = np.random.random(size=(10, 2, 20))
+    X[5][1][12] = np.NAN
+    # TODO: Test others with missing when converters complete
+    assert has_missing(X)
+
+
+@pytest.mark.parametrize("data", COLLECTIONS_DATA_TYPES)
+def test_is_univariate(data):
+    assert is_univariate(EQUAL_LENGTH_UNIVARIATE[data])
+    if data in EQUAL_LENGTH_MULTIVARIATE.keys():
+        assert not is_univariate(EQUAL_LENGTH_MULTIVARIATE[data])
+
+
+@pytest.mark.parametrize("data", COLLECTIONS_DATA_TYPES)
+def test__is_nested_univ_dataframe(data):
+    if data == "nested_univ":
+        assert _is_nested_univ_dataframe(EQUAL_LENGTH_UNIVARIATE[data])
+    else:
+        assert not _is_nested_univ_dataframe(EQUAL_LENGTH_UNIVARIATE[data])
+
+
+@pytest.mark.parametrize("data", COLLECTIONS_DATA_TYPES)
+def test__is_pd_wide(data):
+    if data == "pd-wide":
+        assert _is_pd_wide(EQUAL_LENGTH_UNIVARIATE[data])
+    else:
+        assert not _is_pd_wide(EQUAL_LENGTH_UNIVARIATE[data])