Repository: aeon-toolkit/aeon
Pull request #838 (2023-10-13 21:32:37)

[ENH] change collection conversions from convert_to to convert_collection
#### Reference Issues/PRs

part of #42
#### What does this implement/fix? Explain your changes.

part of the policy of isolating the datatypes module, switch from the datatypes function convert_to to the convert_collection function in utils.


sweep-ai[bot]: ## Apply [Sweep Rules](https://docs.sweep.dev/usage/config#rules) to your PR?
- [ ] **Apply:**  There should not be large chunks of code that are just commented out. Docstrings and explanations in code are okay though.
- [ ] **Apply:**  Update the relevant API page in 'docs/api_reference/' when new public functions and classes are added and not included in the API documentation. Only add functions and classes which are not already in the relevant API documentation and avoid duplicate entries.
review-notebook-app[bot]: Check out this pull request on&nbsp; <a href="https://app.reviewnb.com/aeon-toolkit/aeon/pull/838"><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/aeon/clustering/utils/plotting/_plot_partitions.py b/aeon/clustering/utils/plotting/_plot_partitions.py
index 20438449..d4fb8407 100644
--- a/aeon/clustering/utils/plotting/_plot_partitions.py
+++ b/aeon/clustering/utils/plotting/_plot_partitions.py
@@ -4,11 +4,10 @@ __author__ = ["Christopher Holder", "Tony Bagnall"]
 __all__ = ["plot_cluster_algorithm"]
 
 import numpy as np
-import pandas as pd
 
 from aeon.clustering.base import BaseClusterer
-from aeon.datatypes import convert_to
 from aeon.utils.validation._dependencies import _check_soft_dependencies
+from aeon.utils.validation.collection import convert_collection
 
 
 def _plot(cluster_values, center, axes):
@@ -32,8 +31,7 @@ def plot_series(X):
     import matplotlib.patches as mpatches
     import matplotlib.pyplot as plt
 
-    if isinstance(X, pd.DataFrame):
-        X = convert_to(X, "numpy3D")
+    X = convert_collection(X, "numpy3D")
     plt.figure(figsize=(5, 10))
     plt.rcParams["figure.dpi"] = 100
 
@@ -72,9 +70,8 @@ def plot_cluster_algorithm(model: BaseClusterer, X, k: int):
     import matplotlib.patches as mpatches
     import matplotlib.pyplot as plt
 
-    predict_series = X
-    if isinstance(X, pd.DataFrame):
-        predict_series = convert_to(X, "numpy3D")
+    predict_series = convert_collection(X, "numpy3D")
+
     plt.figure(figsize=(5, 10))
     plt.rcParams["figure.dpi"] = 100
     indexes = model.predict(predict_series)
diff --git a/aeon/datasets/_data_loaders.py b/aeon/datasets/_data_loaders.py
index 8e281196..2ed90892 100644
--- a/aeon/datasets/_data_loaders.py
+++ b/aeon/datasets/_data_loaders.py
@@ -16,6 +16,7 @@ from aeon.datasets.dataset_collections import (
     list_downloaded_tsf_datasets,
 )
 from aeon.datatypes import MTYPE_LIST_HIERARCHICAL, convert
