Repository: aeye-lab/pymovements
Pull request #294 (2023-03-16 20:21:56)

Dataset: don't download in init() but implicitly extract in download()
## Description

This PR removes the download and extract parameters from the initialization of the `PublicDataset` class.
Instead, the user is expected to call `PublicDataset.download()` explicitly. Extracting is done implicitly in this call.

At first this might seem odd as we might already be comfortable how public datasets are downloaded.

But the way it was implemented was rather bad practice, as a constructor is not supposed to do much more than just initializing your object.
That's why `download` was set to `False` per default in `PublicDataset.__init__()`.

Now we have a much more separated responsibilities in the methods:

`PublicDataset.__init__()` initializes your dataset object without any unexpected downloads.

`PublicDataset.download()` downloads and extracts your dataset.

Using `PublicDataset.download(extract=False)` you can disable this.

Using `PublicDataset.download(remove_finished=True` you can remove the original archive files.


And just for comparison to see that now it's also much cleaner from user perspective:

New:
```python
dataset = ToyDataset(root='data')
dataset.download(remove_finished=True)
```

Old:
```python
dataset = ToyDataset(
    root='data',
    download=True,
    remove_finished=True,
)
```

As `download()` now also returns a referrence to `self` we can even to that:
```python
dataset = ToyDataset(root='data').download(remove_finished=True)
```


## Implemented changes

Insert a description of the changes implemented in the pull request.

- [x] Removed download and extract behavior from `init()`
- [x] add `extract` and `remove_finished` argument to `download()`
- [x] Updated examples accordingly
- [x] Updates all tutorials accordingly

## Type of change

- [x] New feature (non-breaking change which adds functionality)
- [x] This change is or requires a documentation update

## How Has This Been Tested?

Two tests were slightly adjusted:

- [x] `test_dataset_download` -> `test_dataset_download_no_extract` by setting `download(extract=False)`
- [x] `test_custom_dataset_download_extract` -> `test_custom_dataset_download_extract` by checking that `download()` also extracts by default

