Repository: aeye-lab/pymovements
Pull request #202 (2023-02-27 10:26:53)

Feature/test checks
## Description

Added tests for functions in `utils/checks.py` to improve test coverage.

Fixes issue #177 

## Implemented changes

- [x] Add test for check_no_zeros
- [x] Add test for check_nan_both_channels
- [x] Add test for check_shapes_positions_velocities

## Type of change

- [x] New feature (non-breaking change which adds functionality)

## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream modules
- [x] I have checked my code and corrected any misspellings


codecov-commenter: # [Codecov](https://codecov.io/gh/aeye-lab/pymovements/pull/202?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) Report
Base: **59.87**% // Head: **60.97**% // Increases project coverage by **`+1.10%`** :tada:
> Coverage data is based on head [(`b663e18`)](https://codecov.io/gh/aeye-lab/pymovements/pull/202?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) compared to base [(`c833d5a`)](https://codecov.io/gh/aeye-lab/pymovements/commit/c833d5a74b6b3ee9e050db7619d6507cfecf2015?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab).
> Patch has no changes to coverable lines.

> :exclamation: Current head b663e18 differs from pull request most recent head d5e5bb8. Consider uploading reports for the commit d5e5bb8 to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #202      +/-   ##
==========================================
+ Coverage   59.87%   60.97%   +1.10%     
==========================================
  Files          21       21              
  Lines         775      802      +27     
  Branches      178      188      +10     
==========================================
+ Hits          464      489      +25     
- Misses        299      303       +4     
+ Partials       12       10       -2     
```


| [Impacted Files](https://codecov.io/gh/aeye-lab/pymovements/pull/202?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) | Coverage Δ | |
|---|---|---|
| [src/pymovements/transforms.py](https://codecov.io/gh/aeye-lab/pymovements/pull/202?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL3RyYW5zZm9ybXMucHk=) | `82.24% <0.00%> (-0.33%)` | :arrow_down: |
| [src/pymovements/events/ivt.py](https://codecov.io/gh/aeye-lab/pymovements/pull/202?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2V2ZW50cy9pdnQucHk=) | `100.00% <0.00%> (ø)` | |
| [src/pymovements/events/events.py](https://codecov.io/gh/aeye-lab/pymovements/pull/202?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2V2ZW50cy9ldmVudHMucHk=) | `100.00% <0.00%> (ø)` | |
| [src/pymovements/synthetic/step\_function.py](https://codecov.io/gh/aeye-lab/pymovements/pull/202?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL3N5bnRoZXRpYy9zdGVwX2Z1bmN0aW9uLnB5) | `100.00% <0.00%> (ø)` | |
| [src/pymovements/events/engbert.py](https://codecov.io/gh/aeye-lab/pymovements/pull/202?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2V2ZW50cy9lbmdiZXJ0LnB5) | `95.55% <0.00%> (+0.31%)` | :arrow_up: |
| [src/pymovements/datasets/dataset.py](https://codecov.io/gh/aeye-lab/pymovements/pull/202?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2RhdGFzZXRzL2RhdGFzZXQucHk=) | `25.12% <0.00%> (+1.28%)` | :arrow_up: |
| [src/pymovements/events/idt.py](https://codecov.io/gh/aeye-lab/pymovements/pull/202?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2V2ZW50cy9pZHQucHk=) | `100.00% <0.00%> (+6.45%)` | :arrow_up: |
| [src/pymovements/utils/checks.py](https://codecov.io/gh/aeye-lab/pymovements/pull/202?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL3V0aWxzL2NoZWNrcy5weQ==) | `100.00% <0.00%> (+16.66%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/aeye-lab/pymovements/pull/202?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab).


Patch:
diff --git a/src/pymovements/datasets/dataset.py b/src/pymovements/datasets/dataset.py
index cf4ff2b..4bd93f8 100644
--- a/src/pymovements/datasets/dataset.py
+++ b/src/pymovements/datasets/dataset.py
@@ -35,17 +35,29 @@ from pymovements.utils.paths import get_filepaths
 
 class Dataset:
     """Dataset base class."""
+    # pylint: disable=too-many-instance-attributes
+    # The Dataset class is exceptionally complex and needs many attributes.
 
     def __init__(
-        self,
-        root: str | Path,
-        experiment: Experiment | None = None,
-        filename_regex: str = '.*',
-        filename_regex_dtypes: dict[str, type] | None = None,
-        custom_read_kwargs: dict[str, Any] | None = None,
+            self,
+            root: str | Path,
+            experiment: Experiment | None = None,
+            filename_regex: str = '.*',
+            filename_regex_dtypes: dict[str, type] | None = None,
+            custom_read_kwargs: dict[str, Any] | None = None,
+            dataset_dirname: str = '.',
+            raw_dirname: str = 'raw',
+            preprocessed_dirname: str = 'preprocessed',
+            events_dirname: str = 'events',
     ):
         """Initialize the dataset object.
 
+        You can set up a custom directory structure by populating the particular dirname attributes.
+        See :py:attr:`~pymovements.dataset.Dataset.dataset_dirname`,
+        :py:attr:`~pymovements.dataset.Dataset.raw_dirname`,
+        :py:attr:`~pymovements.dataset.Dataset.preprocessed_dirname` and
+        :py:attr:`~pymovements.dataset.Dataset.events_dirname` for details.
+
         Parameters
         ----------
         root : str, Path
@@ -60,12 +72,30 @@ class Dataset:
             specific named groups to a particular datatype.
         custom_read_kwargs : dict[str, Any], optional
             If specified, these keyword arguments will be passed to the file reading function.
+        dataset_dirname : str, optional
+            Dataset directory name under root path. Can be `.` if dataset is located in root path.
+            Default: `.`
+        raw_dirname ; str, optional
+            Name of directory under dataset path that contains raw data. Can be `.` if raw data is
+            located in dataset path. We advise the user to keep the original raw data separate from
+            the preprocessed / event data. Default: `raw`
+        preprocessed_dirname : str, optional
+            Name of directory under dataset path that will be used to store preprocessed data. We
+            advise the user to keep the preprocessed data separate from the original raw data.
+            Default: `preprocessed`
+        events_dirname : str, optional
+            Name of directory under dataset path that will be used to store event data. We advise
+            the user to keep the event data separate from the original raw data. Default: `events`
         """
         self.fileinfo: pl.DataFrame = pl.DataFrame()
         self.gaze: list[pl.DataFrame] = []
         self.events: list[pl.DataFrame] = []
 
-        self.root = Path(root)
+        self._root = Path(root)
+        self.dataset_dirname = dataset_dirname
+        self.raw_dirname = raw_dirname
+        self.preprocessed_dirname = preprocessed_dirname
+        self.events_dirname = events_dirname
 
         self.experiment = experiment
 
@@ -545,71 +575,131 @@ class Dataset:
             gaze_df.write_ipc(preprocessed_filepath)
 
     @property
-    def rootpath(self) -> Path:
-        """Get the path to the dataset directory.
+    def path(self) -> Path:
+        """The path to the dataset directory.
+
+        The dataset path points to the dataset directory under the root path. Per default the
+        dataset path points to the exact same directory as the root path. Add ``dataset_dirname``
+        to your initialization call to specify an explicit dataset directory in your root path.
+
+        Example
+        -------
+
+        The default behavior is to locate the dataset in the root path without assuming a further
+        dataset directory:
+        >>> dataset = Dataset(root='/path/to/your/dataset', dataset_dirname='.')
+        >>> dataset.path  # doctest: +SKIP
+        Path('/path/to/your/dataset')
+
+        To locate the dataset you can also specify an explicit dataset directory name in the root
+        path:
+        >>> dataset = Dataset(root='path/to/all/your/datasets/', dataset_dirname='your_dataset')
+        >>> dataset.path  # doctest: +SKIP
+        Path('path/to/all/your/datasets/your_dataset')
+        """
+        return self.root / self.dataset_dirname
 
-        The dataset path points to a directory in the specified root directory which is named the
-        same as the respective class.
+    @property
+    def root(self) -> Path:
+        """The root path to your dataset.
 
         Example
         -------
-        >>> class CustomDataset(Dataset):
-        ...     pass
-        >>> dataset = CustomDataset(root='data')
-        >>> dataset.rootpath  # doctest: +SKIP
-        Path('data/CustomDataset')
+        >>> dataset = Dataset(root='/path/to/your/dataset')
+        >>> dataset.root  # doctest: +SKIP
+        Path('/path/to/your/dataset')
+
+        The root stays unaffected by the dataset directory name:
+        >>> dataset = Dataset(root='/path/to/', dataset_dirname='your_dataset')
+        >>> dataset.root  # doctest: +SKIP
+        Path('/path/to')
         """
-        return self.root / self.__class__.__name__
+        return self._root
 
     @property
     def events_rootpath(self) -> Path:
-        """Get the path to the directory of the event data.
+        """The path to the directory of the event data.
 
-        The event data directory path points to a directory named `events` in the dataset
-        `rootpath`.
+        The path points to the events directory under the root path.
 
         Example
         -------
-        >>> class CustomDataset(Dataset):
-        ...     pass
-        >>> dataset = CustomDataset(root='data')
+
+        >>> dataset = Dataset(root='/path/to/your/datasets/', dataset_dirname='your_dataset')
         >>> dataset.events_rootpath  # doctest: +SKIP
-        Path('data/CustomDataset/events')
+        Path('/path/to/your/datasets/your_dataset/events')
+
+        You can also explicitely specify the event directory name. The default is `events`.
+        >>> dataset = Dataset(
+        ...     root='/path/to/your/datasets/',
+        ...     dataset_dirname='your_dataset',
+        ...     events_dirname='your_events',
+        ... )
+        >>> dataset.events_rootpath  # doctest: +SKIP
+        Path('/path/to/your/datasets/your_dataset/your_events')
         """
-        return self.rootpath / 'events'
+        return self.path / self.events_dirname
 
     @property
     def preprocessed_rootpath(self) -> Path:
-        """Get the path to the directory of the preprocessed gaze data.
+        """The path to the directory of the preprocessed gaze data.
 
-        The preprocessed data directory path points to a directory named `preprocessed` in the
-        dataset `rootpath`.
+        The path points to the preprocessed data directory under the root path.
 
         Example
         -------
-        >>> class CustomDataset(Dataset):
-        ...     pass
-        >>> dataset = CustomDataset(root='data')
+
+        >>> dataset = Dataset(root='/path/to/your/datasets/', dataset_dirname='your_dataset')
         >>> dataset.preprocessed_rootpath  # doctest: +SKIP
-        Path('data/CustomDataset/preprocessed')
+        Path('/path/to/your/datasets/your_dataset/preprocessed')
+
+        You can also explicitely specify the preprocessed directory name. The default is
+        `preprocessed`.
+        >>> dataset = Dataset(
+        ...     root='/path/to/your/datasets/',
+        ...     dataset_dirname='your_dataset',
+        ...     preprocessed_dirname='your_preprocessed_data',
+        ... )
+        >>> dataset.preprocessed_rootpath  # doctest: +SKIP
+        Path('/path/to/your/datasets/your_dataset/your_preprocessed_data')
         """
-        return self.rootpath / 'preprocessed'
+        return self.path / self.preprocessed_dirname
 
     @property
     def raw_rootpath(self) -> Path:
-        """Get the path to the directory of the raw data.
+        """The path to the directory of the raw data.
 
-        The raw data directory path points to a directory named `raw` in the dataset `rootpath`.
+        The path points to the raw data directory under the root path.
 
         Example
         -------
-        >>> class CustomDataset(Dataset):
-        ...     pass
-        >>> dataset = CustomDataset(root='data')
+
+        >>> dataset = Dataset(root='/path/to/your/datasets/', dataset_dirname='your_dataset')
+        >>> dataset.preprocessed_rootpath  # doctest: +SKIP
+        Path('/path/to/your/datasets/your_dataset/raw')
+
+        You can also explicitely specify the preprocessed directory name. The default is `raw`.
+        >>> dataset = Dataset(
+        ...     root='/path/to/your/datasets/',
+        ...     dataset_dirname='your_dataset',
+        ...     raw_dirname='your_raw_data',
+        ... )
+        >>> dataset.raw_rootpath  # doctest: +SKIP
+        Path('/path/to/your/datasets/your_dataset/your_raw_data')
+
+        If your raw data is not in a separate directory under the root path then you can also
+        specify `.` as the directory name. We discourage this and advise the user to keep raw data
+        and preprocessed data separated.
+        ... dataset = Dataset(
+        >>> dataset = Dataset(
+        ...     root='/path/to/your/datasets/',
+        ...     dataset_dirname='your_dataset',
+        ...     raw_dirname='.',
+        ... )
         >>> dataset.raw_rootpath  # doctest: +SKIP
-        Path('data/CustomDataset/raw')
+        Path('/path/to/your/datasets/your_dataset/your_raw_data')
         """
-        return self.rootpath / 'raw'
+        return self.path / self.raw_dirname
 
     def _check_fileinfo(self) -> None:
         """Check if fileinfo attribute is set and there is at least one row present."""
diff --git a/src/pymovements/datasets/gazebase.py b/src/pymovements/datasets/gazebase.py
index 0c9c865..eb488a0 100644
--- a/src/pymovements/datasets/gazebase.py
+++ b/src/pymovements/datasets/gazebase.py
@@ -18,6 +18,10 @@
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 # SOFTWARE.
 """This module provides an interface to the GazeBase dataset."""
+from __future__ import annotations
+
+from pathlib import Path
+
 from pymovements.base import Experiment
 from pymovements.datasets.public_dataset import PublicDataset
 
@@ -49,6 +53,9 @@ class GazeBase(PublicDataset):
     ... )
     >>> dataset.load()  # doctest: +SKIP
     """
+    # pylint: disable=similarities
+    # The PublicDataset child classes potentially share code chunks for definitions.
+
     _mirrors = [
         'https://figshare.com/ndownloader/files/',
     ]
@@ -97,11 +104,102 @@ class GazeBase(PublicDataset):
         'new_columns': list(_column_map.values()),
     }
 
-    def __init__(self, **kwargs):
+    def __init__(
+            self,
+            root: str | Path,
+            download: bool = False,
+            extract: bool = False,
+            remove_finished: bool = False,
+            dataset_dirname: str = 'GazeBase',
+            downloads_dirname: str = 'downloads',
+            raw_dirname: str = 'raw',
+            preprocessed_dirname: str = 'preprocessed',
+            events_dirname: str = 'events',
+    ):
+        """Initialize the GazeBase dataset object.
+
+        If desired, dataset resources are downloaded with ``download=True`` and extracted with
+        ``extract=True``. To save space on your device you can remove the archive files after
+        successful extraction with ``remove_finished=True``.
+
+        Downloaded archives are automatically checked for integrity by comparing MD5 checksums.
+
+        You can set up a custom directory structure by populating the particular dirname attributes.
+        See :py:attr:`~pymovements.dataset.PublicDataset.dataset_dirname`,
+        :py:attr:`~pymovements.dataset.PublicDataset.raw_dirname`,
+        :py:attr:`~pymovements.dataset.PublicDataset.preprocessed_dirname` and
+        :py:attr:`~pymovements.dataset.PublicDataset.events_dirname` and
+        :py:attr:`~pymovements.dataset.PublicDataset.downloads_dirname` for details.
+
+        Parameters
+        ----------
+        root : str, Path
+            Path to the root directory of the dataset.
+        download : bool
+            Download all dataset resources.
+        extract : bool
+            Extract dataset archive files.
+        remove_finished : bool
+            Remove archive files after extraction.
+        dataset_dirname : str, optional
+            Dataset directory name under root path. Can be `.` if dataset is located in root path.
+            Default: `.`
+        downloads_dirname : str, optional
+            Name of directory to store downloaded data.Default: `downloads`
+        raw_dirname ; str, optional
+            Name of directory under dataset path that contains raw data. Can be `.` if raw data is
+            located in dataset path. We advise the user to keep the original raw data separate from
+            the preprocessed / event data. Default: `raw`
+        preprocessed_dirname : str, optional
+            Name of directory under dataset path that will be used to store preprocessed data. We
+            advise the user to keep the preprocessed data separate from the original raw data.
+            Default: `preprocessed`
+        events_dirname : str, optional
+            Name of directory under dataset path that will be used to store event data. We advise
+            the user to keep the event data separate from the original raw data. Default: `events`
+        """
         super().__init__(
+            root=root,
+            download=download,
+            extract=extract,
+            remove_finished=remove_finished,
             experiment=self._experiment,
             filename_regex=self._filename_regex,
             filename_regex_dtypes=self._filename_regex_dtypes,
             custom_read_kwargs=self._read_csv_kwargs,
-            **kwargs,
+            dataset_dirname=dataset_dirname,
+            downloads_dirname=downloads_dirname,
+            raw_dirname=raw_dirname,
+            preprocessed_dirname=preprocessed_dirname,
+            events_dirname=events_dirname,
         )
+
+    @property
+    def path(self) -> Path:
+        """Get the path to the dataset directory.
+
+        The dataset path points to the dataset directory under the root path. Per default the
+        dataset directory name is equal to the class name.
+
+        Example
+        -------
+        The default behaviour is to locate the data set in a directory under the root path with the
+        same name as the class name:
+        >>> dataset = GazeBase(root='/path/to/all/your/datasets')
+        >>> dataset.path  # doctest: +SKIP
+        Path('/path/to/all/your/datasets/GazeBase')
+
+        You can specify an explicit dataset directory name:
+        >>> dataset = GazeBase(
+        ...     root='/path/to/all/your/datasets',
+        ...     dataset_dirname='gazebase',
+        ... )
+        >>> dataset.path  # doctest: +SKIP
+        Path('/path/to/all/your/datasets/gazebase')
+
+        You can specify to use the root path to be the actual dataset directory:
+        >>> dataset = GazeBase(root='/path/to/gazebase/dataset', dataset_dirname='.')
+        >>> dataset.path  # doctest: +SKIP
+        Path('/path/to/gazebase/dataset')
+        """
+        return super().path
diff --git a/src/pymovements/datasets/judo1000.py b/src/pymovements/datasets/judo1000.py
index 673b97c..1b996e6 100644
--- a/src/pymovements/datasets/judo1000.py
+++ b/src/pymovements/datasets/judo1000.py
@@ -18,6 +18,10 @@
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 # SOFTWARE.
 """This module provides an interface to the JuDo1000 dataset."""
+from __future__ import annotations
+
+from pathlib import Path
+
 from pymovements.base import Experiment
 from pymovements.datasets.public_dataset import PublicDataset
 
@@ -45,6 +49,9 @@ class JuDo1000(PublicDataset):
     ... )
     >>> dataset.load()  # doctest: +SKIP
     """
