Repository: aeon-toolkit/aeon
Pull request #888 (2023-11-03 09:01:03)

[ENH] Improve coverage for datasets module
#### Reference Issues/PRs


#### What does this implement/fix? Explain your changes.

Increases coverage for datasets module. Most uncovered code is error checking, so will need to write and delete files to check that. 




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

I have added the following labels to this PR based on the title: **[ $\\color{#FEF1BE}{\\textsf{enhancement}}$ ]**.
I would have added the following labels to this PR based on the changes made: **[ $\\color{#0B1D38}{\\textsf{datasets}}$ ]**, however some package labels are already present.

The "Checks" tab above or the panel below will show the status of our automated tests. From the panel below you can click on "Details" to the right of the check to see more information if there is a failure. If our `pre-commit` code quality check fails, any trivial fixes will automatically be pushed to your PR.

Don't hesitate to ask questions on the `aeon` Slack channel if you have any!
    
sweep-ai[bot]: ## Apply [Sweep Rules](https://docs.sweep.dev/usage/config#rules) to your PR?
- [ ] **Apply:**  All new business logic should have corresponding unit tests.
- [ ] **Apply:**  Refactor large functions to be more modular.

Patch:
diff --git a/.github/workflows/pr_examples.yml b/.github/workflows/pr_examples.yml
index e36538fa..12f18bfa 100644
--- a/.github/workflows/pr_examples.yml
+++ b/.github/workflows/pr_examples.yml
@@ -29,7 +29,11 @@ jobs:
           python-version: "3.10"
 
       - name: Install aeon and dependencies
-        run: python -m pip install .[all_extras,binder,dev]
+        uses: nick-fields/retry@v2
+        with:
+          timeout_minutes: 30
+          max_attempts: 3
+          command: python -m pip install .[all_extras,binder,dev]
 
       - name: Run example notebooks
         run: build_tools/run_examples.sh
diff --git a/.github/workflows/pr_opened.yml b/.github/workflows/pr_opened.yml
index be07f7b6..6b9026fe 100644
--- a/.github/workflows/pr_opened.yml
+++ b/.github/workflows/pr_opened.yml
@@ -31,7 +31,13 @@ jobs:
         env:
           CONTEXT_GITHUB: ${{ toJson(github) }}
 
+      - uses: actions/create-github-app-token@v1
+        id: app-token
+        with:
+          app-id: ${{ vars.PR_APP_ID }}
+          private-key: ${{ secrets.PR_APP_KEY }}
+
       - name: Write pull request comment
-        run: python build_tools/pr_open_commenter.py ${{ steps.label-pr.outputs.title-labels }} ${{ steps.label-pr.outputs.title-labels-new }} ${{ steps.label-pr.outputs.content-labels }} ${{ steps.label-pr.outputs.content-labels-status }}
+        run: python build_tools/pr_open_commenter.py ${{ steps.app-token.outputs.token }} ${{ steps.label-pr.outputs.title-labels }} ${{ steps.label-pr.outputs.title-labels-new }} ${{ steps.label-pr.outputs.content-labels }} ${{ steps.label-pr.outputs.content-labels-status }}
         env:
           CONTEXT_GITHUB: ${{ toJson(github) }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index b2a10f11..4af103be 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -75,10 +75,18 @@ jobs:
 
       - if: runner.os == 'Windows'
         name: Windows install
-        run: python -m pip install "${env:WHEELNAME}[all_extras,dev]"
+        uses: nick-fields/retry@v2
+        with:
+          timeout_minutes: 30
+          max_attempts: 3
+          command: python -m pip install "${env:WHEELNAME}[all_extras,dev]"
       - if: runner.os != 'Windows'
         name: Unix install
-        run: python -m pip install "${{ env.WHEELNAME }}[all_extras,dev]"
+        uses: nick-fields/retry@v2
+        with:
+          timeout_minutes: 30
+          max_attempts: 3
+          command: python -m pip install "${{ env.WHEELNAME }}[all_extras,dev]"
 
       - name: Tests
         run: python -m pytest
diff --git a/.github/workflows/update_contributors.yml b/.github/workflows/update_contributors.yml
index 9fe5fa53..4c674510 100644
--- a/.github/workflows/update_contributors.yml
+++ b/.github/workflows/update_contributors.yml
@@ -25,8 +25,15 @@ jobs:
         id: generate
         run: npx all-contributors generate
 
+      - uses: actions/create-github-app-token@v1
+        id: app-token
+        with:
+          app-id: ${{ vars.PR_APP_ID }}
+          private-key: ${{ secrets.PR_APP_KEY }}
+
       - uses: peter-evans/create-pull-request@v5
         with:
+          token: ${{ steps.app-token.outputs.token }}
           commit-message: "Automated `CONTRIBUTORS.md` update"
           branch: update_contributors
           title: "[MNT] Automated `CONTRIBUTORS.md` update"
diff --git a/aeon/datasets/_data_writers.py b/aeon/datasets/_data_writers.py
index 05de0282..0618ad5e 100644
--- a/aeon/datasets/_data_writers.py
+++ b/aeon/datasets/_data_writers.py
@@ -203,7 +203,7 @@ def write_results_to_uea_format(
     third_line : str
         summary performance information (see comment below)
     """
-    if len(y_true) != len(y_pred):
+    if y_true is not None and len(y_true) != len(y_pred):
         raise IndexError(
             "The number of predicted values is not the same as the "
             "number of actual class values"
@@ -287,11 +287,7 @@ def _write_header(
     extension=None,
 ):
     if class_labels is not None and regression:
-        raise ValueError(
-            "Cannot have class_labels and targetlabel. If the problem "
-            "is classification, add class_labels. If regression, "
-            "set targetlabel to true."
-        )
+        raise ValueError("Cannot have class_labels true for a regression problem")
     # create path if it does not exist
     dir = f"{str(path)}/"
     try:
diff --git a/build_tools/pr_open_commenter.py b/build_tools/pr_open_commenter.py
index 0a791e74..6f226a4a 100644
--- a/build_tools/pr_open_commenter.py
+++ b/build_tools/pr_open_commenter.py
@@ -12,16 +12,16 @@ from github import Github
 context_dict = json.loads(os.getenv("CONTEXT_GITHUB"))
 
 repo = context_dict["repository"]
-g = Github(context_dict["token"])
+g = Github(sys.argv[1])
 repo = g.get_repo(repo)
 pr_number = context_dict["event"]["number"]
 pr = repo.get_pull(number=pr_number)
 
-print(sys.argv)  # noqa
-title_labels = sys.argv[1][1:-1].split(",")
-title_labels_new = sys.argv[2][1:-1].split(",")
-content_labels = sys.argv[3][1:-1].split(",")
-content_labels_status = sys.argv[4]
+print(sys.argv[2:])  # noqa
+title_labels = sys.argv[2][1:-1].split(",")
+title_labels_new = sys.argv[3][1:-1].split(",")
+content_labels = sys.argv[4][1:-1].split(",")
+content_labels_status = sys.argv[5]
 
 labels = [(label.name, label.color) for label in repo.get_labels()]
 title_labels = [


Test patch:
diff --git a/.github/workflows/periodic_tests.yml b/.github/workflows/periodic_tests.yml
index 9a32b66f..160a83d8 100644
--- a/.github/workflows/periodic_tests.yml
+++ b/.github/workflows/periodic_tests.yml
@@ -54,7 +54,11 @@ jobs:
           python-version: "3.10"
 
       - name: Install dependencies
-        run: python -m pip install .[all_extras,binder,dev]
+        uses: nick-fields/retry@v2
+        with:
+          timeout_minutes: 30
+          max_attempts: 3
+          command: python -m pip install .[all_extras,binder,dev]
 
       - name: Run example notebooks
         run: build_tools/run_examples.sh
@@ -71,7 +75,11 @@ jobs:
           python-version: "3.10"
 
       - name: Install aeon and dependencies
-        run: python -m pip install .[dev]
+        uses: nick-fields/retry@v2
+        with:
+          timeout_minutes: 30
+          max_attempts: 3
+          command: python -m pip install .[dev]
 
       - name: Show dependencies
         run: python -m pip list
@@ -93,7 +101,11 @@ jobs:
           python-version: "3.10"
 
       - name: Install aeon and dependencies
-        run: python -m pip install .[dev]
+        uses: nick-fields/retry@v2
+        with:
+          timeout_minutes: 30
+          max_attempts: 3
+          command: python -m pip install .[dev]
 
       - name: Show dependencies
         run: python -m pip list
@@ -118,7 +130,11 @@ jobs:
           python-version: ${{ matrix.python-version }}
 
       - name: Install aeon and dependencies
-        run: python -m pip install .[all_extras,dev]
+        uses: nick-fields/retry@v2
+        with:
+          timeout_minutes: 30
+          max_attempts: 3
+          command: python -m pip install .[all_extras,dev]
 
       - name: Show dependencies
         run: python -m pip list
@@ -140,7 +156,11 @@ jobs:
         run: echo "NUMBA_DISABLE_JIT=1" >> $GITHUB_ENV
 
       - name: Install aeon and dependencies
-        run: python -m pip install .[all_extras,unstable_extras,dev]
+        uses: nick-fields/retry@v2
+        with:
+          timeout_minutes: 30
+          max_attempts: 3
+          command: python -m pip install .[all_extras,unstable_extras,dev]
 
       - name: Show dependencies
         run: python -m pip list
diff --git a/.github/workflows/pr_pytest.yml b/.github/workflows/pr_pytest.yml
index 9d2caed2..b2d2cc45 100644
--- a/.github/workflows/pr_pytest.yml
+++ b/.github/workflows/pr_pytest.yml
@@ -28,7 +28,11 @@ jobs:
           python-version: "3.10"
 
       - name: Install aeon and dependencies
-        run: python -m pip install .[dev]
+        uses: nick-fields/retry@v2
+        with:
+          timeout_minutes: 30
+          max_attempts: 3
+          command: python -m pip install .[dev]
 
       - name: Show dependencies
         run: python -m pip list
@@ -51,7 +55,11 @@ jobs:
           python-version: "3.10"
 
       - name: Install aeon and dependencies
-        run: python -m pip install .[dev]
+        uses: nick-fields/retry@v2
+        with:
+          timeout_minutes: 30
+          max_attempts: 3
+          command: python -m pip install .[dev]
 
       - name: Show dependencies
         run: python -m pip list
@@ -77,7 +85,11 @@ jobs:
           python-version: ${{ matrix.python-version }}
 
       - name: Install aeon and dependencies
-        run: python -m pip install .[all_extras,dev]
+        uses: nick-fields/retry@v2
+        with:
+          timeout_minutes: 30
+          max_attempts: 3
+          command: python -m pip install .[all_extras,dev]
 
       - name: Show dependencies
         run: python -m pip list
@@ -104,7 +116,11 @@ jobs:
         run: echo "NUMBA_DISABLE_JIT=1" >> $GITHUB_ENV
 
       - name: Install aeon and dependencies
-        run: python -m pip install .[all_extras,unstable_extras,dev]
+        uses: nick-fields/retry@v2
+        with:
+          timeout_minutes: 30
+          max_attempts: 3
+          command: python -m pip install .[all_extras,unstable_extras,dev]
 
       - name: Show dependencies
         run: python -m pip list
diff --git a/aeon/datasets/tests/test_data_loaders.py b/aeon/datasets/tests/test_data_loaders.py
index 38d62e2b..8b76dd58 100644
--- a/aeon/datasets/tests/test_data_loaders.py
+++ b/aeon/datasets/tests/test_data_loaders.py
@@ -6,6 +6,7 @@ __all__ = []
 
 import os
 import shutil
+import tempfile
 
 import numpy as np
 import pandas as pd
@@ -158,6 +159,29 @@ def test__load_header_info():
         assert meta_data["classlabel"]
         assert meta_data["class_values"][0] == "1"
         assert meta_data["class_values"][1] == "2"
+    WRONG_STRINGS = ["@data 55", "@missing 42, @classlabel True"]
+    count = 1
+    with tempfile.TemporaryDirectory() as tmp:
+        for name in WRONG_STRINGS:
+            problem_name = f"temp{count}.ts"
+            load_path = os.path.join(tmp, problem_name)
+            temp_file = open(load_path, "w", encoding="utf-8")
+            temp_file.write(name)
+            temp_file.close()
+            with open(load_path, "r", encoding="utf-8") as file:
+                with pytest.raises(IOError):
+                    _load_header_info(file)
+            count = count + 1
+        name = "@missing true \n @classlabel True 0 1"
+        problem_name = "temp_correct.ts"
+        load_path = os.path.join(tmp, problem_name)
+        temp_file = open(load_path, "w", encoding="utf-8")
+        temp_file.write(name)
+        temp_file.close()
+        with open(load_path, "r", encoding="utf-8") as file:
+            meta = _load_header_info(file)
+            assert meta["missing"] is True
+            assert meta["classlabel"] is True
 
 
 def test__load_data():
@@ -182,6 +206,38 @@ def test__load_data():
         meta_data["equallength"] = True
         with pytest.raises(IOError):
             X, y, _ = _load_data(file, meta_data)
+    WRONG_DATA = [
+        "1.0,2.0,3.0:1.0,2.0,3.0, 4.0:0",
+        "1.0,2.0,3.0:1.0,2.0,3.0, 4.0:0\n1.0,2.0,3.0,4.0:0",
+    ]
+    meta_data = {"classlabel": True, "class_values": [0, 1], "equallength": False}
+    count = 1
+    with tempfile.TemporaryDirectory() as tmp:
+        for data in WRONG_DATA:
+            problem_name = f"temp{count}.ts"
+            load_path = os.path.join(tmp, problem_name)
+            temp_file = open(load_path, "w", encoding="utf-8")
+            temp_file.write(data)
+            temp_file.close()
+            with open(load_path, "r", encoding="utf-8") as file:
+                with pytest.raises(IOError):
+                    _load_data(file, meta_data)
+            count = count + 1
+        meta_data = {
+            "classlabel": True,
+            "class_values": [0, 1],
+            "equallength": True,
+            "univariate": True,
+        }
+        data = "1.0,2.0,3.0:0.0\n 1.0,2.0,3.0:1.0\n 2.0,3.0,4.0:0"
+        problem_name = "tempCorrect.ts"
+        load_path = os.path.join(tmp, problem_name)
+        temp_file = open(load_path, "w", encoding="utf-8")
+        temp_file.write(data)
+        temp_file.close()
+        with open(load_path, "r", encoding="utf-8") as file:
+            X, y, meta_data = _load_data(file, meta_data)
+            assert isinstance(X, np.ndarray)
 
 
 @pytest.mark.parametrize("return_X_y", [True, False])
diff --git a/aeon/datasets/tests/test_data_writers.py b/aeon/datasets/tests/test_data_writers.py
index 2ba1d683..c9b5a799 100644
--- a/aeon/datasets/tests/test_data_writers.py
+++ b/aeon/datasets/tests/test_data_writers.py
@@ -8,7 +8,12 @@ import pandas as pd
 import pytest
 
 from aeon.datasets import load_from_tsfile, write_to_tsfile
-from aeon.datasets._data_writers import _write_dataframe_to_tsfile
+from aeon.datasets._data_writers import (
+    _write_data_to_tsfile,
+    _write_dataframe_to_tsfile,
+    _write_header,
+    write_results_to_uea_format,
+)
 from aeon.datasets._dataframe_loaders import load_from_tsfile_to_dataframe
 from aeon.utils._testing.collection import (
     make_3d_test_data,
@@ -101,3 +106,71 @@ def test_write_dataframe_to_ts(tsfile_writer):
         pd.testing.assert_frame_equal(newX, X)
         y2 = pd.Series(y)
         pd.testing.assert_series_equal(y, y2)
+
+
+def test_write_data_to_tsfile():
+    with pytest.raises(TypeError, match="Wrong input data type"):
+        write_to_tsfile("A string", "path")
+    with pytest.raises(TypeError, match="Data provided must be a ndarray or a list"):
+        _write_data_to_tsfile("AFC", "49", "undefeated")
+    X, _ = make_3d_test_data(n_cases=6, n_timepoints=10, n_channels=1)
+    y = np.ndarray([0, 1, 1, 0, 1])
+    with pytest.raises(
+        IndexError,
+        match="The number of cases in X does not match the number of values in y",
+    ):
+        _write_data_to_tsfile(X, "temp", "temp", y=y)
+
+
+def test_write_results_to_uea_format():
+    with tempfile.TemporaryDirectory() as tmp:
+        y_true = np.array([0, 1, 1, 0, 0])
+        y_pred = np.array([0, 1, 1, 0])
+        with pytest.raises(
+            IndexError, match="The number of predicted values is not the same"
+        ):
+            write_results_to_uea_format(
+                "HC", "Testy", y_pred=y_pred, y_true=y_true, output_path=tmp
+            )
+        y_true = np.array([0, 1, 1, 0])
+        write_results_to_uea_format(
+            "HC",
+            "Testy",
+            y_pred=y_pred,
+            output_path=tmp,
+            full_path=False,
+            split="TEST",
+            timing_type="seconds",
+            first_line_comment="Hello",
+        )
+
+        probs = [[0.0, 1.0], [0.0, 1.0], [0.0, 1.0], [0.0, 1.0]]
+        write_results_to_uea_format(
+            "HC",
+            "Testy2",
+            y_pred=y_pred,
+            y_true=y_true,
+            output_path=tmp,
+            full_path=False,
+            split="TEST",
+            timing_type="seconds",
+            first_line_comment="Hello",
+            predicted_probs=probs,
+        )
+
+
+def test__write_header():
+    with tempfile.TemporaryDirectory() as tmp:
+        problem_name = "header.csv"
+        with pytest.raises(
+            ValueError, match="Cannot have class_labels true for a regression problem"
+        ):
+            _write_header(tmp, problem_name, class_labels=True, regression=True)
+    _write_header(
+        tmp,
+        problem_name,
+        suffix="_TRAIN",
+        extension=".csv",
+        comment="Hello",
+        regression=True,
+    )
diff --git a/aeon/datasets/tests/test_single_problem_loaders.py b/aeon/datasets/tests/test_single_problem_loaders.py
index b5649b5b..7b40d661 100644
--- a/aeon/datasets/tests/test_single_problem_loaders.py
+++ b/aeon/datasets/tests/test_single_problem_loaders.py
@@ -1,4 +1,6 @@
 """Test single problem loaders with varying return types."""
+import os
+
 import numpy as np
 import pandas as pd
 import pytest
@@ -13,9 +15,11 @@ from aeon.datasets import (  # Univariate; Unequal length; Multivariate
     load_osuleaf,
     load_plaid,
     load_solar,
+    load_tsf_to_dataframe,
     load_unit_test,
     load_unit_test_tsf,
 )
+from aeon.datasets._single_problem_loaders import MODULE
 
 UNIVARIATE_PROBLEMS = [
     load_acsf1,
@@ -77,6 +81,18 @@ def test_load_unit_test_tsf():
     assert not tuple[4]
 
 
+def test_basic_load_tsf_to_dataframe():
+    """Simple loader test."""
+
+    full_path = os.path.join(MODULE, "data", "UnitTest", "UnitTest_Tsf_Loader.tsf")
+    df, metadata = load_tsf_to_dataframe(full_path)
+    assert isinstance(df, pd.DataFrame)
+    assert metadata["frequency"] == "yearly"
+    assert metadata["forecast_horizon"] == 4
+    assert metadata["contain_missing_values"] is False
+    assert metadata["contain_equal_length"] is False
+
+
 def test_load_solar():
     solar = load_solar(api_version=None)
     assert type(solar) == pd.Series