## 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/294?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) Report
> :exclamation: No coverage uploaded for pull request base (`main@eec2d62`). [Click here to learn what that means](https://docs.codecov.io/docs/error-reference?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#section-missing-base-commit).
> Patch coverage: 100.00% of modified lines in pull request are covered.

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


```diff
@@           Coverage Diff            @@
##             main      #294   +/-   ##
========================================
  Coverage        ?   100.00%           
========================================
  Files           ?        35           
  Lines           ?      1481           
  Branches        ?       335           
========================================
  Hits            ?      1481           
  Misses          ?         0           
  Partials        ?         0           
```


| [Impacted Files](https://codecov.io/gh/aeye-lab/pymovements/pull/294?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) | Coverage Δ | |
|---|---|---|
| [src/pymovements/datasets/gazebase.py](https://codecov.io/gh/aeye-lab/pymovements/pull/294?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2RhdGFzZXRzL2dhemViYXNlLnB5) | `100.00% <ø> (ø)` | |
| [src/pymovements/datasets/judo1000.py](https://codecov.io/gh/aeye-lab/pymovements/pull/294?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2RhdGFzZXRzL2p1ZG8xMDAwLnB5) | `100.00% <ø> (ø)` | |
| [src/pymovements/datasets/toy\_dataset.py](https://codecov.io/gh/aeye-lab/pymovements/pull/294?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2RhdGFzZXRzL3RveV9kYXRhc2V0LnB5) | `100.00% <ø> (ø)` | |
| [src/pymovements/datasets/dataset.py](https://codecov.io/gh/aeye-lab/pymovements/pull/294?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2RhdGFzZXRzL2RhdGFzZXQucHk=) | `100.00% <100.00%> (ø)` | |
| [src/pymovements/datasets/public\_dataset.py](https://codecov.io/gh/aeye-lab/pymovements/pull/294?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2RhdGFzZXRzL3B1YmxpY19kYXRhc2V0LnB5) | `100.00% <100.00%> (ø)` | |

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 in Codecov by Sentry](https://codecov.io/gh/aeye-lab/pymovements/pull/294?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/docs/source/tutorials/R-tutorial.md b/docs/source/tutorials/R-tutorial.md
index 9088744..fa1ccc1 100644
--- a/docs/source/tutorials/R-tutorial.md
+++ b/docs/source/tutorials/R-tutorial.md
@@ -27,9 +27,15 @@ Access functions and data within python modules and classes via the `$` operator
 To test, you can proceed with the "pymovements in 10 minutes" tutorial,
 for example this is how you download the ToyDataset:
 ```r
-dataset = pm$datasets$ToyDataset(root='testdata', download=TRUE)
+dataset = pm$datasets$ToyDataset(root='data_directory')
+dataset$download()
 ```
 
+Now let's load in the dataset into R and display the found files:
+```r
+dataset$load()
+dataset$fileinfo
+```
 
 
 #### Related handy functions:
diff --git a/docs/source/tutorials/detecting-events.ipynb b/docs/source/tutorials/detecting-events.ipynb
index 9d1914a..2d3aee4 100644
--- a/docs/source/tutorials/detecting-events.ipynb
+++ b/docs/source/tutorials/detecting-events.ipynb
@@ -52,7 +52,7 @@
    "id": "532d4aa6",
    "metadata": {},
    "source": [
-    "Let's start by downloading and extracting our `ToyDataset`:"
+    "Let's start by downloading our `ToyDataset` and loading in its data:"
    ]
   },
   {
@@ -64,7 +64,6 @@
    "source": [
     "dataset = pm.datasets.ToyDataset(root='data/')\n",
     "dataset.download()\n",
-    "dataset.extract()\n",
     "dataset.load()"
    ]
   },
diff --git a/docs/source/tutorials/event-properties.ipynb b/docs/source/tutorials/event-properties.ipynb
index 5fba5e8..81bfd42 100644
--- a/docs/source/tutorials/event-properties.ipynb
+++ b/docs/source/tutorials/event-properties.ipynb
@@ -49,7 +49,7 @@
    "id": "532d4aa6",
    "metadata": {},
    "source": [
-    "Let's start by downloading and extracting our `ToyDataset`:"
+    "Let's start by downloading our `ToyDataset` and loading in its data:"
    ]
   },
   {
@@ -61,7 +61,6 @@
    "source": [
     "dataset = pm.datasets.ToyDataset(root='data/')\n",
     "dataset.download()\n",
-    "dataset.extract()\n",
     "dataset.load()"
    ]
   },
diff --git a/docs/source/tutorials/heatmap.ipynb b/docs/source/tutorials/heatmap.ipynb
index a564991..100d073 100644
--- a/docs/source/tutorials/heatmap.ipynb
+++ b/docs/source/tutorials/heatmap.ipynb
@@ -9,6 +9,20 @@
     "In this tutorial, we will demonstrate how to use the `heatmap` function from the `pymovements.plotting` module to create a heatmap of gaze data. The heatmap will show the distribution of gaze positions across the experiment screen, with color values indicating the time spent at each position in seconds."
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Preparations"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "We import `pymovements` as the alias `pm` for convenience."
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": null,
@@ -24,7 +38,7 @@
    "source": [
     "## Loading the Dataset\n",
     "\n",
-    "For demonstration purposes, we will use the `ToyDataset` from `pymovements.datasets`."
+    "Let's start by downloading our `ToyDataset` and loading in its data:"
    ]
   },
   {
@@ -33,13 +47,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "# Load Data\n",
-    "dataset = pm.datasets.ToyDataset(\n",
-    "    root='data/',\n",
-    "    download=True,\n",
-    "    extract=True,\n",
-    "    remove_finished=True,\n",
-    ")"
+    "dataset = pm.datasets.ToyDataset(root='data/')\n",
+    "dataset.download()\n",
+    "dataset.load()"
    ]
   },
   {
@@ -55,7 +65,6 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "dataset.load()\n",
     "dataset.pix2deg()"
    ]
   },
@@ -166,16 +175,16 @@
   "language_info": {
    "codemirror_mode": {
     "name": "ipython",
-    "version": 2
+    "version": 3
    },
    "file_extension": ".py",
    "mimetype": "text/x-python",
    "name": "python",
    "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython2",
-   "version": "2.7.6"
+   "pygments_lexer": "ipython3",
+   "version": "3.10.9"
   }
  },
  "nbformat": 4,
- "nbformat_minor": 0
+ "nbformat_minor": 1
 }