+    # pylint: disable=similarities
+    # The PublicDataset child classes potentially share code chunks for definitions.
+
     _mirrors = [
         'https://osf.io/download/',
     ]
@@ -90,11 +97,102 @@ class JuDo1000(PublicDataset):
         'new_columns': list(_column_map.values()),
     }
 
-    def __init__(self, **kwargs):
+    def __init__(
+            self,
+            root: str | Path,
+            download: bool = False,
+            extract: bool = False,
+            remove_finished: bool = False,
+            dataset_dirname: str = 'JuDo1000',
+            downloads_dirname: str = 'downloads',
+            raw_dirname: str = 'raw',
+            preprocessed_dirname: str = 'preprocessed',
+            events_dirname: str = 'events',
+    ):
+        """Initialize the JuDo1000 dataset object.
+
+        If desired, dataset resources are downloaded with ``download=True`` and extracted with
+        ``extract=True``. To save space on your device you can remove the archive files after
+        successful extraction with ``remove_finished=True``.
+
+        Downloaded archives are automatically checked for integrity by comparing MD5 checksums.
+
+        You can set up a custom directory structure by populating the particular dirname attributes.
+        See :py:attr:`~pymovements.dataset.PublicDataset.dataset_dirname`,
+        :py:attr:`~pymovements.dataset.PublicDataset.raw_dirname`,
+        :py:attr:`~pymovements.dataset.PublicDataset.preprocessed_dirname` and
+        :py:attr:`~pymovements.dataset.PublicDataset.events_dirname` and
+        :py:attr:`~pymovements.dataset.PublicDataset.downloads_dirname` for details.
+
+        Parameters
+        ----------
+        root : str, Path
+            Path to the root directory of the dataset.
+        download : bool
+            Download all dataset resources.
+        extract : bool
+            Extract dataset archive files.
+        remove_finished : bool
+            Remove archive files after extraction.
+        dataset_dirname : str, optional
+            Dataset directory name under root path. Can be `.` if dataset is located in root path.
+            Default: `.`
+        downloads_dirname : str, optional
+            Name of directory to store downloaded data.Default: `downloads`
+        raw_dirname ; str, optional
+            Name of directory under dataset path that contains raw data. Can be `.` if raw data is
+            located in dataset path. We advise the user to keep the original raw data separate from
+            the preprocessed / event data. Default: `raw`
+        preprocessed_dirname : str, optional
+            Name of directory under dataset path that will be used to store preprocessed data. We
+            advise the user to keep the preprocessed data separate from the original raw data.
+            Default: `preprocessed`
+        events_dirname : str, optional
+            Name of directory under dataset path that will be used to store event data. We advise
+            the user to keep the event data separate from the original raw data. Default: `events`
+        """
         super().__init__(
+            root=root,
+            download=download,
+            extract=extract,
+            remove_finished=remove_finished,
             experiment=self._experiment,
             filename_regex=self._filename_regex,
             filename_regex_dtypes=self._filename_regex_dtypes,
             custom_read_kwargs=self._read_csv_kwargs,
-            **kwargs,
+            dataset_dirname=dataset_dirname,
+            downloads_dirname=downloads_dirname,
+            raw_dirname=raw_dirname,
+            preprocessed_dirname=preprocessed_dirname,
+            events_dirname=events_dirname,
         )
