Repository: aeye-lab/pymovements
Pull request #643 (2023-12-13 12:52:45)

feat: timestamp unit specification
## Description

Fixes issue #626 

## Implemented changes

- Introduced support for seconds, milliseconds and steps as units of timestamps in the `time_column`

- Added a `time_unit` parameter to the constructor of GazeDataFrame
- Add a `time_unit` attribute in the DatasetDefinition class
- Developed a private function in GazeDataFrame to convert input timestamps to milliseconds.- Expand functions  `gaze.from_csv`, `gaze.from_pandas`, `gaze.from_numpy` to include a `time_unit` parameter
- Incorporated `time_unit` into existing local dataset definitions
- Updated tests and documentation examples to reflect these changes.

A `time_unit` specification is mandatory when a `time_column` is included in the data. To  ensure compatibility with certain event detection methods (e.g. idt) I choosed to cast time values to integer after conversion to millis when possible. For example, float timestamps in seconds with millisecond precision (e.g., `[0.001, 0.002]`) are converted to integer milliseconds (`[1, 2]`).

## Type of change

- [x] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

- Enhanced existing tests: Updated tests that utilize time values in `GazeDataFrame` to include the specification of the `time_unit`.
- Added conversion tests to the GazeDataFrame init tests.

## 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[bot]: ## [Codecov](https://app.codecov.io/gh/aeye-lab/pymovements/pull/643?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`a9edc9d`)](https://app.codecov.io/gh/aeye-lab/pymovements/commit/a9edc9dada4bf13ab13f034429570cb942dce3be?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) 100.00% compared to head [(`718a61d`)](https://app.codecov.io/gh/aeye-lab/pymovements/pull/643?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) 100.00%.
> Report is 1 commits behind head on main.


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


