Repository: aeye-lab/pymovements
Pull request #485 (2023-08-31 11:21:41)

feat: Add GazeDataFrame.copy()
Adds the possibility to copy a GazeDataFrame.

Will be needed in resolving #480 

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/aeye-lab/pymovements/pull/485?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) Report
Patch coverage: **`100.00%`** and no project coverage change.
> Comparison is base [(`b4ed624`)](https://app.codecov.io/gh/aeye-lab/pymovements/commit/b4ed6248baed67566674eb2e52834d4a3ee97b98?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) 100.00% compared to head [(`bed31c6`)](https://app.codecov.io/gh/aeye-lab/pymovements/pull/485?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) 100.00%.

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

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


```diff
@@            Coverage Diff            @@
##              main      #485   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           48        48           
  Lines         2080      2083    +3     
  Branches       502       502           
=========================================
+ Hits          2080      2083    +3     
```


| [Files Changed](https://app.codecov.io/gh/aeye-lab/pymovements/pull/485?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) | Coverage Δ | |
|---|---|---|
| [src/pymovements/gaze/gaze\_dataframe.py](https://app.codecov.io/gh/aeye-lab/pymovements/pull/485?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2dhemUvZ2F6ZV9kYXRhZnJhbWUucHk=) | `100.00% <100.00%> (ø)` | |


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/aeye-lab/pymovements/pull/485?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/src/pymovements/gaze/gaze_dataframe.py b/src/pymovements/gaze/gaze_dataframe.py
index 3440234..7e32222 100644
--- a/src/pymovements/gaze/gaze_dataframe.py
+++ b/src/pymovements/gaze/gaze_dataframe.py
@@ -22,6 +22,7 @@ from __future__ import annotations
 
 import inspect
 from collections.abc import Callable
+from copy import deepcopy
 from typing import Any
 
 import polars as pl
@@ -461,6 +462,19 @@ class GazeDataFrame:
             ],
         ).drop(column)
 
+    def copy(self) -> GazeDataFrame:
+        """Return a copy of the GazeDataFrame.
+
+        Returns
+        -------
+        GazeDataFrame
+            A copy of the GazeDataFrame.
+        """
+        return GazeDataFrame(
+            data=self.frame.clone(),
+            experiment=deepcopy(self.experiment),
+        )
+
     def _check_experiment(self) -> None:
         """Check if experiment attribute has been set."""
         if self.experiment is None:


Test patch:
diff --git a/tests/gaze/gaze_dataframe_test.py b/tests/gaze/gaze_dataframe_test.py
index 1bb82c6..39b389d 100644
--- a/tests/gaze/gaze_dataframe_test.py
+++ b/tests/gaze/gaze_dataframe_test.py
@@ -20,12 +20,13 @@
 """Test all GazeDataFrame functionality."""
 import polars as pl
 import pytest
+from polars.testing import assert_frame_equal
 
-from pymovements.gaze.gaze_dataframe import GazeDataFrame
+import pymovements as pm
 
 
 @pytest.mark.parametrize(
-    ('init_arg'),
+    'init_arg',
     [
         pytest.param(
             None,
@@ -38,7 +39,7 @@ from pymovements.gaze.gaze_dataframe import GazeDataFrame
     ],
 )
 def test_gaze_dataframe_init(init_arg):
-    gaze_df = GazeDataFrame(init_arg)
+    gaze_df = pm.GazeDataFrame(init_arg)
     assert isinstance(gaze_df.frame, pl.DataFrame)
 
 
@@ -78,7 +79,7 @@ def test_gaze_dataframe_init(init_arg):
     ],
 )
 def test_gaze_dataframe_velocity_columns(init_df, velocity_columns):
-    gaze_df = GazeDataFrame(init_df, velocity_columns=velocity_columns)
+    gaze_df = pm.GazeDataFrame(init_df, velocity_columns=velocity_columns)
 
     assert 'velocity' in gaze_df.columns
 
@@ -119,7 +120,7 @@ def test_gaze_dataframe_velocity_columns(init_df, velocity_columns):
     ],
 )
 def test_gaze_dataframe_pixel_position_columns(init_df, pixel_columns):
-    gaze_df = GazeDataFrame(init_df, pixel_columns=pixel_columns)
+    gaze_df = pm.GazeDataFrame(init_df, pixel_columns=pixel_columns)
 
     assert 'pixel' in gaze_df.columns
 
@@ -160,6 +161,47 @@ def test_gaze_dataframe_pixel_position_columns(init_df, pixel_columns):
     ],
 )
 def test_gaze_dataframe_position_columns(init_df, position_columns):
-    gaze_df = GazeDataFrame(init_df, position_columns=position_columns)
+    gaze_df = pm.GazeDataFrame(init_df, position_columns=position_columns)
 
     assert 'position' in gaze_df.columns
+
+
+def test_gaze_dataframe_copy_with_experiment():
+    gaze = pm.GazeDataFrame(
+        pl.DataFrame(schema={'x': pl.Float64, 'y': pl.Float64}),
+        experiment=pm.Experiment(1024, 768, 38, 30, 60, 'center', 1000),
+        position_columns=['x', 'y'],
+    )
+
+    gaze_copy = gaze.copy()
+
+    # We want to have separate dataframes but with the exact same data.
+    assert gaze.frame is not gaze_copy.frame
+    assert_frame_equal(gaze.frame, gaze_copy.frame)
+
+    # We want to have separate experiment instances but the same values.
+    assert gaze.experiment is not gaze_copy.experiment
+    assert gaze.experiment.screen.width_px == gaze_copy.experiment.screen.width_px
+    assert gaze.experiment.screen.height_px == gaze_copy.experiment.screen.height_px
+    assert gaze.experiment.screen.width_cm == gaze_copy.experiment.screen.width_cm
+    assert gaze.experiment.screen.height_cm == gaze_copy.experiment.screen.height_cm
+    assert gaze.experiment.screen.distance_cm == gaze_copy.experiment.screen.distance_cm
+    assert gaze.experiment.screen.origin == gaze_copy.experiment.screen.origin
+    assert gaze.experiment.sampling_rate == gaze_copy.experiment.sampling_rate
+
+
+def test_gaze_dataframe_copy_no_experiment():
+    gaze = pm.GazeDataFrame(
+        pl.DataFrame(schema={'x': pl.Float64, 'y': pl.Float64}),
+        experiment=None,
+        position_columns=['x', 'y'],
+    )
+
+    gaze_copy = gaze.copy()
+
+    # We want to have separate dataframes but with the exact same data.
+    assert gaze.frame is not gaze_copy.frame
+    assert_frame_equal(gaze.frame, gaze_copy.frame)
+
+    # We want to have separate experiment instances but the same values.
+    assert gaze.experiment is gaze_copy.experiment