+
+    @property
+    def path(self) -> Path:
+        """The path to the dataset directory.
+
+        The dataset path points to the dataset directory under the root path. Per default the
+        dataset directory name is equal to the class name.
+
+        Example
+        -------
+        The default behaviour is to locate the data set in a directory under the root path with the
+        same name as the class name:
+        >>> dataset = JuDo1000(root='/path/to/all/your/datasets')
+        >>> dataset.path  # doctest: +SKIP
+        Path('/path/to/all/your/datasets/JuDo1000')
+
+        You can specify an explicit dataset directory name:
+        >>> dataset = JuDo1000(
+        ...     root='/path/to/all/your/datasets',
+        ...     dataset_dirname='judo',
+        ... )
+        >>> dataset.path  # doctest: +SKIP
+        Path('/path/to/all/your/datasets/judo')
+
+        You can specify to use the root path to be the actual dataset directory:
+        >>> dataset = JuDo1000(root='/path/to/judo/dataset', dataset_dirname='.')
+        >>> dataset.path  # doctest: +SKIP
+        Path('/path/to/judo/dataset')
+        """
+        return super().path
diff --git a/src/pymovements/datasets/public_dataset.py b/src/pymovements/datasets/public_dataset.py
index dac3f44..557e051 100644
--- a/src/pymovements/datasets/public_dataset.py
+++ b/src/pymovements/datasets/public_dataset.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2023-2023 The pymovements Project Authors
+# Copyright (c) 2023 The pymovements Project Authors
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
 # of this software and associated documentation files (the "Software"), to deal