diff --git a/docs/source/tutorials/local-dataset.ipynb b/docs/source/tutorials/local-dataset.ipynb
index 1d21954..9761804 100644
--- a/docs/source/tutorials/local-dataset.ipynb
+++ b/docs/source/tutorials/local-dataset.ipynb
@@ -16,12 +16,48 @@
     "In this tutorial, we will show how to use your own local dataset with the Dataset class. The Dataset class can help you to manage and process your eyetracking data."
    ]
   },
+  {
+   "cell_type": "markdown",
+   "id": "15ff8a0c",
+   "metadata": {},
+   "source": [
+    "## Preparations"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a01fa671",
+   "metadata": {},
+   "source": [
+    "We import `pymovements` as the alias `pm` for convenience."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "25f67fb1",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import pymovements as pm"
+   ]
+  },
   {
    "cell_type": "markdown",
    "id": "92ac248d",
    "metadata": {},
    "source": [
-    "For demonstration purposes, we will use the raw data provided by the Toy dataset, a sample dataset that comes with pymovements."
+    "For demonstration purposes, we will use the raw data provided by the Toy dataset, a sample dataset that comes with *pymovements*.\n",
+    "\n",
+    "We will download the resources of this dataset the directory to simulate a local dataset for you.\n",
+    "All downloaded archive files are automatically extracted and then removed.\n",
+    "The directory of the dataset will be `data/ToyDataset`.\n",
+    "\n",
+    "After that we won't use the python class anymore and delete the object\n",
+    "(the files on your system will stay in place).\n",
+    "Don't worry if you're confused about these lines as they are not relevant to your use case.\n",
+    "\n",
+    "Just keep in mind that we now have some files with gaze data in the directory `data/ToyDataset`."
    ]
   },
   {
@@ -31,14 +67,10 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "import pymovements as pm\n",
+    "toy_dataset = pm.datasets.ToyDataset(root='data/')\n",
+    "toy_dataset.download(remove_finished=True)\n",
     "\n",
-    "toy_dataset = pm.datasets.ToyDataset(\n",
-    "    root='data/',\n",
-    "    download=True,\n",
-    "    extract=True,\n",
-    "    remove_finished=True,\n",
-    ")"
+    "del toy_dataset"
    ]
   },
   {
@@ -314,7 +346,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.10.6"
+   "version": "3.10.9"
   }
  },
  "nbformat": 4,
diff --git a/docs/source/tutorials/plot-main-sequence.ipynb b/docs/source/tutorials/plot-main-sequence.ipynb
index 32321f1..7c98a1b 100644
--- a/docs/source/tutorials/plot-main-sequence.ipynb
+++ b/docs/source/tutorials/plot-main-sequence.ipynb
@@ -46,7 +46,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "First, you have to define your dataset. You can already download and extract all the files."
+    "Let's start by downloading our `ToyDataset` and loading in its data:"
    ]
   },
   {
@@ -55,8 +55,8 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "dataset = pm.datasets.toy_dataset.ToyDataset(\n",
-    "    root='data/', download=True, extract=True, remove_finished=True)\n",
+    "dataset = pm.datasets.toy_dataset.ToyDataset(root='data/')\n",
+    "dataset.download()\n",
     "dataset.load()"
    ]
   },
diff --git a/docs/source/tutorials/preprocessing-raw-data.ipynb b/docs/source/tutorials/preprocessing-raw-data.ipynb
index 58e8093..c18e422 100644
--- a/docs/source/tutorials/preprocessing-raw-data.ipynb
+++ b/docs/source/tutorials/preprocessing-raw-data.ipynb
@@ -50,7 +50,7 @@
    "id": "2169ec44",
    "metadata": {},
    "source": [
-    "Let's start by downloading and extracting our `ToyDataset`:"
+    "Let's start by downloading our `ToyDataset` and loading in its data:"
    ]
   },
   {
@@ -62,7 +62,6 @@
    "source": [
     "dataset = pm.datasets.ToyDataset(root='data/')\n",
     "dataset.download()\n",
-    "dataset.extract()\n",
     "dataset.load()"
    ]
   },