+from aeon.utils.validation.collection import convert_collection
 
 __all__ = [  # Load functions
     "load_from_tsfile",
@@ -311,36 +312,20 @@ def _load_saved_dataset(
         y = np.concatenate([y_train, y_test])
     else:
         raise ValueError("Invalid `split` value =", split)
-
-    # All this is to allow for the user to configure to load into different data
-    # structures. Its all for backward compatibility.
-    if isinstance(X, list):
-        loaded_type = "np-list"
-    elif isinstance(X, np.ndarray):
-        loaded_type = "numpy3D"
-
-    if return_type == "nested_univ":
-        X = convert(X, from_type="np-list", to_type="nested_univ")
-        loaded_type = "nested_univ"
-    elif meta_data["equallength"]:
-        if return_type == "numpyflat" and X.shape[1] == 1:
-            X = X.squeeze()
-            loaded_type = "numpyflat"
-    elif return_type is not None and loaded_type != return_type:
-        X = convert(X, from_type=loaded_type, to_type=return_type)
+    if return_type is not None:
+        X = convert_collection(X, return_type)
 
     if return_X_y:
         if return_meta:
             return X, y, meta_data
         else:
             return X, y
-    else:  # TODO: do this better, do we want it all in dataframes?
-        X = convert(X, from_type=loaded_type, to_type="nested_univ")
-        X["class_val"] = pd.Series(y)
+    else:
+        combo = (X, y)
         if return_meta:
-            return X, meta_data
+            return combo, meta_data
         else:
-            return X
+            return combo
 
 
 def _download_and_extract(url, extract_path=None):
@@ -690,7 +675,6 @@ def load_tsf_to_dataframe(
                 loaded_data = convert(
                     loaded_data, from_type="pd_multiindex_hier", to_type=return_type
                 )
-
         return loaded_data, metadata
 
 
diff --git a/aeon/transformations/collection/convolution_based/_multirocket.py b/aeon/transformations/collection/convolution_based/_multirocket.py
index d025d050..499f8d8e 100644
--- a/aeon/transformations/collection/convolution_based/_multirocket.py
+++ b/aeon/transformations/collection/convolution_based/_multirocket.py
@@ -111,11 +111,11 @@ class MultiRocket(BaseCollectionTransformer):
         self
         """
         X = X.astype(np.float64)
+        X = X.squeeze()
         if self.normalise:
             X = (X - X.mean(axis=-1, keepdims=True)) / (
                 X.std(axis=-1, keepdims=True) + 1e-8
             )
-        X = X.reshape((X.shape[0], X.shape[1] * X.shape[2]))
 
         self.parameter = self._get_parameter(X)
 
@@ -138,7 +138,7 @@ class MultiRocket(BaseCollectionTransformer):
         pandas DataFrame, transformed features
         """
         X = X.astype(np.float64)
-        X = X.reshape((X.shape[0], X.shape[1] * X.shape[2]))
+        X = X.squeeze()
         if self.normalise:
             X = (X - X.mean(axis=-1, keepdims=True)) / (
                 X.std(axis=-1, keepdims=True) + 1e-8
diff --git a/aeon/transformations/collection/summary.py b/aeon/transformations/collection/summary.py
index 85959d6e..15d7a5ef 100644
--- a/aeon/transformations/collection/summary.py
+++ b/aeon/transformations/collection/summary.py
@@ -45,6 +45,14 @@ class SevenNumberSummaryTransformer(BaseCollectionTransformer):
      1.39573111]
     """
 
+    _tags = {
+        "input_data_type": "Collection",
+        "output_data_type": "Tabular",
+        "capability:multivariate": True,
+        "capability:unequal_length": True,
+        "fit_is_empty": True,
+    }
+
     def __init__(
         self,
         summary_stats="default",
@@ -54,18 +62,19 @@ class SevenNumberSummaryTransformer(BaseCollectionTransformer):
         super(SevenNumberSummaryTransformer, self).__init__()
 
     def _transform(self, X, y=None):
-        n_instances, n_dims, _ = X.shape
+        n_instances = len(X)
+        n_channels, _ = X[0].shape
 
         functions = self._get_functions()
 
-        Xt = np.zeros((n_instances, 7 * n_dims))
+        Xt = np.zeros((n_instances, 7 * n_channels))
         for i in range(n_instances):
             for n, f in enumerate(functions):
-                idx = n * n_dims
+                idx = n * n_channels
                 if isinstance(f, float):
-                    Xt[i, idx : idx + n_dims] = row_quantile(X[i], f)
+                    Xt[i, idx : idx + n_channels] = row_quantile(X[i], f)
                 else:
-                    Xt[i, idx : idx + n_dims] = f(X[i])
+                    Xt[i, idx : idx + n_channels] = f(X[i])
 
         return Xt
 


Test patch:
diff --git a/aeon/datasets/tests/test_data_loaders.py b/aeon/datasets/tests/test_data_loaders.py
index ce948f4d..38d62e2b 100644
--- a/aeon/datasets/tests/test_data_loaders.py
+++ b/aeon/datasets/tests/test_data_loaders.py
@@ -196,13 +196,15 @@ def test_load_provided_dataset(return_X_y, return_type):
         assert isinstance(y, np.ndarray)
     else:
         X = _load_saved_dataset("UnitTest", "TRAIN", return_X_y, return_type)
-    if not return_X_y or return_type == "nested_univ":
+    if not return_X_y:
+        assert isinstance(X, tuple)
+        X = X[0]
+    if return_type == "nested_univ":
         assert isinstance(X, pd.DataFrame)
     elif return_type == "numpy3D":
         assert isinstance(X, np.ndarray) and X.ndim == 3
     elif return_type == "numpy2D":
         assert isinstance(X, np.ndarray) and X.ndim == 2
-
     # Check whether object is same mtype or not, via bool
 
 
diff --git a/aeon/datasets/tests/test_single_problem_loaders.py b/aeon/datasets/tests/test_single_problem_loaders.py
index 0feb0e88..b5649b5b 100644
--- a/aeon/datasets/tests/test_single_problem_loaders.py
+++ b/aeon/datasets/tests/test_single_problem_loaders.py
@@ -43,7 +43,7 @@ def test_load_dataframe(loader):
     assert isinstance(y, np.ndarray)
     assert y.ndim == 1
     X = loader(return_X_y=False)
-    assert isinstance(X, pd.DataFrame)
+    assert isinstance(X, tuple)
 
 
 @pytest.mark.parametrize("loader", UNIVARIATE_PROBLEMS + MULTIVARIATE_PROBLEMS)
@@ -98,4 +98,4 @@ def test_load_covid_3month():
     assert X.shape == (201, 1, 84)
     assert isinstance(y, np.ndarray)
     X = load_covid_3month(return_X_y=False)
-    assert isinstance(X, pd.DataFrame)
+    assert isinstance(X, tuple)
diff --git a/aeon/distances/tests/test_cost_matrix.py b/aeon/distances/tests/test_cost_matrix.py
index 5c639633..0fbd56c6 100644
--- a/aeon/distances/tests/test_cost_matrix.py
+++ b/aeon/distances/tests/test_cost_matrix.py
@@ -17,7 +17,7 @@ def _validate_cost_matrix_result(
     cost_matrix_result = cost_matrix(x, y)
 
     assert isinstance(cost_matrix_result, np.ndarray)
-    assert np.array_equal(cost_matrix_result, compute_cost_matrix(x, y, metric=name))
+    assert_almost_equal(cost_matrix_result, compute_cost_matrix(x, y, metric=name))
     if name == "ddtw" or name == "wddtw":
         assert cost_matrix_result.shape == (x.shape[-1] - 2, y.shape[-1] - 2)
     elif name == "lcss":
diff --git a/aeon/transformations/collection/tests/test_tabularizer.py b/aeon/transformations/collection/tests/test_tabularizer.py
new file mode 100644
index 00000000..d7fd46d2
--- /dev/null
+++ b/aeon/transformations/collection/tests/test_tabularizer.py
@@ -0,0 +1,13 @@
+import numpy as np
+
+from aeon.transformations.collection.reduce import Tabularizer
+
+
+def test_tabularizer():
+    """Test Tabularizer."""
+    tab = Tabularizer()
+    arr = np.random.random(size=(10, 3, 100))
+    res = tab.fit_transform(arr)
+    assert res.shape == (10, 300)
+    res = tab.fit_transform(arr)
+    assert res.shape == (10, 300)