@@ -22,8 +22,11 @@ from __future__ import annotations
 
 from abc import ABCMeta
 from abc import abstractmethod
+from pathlib import Path
+from typing import Any
 from urllib.error import URLError
 
+from pymovements.base import Experiment
 from pymovements.datasets.dataset import Dataset
 from pymovements.utils.archives import extract_archive
 from pymovements.utils.downloads import download_file
@@ -38,27 +41,90 @@ class PublicDataset(Dataset, metaclass=ABCMeta):
     """
 
     def __init__(
-        self,
-        root: str,
-        download: bool = False,
-        extract: bool = False,
-        remove_finished: bool = False,
-        **kwargs,
+            self,
+            root: str | Path,
+            download: bool = False,
+            extract: bool = False,
+            remove_finished: bool = False,
+            experiment: Experiment | None = None,
+            filename_regex: str = '.*',
+            filename_regex_dtypes: dict[str, type] | None = None,
+            custom_read_kwargs: dict[str, Any] | None = None,
+            dataset_dirname: str | None = None,
+            downloads_dirname: str = 'downloads',
+            raw_dirname: str = 'raw',
+            preprocessed_dirname: str = 'preprocessed',
+            events_dirname: str = 'events',
     ):
-        """Initialize dataset.
+        """Initialize the public dataset object.
 
-        If desired, dataset resources will be downloaded and extracted.
+        If desired, dataset resources are downloaded with ``download=True`` and extracted with
+        ``extract=True``. To save space on your device you can remove the archive files after
+        successful extraction with ``remove_finished=True``.
+
+        Downloaded archives are automatically checked for integrity by comparing MD5 checksums.
+
+        You can set up a custom directory structure by populating the particular dirname attributes.
+        See :py:attr:`~pymovements.dataset.PublicDataset.dataset_dirname`,
+        :py:attr:`~pymovements.dataset.PublicDataset.raw_dirname`,
+        :py:attr:`~pymovements.dataset.PublicDataset.preprocessed_dirname` and
+        :py:attr:`~pymovements.dataset.PublicDataset.events_dirname` and
+        :py:attr:`~pymovements.dataset.PublicDataset.downloads_dirname` for details.
 
         Parameters
         ----------
+        root : str, Path
+            Path to the root directory of the dataset.
         download : bool
             Download all dataset resources.
         extract : bool
             Extract dataset archive files.
         remove_finished : bool
             Remove archive files after extraction.
+        experiment : Experiment
+            The experiment definition.
+        filename_regex : str
+            Regular expression which needs to be matched before trying to load the file. Named
+            groups will appear in the `fileinfo` dataframe.
+        filename_regex_dtypes : dict[str, type], optional
+            If named groups are present in the `filename_regex`, this makes it possible to cast
+            specific named groups to a particular datatype.
+        custom_read_kwargs : dict[str, Any], optional
+            If specified, these keyword arguments will be passed to the file reading function.
+        dataset_dirname : str, optional
+            Dataset directory name under root path. Can be `.` if dataset is located in root path.
+            Default: `.`
+        downloads_dirname : str, optional
+            Name of directory to store downloaded data.Default: `downloads`
+        raw_dirname ; str, optional
+            Name of directory under dataset path that contains raw data. Can be `.` if raw data is
+            located in dataset path. We advise the user to keep the original raw data separate from
+            the preprocessed / event data. Default: `raw`
+        preprocessed_dirname : str, optional
+            Name of directory under dataset path that will be used to store preprocessed data. We
+            advise the user to keep the preprocessed data separate from the original raw data.
+            Default: `preprocessed`
+        events_dirname : str, optional
+            Name of directory under dataset path that will be used to store event data. We advise
+            the user to keep the event data separate from the original raw data. Default: `events`
         """