diff --git a/docs/source/tutorials/public-datasets.ipynb b/docs/source/tutorials/public-datasets.ipynb
index 1d107fe..7c5466d 100644
--- a/docs/source/tutorials/public-datasets.ipynb
+++ b/docs/source/tutorials/public-datasets.ipynb
@@ -173,46 +173,56 @@
   },
   {
    "cell_type": "markdown",
-   "id": "b732b465",
+   "id": "8310fc82",
    "metadata": {},
    "source": [
-    "## Extracting"
+    "By default, all archives are recursively extracted to the `raw_rootpath`:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "782311cf",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "dataset.raw_rootpath"
    ]
   },
   {
    "cell_type": "markdown",
-   "id": "e91345c0",
+   "id": "8f93ef98",
    "metadata": {},
    "source": [
-    "You can then extract you downloaded data by calling:"
+    "If you want to remove the downloaded archives after extraction to save some space, you can set `remove_finished` to `True`:"
    ]
   },
   {
    "cell_type": "code",
    "execution_count": null,
-   "id": "244859b2",
+   "id": "dda16489",
    "metadata": {},
    "outputs": [],
    "source": [
-    "dataset.extract()"
+    "dataset.extract(remove_finished=True)"
    ]
   },
   {
    "cell_type": "markdown",
-   "id": "b573a28d",
+   "id": "c1f2e1a8",
    "metadata": {},
    "source": [
-    "Your data is now extracted to the following directory:"
+    "This is also available for the `PublicDataset.download()` method:"
    ]
   },
   {
    "cell_type": "code",
    "execution_count": null,
-   "id": "782311cf",
+   "id": "0afffa82",
    "metadata": {},
    "outputs": [],
    "source": [
-    "dataset.raw_rootpath"
+    "dataset.download(remove_finished=True)"
    ]
   },
   {
@@ -228,7 +238,9 @@
    "id": "1f07d791",
    "metadata": {},
    "source": [
-    "Finally we can load the data into our working memory by using the common `load()` method:"
+    "The `PublicDataset` class is a subset of the `Dataset` class and thus inherits all its functionality.\n",
+    "\n",
+    "Hende, we can load the data into our working memory by using the common `load()` method:"
    ]
   },
   {
@@ -264,7 +276,7 @@
    "id": "8f8d46dd",
    "metadata": {},
    "source": [
-    "Wonderful, all of our data has been downloaded successfully!"
+    "Wonderful, all of our data has been downloaded and loaded in successfully!"
    ]
   },
   {
diff --git a/docs/source/tutorials/pymovements-in-10-minutes.ipynb b/docs/source/tutorials/pymovements-in-10-minutes.ipynb
index 1a32d93..36120ba 100644
--- a/docs/source/tutorials/pymovements-in-10-minutes.ipynb
+++ b/docs/source/tutorials/pymovements-in-10-minutes.ipynb
@@ -87,8 +87,25 @@
    "outputs": [],
    "source": [
     "dataset = pm.datasets.ToyDataset(root='data/')\n",
-    "dataset.download()\n",
-    "dataset.extract()"
+    "dataset.download()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "b82bcfe3",
+   "metadata": {},
+   "source": [
+    "Archive files are automatically extracted into the `dataset.raw_rootpath`:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "7d897514",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "dataset.raw_rootpath"
    ]
   },
   {
diff --git a/docs/source/tutorials/saving-loading-events.ipynb b/docs/source/tutorials/saving-loading-events.ipynb
index 3c77ddd..5f94339 100644
--- a/docs/source/tutorials/saving-loading-events.ipynb
+++ b/docs/source/tutorials/saving-loading-events.ipynb
@@ -50,7 +50,7 @@
    "id": "26acca82",
    "metadata": {},
    "source": [
-    "Let's start by downloading and extracting our `ToyDataset`:"
+    "Let's start by downloading our `ToyDataset` and loading in its data:"
    ]
   },
   {
@@ -62,7 +62,7 @@
    "source": [
     "dataset = pm.datasets.ToyDataset(root='data/')\n",
     "dataset.download()\n",
-    "dataset.extract()"
+    "dataset.load()"
    ]
   },
   {
@@ -70,7 +70,7 @@
    "id": "e1cfa35f",
    "metadata": {},
    "source": [
-    "Now let's load in the data and do some preprocessing:"
+    "Now let's do some preprocessing:"
    ]
   },
   {
@@ -80,7 +80,6 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "dataset.load()\n",
     "dataset.pix2deg()\n",
     "dataset.pos2vel()\n",
     "\n",
diff --git a/docs/source/tutorials/saving-loading-preprocessed.ipynb b/docs/source/tutorials/saving-loading-preprocessed.ipynb
index 3d68b9d..bd6919a 100644
--- a/docs/source/tutorials/saving-loading-preprocessed.ipynb
+++ b/docs/source/tutorials/saving-loading-preprocessed.ipynb
@@ -50,7 +50,7 @@
    "id": "26acca82",
    "metadata": {},
    "source": [
-    "Let's start by downloading and extracting our `ToyDataset`:"
+    "Let's start by downloading our `ToyDataset` and loading in its data:"
    ]
   },
   {
@@ -62,7 +62,7 @@
    "source": [
     "dataset = pm.datasets.ToyDataset(root='data/')\n",
     "dataset.download()\n",
-    "dataset.extract()"
+    "dataset.load()"
    ]
   },
   {
@@ -80,7 +80,6 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "dataset.load()\n",
     "dataset.pix2deg()\n",
     "dataset.pos2vel()\n",
     "\n",
diff --git a/src/pymovements/datasets/dataset.py b/src/pymovements/datasets/dataset.py
index dc09da6..860959c 100644
--- a/src/pymovements/datasets/dataset.py
+++ b/src/pymovements/datasets/dataset.py
@@ -123,9 +123,12 @@ class Dataset:
             events_dirname: str | None = None,
             preprocessed_dirname: str | None = None,
             extension: str = 'feather',
-    ):
+    ) -> Dataset:
         """Parse file information and load all gaze files.
 
+        The parsed file information is assigned to the `fileinfo` attribute.
+        All gaze files will be loaded as dataframes and assigned to the `gaze` attribute.
+
         Parameters
         ----------
         events : bool
@@ -150,8 +153,10 @@ class Dataset:
             Specifies the file format for loading data. Valid options are: `csv`, `feather`.
             :Default: `feather`.
 
-        The parsed file information is assigned to the `fileinfo` attribute.
-        All gaze files will be loaded as dataframes and assigned to the `gaze` attribute.
+        Returns
+        -------
+        Dataset
+            Returns self, useful for method cascading.
         """
         fileinfo = self.infer_fileinfo()
         self.fileinfo = self.take_subset(fileinfo=fileinfo, subset=subset)