```diff
@@            Coverage Diff            @@
##              main      #643   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           54        54           
  Lines         2411      2440   +29     
  Branches       605       612    +7     
=========================================
+ Hits          2411      2440   +29     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/aeye-lab/pymovements/pull/643?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab).   
:loudspeaker: Have feedback on the report? [Share it here](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/local-dataset.ipynb b/docs/source/tutorials/local-dataset.ipynb
index 76fe2bb..d565c0c 100644
--- a/docs/source/tutorials/local-dataset.ipynb
+++ b/docs/source/tutorials/local-dataset.ipynb
@@ -204,7 +204,7 @@
    "id": "19",
    "metadata": {},
    "source": [
-    "The `time_column` and `pixel_columns` arguments can be used to correctly map the columns in your dataframes.\n",
+    "The `time_column` and `pixel_columns` arguments can be used to correctly map the columns in your dataframes. If the time unit differs from the default milliseconds `ms` one must also specify the `time_unit` for correct computations.\n",
     "\n",
     "Depending on the content of your dataset, you can alternatively also provide `position_columns`, `velocity_columns` and `acceleration_columns`.\n",
     "\n",
@@ -221,6 +221,7 @@
    "outputs": [],
    "source": [
     "time_column = 'timestamp'\n",
+    "time_unit = 'ms'\n",
     "pixel_columns = ['x', 'y']"
    ]
   },
@@ -254,6 +255,7 @@
     "    filename_format_dtypes=filename_format_dtypes,\n",
     "    custom_read_kwargs=custom_read_kwargs,\n",
     "    time_column=time_column,\n",
+    "    time_unit=time_unit,\n",
     "    pixel_columns=pixel_columns,\n",
     ")"
    ]
diff --git a/src/pymovements/dataset/dataset_definition.py b/src/pymovements/dataset/dataset_definition.py
index 451c43c..88d839c 100644
--- a/src/pymovements/dataset/dataset_definition.py
+++ b/src/pymovements/dataset/dataset_definition.py
@@ -71,6 +71,13 @@ class DatasetDefinition:
     time_column: str | None
         The name of the timestamp column in the input data frame. This column will be renamed to
         ``time``. (default: None)
+
+    time_unit: str | None
+        The unit of the timestamps in the timestamp column in the input data frame. Supported
+        units are 's' for seconds, 'ms' for milliseconds and 'step' for steps. If the unit is
+        'step' the experiment definition must be specified. All timestamps will be converted to
+        milliseconds. (default: 'ms')
+
     pixel_columns: list[str] | None
         The name of the pixel position columns in the input data frame. These columns will be
         nested into the column ``pixel``. If the list is empty or None, the nested ``pixel``
@@ -133,6 +140,7 @@ class DatasetDefinition:
 
     trial_columns: list[str] | None = None
     time_column: str | None = None
+    time_unit: str | None = 'ms'
     pixel_columns: list[str] | None = None
     position_columns: list[str] | None = None
     velocity_columns: list[str] | None = None
diff --git a/src/pymovements/dataset/dataset_files.py b/src/pymovements/dataset/dataset_files.py
index 5274f0a..c8c1872 100644
--- a/src/pymovements/dataset/dataset_files.py
+++ b/src/pymovements/dataset/dataset_files.py
@@ -220,6 +220,7 @@ def load_gaze_files(
             custom_read_kwargs=definition.custom_read_kwargs,
         )
         gaze_dfs.append(gaze_df)
+
     return gaze_dfs
 
 
@@ -268,9 +269,13 @@ def load_gaze_file(
 
     if filepath.suffix in {'.csv', '.txt', '.tsv'}:
         if preprocessed:
+            # Time unit is always milliseconds for preprocessed data if a time column is present.
+            time_unit = 'ms'
+
             gaze_df = from_csv(
                 filepath,
                 trial_columns=definition.trial_columns,
+                time_unit=time_unit,
                 add_columns=add_columns,
                 column_dtypes=definition.filename_format_dtypes,
             )
@@ -311,6 +316,7 @@ def load_gaze_file(
                 filepath,
                 experiment=definition.experiment,
                 time_column=definition.time_column,
+                time_unit=definition.time_unit,
                 distance_column=definition.distance_column,
                 pixel_columns=definition.pixel_columns,
                 position_columns=definition.position_columns,
diff --git a/src/pymovements/datasets/gaze_graph.py b/src/pymovements/datasets/gaze_graph.py
index c597ebb..eade42f 100644
--- a/src/pymovements/datasets/gaze_graph.py
+++ b/src/pymovements/datasets/gaze_graph.py
@@ -138,6 +138,8 @@ class GazeGraph(DatasetDefinition):
 
     time_column: Any = None
 
+    time_unit: Any = None
+
     pixel_columns: list[str] = field(default_factory=lambda: ['x', 'y'])
 
     column_map: dict[str, str] = field(default_factory=lambda: {})
diff --git a/src/pymovements/datasets/gaze_on_faces.py b/src/pymovements/datasets/gaze_on_faces.py
index 204f526..2103d73 100644
--- a/src/pymovements/datasets/gaze_on_faces.py
+++ b/src/pymovements/datasets/gaze_on_faces.py
@@ -135,6 +135,8 @@ class GazeOnFaces(DatasetDefinition):
 
     time_column: Any = None
 
+    time_unit: Any = None
+
     pixel_columns: list[str] = field(default_factory=lambda: ['x', 'y'])
 
     column_map: dict[str, str] = field(default_factory=lambda: {})
diff --git a/src/pymovements/datasets/gazebase.py b/src/pymovements/datasets/gazebase.py
index 4da6643..640326f 100644
--- a/src/pymovements/datasets/gazebase.py
+++ b/src/pymovements/datasets/gazebase.py
@@ -146,6 +146,8 @@ class GazeBase(DatasetDefinition):
 
     time_column: str = 'n'
 
+    time_unit: str = 'ms'
+
     position_columns: list[str] = field(default_factory=lambda: ['x', 'y'])
 
     column_map: dict[str, str] = field(
diff --git a/src/pymovements/datasets/gazebasevr.py b/src/pymovements/datasets/gazebasevr.py
index e0ce449..d54af21 100644
--- a/src/pymovements/datasets/gazebasevr.py
+++ b/src/pymovements/datasets/gazebasevr.py
@@ -147,6 +147,8 @@ class GazeBaseVR(DatasetDefinition):
 
     time_column: str = 'n'
 
+    time_unit: str = 'ms'
+
     position_columns: list[str] = field(default_factory=lambda: ['lx', 'ly', 'rx', 'ry', 'x', 'y'])
 
     column_map: dict[str, str] = field(
diff --git a/src/pymovements/datasets/hbn.py b/src/pymovements/datasets/hbn.py
index bb49a1a..ba5b68b 100644
--- a/src/pymovements/datasets/hbn.py
+++ b/src/pymovements/datasets/hbn.py
@@ -134,6 +134,8 @@ class HBN(DatasetDefinition):
 
     time_column: str = 'time'
 
+    time_unit: str = 'step'
+
     pixel_columns: list[str] = field(default_factory=lambda: ['x_pix', 'y_pix'])
 
     column_map: dict[str, str] = field(default_factory=lambda: {})
diff --git a/src/pymovements/datasets/judo1000.py b/src/pymovements/datasets/judo1000.py
index 3c5143e..4f3fbb6 100644
--- a/src/pymovements/datasets/judo1000.py
+++ b/src/pymovements/datasets/judo1000.py
@@ -134,6 +134,9 @@ class JuDo1000(DatasetDefinition):
     )
 
     time_column: str = 'time'
+
+    time_unit: str = 'ms'
+
     pixel_columns: list[str] = field(
         default_factory=lambda: [
             'x_left', 'y_left', 'x_right', 'y_right',
diff --git a/src/pymovements/datasets/potec.py b/src/pymovements/datasets/potec.py
index 399597b..a0ad905 100644
--- a/src/pymovements/datasets/potec.py
+++ b/src/pymovements/datasets/potec.py
@@ -144,6 +144,9 @@ class PoTeC(DatasetDefinition):
     )
 
     time_column: str = 'time'
+
+    time_unit: str = 'ms'
+
     pixel_columns: list[str] = field(
         default_factory=lambda: [
             'x', 'y',
diff --git a/src/pymovements/datasets/sb_sat.py b/src/pymovements/datasets/sb_sat.py
index 565ba45..9ef9960 100644
--- a/src/pymovements/datasets/sb_sat.py
+++ b/src/pymovements/datasets/sb_sat.py
@@ -132,6 +132,8 @@ class SBSAT(DatasetDefinition):
 
     time_column: str = 'time'
 
+    time_unit: str = 'ms'
+
     pixel_columns: list[str] = field(default_factory=lambda: ['x_left', 'y_left'])
 
     column_map: dict[str, str] = field(default_factory=lambda: {})
diff --git a/src/pymovements/datasets/toy_dataset.py b/src/pymovements/datasets/toy_dataset.py
index a36d298..661b7bf 100644
--- a/src/pymovements/datasets/toy_dataset.py
+++ b/src/pymovements/datasets/toy_dataset.py
@@ -131,6 +131,8 @@ class ToyDataset(DatasetDefinition):
 
     time_column: str = 'timestamp'
 
+    time_unit: str = 'ms'
+
     pixel_columns: list[str] = field(default_factory=lambda: ['x', 'y'])
 
     column_map: dict[str, str] = field(default_factory=lambda: {})
diff --git a/src/pymovements/datasets/toy_dataset_eyelink.py b/src/pymovements/datasets/toy_dataset_eyelink.py
index fc425d5..a62841e 100644
--- a/src/pymovements/datasets/toy_dataset_eyelink.py
+++ b/src/pymovements/datasets/toy_dataset_eyelink.py
@@ -140,6 +140,8 @@ class ToyDatasetEyeLink(DatasetDefinition):
 
     time_column: str = 'time'
 
+    time_unit: str = 'ms'
+
     pixel_columns: list[str] = field(default_factory=lambda: ['x_pix', 'y_pix'])
 
     custom_read_kwargs: dict[str, Any] = field(
diff --git a/src/pymovements/gaze/gaze_dataframe.py b/src/pymovements/gaze/gaze_dataframe.py
index 47120c0..c619e61 100644
--- a/src/pymovements/gaze/gaze_dataframe.py
+++ b/src/pymovements/gaze/gaze_dataframe.py
@@ -57,7 +57,12 @@ class GazeDataFrame:
     time_column: str | None
         The name of the timestamp column in the input data frame. This column will be renamed to
         ``time``. (default: None)
-    pixel_columns: list[str] | None
+    time_unit: str | None
+        The unit of the timestamps in the timestamp column in the input data frame. Supported
+        units are 's' for seconds, 'ms' for milliseconds and 'step' for steps. If the unit is
+        'step' the experiment definition must be specified. All timestamps will be converted to
+        milliseconds. (default: 'ms')
+    pixel_columns:list[str] | None
         The name of the pixel position columns in the input data frame. These columns will be
         nested into the column ``pixel``. If the list is empty or None, the nested ``pixel``
         column will not be created. (default: None)
@@ -119,10 +124,10 @@ class GazeDataFrame:
     │ 1002 ┆ 0.3 ┆ 0.3 │
     └──────┴─────┴─────┘
 
-    We can now initialize our ``GazeDataFrame`` by specyfing the names of the time and pixel
-    position columns.
+    We can now initialize our ``GazeDataFrame`` by specyfing the names of the pixel position
+    columns, the timestamp column and the unit of the timestamps.
 
-    >>> gaze = GazeDataFrame(data=df, time_column='t', pixel_columns=['x', 'y'])
+    >>> gaze = GazeDataFrame(data=df, pixel_columns=['x', 'y'], time_column='t', time_unit='ms')
     >>> gaze.frame
     shape: (3, 2)
     ┌──────┬────────────┐
@@ -159,27 +164,14 @@ class GazeDataFrame:
     ┌──────┬────────────┐
     │ time ┆ pixel      │
     │ ---  ┆ ---        │
-    │ f64  ┆ list[f64]  │
-    ╞══════╪════════════╡
-    │ 0.0  ┆ [0.1, 0.1] │
-    │ 10.0 ┆ [0.2, 0.2] │
-    │ 20.0 ┆ [0.3, 0.3] │
-    └──────┴────────────┘
-
-    Leaving out the experiment definition will create a continuous integer column in step units.
-    >>> gaze = GazeDataFrame(data=df_no_time, pixel_columns=['x', 'y'])
-    >>> gaze.frame
-    shape: (3, 2)
-    ┌──────┬────────────┐
-    │ time ┆ pixel      │
-    │ ---  ┆ ---        │
     │ i64  ┆ list[f64]  │
     ╞══════╪════════════╡
     │ 0    ┆ [0.1, 0.1] │
-    │ 1    ┆ [0.2, 0.2] │
-    │ 2    ┆ [0.3, 0.3] │
+    │ 10   ┆ [0.2, 0.2] │
+    │ 20   ┆ [0.3, 0.3] │
     └──────┴────────────┘
 
+
     """
 
     def __init__(
@@ -190,6 +182,7 @@ class GazeDataFrame:
             *,
             trial_columns: str | list[str] | None = None,
             time_column: str | None = None,
+            time_unit: str | None = 'ms',
             pixel_columns: list[str] | None = None,
             position_columns: list[str] | None = None,
             velocity_columns: list[str] | None = None,
@@ -203,24 +196,26 @@ class GazeDataFrame:
         self.frame = data
 
         self.trial_columns = [trial_columns] if isinstance(trial_columns, str) else trial_columns
+        self.experiment = experiment
 
         # In case the 'time' column is already present we don't need to do anything.
-        # Otherwise, create a new time column starting with zero.
+        # Otherwise, create a new time column starting with zero and set time unit to steps
         if time_column is None and 'time' not in self.frame.columns:
-            # In case we have an experiment with sampling rate given, we convert to milliseconds.
+            # In case we have an experiment with sampling rate given, we create a time
             if experiment is not None and experiment.sampling_rate is not None:
-                sampling_rate_factor = 1000 / experiment.sampling_rate
-            else:
-                sampling_rate_factor = 1
+                self.frame = self.frame.with_columns(
+                    time=pl.arange(0, len(self.frame)),
+                )
 
-            self.frame = self.frame.with_columns(
-                time=pl.arange(0, len(self.frame)) * sampling_rate_factor,
-            )
+                time_column = 'time'
+                time_unit = 'step'
 
-        # This if clause is mutually exclusive with the previous one.
         if time_column is not None:
             self.frame = self.frame.rename({time_column: 'time'})
 
+        if 'time' in self.frame.columns:
+            self._convert_time_units(time_unit)
+
         if distance_column is not None:
             self.frame = self.frame.rename({distance_column: 'distance'})
 
@@ -249,7 +244,6 @@ class GazeDataFrame:
             column_specifiers.append(acceleration_columns)
 
         self.n_components = self._infer_n_components(column_specifiers)
-        self.experiment = experiment
 
         if events is None:
             if self.trial_columns is None:
@@ -1064,3 +1058,36 @@ class GazeDataFrame:
             kwargs['timesteps'] = gaze.get_column('time').to_numpy()
 
         return kwargs
+
+    def _convert_time_units(self, time_unit: str | None) -> None:
+        """Convert the time column to milliseconds based on the specified time unit."""
+        if time_unit == 's':
+            self.frame = self.frame.with_columns(pl.col('time').mul(1000))
+
+        elif time_unit == 'step':
+            if self.experiment is not None:
+                self.frame = self.frame.with_columns(
+                    pl.col('time').mul(1000).truediv(self.experiment.sampling_rate),
+                )
+            else:
+                raise ValueError(
+                    "experiment with sampling rate must be specified if time_unit is 'step'",
+                )
+
+        elif time_unit != 'ms':
+            raise ValueError(
+                f"unsupported time unit '{time_unit}'. "
+                "Supported units are 's' for seconds, 'ms' for milliseconds and "
+                "'step' for steps.",
+            )
+
+        # Convert to int if possible.
+        if self.frame.schema['time'] == pl.Float64:
+            all_decimals = self.frame.select(
+                pl.col('time').round().eq(pl.col('time')).all(),
+            ).item()
+
+            if all_decimals:
+                self.frame = self.frame.with_columns(
+                    pl.col('time').cast(pl.Int64),
+                )
diff --git a/src/pymovements/gaze/integration.py b/src/pymovements/gaze/integration.py
index bdd5087..06ee617 100644
--- a/src/pymovements/gaze/integration.py
+++ b/src/pymovements/gaze/integration.py
@@ -48,6 +48,7 @@ def from_numpy(
         orient: Literal['col', 'row'] = 'col',
         trial_columns: str | list[str] | None = None,
         time_column: str | None = None,
+        time_unit: str | None = 'ms',
         pixel_columns: list[str] | None = None,
         position_columns: list[str] | None = None,
         velocity_columns: list[str] | None = None,
@@ -99,6 +100,10 @@ def from_numpy(
         methods will be applied to each trial separately. (default: None)
     time_column: str | None
         The name of the timestamp column in the input data frame. (default: None)
+    time_unit: str | None
+        The unit of the timestamps. Supported units are 's' for seconds, 'ms' for milliseconds and
+        'step' for steps. If the unit is 'step' the experiment definition must be specified. All
+        timestamps will be converted to milliseconds. (default: None)
     pixel_columns: list[str] | None
         The name of the pixel position columns in the input data frame. (default: None)
     position_columns: list[str] | None
@@ -137,6 +142,7 @@ def from_numpy(
     ...     arr,
     ...     schema=schema,
     ...     time_column='t',
+    ...     time_unit='ms',
     ...     position_columns=['x', 'y'],
     ...     orient='col',
     ... )
@@ -145,17 +151,17 @@ def from_numpy(
     ┌──────┬────────────┐
     │ time ┆ position   │
     │ ---  ┆ ---        │
-    │ f64  ┆ list[f64]  │
+    │ i64  ┆ list[f64]  │
     ╞══════╪════════════╡
-    │ 0.0  ┆ [0.0, 0.0] │
-    │ 0.0  ┆ [0.0, 0.0] │
-    │ 0.0  ┆ [0.0, 0.0] │
-    │ 0.0  ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
     │ …    ┆ …          │
-    │ 0.0  ┆ [0.0, 0.0] │
-    │ 0.0  ┆ [0.0, 0.0] │
-    │ 0.0  ┆ [0.0, 0.0] │
-    │ 0.0  ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
     └──────┴────────────┘
 
     Use the ``orient`` keyword argument to specify the layout of your array.
@@ -166,6 +172,7 @@ def from_numpy(
     ...     arr.T,
     ...     schema=schema,
     ...     time_column='t',
+    ...     time_unit='ms',
     ...     position_columns=['x', 'y'],
     ...     orient='row',
     ... )
@@ -174,22 +181,23 @@ def from_numpy(
     ┌──────┬────────────┐
     │ time ┆ position   │
     │ ---  ┆ ---        │
-    │ f64  ┆ list[f64]  │
+    │ i64  ┆ list[f64]  │
     ╞══════╪════════════╡
-    │ 0.0  ┆ [0.0, 0.0] │
-    │ 0.0  ┆ [0.0, 0.0] │
-    │ 0.0  ┆ [0.0, 0.0] │
-    │ 0.0  ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
     │ …    ┆ …          │
-    │ 0.0  ┆ [0.0, 0.0] │
-    │ 0.0  ┆ [0.0, 0.0] │
-    │ 0.0  ┆ [0.0, 0.0] │
-    │ 0.0  ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
     └──────┴────────────┘
 
     Pass the data explicitly via the specific keyword arguments, without having to specify a schema.
     >>> gaze = pm.gaze.from_numpy(
     ...     time=arr[0],
+    ...     time_unit='ms',
     ...     position=arr[[1, 2]],
     ...     orient='col',
     ... )
@@ -198,17 +206,17 @@ def from_numpy(
     ┌──────┬────────────┐
     │ time ┆ position   │
     │ ---  ┆ ---        │
-    │ f64  ┆ list[f64]  │
+    │ i64  ┆ list[f64]  │
     ╞══════╪════════════╡
-    │ 0.0  ┆ [0.0, 0.0] │
-    │ 0.0  ┆ [0.0, 0.0] │
-    │ 0.0  ┆ [0.0, 0.0] │
-    │ 0.0  ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
     │ …    ┆ …          │
-    │ 0.0  ┆ [0.0, 0.0] │
-    │ 0.0  ┆ [0.0, 0.0] │
-    │ 0.0  ┆ [0.0, 0.0] │
-    │ 0.0  ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
+    │ 0    ┆ [0.0, 0.0] │
     └──────┴────────────┘
     """
     # Either data or {time, pixel, position, velocity, acceleration} must be None.
@@ -227,6 +235,7 @@ def from_numpy(
             events=events,
             trial_columns=trial_columns,
             time_column=time_column,
+            time_unit=time_unit,
             pixel_columns=pixel_columns,
             position_columns=position_columns,
             velocity_columns=velocity_columns,
@@ -286,6 +295,7 @@ def from_numpy(
         experiment=experiment,
         events=events,
         time_column=time_column,
+        time_unit=time_unit,
         trial_columns=trial_columns,
         pixel_columns=pixel_columns,
         position_columns=position_columns,
@@ -302,6 +312,7 @@ def from_pandas(
         *,
         trial_columns: str | list[str] | None = None,
         time_column: str | None = None,
+        time_unit: str | None = 'ms',
         pixel_columns: list[str] | None = None,
         position_columns: list[str] | None = None,
         velocity_columns: list[str] | None = None,
@@ -325,6 +336,11 @@ def from_pandas(
         methods will be applied to each trial separately. (default: None)
     time_column: str | None
         The name of the timestamp column in the input data frame. (default: None)
+    time_unit: str | None
+        The unit of the timestamps in the input data frame. Supported units are 's' for seconds,
+        'ms' for milliseconds and 'step' for steps. If the unit is 'step' the experiment
+        definition must be specified. All timestamps will be converted to milliseconds.
+        (default: 'ms')
     pixel_columns: list[str] | None
         The name of the pixel position columns in the input data frame. (default: None)
     position_columns: list[str] | None
@@ -351,6 +367,7 @@ def from_pandas(
         events=events,
         trial_columns=trial_columns,
         time_column=time_column,
+        time_unit=time_unit,
         pixel_columns=pixel_columns,
         position_columns=position_columns,
         velocity_columns=velocity_columns,
diff --git a/src/pymovements/gaze/io.py b/src/pymovements/gaze/io.py
index 3f3cdc9..78979ca 100644
--- a/src/pymovements/gaze/io.py
+++ b/src/pymovements/gaze/io.py
@@ -36,6 +36,7 @@ def from_csv(
         *,
         trial_columns: list[str] | None = None,
         time_column: str | None = None,
+        time_unit: str | None = 'ms',
         pixel_columns: list[str] | None = None,
         position_columns: list[str] | None = None,
         velocity_columns: list[str] | None = None,
@@ -61,6 +62,11 @@ def from_csv(
         methods will be applied to each trial separately. (default: None)
     time_column: str | None
         The name of the timestamp column in the input data frame. (default: None)
+    time_unit: str | None
+        The unit of the timestamps in the timespamp column in the input data frame. Supported
+        units are 's' for seconds, 'ms' for milliseconds and 'step' for steps. If the unit is
+        'step' the experiment definition must be specified. All timestamps will be converted to
+        milliseconds. (default: 'ms')
     pixel_columns: list[str] | None
         The name of the pixel position columns in the input data frame. These columns will be
         nested into the column ``pixel``. If the list is empty or None, the nested ``pixel``
@@ -150,6 +156,7 @@ def from_csv(
     >>> gaze = from_csv(
     ...     file='tests/files/monocular_example.csv',
     ...     time_column = 'time',
+    ...     time_unit='ms',
     ...     pixel_columns = ['x_left_pix','y_left_pix'],
     ...     separator = ',',
     ... )
@@ -180,6 +187,7 @@ def from_csv(
     >>> gaze = from_csv(
     ...     file='tests/files/monocular_example.csv',
     ...     time_column = 'time',
+    ...     time_unit='ms',
     ...     pixel_columns = ['x_left_pix','y_left_pix'],
     ...     dtypes = {'time': pl.Int64, 'x_left_pix': pl.Int64, 'y_left_pix': pl.Int64},
     ... )
@@ -232,6 +240,7 @@ def from_csv(
         experiment=experiment,
         trial_columns=trial_columns,
         time_column=time_column,
+        time_unit=time_unit,
         pixel_columns=pixel_columns,
         position_columns=position_columns,
         velocity_columns=velocity_columns,
@@ -329,6 +338,7 @@ def from_asc(
         gaze_data,
         experiment=experiment,
         time_column='time',
+        time_unit='ms',
         pixel_columns=['x_pix', 'y_pix'],
     )
     return gaze_df


Test patch:
diff --git a/tests/functional/dataset_processing_test.py b/tests/functional/dataset_processing_test.py
index 51e0747..25becc3 100644
--- a/tests/functional/dataset_processing_test.py
+++ b/tests/functional/dataset_processing_test.py
@@ -44,6 +44,7 @@ def fixture_dataset_init_kwargs(request):
     init_param_dict = {
         'csv_monocular': pm.dataset.DatasetDefinition(
             time_column='time',
+            time_unit='ms',
             pixel_columns=['x_left_pix', 'y_left_pix'],
             experiment=pm.Experiment(1024, 768, 38, 30, 60, 'center', 1000),
             filename_format='monocular_example.csv',
@@ -51,6 +52,7 @@ def fixture_dataset_init_kwargs(request):
         'csv_binocular': pm.dataset.DatasetDefinition(
             filename_format='binocular_example.csv',
             time_column='time',
+            time_unit='ms',
             pixel_columns=['x_left_pix', 'y_left_pix', 'x_right_pix', 'y_right_pix'],
             position_columns=['x_left_pos', 'y_left_pos', 'x_right_pos', 'y_right_pos'],
             experiment=pm.Experiment(1024, 768, 38, 30, 60, 'center', 1000),
@@ -65,41 +67,57 @@ def fixture_dataset_init_kwargs(request):
         ),
         'hbn': pm.datasets.HBN(
             filename_format='hbn_example.csv',
+            time_column=pm.datasets.HBN().time_column,
+            time_unit=pm.datasets.HBN().time_unit,
             filename_format_dtypes={},
             trial_columns=None,
         ),
         'sbsat': pm.datasets.SBSAT(
             filename_format='sbsat_example.csv',
+            time_column=pm.datasets.SBSAT().time_column,
+            time_unit=pm.datasets.SBSAT().time_unit,
             filename_format_dtypes={},
 
         ),
         'gaze_on_faces': pm.datasets.GazeOnFaces(
             filename_format='gaze_on_faces_example.csv',
+            time_column=pm.datasets.GazeOnFaces().time_column,
+            time_unit=pm.datasets.GazeOnFaces().time_unit,
             filename_format_dtypes={},
             trial_columns=None,
         ),
         'gazebase': pm.datasets.GazeBase(
             filename_format='gazebase_example.csv',
+            time_column=pm.datasets.GazeBase().time_column,
+            time_unit=pm.datasets.GazeBase().time_unit,
             filename_format_dtypes={},
             trial_columns=None,
         ),
         'gazebase_vr': pm.datasets.GazeBaseVR(
             filename_format='gazebase_vr_example.csv',
+            time_column=pm.datasets.GazeBaseVR().time_column,
+            time_unit=pm.datasets.GazeBaseVR().time_unit,
             filename_format_dtypes={},
             trial_columns=None,
         ),
         'gazegraph': pm.datasets.GazeGraph(
             filename_format='gazegraph_example.csv',
+            time_column=pm.datasets.GazeGraph().time_column,
+            time_unit=pm.datasets.GazeGraph().time_unit,
             filename_format_dtypes={},
             trial_columns=None,
         ),
         'judo1000': pm.datasets.JuDo1000(
             filename_format='judo1000_example.csv',
+            time_column=pm.datasets.JuDo1000().time_column,
+            time_unit=pm.datasets.JuDo1000().time_unit,
             filename_format_dtypes={},
             trial_columns=['trial_id'],
         ),
         'potec': pm.datasets.PoTeC(
             filename_format='potec_example.tsv',
+            time_column=pm.datasets.PoTeC().time_column,
+            time_unit=pm.datasets.PoTeC().time_unit,
             filename_format_dtypes={},
             trial_columns=None,
         ),
diff --git a/tests/functional/gaze_file_processing_test.py b/tests/functional/gaze_file_processing_test.py
index 331f75e..c8e40a7 100644
--- a/tests/functional/gaze_file_processing_test.py
+++ b/tests/functional/gaze_file_processing_test.py
@@ -46,12 +46,15 @@ def fixture_gaze_init_kwargs(request):
     init_param_dict = {
         'csv_monocular': {
             'file': 'tests/files/monocular_example.csv',
-            'time_column': 'time', 'pixel_columns': ['x_left_pix', 'y_left_pix'],
+            'time_column': 'time',
+            'time_unit': 'ms',
+            'pixel_columns': ['x_left_pix', 'y_left_pix'],
             'experiment': pm.Experiment(1024, 768, 38, 30, 60, 'center', 1000),
         },
         'csv_binocular': {
             'file': 'tests/files/binocular_example.csv',
             'time_column': 'time',
+            'time_unit': 'ms',
             'pixel_columns': ['x_left_pix', 'y_left_pix', 'x_right_pix', 'y_right_pix'],
             'position_columns': ['x_left_pos', 'y_left_pos', 'x_right_pos', 'y_right_pos'],
             'experiment': pm.Experiment(1024, 768, 38, 30, 60, 'center', 1000),
@@ -71,12 +74,14 @@ def fixture_gaze_init_kwargs(request):
         'hbn': {
             'file': 'tests/files/hbn_example.csv',
             'time_column': pm.datasets.HBN().time_column,
+            'time_unit': pm.datasets.HBN().time_unit,
             'pixel_columns': pm.datasets.HBN().pixel_columns,
             'experiment': pm.datasets.HBN().experiment,
         },
         'sbsat': {
             'file': 'tests/files/sbsat_example.csv',
             'time_column': pm.datasets.SBSAT().time_column,
+            'time_unit': pm.datasets.SBSAT().time_unit,
             'pixel_columns': pm.datasets.SBSAT().pixel_columns,
             'experiment': pm.datasets.SBSAT().experiment,
             **pm.datasets.SBSAT().custom_read_kwargs,
@@ -84,6 +89,7 @@ def fixture_gaze_init_kwargs(request):
         'gaze_on_faces': {
             'file': 'tests/files/gaze_on_faces_example.csv',
             'time_column': pm.datasets.GazeOnFaces().time_column,
+            'time_unit': pm.datasets.GazeOnFaces().time_unit,
             'pixel_columns': pm.datasets.GazeOnFaces().pixel_columns,
             'experiment': pm.datasets.GazeOnFaces().experiment,
             **pm.datasets.GazeOnFaces().custom_read_kwargs,
@@ -91,18 +97,21 @@ def fixture_gaze_init_kwargs(request):
         'gazebase': {
             'file': 'tests/files/gazebase_example.csv',
             'time_column': pm.datasets.GazeBase().time_column,
+            'time_unit': pm.datasets.GazeBase().time_unit,
             'position_columns': pm.datasets.GazeBase().position_columns,
             'experiment': pm.datasets.GazeBase().experiment,
         },
         'gazebase_vr': {
             'file': 'tests/files/gazebase_vr_example.csv',
             'time_column': pm.datasets.GazeBaseVR().time_column,
+            'time_unit': pm.datasets.GazeBaseVR().time_unit,
             'position_columns': pm.datasets.GazeBaseVR().position_columns,
             'experiment': pm.datasets.GazeBaseVR().experiment,
         },
         'judo1000': {
             'file': 'tests/files/judo1000_example.csv',
             'time_column': pm.datasets.JuDo1000().time_column,
+            'time_unit': pm.datasets.JuDo1000().time_unit,
             'pixel_columns': pm.datasets.JuDo1000().pixel_columns,
             'experiment': pm.datasets.JuDo1000().experiment,
             **pm.datasets.JuDo1000().custom_read_kwargs,
@@ -110,6 +119,7 @@ def fixture_gaze_init_kwargs(request):
         'potec': {
             'file': 'tests/files/potec_example.tsv',
             'time_column': pm.datasets.PoTeC().time_column,
+            'time_unit': pm.datasets.PoTeC().time_unit,
             'pixel_columns': pm.datasets.PoTeC().pixel_columns,
             'experiment': pm.datasets.PoTeC().experiment,
             **pm.datasets.PoTeC().custom_read_kwargs,
diff --git a/tests/unit/dataset/dataset_test.py b/tests/unit/dataset/dataset_test.py
index 39a1d63..3dc45e5 100644
--- a/tests/unit/dataset/dataset_test.py
+++ b/tests/unit/dataset/dataset_test.py
@@ -276,6 +276,7 @@ def mock_toy(rootpath, raw_fileformat, eyes, remote=False):
         filename_format=r'{subject_id:d}.' + raw_fileformat,
         filename_format_dtypes={'subject_id': pl.Int64},
         time_column='time',
+        time_unit='ms',
         distance_column=distance_column,
         pixel_columns=pixel_columns,
     )
diff --git a/tests/unit/gaze/gaze_dataframe_unnest_test.py b/tests/unit/gaze/gaze_dataframe_unnest_test.py
index 95fac71..29419cc 100644
--- a/tests/unit/gaze/gaze_dataframe_unnest_test.py
+++ b/tests/unit/gaze/gaze_dataframe_unnest_test.py
@@ -73,21 +73,21 @@ import pymovements as pm
         pytest.param(
             pl.DataFrame({'pixel': [[1.23, 4.56]]}),
             {'input_columns': 'pixel', 'output_columns': ['x', 'y']},
-            pl.DataFrame({'time': [0], 'x': [1.23], 'y': [4.56]}),
+            pl.DataFrame({'x': [1.23], 'y': [4.56]}),
             id='df_single_row_two_pixel_columns',
         ),
 
         pytest.param(
             pl.DataFrame({'abc': [1], 'pixel': [[1.23, 4.56]]}),
             {'input_columns': 'pixel', 'output_columns': ['x', 'y']},
-            pl.DataFrame({'abc': [1], 'time': [0], 'x': [1.23], 'y': [4.56]}),
+            pl.DataFrame({'abc': [1], 'x': [1.23], 'y': [4.56]}),
             id='df_single_row_three_columns_two_pixel_columns',
         ),
 
         pytest.param(
             pl.DataFrame({'pixel': [[1.2, 3.4, 5.6, 7.8]]}),
             {'input_columns': 'pixel', 'output_columns': ['xl', 'yl', 'xr', 'yr']},
-            pl.DataFrame({'time': [0], 'xl': [1.2], 'yl': [3.4], 'xr': [5.6], 'yr': [7.8]}),
+            pl.DataFrame({'xl': [1.2], 'yl': [3.4], 'xr': [5.6], 'yr': [7.8]}),
             id='df_single_row_four_pixel_columns',
         ),
 
@@ -95,7 +95,7 @@ import pymovements as pm
             pl.DataFrame({'pixel': [[0.1, 0.2, 0.3, 0.4, 0.5, 0.6]]}),
             {'input_columns': 'pixel', 'output_columns': ['xl', 'yl', 'xr', 'yr', 'xa', 'ya']},
             pl.DataFrame({
-                'time': [0], 'xl': [.1], 'yl': [.2], 'xr': [.3], 'yr': [.4], 'xa': [.5], 'ya': [.6],
+                'xl': [.1], 'yl': [.2], 'xr': [.3], 'yr': [.4], 'xa': [.5], 'ya': [.6],
             }),
             id='df_single_row_six_pixel_columns',
         ),
@@ -157,14 +157,14 @@ import pymovements as pm
         pytest.param(
             pl.DataFrame({'pixel': [[1.23, 4.56]]}),
             {'input_columns': 'pixel', 'output_suffixes': ['_x', '_y']},
-            pl.DataFrame({'time': [0], 'pixel_x': [1.23], 'pixel_y': [4.56]}),
+            pl.DataFrame({'pixel_x': [1.23], 'pixel_y': [4.56]}),
             id='df_single_row_two_pixel_suffixes',
         ),
 
         pytest.param(
             pl.DataFrame({'abc': [1], 'pixel': [[1.23, 4.56]]}),
             {'input_columns': 'pixel', 'output_suffixes': ['_x', '_y']},
-            pl.DataFrame({'abc': [1], 'time': [0], 'pixel_x': [1.23], 'pixel_y': [4.56]}),
+            pl.DataFrame({'abc': [1], 'pixel_x': [1.23], 'pixel_y': [4.56]}),
             id='df_single_row_three_columns_two_pixel_suffixes',
         ),
 
@@ -172,7 +172,6 @@ import pymovements as pm
             pl.DataFrame({'pixel': [[1.2, 3.4, 5.6, 7.8]]}),
             {'input_columns': 'pixel', 'output_suffixes': ['_xl', '_yl', '_xr', '_yr']},
             pl.DataFrame({
-                'time': [0],
                 'pixel_xl': [1.2], 'pixel_yl': [3.4],
                 'pixel_xr': [5.6], 'pixel_yr': [7.8],
             }),
@@ -186,7 +185,6 @@ import pymovements as pm
                 'output_suffixes': ['_xl', '_yl', '_xr', '_yr', '_xa', '_ya'],
             },
             pl.DataFrame({
-                'time': [0],
                 'pixel_xl': [.1], 'pixel_yl': [.2],
                 'pixel_xr': [.3], 'pixel_yr': [.4],
                 'pixel_xa': [.5], 'pixel_ya': [.6],
@@ -197,14 +195,14 @@ import pymovements as pm
         pytest.param(
             pl.DataFrame({'pixel': [[1.23, 4.56]]}),
             {'input_columns': 'pixel'},
-            pl.DataFrame({'time': [0], 'pixel_x': [1.23], 'pixel_y': [4.56]}),
+            pl.DataFrame({'pixel_x': [1.23], 'pixel_y': [4.56]}),
             id='df_single_row_two_pixel_suffixes_default_values',
         ),
 
         pytest.param(
             pl.DataFrame({'abc': [1], 'pixel': [[1.23, 4.56]]}),
             {'input_columns': 'pixel'},
-            pl.DataFrame({'abc': [1], 'time': [0], 'pixel_x': [1.23], 'pixel_y': [4.56]}),
+            pl.DataFrame({'abc': [1], 'pixel_x': [1.23], 'pixel_y': [4.56]}),
             id='df_single_row_three_columns_two_pixel_suffixes_default_values',
         ),
 
@@ -212,7 +210,6 @@ import pymovements as pm
             pl.DataFrame({'pixel': [[1.2, 3.4, 5.6, 7.8]]}),
             {'input_columns': 'pixel'},
             pl.DataFrame({
-                'time': [0],
                 'pixel_xl': [1.2], 'pixel_yl': [3.4],
                 'pixel_xr': [5.6], 'pixel_yr': [7.8],
             }),
@@ -223,7 +220,6 @@ import pymovements as pm
             pl.DataFrame({'pixel': [[0.1, 0.2, 0.3, 0.4, 0.5, 0.6]]}),
             {'input_columns': 'pixel'},
             pl.DataFrame({
-                'time': [0],
                 'pixel_xl': [.1], 'pixel_yl': [.2],
                 'pixel_xr': [.3], 'pixel_yr': [.4],
                 'pixel_xa': [.5], 'pixel_ya': [.6],
@@ -254,7 +250,6 @@ def test_gaze_dataframe_unnest_has_expected_frame(init_data, unnest_kwargs, expe
                 'position': [[1.23, 4.56]],
                 'velocity': [[1.23, 4.56]],
                 'acceleration': [[1.23, 4.56]],
-                'time': [0],
                 'x': [1.23],
                 'y': [4.56],
             }),
@@ -269,7 +264,6 @@ def test_gaze_dataframe_unnest_has_expected_frame(init_data, unnest_kwargs, expe
             }),
             {},
             pl.DataFrame({
-                'time': [0],
                 'pixel_x': [1.23], 'pixel_y': [4.56],
                 'position_x': [1.23], 'position_y': [4.56],
                 'velocity_x': [1.23], 'velocity_y': [4.56],
@@ -286,7 +280,6 @@ def test_gaze_dataframe_unnest_has_expected_frame(init_data, unnest_kwargs, expe
             }),
             {},
             pl.DataFrame({
-                'time': [0],
                 'pixel_xl': [1.23], 'pixel_yl': [4.56],
                 'pixel_xr': [7.89], 'pixel_yr': [10.11],
                 'position_xl': [1.23], 'position_yl': [4.56],
@@ -307,7 +300,6 @@ def test_gaze_dataframe_unnest_has_expected_frame(init_data, unnest_kwargs, expe
             }),
             {},
             pl.DataFrame({
-                'time': [0],
                 'pixel_xl': [1.23], 'pixel_yl': [4.56],
                 'pixel_xr': [7.89], 'pixel_yr': [10.11],
                 'pixel_xa': [12.13], 'pixel_ya': [14.15],
@@ -336,7 +328,6 @@ def test_gaze_dataframe_unnest_has_expected_frame(init_data, unnest_kwargs, expe
                 'pixel': [[1.23, 4.56]],
                 'velocity': [[1.23, 4.56]],
                 'acceleration': [[1.23, 4.56]],
-                'time': [0],
                 'position_x': [1.23], 'position_y': [4.56],
             }),
             id='df_single_row_two_components_unnest_one_multiple_columns_suffix_specified',
@@ -353,7 +344,6 @@ def test_gaze_dataframe_unnest_has_expected_frame(init_data, unnest_kwargs, expe
             pl.DataFrame({
                 'pixel': [[1.23, 4.56]],
                 'velocity': [[1.23, 4.56]],
-                'time': [0],
                 'position_x': [1.23], 'position_y': [4.56],
                 'acceleration_x': [1.23], 'acceleration_y': [4.56],
             }),
diff --git a/tests/unit/gaze/gaze_init_test.py b/tests/unit/gaze/gaze_init_test.py
index 016e397..a6cae88 100644
--- a/tests/unit/gaze/gaze_init_test.py
+++ b/tests/unit/gaze/gaze_init_test.py
@@ -33,7 +33,7 @@ import pymovements as pm
             {
                 'data': pl.DataFrame(),
             },
-            pl.DataFrame(schema={'time': pl.Int64}),
+            pl.DataFrame(schema={}),
             None,
             id='empty_df_no_schema',
         ),
@@ -42,7 +42,7 @@ import pymovements as pm
             {
                 'data': pl.DataFrame(schema={'abc': pl.Int64}),
             },
-            pl.DataFrame(schema={'abc': pl.Int64, 'time': pl.Int64}),
+            pl.DataFrame(schema={'abc': pl.Int64}),
             None,
             id='empty_df_with_schema_no_component_columns',
         ),
@@ -55,7 +55,7 @@ import pymovements as pm
                 'velocity_columns': [],
                 'acceleration_columns': [],
             },
-            pl.DataFrame(schema={'abc': pl.Int64, 'time': pl.Int64}),
+            pl.DataFrame(schema={'abc': pl.Int64}),
             None,
             id='empty_df_with_schema_all_component_columns_empty_lists',
         ),
@@ -65,7 +65,7 @@ import pymovements as pm
                 'data': pl.DataFrame(schema={'x': pl.Float64, 'y': pl.Float64}),
                 'pixel_columns': ['x', 'y'],
             },
-            pl.DataFrame(schema={'time': pl.Int64, 'pixel': pl.List(pl.Float64)}),
+            pl.DataFrame(schema={'pixel': pl.List(pl.Float64)}),
             2,
             id='empty_df_with_schema_two_pixel_columns',
         ),
@@ -75,7 +75,7 @@ import pymovements as pm
                 'data': pl.DataFrame(schema={'abc': pl.Int64, 'x': pl.Float64, 'y': pl.Float64}),
                 'pixel_columns': ['x', 'y'],
             },
-            pl.DataFrame(schema={'abc': pl.Int64, 'time': pl.Int64, 'pixel': pl.List(pl.Float64)}),
+            pl.DataFrame(schema={'abc': pl.Int64, 'pixel': pl.List(pl.Float64)}),
             2,
             id='empty_df_with_three_column_schema_two_pixel_columns',
         ),
@@ -89,7 +89,7 @@ import pymovements as pm
                 ),
                 'pixel_columns': ['xr', 'yr', 'xl', 'yl'],
             },
-            pl.DataFrame(schema={'time': pl.Int64, 'pixel': pl.List(pl.Float64)}),
+            pl.DataFrame(schema={'pixel': pl.List(pl.Float64)}),
             4,
             id='empty_df_with_schema_four_pixel_columns',
         ),
@@ -107,7 +107,7 @@ import pymovements as pm
                     'x_right', 'y_right', 'x_left', 'y_left', 'x_avg', 'y_avg',
                 ],
             },
-            pl.DataFrame(schema={'time': pl.Int64, 'pixel': pl.List(pl.Float64)}),
+            pl.DataFrame(schema={'pixel': pl.List(pl.Float64)}),
             6,
             id='empty_df_with_schema_six_pixel_columns',
         ),
@@ -120,8 +120,8 @@ import pymovements as pm
                 'pixel_columns': ['x', 'y'],
             },
             pl.from_dict(
-                {'time': [0], 'pixel': [[1.23, 4.56]]},
-                schema={'time': pl.Int64, 'pixel': pl.List(pl.Float64)},
+                {'pixel': [[1.23, 4.56]]},
+                schema={'pixel': pl.List(pl.Float64)},
             ),
             2,
             id='df_single_row_two_pixel_columns',
@@ -136,8 +136,8 @@ import pymovements as pm
                 'pixel_columns': ['x', 'y'],
             },
             pl.from_dict(
-                {'time': [0], 'abc': [1], 'pixel': [[1.23, 4.56]]},
-                schema={'abc': pl.Int64, 'time': pl.Int64, 'pixel': pl.List(pl.Float64)},
+                {'abc': [1], 'pixel': [[1.23, 4.56]]},
+                schema={'abc': pl.Int64, 'pixel': pl.List(pl.Float64)},
             ),
             2,
             id='df_single_row_three_columns_two_pixel_columns',
@@ -152,8 +152,8 @@ import pymovements as pm
                 'pixel_columns': ['xl', 'yl', 'xr', 'yr'],
             },
             pl.from_dict(
-                {'time': [0], 'pixel': [[1.2, 3.4, 5.6, 7.8]]},
-                schema={'time': pl.Int64, 'pixel': pl.List(pl.Float64)},
+                {'pixel': [[1.2, 3.4, 5.6, 7.8]]},
+                schema={'pixel': pl.List(pl.Float64)},
             ),
             4,
             id='df_single_row_four_pixel_columns',
@@ -178,8 +178,8 @@ import pymovements as pm
                 ],
             },
             pl.from_dict(
-                {'time': [0], 'pixel': [[0.1, 0.2, 0.3, 0.4, 0.5, 0.6]]},
-                schema={'time': pl.Int64, 'pixel': pl.List(pl.Float64)},
+                {'pixel': [[0.1, 0.2, 0.3, 0.4, 0.5, 0.6]]},
+                schema={'pixel': pl.List(pl.Float64)},
             ),
             6,
             id='df_single_row_six_pixel_columns',
@@ -190,7 +190,7 @@ import pymovements as pm
                 'data': pl.DataFrame(schema={'x': pl.Float64, 'y': pl.Float64}),
                 'position_columns': ['x', 'y'],
             },
-            pl.DataFrame(schema={'time': pl.Int64, 'position': pl.List(pl.Float64)}),
+            pl.DataFrame(schema={'position': pl.List(pl.Float64)}),
             2,
             id='empty_df_with_schema_two_position_columns',
         ),
@@ -202,7 +202,7 @@ import pymovements as pm
             },
             pl.DataFrame(
                 schema={
-                    'abc': pl.Int64, 'time': pl.Int64, 'position': pl.List(pl.Float64),
+                    'abc': pl.Int64, 'position': pl.List(pl.Float64),
                 },
             ),
             2,
@@ -218,7 +218,7 @@ import pymovements as pm
                 ),
                 'position_columns': ['xr', 'yr', 'xl', 'yl'],
             },
-            pl.DataFrame(schema={'time': pl.Int64, 'position': pl.List(pl.Float64)}),
+            pl.DataFrame(schema={'position': pl.List(pl.Float64)}),
             4,
             id='empty_df_with_schema_four_position_columns',
         ),
@@ -236,7 +236,7 @@ import pymovements as pm
                     'x_right', 'y_right', 'x_left', 'y_left', 'x_avg', 'y_avg',
                 ],
             },
-            pl.DataFrame(schema={'time': pl.Int64, 'position': pl.List(pl.Float64)}),
+            pl.DataFrame(schema={'position': pl.List(pl.Float64)}),
             6,
             id='empty_df_with_schema_six_position_columns',
         ),
@@ -249,8 +249,8 @@ import pymovements as pm
                 'position_columns': ['x', 'y'],
             },
             pl.from_dict(
-                {'time': [0], 'position': [[1.23, 4.56]]},
-                schema={'time': pl.Int64, 'position': pl.List(pl.Float64)},
+                {'position': [[1.23, 4.56]]},
+                schema={'position': pl.List(pl.Float64)},
             ),
             2,
             id='df_single_row_two_position_columns',
@@ -265,8 +265,8 @@ import pymovements as pm
                 'position_columns': ['x', 'y'],
             },
             pl.from_dict(
-                {'time': [0], 'abc': [1], 'position': [[1.23, 4.56]]},
-                schema={'abc': pl.Int64, 'time': pl.Int64, 'position': pl.List(pl.Float64)},
+                {'abc': [1], 'position': [[1.23, 4.56]]},
+                schema={'abc': pl.Int64, 'position': pl.List(pl.Float64)},
             ),
             2,
             id='df_single_row_three_columns_two_position_columns',
@@ -281,8 +281,8 @@ import pymovements as pm
                 'position_columns': ['xl', 'yl', 'xr', 'yr'],
             },
             pl.from_dict(
-                {'time': [0], 'position': [[1.2, 3.4, 5.6, 7.8]]},
-                schema={'time': pl.Int64, 'position': pl.List(pl.Float64)},
+                {'position': [[1.2, 3.4, 5.6, 7.8]]},
+                schema={'position': pl.List(pl.Float64)},
             ),
             4,
             id='df_single_row_four_position_columns',
@@ -307,8 +307,8 @@ import pymovements as pm
                 ],
             },
             pl.from_dict(
-                {'time': [0], 'position': [[0.1, 0.2, 0.3, 0.4, 0.5, 0.6]]},
-                schema={'time': pl.Int64, 'position': pl.List(pl.Float64)},
+                {'position': [[0.1, 0.2, 0.3, 0.4, 0.5, 0.6]]},
+                schema={'position': pl.List(pl.Float64)},
             ),
             6,
             id='df_single_row_six_position_columns',
@@ -319,7 +319,7 @@ import pymovements as pm
                 'data': pl.DataFrame(schema={'x_vel': pl.Float64, 'y_vel': pl.Float64}),
                 'velocity_columns': ['x_vel', 'y_vel'],
             },
-            pl.DataFrame(schema={'time': pl.Int64, 'velocity': pl.List(pl.Float64)}),
+            pl.DataFrame(schema={'velocity': pl.List(pl.Float64)}),
             2,
             id='empty_df_with_schema_two_velocity_columns',
         ),
@@ -335,7 +335,7 @@ import pymovements as pm
             },
             pl.DataFrame(
                 schema={
-                    'abc': pl.Int64, 'time': pl.Int64, 'velocity': pl.List(pl.Float64),
+                    'abc': pl.Int64, 'velocity': pl.List(pl.Float64),
                 },
             ),
             2,
@@ -352,7 +352,7 @@ import pymovements as pm
                 ),
                 'velocity_columns': ['xr_vel', 'yr_vel', 'xl_vel', 'yl_vel'],
             },
-            pl.DataFrame(schema={'time': pl.Int64, 'velocity': pl.List(pl.Float64)}),
+            pl.DataFrame(schema={'velocity': pl.List(pl.Float64)}),
             4,
             id='empty_df_with_schema_four_velocity_columns',
         ),
@@ -372,7 +372,7 @@ import pymovements as pm
                     'x_avg_vel', 'y_avg_vel',
                 ],
             },
-            pl.DataFrame(schema={'time': pl.Int64, 'velocity': pl.List(pl.Float64)}),
+            pl.DataFrame(schema={'velocity': pl.List(pl.Float64)}),
             6,
             id='empty_df_with_schema_six_velocity_columns',
         ),
@@ -386,8 +386,8 @@ import pymovements as pm
                 'velocity_columns': ['x_vel', 'y_vel'],
             },
             pl.from_dict(
-                {'time': [0], 'velocity': [[1.23, 4.56]]},
-                schema={'time': pl.Int64, 'velocity': pl.List(pl.Float64)},
+                {'velocity': [[1.23, 4.56]]},
+                schema={'velocity': pl.List(pl.Float64)},
             ),
             2,
             id='df_single_row_two_velocity_columns',
@@ -402,8 +402,8 @@ import pymovements as pm
                 'velocity_columns': ['x_vel', 'y_vel'],
             },
             pl.from_dict(
-                {'time': [0], 'abc': [1], 'velocity': [[1.23, 4.56]]},
-                schema={'abc': pl.Int64, 'time': pl.Int64, 'velocity': pl.List(pl.Float64)},
+                {'abc': [1], 'velocity': [[1.23, 4.56]]},
+                schema={'abc': pl.Int64, 'velocity': pl.List(pl.Float64)},
             ),
             2,
             id='df_single_row_three_columns_two_velocity_columns',
@@ -424,8 +424,8 @@ import pymovements as pm
                 'velocity_columns': ['xl_vel', 'yl_vel', 'xr_vel', 'yr_vel'],
             },
             pl.from_dict(
-                {'time': [0], 'velocity': [[1.2, 3.4, 5.6, 7.8]]},
-                schema={'time': pl.Int64, 'velocity': pl.List(pl.Float64)},
+                {'velocity': [[1.2, 3.4, 5.6, 7.8]]},
+                schema={'velocity': pl.List(pl.Float64)},
             ),
             4,
             id='df_single_row_four_velocity_columns',
@@ -452,8 +452,8 @@ import pymovements as pm
                 ],
             },
             pl.from_dict(
-                {'time': [0], 'velocity': [[0.1, 0.2, 0.3, 0.4, 0.5, 0.6]]},
-                schema={'time': pl.Int64, 'velocity': pl.List(pl.Float64)},
+                {'velocity': [[0.1, 0.2, 0.3, 0.4, 0.5, 0.6]]},
+                schema={'velocity': pl.List(pl.Float64)},
             ),
             6,
             id='df_single_row_six_velocity_columns',
@@ -464,7 +464,7 @@ import pymovements as pm
                 'data': pl.DataFrame(schema={'x_acc': pl.Float64, 'y_acc': pl.Float64}),
                 'acceleration_columns': ['x_acc', 'y_acc'],
             },
-            pl.DataFrame(schema={'time': pl.Int64, 'acceleration': pl.List(pl.Float64)}),
+            pl.DataFrame(schema={'acceleration': pl.List(pl.Float64)}),
             2,
             id='empty_df_with_schema_two_acceleration_columns',
         ),
@@ -480,7 +480,7 @@ import pymovements as pm
             },
             pl.DataFrame(
                 schema={
-                    'abc': pl.Int64, 'time': pl.Int64, 'acceleration': pl.List(pl.Float64),
+                    'abc': pl.Int64, 'acceleration': pl.List(pl.Float64),
                 },
             ),
             2,
@@ -497,7 +497,7 @@ import pymovements as pm
                 ),
                 'acceleration_columns': ['xr_acc', 'yr_acc', 'xl_acc', 'yl_acc'],
             },
-            pl.DataFrame(schema={'time': pl.Int64, 'acceleration': pl.List(pl.Float64)}),
+            pl.DataFrame(schema={'acceleration': pl.List(pl.Float64)}),
             4,
             id='empty_df_with_schema_four_acceleration_columns',
         ),
@@ -517,7 +517,7 @@ import pymovements as pm
                     'x_avg_acc', 'y_avg_acc',
                 ],
             },
-            pl.DataFrame(schema={'time': pl.Int64, 'acceleration': pl.List(pl.Float64)}),
+            pl.DataFrame(schema={'acceleration': pl.List(pl.Float64)}),
             6,
             id='empty_df_with_schema_six_acceleration_columns',
         ),
@@ -531,8 +531,8 @@ import pymovements as pm
                 'acceleration_columns': ['x_acc', 'y_acc'],
             },
             pl.from_dict(
-                {'time': [0], 'acceleration': [[1.23, 4.56]]},
-                schema={'time': pl.Int64, 'acceleration': pl.List(pl.Float64)},
+                {'acceleration': [[1.23, 4.56]]},
+                schema={'acceleration': pl.List(pl.Float64)},
             ),
             2,
             id='df_single_row_two_acceleration_columns',
@@ -547,8 +547,8 @@ import pymovements as pm
                 'acceleration_columns': ['x_acc', 'y_acc'],
             },
             pl.from_dict(
-                {'time': [0], 'abc': [1], 'acceleration': [[1.23, 4.56]]},
-                schema={'abc': pl.Int64, 'time': pl.Int64, 'acceleration': pl.List(pl.Float64)},
+                {'abc': [1], 'acceleration': [[1.23, 4.56]]},
+                schema={'abc': pl.Int64, 'acceleration': pl.List(pl.Float64)},
             ),
             2,
             id='df_single_row_three_columns_two_acceleration_columns',
@@ -569,8 +569,8 @@ import pymovements as pm
                 'acceleration_columns': ['xl_acc', 'yl_acc', 'xr_acc', 'yr_acc'],
             },
             pl.from_dict(
-                {'time': [0], 'acceleration': [[1.2, 3.4, 5.6, 7.8]]},
-                schema={'time': pl.Int64, 'acceleration': pl.List(pl.Float64)},
+                {'acceleration': [[1.2, 3.4, 5.6, 7.8]]},
+                schema={'acceleration': pl.List(pl.Float64)},
             ),
             4,
             id='df_single_row_four_acceleration_columns',
@@ -597,8 +597,8 @@ import pymovements as pm
                 ],
             },
             pl.from_dict(
-                {'time': [0], 'acceleration': [[0.1, 0.2, 0.3, 0.4, 0.5, 0.6]]},
-                schema={'time': pl.Int64, 'acceleration': pl.List(pl.Float64)},
+                {'acceleration': [[0.1, 0.2, 0.3, 0.4, 0.5, 0.6]]},
+                schema={'acceleration': pl.List(pl.Float64)},
             ),
             6,
             id='df_single_row_six_acceleration_columns',
@@ -627,14 +627,12 @@ import pymovements as pm
             },
             pl.from_dict(
                 {
-                    'time': [0],
                     'pixel': [[0.1, 0.2]],
                     'position': [[1.1, 1.2]],
                     'velocity': [[3.1, 3.2]],
                     'acceleration': [[5.1, 5.2]],
                 },
                 schema={
-                    'time': pl.Int64,
                     'pixel': pl.List(pl.Float64),
                     'position': pl.List(pl.Float64),
                     'velocity': pl.List(pl.Float64),
@@ -700,14 +698,12 @@ import pymovements as pm
             },
             pl.from_dict(
                 {
-                    'time': [0],
                     'pixel': [[0.1, 0.2, 0.3, 0.4, 0.5, 0.6]],
                     'position': [[1.1, 1.2, 1.3, 1.4, 1.5, 1.6]],
                     'velocity': [[3.1, 3.2, 3.3, 3.4, 3.5, 3.6]],
                     'acceleration': [[5.1, 5.2, 5.3, 5.4, 5.5, 5.6]],
                 },
                 schema={
-                    'time': pl.Int64,
                     'pixel': pl.List(pl.Float64),
                     'position': pl.List(pl.Float64),
                     'velocity': pl.List(pl.Float64),
@@ -718,6 +714,139 @@ import pymovements as pm
             id='df_single_row_all_types_six_columns',
         ),
 
+        pytest.param(
+            {
+                'data': pl.from_dict(
+                    {
+                        'time': [1.0, 1.5, 2.],
+                        'x': [0., 1., 2.],
+                        'y': [3., 4., 5.],
+                    },
+                    schema={'time': pl.Float64, 'x': pl.Float64, 'y': pl.Float64},
+                ),
+                'pixel_columns': ['x', 'y'],
+                'time_unit': 'ms',
+                'time_column': 'time',
+            },
+            pl.from_dict(
+                {
+                    'time': [1.0, 1.5, 2.],
+                    'pixel': [[0., 3.], [1., 4.], [2., 5.]],
+                },
+                schema={'time': pl.Float64, 'pixel': pl.List(pl.Float64)},
+            ),
+            2,
+            id='df_three_rows_two_position_float_millis_time_no_conversion',
+        ),
+
+        pytest.param(
+            {
+                'data': pl.from_dict(
+                    {
+                        'time': [1, 2, 3],
+                        'x': [0., 1., 2.],
+                        'y': [3., 4., 5.],
+                    },
+                    schema={'time': pl.Int64, 'x': pl.Float64, 'y': pl.Float64},
+                ),
+                'pixel_columns': ['x', 'y'],
+                'time_unit': 'ms',
+                'time_column': 'time',
+            },
+            pl.from_dict(
+                {
+                    'time': [1, 2, 3],
+                    'pixel': [[0., 3.], [1., 4.], [2., 5.]],
+                },
+                schema={'time': pl.Int64, 'pixel': pl.List(pl.Float64)},
+            ),
+            2,
+            id='df_three_rows_two_position_int_millis_time_no_conversion',
+        ),
+
+        pytest.param(
+            {
+                'data': pl.from_dict(
+                    {
+                        'time': [1.0, 1.1, 1.2],
+                        'x': [0., 1., 2.],
+                        'y': [3., 4., 5.],
+                    },
+                    schema={'time': pl.Float64, 'x': pl.Float64, 'y': pl.Float64},
+                ),
+                'pixel_columns': ['x', 'y'],
+                'time_unit': 's',
+                'time_column': 'time',
+            },
+            pl.from_dict(
+                {
+                    'time': [1000, 1100, 1200],
+                    'pixel': [[0., 3.], [1., 4.], [2., 5.]],
+                },
+                schema={'time': pl.Int64, 'pixel': pl.List(pl.Float64)},
+            ),
+            2,
+            id='df_three_rows_two_position_float_seconds_time_converts_to_int_millis',
+        ),
+
+        pytest.param(
+            {
+                'data': pl.from_dict(
+                    {
+                        'time': [1.0005, 1.001, 1.0015],
+                        'x': [0., 1., 2.],
+                        'y': [3., 4., 5.],
+                    },
+                    schema={'time': pl.Float64, 'x': pl.Float64, 'y': pl.Float64},
+                ),
+                'pixel_columns': ['x', 'y'],
+                'time_unit': 's',
+                'time_column': 'time',
+            },
+            pl.from_dict(
+                {
+                    'time': [1000.5, 1001., 1001.5],
+                    'pixel': [[0., 3.], [1., 4.], [2., 5.]],
+                },
+                schema={'time': pl.Float64, 'pixel': pl.List(pl.Float64)},
+            ),
+            2,
+            id='df_three_rows_two_position_float_seconds_time_converts_to_float_millis',
+        ),
+
+        pytest.param(
+            {
+                'data': pl.from_dict(
+                    {
+                        'time': [1, 2, 3],
+                        'x': [0., 1., 2.],
+                        'y': [3., 4., 5.],
+                    },
+                    schema={'time': pl.Int64, 'x': pl.Float64, 'y': pl.Float64},
+                ),
+                'pixel_columns': ['x', 'y'],
+                'time_unit': 'step',
+                'time_column': 'time',
+                'experiment': pm.Experiment(
+                    screen_width_px=1,
+                    screen_width_cm=1,
+                    screen_height_px=1,
+                    screen_height_cm=1,
+                    sampling_rate=200,
+                ),
+            },
+            pl.from_dict(
+                {
+                    'time': [5, 10, 15],
+                    'pixel': [[0., 3.], [1., 4.], [2., 5.]],
+                },
+                schema={'time': pl.Int64, 'pixel': pl.List(pl.Float64)},
+            ),
+            2,
+            id='df_three_rows_two_position_float_step_time_converts_to_int_millis',
+        ),
+
+
         pytest.param(
             {
                 'data': pl.from_dict(
@@ -727,8 +856,8 @@ import pymovements as pm
                 'position_columns': ['x', 'y'],
             },
             pl.from_dict(
-                {'time': [0, 1, 2], 'position': [[1.23, 4.56], [2.34, 5.67], [3.45, 6.78]]},
-                schema={'time': pl.Int64, 'position': pl.List(pl.Float64)},
+                {'position': [[1.23, 4.56], [2.34, 5.67], [3.45, 6.78]]},
+                schema={'position': pl.List(pl.Float64)},
             ),
             2,
             id='df_three_rows_two_position_columns_no_time_no_experiment',
@@ -744,8 +873,8 @@ import pymovements as pm
                 'experiment': pm.Experiment(1024, 768, 38, 30, None, 'center', 100),
             },
             pl.from_dict(
-                {'time': [0.0, 10.0, 20.0], 'position': [[1.23, 4.56], [2.34, 5.67], [3.45, 6.78]]},
-                schema={'time': pl.Float64, 'position': pl.List(pl.Float64)},
+                {'time': [0, 10, 20], 'position': [[1.23, 4.56], [2.34, 5.67], [3.45, 6.78]]},
+                schema={'time': pl.Int64, 'position': pl.List(pl.Float64)},
             ),
             2,
             id='df_three_rows_two_position_columns_no_time_100_hz',
@@ -761,13 +890,12 @@ import pymovements as pm
                 'experiment': pm.Experiment(1024, 768, 38, 30, None, 'center', 1000),
             },
             pl.from_dict(
-                {'time': [0.0, 1.0, 2.0], 'position': [[1.23, 4.56], [2.34, 5.67], [3.45, 6.78]]},
-                schema={'time': pl.Float64, 'position': pl.List(pl.Float64)},
+                {'time': [0, 1, 2], 'position': [[1.23, 4.56], [2.34, 5.67], [3.45, 6.78]]},
+                schema={'time': pl.Int64, 'position': pl.List(pl.Float64)},
             ),
             2,
             id='df_three_rows_two_position_columns_no_time_1000_hz',
         ),
-
     ],
 )
 def test_init_gaze_dataframe_has_expected_attrs(init_kwargs, expected_frame, expected_n_components):
@@ -1284,6 +1412,42 @@ def test_init_gaze_dataframe_has_expected_trial_columns(init_kwargs, expected_tr
             id='inconsistent_number_of_components',
         ),
 
+        pytest.param(
+            {
+                'data': pl.DataFrame(
+                    schema={
+                        'x': pl.Float64, 'y': pl.Float64,
+                        'time': pl.Float64,
+                    },
+                ),
+                'pixel_columns': ['x', 'y'],
+                'time_column': 'time',
+                'time_unit': 'step',
+            },
+            ValueError,
+            "experiment with sampling rate must be specified if time_unit is 'step'",
+            id='time_unit_step_no_experiment',
+        ),
+
+        pytest.param(
+            {
+                'data': pl.DataFrame(
+                    schema={
+                        'x': pl.Float64, 'y': pl.Float64,
+                        'time': pl.Float64,
+                    },
+                ),
+                'pixel_columns': ['x', 'y'],
+                'time_column': 'time',
+                'time_unit': 'invalid',
+            },
+            ValueError,
+            "unsupported time unit 'invalid'. "
+            "Supported units are 's' for seconds, 'ms' for milliseconds and "
+            "'step' for steps.",
+            id='time_unit_unsupported',
+        ),
+
     ],
 )
 def test_gaze_dataframe_init_exceptions(init_kwargs, exception, exception_msg):
@@ -1299,8 +1463,8 @@ def test_gaze_copy_init_has_same_n_components():
 
     Refers to issue #514.
     """
-    df_orig = pl.from_numpy(np.zeros((2, 1000)), orient='col', schema=['x', 'y'])
-    gaze = pm.GazeDataFrame(df_orig, position_columns=['x', 'y'])
+    df_orig = pl.from_numpy(np.zeros((3, 1000)), orient='col', schema=['t', 'x', 'y'])
+    gaze = pm.GazeDataFrame(df_orig, position_columns=['x', 'y'], time_column='t')
 
     df_copy = gaze.frame.clone()
     gaze_copy = pm.GazeDataFrame(df_copy)
diff --git a/tests/unit/gaze/gaze_transform_test.py b/tests/unit/gaze/gaze_transform_test.py
index fdc5acc..0f29e22 100644
--- a/tests/unit/gaze/gaze_transform_test.py
+++ b/tests/unit/gaze/gaze_transform_test.py
@@ -718,6 +718,7 @@ def fixture_experiment():
             {
                 'data': pl.from_dict(
                     {
+                        'time': [1000, 1001, 1002, 1003, 1004, 1005],
                         'x_dva': [1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
                         'y_dva': [2.0, 2.0, 2.0, 2.0, 2.0, 2.0],
                     },
@@ -728,6 +729,7 @@ def fixture_experiment():
             pm.GazeDataFrame(
                 data=pl.from_dict(
                     {
+                        'time': [1000, 1001, 1002, 1003, 1004, 1005],
                         'x_dva': [1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
                         'y_dva': [2.0, 2.0, 2.0, 2.0, 2.0, 2.0],
                     },
@@ -740,6 +742,7 @@ def fixture_experiment():
             {
                 'data': pl.from_dict(
                     {
+                        'time': [1000, 1001, 1002, 1003, 1004, 1005],
                         'x_dva': [1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
                         'y_dva': [2.0, 2.0, 2.0, 2.0, 2.0, 2.0],
                     },
@@ -750,6 +753,7 @@ def fixture_experiment():
             pm.GazeDataFrame(
                 data=pl.from_dict(
                     {
+                        'time': [1000, 1001, 1002, 1003, 1004, 1005],
                         'x_dva': [1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
                         'y_dva': [2.0, 2.0, 2.0, 2.0, 2.0, 2.0],
                     },
@@ -1273,6 +1277,7 @@ def test_gaze_dataframe_pos2vel_exceptions(init_kwargs, exception, expected_msg)
             {
                 'data': pl.from_dict(
                     {
+                        'time': [1000, 1001, 1002, 1003, 1004, 1005],
                         'x_pix': [0.0, 1.0, 0.0, 0.0, 1.0, 0.0],
                         'y_pix': [0.0, 1.0, 0.0, 0.0, 1.0, 0.0],
                         'x_dva': [0.0, 1.0, 0.0, 0.0, 1.0, 0.0],
@@ -1286,7 +1291,7 @@ def test_gaze_dataframe_pos2vel_exceptions(init_kwargs, exception, expected_msg)
             pm.GazeDataFrame(
                 data=pl.from_dict(
                     {
-
+                        'time': [1000, 1001, 1002, 1003, 1004, 1005],
                         'x_pix': [1 / 3, 1 / 3, 1 / 3, 1 / 3, 1 / 3, 1 / 3],
                         'y_pix': [1 / 3, 1 / 3, 1 / 3, 1 / 3, 1 / 3, 1 / 3],
                         'x_dva': [0.0, 1.0, 0.0, 0.0, 1.0, 0.0],
@@ -1302,6 +1307,7 @@ def test_gaze_dataframe_pos2vel_exceptions(init_kwargs, exception, expected_msg)
             {
                 'data': pl.from_dict(
                     {
+                        'time': [1000, 1001, 1002, 1003, 1004, 1005],
                         'x_pix': [0.0, 1.0, 0.0, 0.0, 1.0, 0.0],
                         'y_pix': [0.0, 1.0, 0.0, 0.0, 1.0, 0.0],
                         'x_dva': [0.0, 1.0, 0.0, 0.0, 1.0, 0.0],
@@ -1315,7 +1321,7 @@ def test_gaze_dataframe_pos2vel_exceptions(init_kwargs, exception, expected_msg)
             pm.GazeDataFrame(
                 data=pl.from_dict(
                     {
-
+                        'time': [1000, 1001, 1002, 1003, 1004, 1005],
                         'x_pix': [0.0, 1.0, 0.0, 0.0, 1.0, 0.0],
                         'y_pix': [0.0, 1.0, 0.0, 0.0, 1.0, 0.0],
                         'x_dva': [1 / 3, 1 / 3, 1 / 3, 1 / 3, 1 / 3, 1 / 3],
diff --git a/tests/unit/gaze/integration_numpy_test.py b/tests/unit/gaze/integration_numpy_test.py
index ca3f2de..75ac316 100644
--- a/tests/unit/gaze/integration_numpy_test.py
+++ b/tests/unit/gaze/integration_numpy_test.py
@@ -92,6 +92,7 @@ def test_from_numpy_with_schema():
         schema=schema,
         experiment=experiment,
         time_column='t',
+        time_unit='ms',
         distance_column='d',
         pixel_columns=['x_pix', 'y_pix'],
         position_columns=['x_pos', 'y_pos'],
@@ -109,7 +110,7 @@ def test_from_numpy_with_schema():
             'acceleration': [[2, 6], [3, 7], [4, 8], [5, 9]],
         },
         schema={
-            'time': pl.Float64,
+            'time': pl.Int64,
             'distance': pl.Float64,
             'pixel': pl.List(pl.Float64),
             'position': pl.List(pl.Float64),
@@ -162,7 +163,7 @@ def test_from_numpy_with_trial_id():
         },
         schema={
             'trial_id': pl.Float64,
-            'time': pl.Float64,
+            'time': pl.Int64,
             'pixel': pl.List(pl.Float64),
         },
     )
@@ -192,6 +193,7 @@ def test_from_numpy_explicit_columns():
 
     gaze = pm.gaze.from_numpy(
         time=time,
+        time_unit='ms',
         distance=distance,
         pixel=pixel,
         position=position,
@@ -269,7 +271,7 @@ def test_from_numpy_all_none():
         acceleration_columns=None,
     )
 
-    expected = pl.DataFrame(schema={'time': pl.Int64})
+    expected = pl.DataFrame()
 
     assert_frame_equal(gaze.frame, expected)
     assert gaze.n_components is None
diff --git a/tests/unit/gaze/io/csv_test.py b/tests/unit/gaze/io/csv_test.py
index f349954..736c320 100644
--- a/tests/unit/gaze/io/csv_test.py
+++ b/tests/unit/gaze/io/csv_test.py
@@ -30,6 +30,7 @@ import pymovements as pm
             {
                 'file': 'tests/files/monocular_example.csv',
                 'time_column': 'time',
+                'time_unit': 'ms',
                 'pixel_columns': ['x_left_pix', 'y_left_pix'],
             },
             (10, 2),
@@ -39,6 +40,7 @@ import pymovements as pm
             {
                 'file': 'tests/files/binocular_example.csv',
                 'time_column': 'time',
+                'time_unit': 'ms',
                 'pixel_columns': ['x_left_pix', 'y_left_pix', 'x_right_pix', 'y_right_pix'],
                 'position_columns': ['x_left_pos', 'y_left_pos', 'x_right_pos', 'y_right_pos'],
             },
@@ -49,6 +51,8 @@ import pymovements as pm
             {
                 'file': 'tests/files/hbn_example.csv',
                 'time_column': pm.datasets.HBN().time_column,
+                'time_unit': pm.datasets.HBN().time_unit,
+                'experiment': pm.datasets.HBN().experiment,
                 'pixel_columns': pm.datasets.HBN().pixel_columns,
             },
             (10, 2),
@@ -58,6 +62,7 @@ import pymovements as pm
             {
                 'file': 'tests/files/sbsat_example.csv',
                 'time_column': pm.datasets.SBSAT().time_column,
+                'time_unit': pm.datasets.SBSAT().time_unit,
                 'pixel_columns': pm.datasets.SBSAT().pixel_columns,
                 **pm.datasets.SBSAT().custom_read_kwargs,
             },
@@ -68,6 +73,7 @@ import pymovements as pm
             {
                 'file': 'tests/files/gazebase_example.csv',
                 'time_column': pm.datasets.GazeBase().time_column,
+                'time_unit': pm.datasets.GazeBase().time_unit,
                 'position_columns': pm.datasets.GazeBase().position_columns,
                 **pm.datasets.GazeBase().custom_read_kwargs,
             },
@@ -78,16 +84,18 @@ import pymovements as pm
             {
                 'file': 'tests/files/gaze_on_faces_example.csv',
                 'time_column': pm.datasets.GazeOnFaces().time_column,
+                'time_unit': pm.datasets.GazeOnFaces().time_unit,
                 'pixel_columns': pm.datasets.GazeOnFaces().pixel_columns,
                 **pm.datasets.GazeOnFaces().custom_read_kwargs,
             },
-            (10, 2),
+            (10, 1),
             id='gaze_on_faces_dataset_example',
         ),
         pytest.param(
             {
                 'file': 'tests/files/gazebase_vr_example.csv',
                 'time_column': pm.datasets.GazeBaseVR().time_column,
+                'time_unit': pm.datasets.GazeBaseVR().time_unit,
                 'position_columns': pm.datasets.GazeBaseVR().position_columns,
             },
             (10, 11),
@@ -97,6 +105,7 @@ import pymovements as pm
             {
                 'file': 'tests/files/judo1000_example.csv',
                 'time_column': pm.datasets.JuDo1000().time_column,
+                'time_unit': pm.datasets.JuDo1000().time_unit,
                 'pixel_columns': pm.datasets.JuDo1000().pixel_columns,
                 **pm.datasets.JuDo1000().custom_read_kwargs,
             },