-        super().__init__(root=root, **kwargs)
+        if dataset_dirname is None:
+            dataset_dirname = self.__class__.__name__
+
+        super().__init__(
+            root=root,
+            experiment=experiment,
+            filename_regex=filename_regex,
+            filename_regex_dtypes=filename_regex_dtypes,
+            custom_read_kwargs=custom_read_kwargs,
+            dataset_dirname=dataset_dirname,
+            raw_dirname=raw_dirname,
+            preprocessed_dirname=preprocessed_dirname,
+            events_dirname=events_dirname,
+        )
+
+        self.downloads_dirname = downloads_dirname
+
         if download:
             self.download()
 
@@ -85,7 +151,7 @@ class PublicDataset(Dataset, metaclass=ABCMeta):
                 try:
                     download_file(
                         url=url,
-                        dirpath=self.rootpath,
+                        dirpath=self.root,
                         filename=resource['filename'],
                         md5=resource['md5'],
                     )
@@ -116,7 +182,7 @@ class PublicDataset(Dataset, metaclass=ABCMeta):
 
         for resource in self._resources:
             extract_archive(
-                source_path=self.rootpath / resource['filename'],
+                source_path=self.downloads_rootpath / resource['filename'],
                 destination_path=self.raw_rootpath,
                 recursive=True,
                 remove_finished=remove_finished,
@@ -143,3 +209,90 @@ class PublicDataset(Dataset, metaclass=ABCMeta):
 
         All values should be of type string.
         """
+
+    @property
+    def path(self) -> Path:
+        """The path to the dataset directory.
+
+        The dataset path points to the dataset directory under the root path. Per default the
+        dataset directory name is equal to the class name.
+
+        Example
+        -------
+        Let's define a custom class first with one mirror and one resource.
+        >>> class CustomPublicDataset(PublicDataset):
+        ...     _mirrors = ['https://www.example.com/']
+        ...     _resources = [
+        ...         {
+        ...             'resource': 'relative_path_to_resource',
+        ...             'filename': 'example_dataset.zip',
+        ...             'md5': 'md5md5md5md5md5md5md5md5md5md5md',
+        ...         },
+        ...     ]
+
+        The default behaviour is to locate the data set in a directory under the root path with the
+        same name as the class name:
+        >>> dataset = CustomPublicDataset(root='/path/to/all/your/datasets')
+        >>> dataset.path  # doctest: +SKIP
+        Path('/path/to/all/your/datasets/CustomPublicDataset')
+
+        You can specify an explicit dataset directory name:
+        >>> dataset = CustomPublicDataset(
+        ...     root='/path/to/all/your/datasets',
+        ...     dataset_dirname='custom_dataset',
+        ... )
+        >>> dataset.path  # doctest: +SKIP
+        Path('/path/to/all/your/datasets/custom_dataset')
+
+        You can specify to use the root path to be the actual dataset directory:
+        >>> dataset = CustomPublicDataset(root='/path/to/your/dataset', dataset_dirname='.')
+        >>> dataset.path  # doctest: +SKIP
+        Path('/path/to/your/dataset')
+        """
+        return super().path
+
+    @property
+    def downloads_rootpath(self) -> Path:
+        """The path to the directory of the raw data.
+
+        The download path points to the download directory under the root path.
+
+        Example
+        -------
+        Let's define a custom class first with one mirror and one resource.
+        >>> class CustomPublicDataset(PublicDataset):
+        ...     _mirrors = ['https://www.example.com/']
+        ...     _resources = [
+        ...         {
+        ...             'resource': 'relative_path_to_resource',
+        ...             'filename': 'example_dataset.zip',
+        ...             'md5': 'md5md5md5md5md5md5md5md5md5md5md',
+        ...         },
+        ...     ]
+
+        >>> dataset = CustomPublicDataset(root='/path/to/your/datasets/')
+        >>> dataset.downloads_rootpath  # doctest: +SKIP
+        Path('/path/to/your/datasets/CustomPublicDataset/downloads')
+
+        You can also explicitely specify the preprocessed directory name. The default is `raw`.
+        >>> dataset = CustomPublicDataset(
+        ...     root='/path/to/your/datasets/',
+        ...     dataset_dirname='your_dataset',
+        ...     downloads_dirname='your_raw_data',
+        ... )
+        >>> dataset.downloads_rootpath  # doctest: +SKIP
+        Path('/path/to/your/datasets/your_dataset/your_raw_data')
+
+        If your raw data is not in a separate directory under the root path then you can also
+        specify `.` as the directory name. We discourage this and advise the user to keep raw data
+        and preprocessed data separated.
+        ... dataset = Dataset(
+        >>> dataset = CustomPublicDataset(
+        ...     root='/path/to/your/datasets/',
+        ...     dataset_dirname='your_dataset',
+        ...     raw_dirname='.',
+        ... )
+        >>> dataset.preprocessed_rootpath  # doctest: +SKIP
+        Path('/path/to/your/datasets/your_dataset/your_raw_data')
+        """
+        return self.path / self.downloads_dirname


Test patch:
diff --git a/tests/datasets/test_dataset.py b/tests/datasets/test_dataset.py
index 87aa9f5..88de246 100644
--- a/tests/datasets/test_dataset.py
+++ b/tests/datasets/test_dataset.py
@@ -18,6 +18,8 @@
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 # SOFTWARE.
 """Test all functionality in pymovements.datasets.dataset."""
+from pathlib import Path
+
 import polars as pl
 import pytest
 from polars.testing import assert_frame_equal
@@ -61,7 +63,6 @@ from pymovements.events.events import Event
     ],
 )
 def test_clear_events(events_init, events_expected):
-    """Test if idt detects fixations."""
     dataset = Dataset(root='data')
     dataset.events = events_init
     dataset.clear_events()
@@ -72,3 +73,87 @@ def test_clear_events(events_init, events_expected):
     else:
         for events_df_result, events_df_expected in zip(dataset.events, events_expected):
             assert_frame_equal(events_df_result, events_df_expected)
+
+
+@pytest.mark.parametrize(
+    'init_kwargs, expected_paths',
+    [
+        pytest.param(
+            {'root': '/data/set/path'},
+            {
+                'root': Path('/data/set/path/'),
+                'path': Path('/data/set/path/'),
+                'raw': Path('/data/set/path/raw'),
+                'preprocessed': Path('/data/set/path/preprocessed'),
+                'events': Path('/data/set/path/events'),
+            },
+        ),
+        pytest.param(
+            {'root': '/data/set/path', 'dataset_dirname': '.'},
+            {
+                'root': Path('/data/set/path/'),
+                'path': Path('/data/set/path/'),
+                'raw': Path('/data/set/path/raw'),
+                'preprocessed': Path('/data/set/path/preprocessed'),
+                'events': Path('/data/set/path/events'),
+            },
+        ),
+        pytest.param(
+            {'root': '/data/set/path', 'dataset_dirname': 'dataset'},
+            {
+                'root': Path('/data/set/path/'),
+                'path': Path('/data/set/path/dataset'),
+                'raw': Path('/data/set/path/dataset/raw'),
+                'preprocessed': Path('/data/set/path/dataset/preprocessed'),
+                'events': Path('/data/set/path/dataset/events'),
+            },
+        ),
+        pytest.param(
+            {
+                'root': '/data/set/path', 'dataset_dirname': 'dataset',
+                'events_dirname': 'custom_events',
+            },
+            {
+                'root': Path('/data/set/path/'),
+                'path': Path('/data/set/path/dataset'),
+                'raw': Path('/data/set/path/dataset/raw'),
+                'preprocessed': Path('/data/set/path/dataset/preprocessed'),
+                'events': Path('/data/set/path/dataset/custom_events'),
+            },
+        ),
+        pytest.param(
+            {
+                'root': '/data/set/path', 'dataset_dirname': 'dataset',
+                'preprocessed_dirname': 'custom_preprocessed',
+            },
+            {
+                'root': Path('/data/set/path/'),
+                'path': Path('/data/set/path/dataset'),
+                'raw': Path('/data/set/path/dataset/raw'),
+                'preprocessed': Path('/data/set/path/dataset/custom_preprocessed'),
+                'events': Path('/data/set/path/dataset/events'),
+            },
+        ),
+        pytest.param(
+            {
+                'root': '/data/set/path', 'dataset_dirname': 'dataset',
+                'raw_dirname': 'custom_raw',
+            },
+            {
+                'root': Path('/data/set/path/'),
+                'path': Path('/data/set/path/dataset'),
+                'raw': Path('/data/set/path/dataset/custom_raw'),
+                'preprocessed': Path('/data/set/path/dataset/preprocessed'),
+                'events': Path('/data/set/path/dataset/events'),
+            },
+        ),
+    ],
+)
+def test_paths(init_kwargs, expected_paths):
+    dataset = Dataset(**init_kwargs)
+
+    assert dataset.root == expected_paths['root']
+    assert dataset.path == expected_paths['path']
+    assert dataset.raw_rootpath == expected_paths['raw']
+    assert dataset.preprocessed_rootpath == expected_paths['preprocessed']
+    assert dataset.events_rootpath == expected_paths['events']
diff --git a/tests/datasets/test_gazebase.py b/tests/datasets/test_gazebase.py
new file mode 100644
index 0000000..53ad744
--- /dev/null
+++ b/tests/datasets/test_gazebase.py
@@ -0,0 +1,70 @@
+# Copyright (c) 2023 The pymovements Project Authors
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+"""Test all functionality in pymovements.datasets.gazebase."""
+from pathlib import Path
+
+import pytest
+
+from pymovements.datasets.gazebase import GazeBase
+
+
+@pytest.mark.parametrize(
+    'init_kwargs, expected_paths',
+    [
+        pytest.param(
+            {'root': '/data/set/path'},
+            {
+                'root': Path('/data/set/path/'),
+                'path': Path('/data/set/path/GazeBase'),
+                'download': Path('/data/set/path/GazeBase/downloads'),
+            },
+        ),
+        pytest.param(
+            {'root': '/data/set/path', 'dataset_dirname': '.'},
+            {
+                'root': Path('/data/set/path/'),
+                'path': Path('/data/set/path/'),
+                'download': Path('/data/set/path/downloads'),
+            },
+        ),
+        pytest.param(
+            {'root': '/data/set/path', 'dataset_dirname': 'dataset'},
+            {
+                'root': Path('/data/set/path/'),
+                'path': Path('/data/set/path/dataset'),
+                'download': Path('/data/set/path/dataset/downloads'),
+            },
+        ),
+        pytest.param(
+            {'root': '/data/set/path', 'downloads_dirname': 'custom_download_dirname'},
+            {
+                'root': Path('/data/set/path/'),
+                'path': Path('/data/set/path/GazeBase'),
+                'download': Path('/data/set/path/GazeBase/custom_download_dirname'),
+            },
+        ),
+    ],
+)
+def test_paths(init_kwargs, expected_paths):
+    dataset = GazeBase(**init_kwargs)
+
+    assert dataset.root == expected_paths['root']
+    assert dataset.path == expected_paths['path']
+    assert dataset.downloads_rootpath == expected_paths['download']
diff --git a/tests/datasets/test_judo1000.py b/tests/datasets/test_judo1000.py
new file mode 100644
index 0000000..f569949
--- /dev/null
+++ b/tests/datasets/test_judo1000.py
@@ -0,0 +1,70 @@
+# Copyright (c) 2023 The pymovements Project Authors
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+"""Test all functionality in pymovements.datasets.judo1000."""
+from pathlib import Path
+
+import pytest
+
+from pymovements.datasets.judo1000 import JuDo1000
+
+
+@pytest.mark.parametrize(
+    'init_kwargs, expected_paths',
+    [
+        pytest.param(
+            {'root': '/data/set/path'},
+            {
+                'root': Path('/data/set/path/'),
+                'path': Path('/data/set/path/JuDo1000'),
+                'download': Path('/data/set/path/JuDo1000/downloads'),
+            },
+        ),
+        pytest.param(
+            {'root': '/data/set/path', 'dataset_dirname': '.'},
+            {
+                'root': Path('/data/set/path/'),
+                'path': Path('/data/set/path/'),
+                'download': Path('/data/set/path/downloads'),
+            },
+        ),
+        pytest.param(
+            {'root': '/data/set/path', 'dataset_dirname': 'dataset'},
+            {
+                'root': Path('/data/set/path/'),
+                'path': Path('/data/set/path/dataset'),
+                'download': Path('/data/set/path/dataset/downloads'),
+            },
+        ),
+        pytest.param(
+            {'root': '/data/set/path', 'downloads_dirname': 'custom_download_dirname'},
+            {
+                'root': Path('/data/set/path/'),
+                'path': Path('/data/set/path/JuDo1000'),
+                'download': Path('/data/set/path/JuDo1000/custom_download_dirname'),
+            },
+        ),
+    ],
+)
+def test_paths(init_kwargs, expected_paths):
+    dataset = JuDo1000(**init_kwargs)
+
+    assert dataset.root == expected_paths['root']
+    assert dataset.path == expected_paths['path']
+    assert dataset.downloads_rootpath == expected_paths['download']
diff --git a/tests/datasets/test_public_dataset.py b/tests/datasets/test_public_dataset.py
new file mode 100644
index 0000000..df19a82
--- /dev/null
+++ b/tests/datasets/test_public_dataset.py
@@ -0,0 +1,80 @@
+# Copyright (c) 2023 The pymovements Project Authors
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+"""Test all functionality in pymovements.datasets.public_dataset."""
+from pathlib import Path
+
+import pytest
+
+from pymovements.datasets.public_dataset import PublicDataset
+
+
+@pytest.mark.parametrize(
+    'init_kwargs, expected_paths',
+    [
+        pytest.param(
+            {'root': '/data/set/path'},
+            {
+                'root': Path('/data/set/path/'),
+                'path': Path('/data/set/path/CustomPublicDataset'),
+                'download': Path('/data/set/path/CustomPublicDataset/downloads'),
+            },
+        ),
+        pytest.param(
+            {'root': '/data/set/path', 'dataset_dirname': '.'},
+            {
+                'root': Path('/data/set/path/'),
+                'path': Path('/data/set/path/'),
+                'download': Path('/data/set/path/downloads'),
+            },
+        ),
+        pytest.param(
+            {'root': '/data/set/path', 'dataset_dirname': 'dataset'},
+            {
+                'root': Path('/data/set/path/'),
+                'path': Path('/data/set/path/dataset'),
+                'download': Path('/data/set/path/dataset/downloads'),
+            },
+        ),
+        pytest.param(
+            {'root': '/data/set/path', 'downloads_dirname': 'custom_download_dirname'},
+            {
+                'root': Path('/data/set/path/'),
+                'path': Path('/data/set/path/CustomPublicDataset'),
+                'download': Path('/data/set/path/CustomPublicDataset/custom_download_dirname'),
+            },
+        ),
+    ],
+)
+def test_paths(init_kwargs, expected_paths):
+    class CustomPublicDataset(PublicDataset):
+        _mirrors = ['https://www.example.com/']
+        _resources = [
+            {
+                'resource': 'relative_path_to_resource',
+                'filename': 'example_dataset.zip',
+                'md5': 'md5md5md5md5md5md5md5md5md5md5md',
+            },
+        ]
+
+    dataset = CustomPublicDataset(**init_kwargs)
+
+    assert dataset.root == expected_paths['root']
+    assert dataset.path == expected_paths['path']
+    assert dataset.downloads_rootpath == expected_paths['download']
diff --git a/tests/utils/test_checks.py b/tests/utils/test_checks.py
index 08c20aa..65213cd 100644
--- a/tests/utils/test_checks.py
+++ b/tests/utils/test_checks.py
@@ -20,12 +20,132 @@
 """
 Test pymovements checks.
 """
+import numpy as np
+import pytest
 
+from pymovements.utils.checks import check_nan_both_channels
+from pymovements.utils.checks import check_no_zeros
+from pymovements.utils.checks import check_shapes_positions_velocities
 
-def test_check_no_zeros_exception():
-    # use fixtures: (single variable, list, np.array)
-    # check both valid and invalid inputs with the help of a second variable
+
+@pytest.mark.parametrize(
+    'variable, expected_error',
+    [
+        pytest.param(5, None, id='non_zero_single_variable_raises_no_error'),
+        pytest.param(0, ValueError, id='zero_single_variable_raises_value_error'),
+        pytest.param([1, 2, 3], None, id='non_zero_list_raises_no_error'),
+        pytest.param([1, 0, 3], ValueError, id='zero_list_raises_value_error'),
+        pytest.param(np.array([1, 2, 3]), None, id='non_zero_np_array_raises_no_error'),
+        pytest.param(np.array([1, 0, 3]), ValueError, id='zero_np_array_raises_value_error'),
+    ],
+)
+def test_check_no_zeros_raises_error(variable, expected_error):
+    """
+    Test that check_no_zeros() only raises an Exception if there are zeros in the input array.
+    """
+    if expected_error is None:
+        check_no_zeros(variable)
+    else:
+        with pytest.raises(expected_error):
+            check_no_zeros(variable)
+
+
+@pytest.mark.parametrize(
+    'arr, expected_error',
+    [
+        pytest.param(
+            np.array([[1, 2], [3, 4]]),
+            None,
+            id='no_nans_raises_no_error',
+        ),
+        pytest.param(
+            np.array([[1, 2], [np.nan, np.nan]]),
+            None,
+            id='nans_same_time_steps_raises_no_error',
+        ),
+        pytest.param(
+            np.array([[np.nan, 2], [np.nan, 4]]),
+            ValueError,
+            id='nans_different_time_steps_raises_value_error',
+        ),
+        pytest.param(
+            np.array([[np.nan, 2], [3, 4]]),
+            ValueError,
+            id='nans_only_left_channel_raises_value_error',
+        ),
+        pytest.param(
+            np.array([[1, np.nan], [3, 4]]),
+            ValueError,
+            id='nans_only_right_channel_raises_value_error',
+        ),
+    ],
+)
+def test_check_nan_both_channels_raises_error(arr, expected_error):
+    """
+    Test that check_nan_both_channels() only raises an Exception if all nans
+    occur at the same time step for both channels.
+    """
+    if expected_error is None:
+        check_nan_both_channels(arr)
+    else:
+        with pytest.raises(expected_error):
+            check_nan_both_channels(arr)
+
+
+# Test check_shapes_positions_velocities
+@pytest.mark.parametrize(
+    'kwargs, expected_error',
+    [
+        pytest.param(
+            {
+                'positions': np.array([[1, 2], [3, 4]]),
+                'velocities': np.array([[1, 2], [3, 4]]),
+            },
+            None,
+            id='positions_and_velocities_shape_N_2_raises_no_error',
+        ),
+        pytest.param(
+            {
+                'positions': np.array([[1, 2], [3, 4]]),
+                'velocities': np.array([1, 2, 3, 4]),
+            },
+            ValueError,
+            id='positions_shape_N_2_velocities_not_shape_N_2_raises_value_error',
+        ),
+        pytest.param(
+            {
+                'positions': np.array([1, 2, 3, 4]),
+                'velocities': np.array([[1, 2], [3, 4]]),
+            },
+            ValueError,
+            id='positions_not_shape_N_2_velocities_shape_N_2_raises_value_error',
+        ),
+        pytest.param(
+            {
+                'positions': np.array([1, 2, 3, 4]),
+                'velocities': np.array([1, 2, 3, 4]),
+            },
+            ValueError,
+            id='positions_and_velocities_not_shape_N_2_raises_value_error',
+        ),
+        pytest.param(
+            {
+                'positions': np.array([[1, 2], [3, 4]]),
+                'velocities': np.array([[1, 2], [3, 4], [5, 6]]),
+            },
+            ValueError,
+            id='positions_and_velocities_N_2_but_different_lengths_raises_value_error',
+        ),
+    ],
+)
+def test_check_shapes_positions_velocities_raises_error(kwargs, expected_error):
     """
-    Test that check_no_zeros() only raises an Exception iff there are zeros in the input array.
+    Test that check_shapes_positions_velocities() only raises an Exception if
+    the shapes of the positions and velocities are not (N, 2) or if the lengths
+    of the positions and velocities are not equal.
     """
-    return
+    if expected_error is None:
+        check_shapes_positions_velocities(**kwargs)
+    else:
+        with pytest.raises(expected_error):
+            check_shapes_positions_velocities(**kwargs)