@@ -166,6 +171,8 @@ class Dataset:
                 extension=extension,
             )
 
+        return self
+
     def infer_fileinfo(self) -> pl.DataFrame:
         """Infer information from filepaths and filenames.
 
@@ -394,7 +401,7 @@ class Dataset:
 
         return event_dfs
 
-    def pix2deg(self, verbose: bool = True) -> None:
+    def pix2deg(self, verbose: bool = True) -> Dataset:
         """Compute gaze positions in degrees of visual angle from pixel coordinates.
 
         This method requires a properly initialized :py:attr:`~.Dataset.experiment` attribute.
@@ -411,6 +418,11 @@ class Dataset:
         AttributeError
             If `gaze` is None or there are no gaze dataframes present in the `gaze` attribute, or
             if experiment is None.
+
+        Returns
+        -------
+        Dataset
+            Returns self, useful for method cascading.
         """
         self._check_gaze_dataframe()
 
@@ -418,7 +430,9 @@ class Dataset:
         for gaze_df in tqdm(self.gaze, disable=disable_progressbar):
             gaze_df.pix2deg()
 
-    def pos2vel(self, method: str = 'smooth', verbose: bool = True, **kwargs) -> None:
+        return self
+
+    def pos2vel(self, method: str = 'smooth', verbose: bool = True, **kwargs) -> Dataset:
         """Compute gaze velocites in dva/s from dva coordinates.
 
         This method requires a properly initialized :py:attr:`~.Dataset.experiment` attribute.
@@ -439,6 +453,11 @@ class Dataset:
         AttributeError
             If `gaze` is None or there are no gaze dataframes present in the `gaze` attribute, or
             if experiment is None.
+
+        Returns
+        -------
+        Dataset
+            Returns self, useful for method cascading.
         """
         self._check_gaze_dataframe()
 
@@ -446,6 +465,8 @@ class Dataset:
         for gaze_df in tqdm(self.gaze, disable=disable_progressbar):
             gaze_df.pos2vel(method=method, **kwargs)
 
+        return self
+
     def detect_events(
             self,
             method: EventDetectionCallable,
@@ -453,7 +474,7 @@ class Dataset:
             clear: bool = False,
             verbose: bool = True,
             **kwargs,
-    ) -> None:
+    ) -> Dataset:
         """Detect events by applying a specific event detection method.
 
         Parameters
@@ -476,6 +497,11 @@ class Dataset:
         ------
         AttributeError
             If gaze files have not been loaded yet or gaze files do not contain the right columns.
+
+        Returns
+        -------
+        Dataset
+            Returns self, useful for method cascading.
         """
         self._check_gaze_dataframe()
 
@@ -528,19 +554,20 @@ class Dataset:
 
         if not self.events or clear:
             self.events = event_dfs
-            return
+            return self
 
         for file_id, event_df in enumerate(event_dfs):
             self.events[file_id].frame = pl.concat(
                 [self.events[file_id].frame, event_df.frame],
                 how='diagonal',
             )
+        return self
 
     def compute_event_properties(
             self,
             event_properties: str | list[str],
             verbose: bool = True,
-    ) -> None:
+    ) -> Dataset:
         """Calculate an event property for and add it as a column to the event dataframe.
 
         Parameters
@@ -555,6 +582,11 @@ class Dataset:
         InvalidProperty
             If ``property_name`` is not a valid property. See
             :py:mod:`pymovements.events.event_properties` for an overview of supported properties.
+
+        Returns
+        -------
+        Dataset
+            Returns self, useful for method cascading.
         """
         processor = EventGazeProcessor(event_properties)
 
@@ -569,21 +601,31 @@ class Dataset:
 
             events.add_event_properties(new_properties)
 
-    def clear_events(self) -> None:
-        """Clear event DataFrame."""
+        return self
+
+    def clear_events(self) -> Dataset:
+        """Clear event DataFrame.
+
+        Returns
+        -------
+        Dataset
+            Returns self, useful for method cascading.
+        """
         if len(self.events) == 0:
-            return
+            return self
 
         for file_id, _ in enumerate(self.events):
             self.events[file_id] = EventDataFrame()
 
+        return self
+
     def save(
             self,
             events_dirname: str | None = None,
             preprocessed_dirname: str | None = None,
             verbose: int = 1,
             extension: str = 'feather',
-    ):
+    ) -> Dataset:
         """Save preprocessed gaze and event files.
 
         Data will be saved as feather/csv files to ``Dataset.preprocessed_roothpath`` or
@@ -603,15 +645,21 @@ class Dataset:
             Verbosity level (0: no print output, 1: show progress bar, 2: print saved filepaths)
         extension:
             extension specifies the fileformat to store the data
+
+        Returns
+        -------
+        Dataset
+            Returns self, useful for method cascading.
         """
         self.save_events(events_dirname, verbose=verbose, extension=extension)
         self.save_preprocessed(preprocessed_dirname, verbose=verbose, extension=extension)
+        return self
 
     def save_events(
         self, events_dirname: str | None = None,
         verbose: int = 1,
         extension: str = 'feather',
-    ):
+    ) -> Dataset:
         """Save events to files.
 
         Data will be saved as feather files to ``Dataset.events_roothpath`` with the same directory
@@ -633,6 +681,11 @@ class Dataset:
         ------
         ValueError
             If extension is not in list of valid extensions.
+
+        Returns
+        -------
+        Dataset
+            Returns self, useful for method cascading.
         """
         disable_progressbar = not verbose
 
@@ -662,12 +715,13 @@ class Dataset:
                     f'unsupported file format "{extension}".'
                     f'Supported formats are: {valid_extensions}',
                 )
+        return self
 
     def save_preprocessed(
         self, preprocessed_dirname: str | None = None,
         verbose: int = 1,
         extension: str = 'feather',
-    ):
+    ) -> Dataset:
         """Save preprocessed gaze files.
 
         Data will be saved as feather files to ``Dataset.preprocessed_roothpath`` with the same
@@ -689,6 +743,11 @@ class Dataset:
         ------
         ValueError
             If extension is not in list of valid extensions.
+
+        Returns
+        -------
+        Dataset
+            Returns self, useful for method cascading.
         """
         disable_progressbar = not verbose
 
@@ -718,6 +777,7 @@ class Dataset:
                     f'unsupported file format "{extension}".'
                     f'Supported formats are: {valid_extensions}',
                 )
+        return self
 
     @property
     def path(self) -> Path:
diff --git a/src/pymovements/datasets/gazebase.py b/src/pymovements/datasets/gazebase.py
index b9d9745..131f4bf 100644
--- a/src/pymovements/datasets/gazebase.py
+++ b/src/pymovements/datasets/gazebase.py
@@ -43,14 +43,14 @@ class GazeBase(PublicDataset):
 
     Check the respective paper for details :cite:p:`GazeBase`.
 
-    Change to ``download=True`` and ``extract=True`` for downloading and extracting the dataset.
-
-    >>> dataset = GazeBase(
-    ...     root='data/',
-    ...     download=False,
-    ...     extract=False,
-    ...     remove_finished=False,
-    ... )
+    Examples
+    --------
+
+    This will initialize your :py:class:`pymovements.PublicDataset` object, download its resources
+    and load the data into memory.
+
+    >>> dataset = GazeBase(root='data/')
+    >>> dataset.download()  # doctest: +SKIP
     >>> dataset.load()  # doctest: +SKIP
     """
     # pylint: disable=similarities
@@ -107,9 +107,6 @@ class GazeBase(PublicDataset):
     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',
@@ -135,12 +132,6 @@ class GazeBase(PublicDataset):
         ----------
         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: `.`
@@ -160,9 +151,6 @@ class GazeBase(PublicDataset):
         """
         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,
diff --git a/src/pymovements/datasets/judo1000.py b/src/pymovements/datasets/judo1000.py
index cb622cb..767b8e6 100644
--- a/src/pymovements/datasets/judo1000.py
+++ b/src/pymovements/datasets/judo1000.py
@@ -39,14 +39,11 @@ class JuDo1000(PublicDataset):
 
     Examples
     --------
-    Change to ``download=True`` and ``extract=True`` for downloading and extracting the dataset.
-
-    >>> dataset = JuDo1000(
-    ...     root='data/',
-    ...     download=False,
-    ...     extract=False,
-    ...     remove_finished=False,
-    ... )
+    This will initialize your :py:class:`pymovements.PublicDataset` object, download its resources
+    and load the data into memory.
+
+    >>> dataset = JuDo1000(root='data/')
+    >>> dataset.download()  # doctest: +SKIP
     >>> dataset.load()  # doctest: +SKIP
     """
     # pylint: disable=similarities
@@ -100,9 +97,6 @@ class JuDo1000(PublicDataset):
     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',
@@ -128,12 +122,6 @@ class JuDo1000(PublicDataset):
         ----------
         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: `.`
@@ -153,9 +141,6 @@ class JuDo1000(PublicDataset):
         """
         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,
diff --git a/src/pymovements/datasets/public_dataset.py b/src/pymovements/datasets/public_dataset.py
index c8c182f..8ec0963 100644
--- a/src/pymovements/datasets/public_dataset.py
+++ b/src/pymovements/datasets/public_dataset.py
@@ -43,9 +43,6 @@ class PublicDataset(Dataset, metaclass=ABCMeta):
     def __init__(
             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,
@@ -77,10 +74,6 @@ class PublicDataset(Dataset, metaclass=ABCMeta):
             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
@@ -122,22 +115,36 @@ class PublicDataset(Dataset, metaclass=ABCMeta):
             preprocessed_dirname=preprocessed_dirname,
             events_dirname=events_dirname,
         )
-
         self.downloads_dirname = downloads_dirname
 
-        if download:
-            self.download()
+    def download(self, *, extract: bool = True, remove_finished: bool = False) -> Dataset:
+        """Download dataset.
+
+        This downloads all resources of the dataset. Per default this also extracts all archives
+        into :py:meth:`Dataset.raw_rootpath`,
 
-        if extract:
-            self.extract(remove_finished=remove_finished)
+        If a corresponding file already exists in the local system, its checksum is calculated and
+        checked against the expected checksum.
+        Downloading will be evaded if the integrity of the existing file can be verified.
+        If the existing file does not match the expected checksum it is overwritten with the
+        downloaded new file.
 
-    def download(self) -> None:
-        """Download dataset.
+        Parameters
+        ----------
+        extract : bool
+            Extract dataset archive files.
+        remove_finished : bool
+            Remove archive files after extraction.
 
         Raises
         ------
         RuntimeError
             If downloading a resource failed for all given mirrors.
+
+        Returns
+        -------
+        PublicDataset
+            Returns self, useful for method cascading.
         """
         self.raw_rootpath.mkdir(parents=True, exist_ok=True)
 
@@ -171,13 +178,23 @@ class PublicDataset(Dataset, metaclass=ABCMeta):
                     f"downloading resource {resource['resource']} failed for all mirrors.",
                 )
 
-    def extract(self, remove_finished: bool = False) -> None:
+        if extract:
+            self.extract(remove_finished=remove_finished)
+
+        return self
+
+    def extract(self, remove_finished: bool = False) -> Dataset:
         """Extract dataset archives.
 
         Parameters
         ----------
         remove_finished : bool
             Remove archive files after extraction.
+
+        Returns
+        -------
+        PublicDataset
+            Returns self, useful for method cascading.
         """
         self.raw_rootpath.mkdir(parents=True, exist_ok=True)
 
@@ -188,6 +205,7 @@ class PublicDataset(Dataset, metaclass=ABCMeta):
                 recursive=True,
                 remove_finished=remove_finished,
             )
+        return self
 
     @property
     @abstractmethod
diff --git a/src/pymovements/datasets/toy_dataset.py b/src/pymovements/datasets/toy_dataset.py
index 96ef186..10ce08e 100644
--- a/src/pymovements/datasets/toy_dataset.py
+++ b/src/pymovements/datasets/toy_dataset.py
@@ -37,14 +37,11 @@ class ToyDataset(PublicDataset):
 
     Examples
     --------
-    Change to ``download=True`` and ``extract=True`` for downloading and extracting the dataset.
-
-    >>> dataset = ToyDataset(
-    ...     root='data/',
-    ...     download=False,
-    ...     extract=False,
-    ...     remove_finished=False,
-    ... )
+    This will initialize your :py:class:`pymovements.PublicDataset` object, download its resources
+    and load the data into memory.
+
+    >>> dataset = ToyDataset(root='data/')
+    >>> dataset.download()  # doctest: +SKIP
     >>> dataset.load()  # doctest: +SKIP
     """
     # pylint: disable=similarities
@@ -95,9 +92,6 @@ class ToyDataset(PublicDataset):
     def __init__(
             self,
             root: str | Path,
-            download: bool = False,
-            extract: bool = False,
-            remove_finished: bool = False,
             dataset_dirname: str = 'ToyDataset',
             downloads_dirname: str = 'downloads',
             raw_dirname: str = 'raw',
@@ -123,12 +117,6 @@ class ToyDataset(PublicDataset):
         ----------
         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: `.`
@@ -148,9 +136,6 @@ class ToyDataset(PublicDataset):
         """
         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,


Test patch:
diff --git a/tests/datasets/public_dataset_test.py b/tests/datasets/public_dataset_test.py
index 93cb92d..1fd30b6 100644
--- a/tests/datasets/public_dataset_test.py
+++ b/tests/datasets/public_dataset_test.py
@@ -135,10 +135,10 @@ def test_dataset_download_file_not_found(mock_download_file, tmp_path, dataset):
 
 
 @mock.patch('pymovements.datasets.public_dataset.download_file')
-def test_dataset_download(mock_download_file, tmp_path, dataset):
+def test_dataset_download_no_extract(mock_download_file, tmp_path, dataset):
     mock_download_file.return_value = 'path'
 
-    dataset.download()
+    dataset.download(extract=False)
 
     mock_download_file.assert_has_calls([
         mock.call(
@@ -190,9 +190,9 @@ def test_dataset_extract_remove_finished_false(
     ])
 
 
-@mock.patch('pymovements.datasets.public_dataset.PublicDataset.download')
-@mock.patch('pymovements.datasets.public_dataset.PublicDataset.extract')
-def test_custom_dataset_download_extract(mock_extract, mock_download, tmp_path):
+@mock.patch('pymovements.datasets.public_dataset.download_file')
+@mock.patch('pymovements.datasets.public_dataset.extract_archive')
+def test_custom_dataset_download_default_extract(mock_extract, mock_download, tmp_path):
     mock_extract.return_value = None
     mock_download.return_value = None
 
@@ -206,8 +206,7 @@ def test_custom_dataset_download_extract(mock_extract, mock_download, tmp_path):
             },
         ]
 
-    CustomPublicDataset(root=tmp_path, download=True)
-    CustomPublicDataset(root=tmp_path, extract=True)
+    CustomPublicDataset(root=tmp_path).download()
 
     mock_download.assert_called_once()
     mock_extract.assert_called_once()