From 5240e9321ca912e03377655d98c2e5286fadfbed Mon Sep 17 00:00:00 2001 From: "David R. Reich" <43832476+SiQube@users.noreply.github.com> Date: Wed, 13 Sep 2023 08:40:40 -0400 Subject: [PATCH 1/7] feat: Raise helpful missing column error message for transforms (#542) --- src/pymovements/gaze/gaze_dataframe.py | 27 +++++++++++++++ tests/gaze/gaze_transform_test.py | 48 ++++++++++++++++++++++---- 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/src/pymovements/gaze/gaze_dataframe.py b/src/pymovements/gaze/gaze_dataframe.py index a95181eef..5561b358c 100644 --- a/src/pymovements/gaze/gaze_dataframe.py +++ b/src/pymovements/gaze/gaze_dataframe.py @@ -266,6 +266,33 @@ def transform( _check_n_components(self.n_components) kwargs['n_components'] = self.n_components + if transform_method.__name__ in {'pos2vel', 'pos2acc'}: + if 'position' not in self.frame.columns and 'position_column' not in kwargs: + if 'pixel' in self.frame.columns: + raise pl.exceptions.ColumnNotFoundError( + "Neither 'position' is in the columns of the dataframe: " + f'{self.frame.columns} nor is the position column specified. ' + "Since the dataframe has a 'pixel' column, consider running " + f'pix2deg() before {transform_method.__name__}(). If you want ' + 'to calculate pixel transformations, you can do so by using ' + f"{transform_method.__name__}(position_column='pixel'). " + f'Available dataframe columns are {self.frame.columns}', + ) + raise pl.exceptions.ColumnNotFoundError( + "Neither 'position' is in the columns of the dataframe: " + f'{self.frame.columns} nor is the position column specified. ' + f'Available dataframe columns are {self.frame.columns}', + ) + if transform_method.__name__ in {'pix2deg'}: + if 'pixel' not in self.frame.columns and 'pixel_column' not in kwargs: + raise pl.exceptions.ColumnNotFoundError( + "Neither 'position' is in the columns of the dataframe: " + f'{self.frame.columns} nor is the pixel column specified. ' + 'You can specify the pixel column via: ' + f'{transform_method.__name__}(pixel_column="name_of_your_pixel_column"). ' + f'Available dataframe columns are {self.frame.columns}', + ) + if self.trial_columns is None: self.frame = self.frame.with_columns(transform_method(**kwargs)) else: diff --git a/tests/gaze/gaze_transform_test.py b/tests/gaze/gaze_transform_test.py index 24fea7b24..f92ee5c5b 100644 --- a/tests/gaze/gaze_transform_test.py +++ b/tests/gaze/gaze_transform_test.py @@ -528,8 +528,10 @@ def test_gaze_dataframe_pix2deg_creates_position_column(data, experiment, pixel_ }, pl.exceptions.ColumnNotFoundError, ( - 'pixel\n\nError originated just after this operation:\nDF ["acceleration"]; ' - 'PROJECT */1 COLUMNS; SELECTION: "None"' + "Neither 'position' is in the columns of the dataframe: ['acceleration'] nor " + 'is the pixel column specified. You can specify the pixel column via: ' + 'pix2deg(pixel_column="name_of_your_pixel_column"). Available dataframe ' + "columns are ['acceleration']" ), id='no_pixel_column', ), @@ -609,8 +611,25 @@ def test_gaze_dataframe_pos2acc_creates_acceleration_column(data, experiment, po }, pl.exceptions.ColumnNotFoundError, ( - 'position\n\nError originated just after this operation:\nDF ["pixel"]; ' - 'PROJECT */1 COLUMNS; SELECTION: "None"' + "Neither 'position' is in the columns of the dataframe: ['pixel'] nor is the " + "position column specified. Since the dataframe has a 'pixel' column, " + 'consider running pix2deg() before pos2acc(). If you want to calculate pixel ' + "transformations, you can do so by using pos2acc(position_column='pixel'). " + "Available dataframe columns are ['pixel']" + ), + id='no_position_column', + ), + pytest.param( + { + 'data': pl.from_dict({'x': [0.1], 'y': [0.2]}), + 'experiment': pm.Experiment(1024, 768, 38, 30, 60, 'center', 1000), + 'acceleration_columns': ['x', 'y'], + }, + pl.exceptions.ColumnNotFoundError, + ( + "Neither 'position' is in the columns of the dataframe: ['acceleration'] nor " + 'is the position column specified. Available dataframe columns are ' + "['acceleration']" ), id='no_position_column', ), @@ -690,8 +709,25 @@ def test_gaze_dataframe_pos2vel_creates_velocity_column(data, experiment, positi }, pl.exceptions.ColumnNotFoundError, ( - 'position\n\nError originated just after this operation:\nDF ["pixel"]; ' - 'PROJECT */1 COLUMNS; SELECTION: "None"' + "Neither 'position' is in the columns of the dataframe: ['pixel'] nor is the " + "position column specified. Since the dataframe has a 'pixel' column, " + 'consider running pix2deg() before pos2vel(). If you want to calculate pixel ' + "transformations, you can do so by using pos2vel(position_column='pixel'). " + "Available dataframe columns are ['pixel']" + ), + id='no_position_column', + ), + pytest.param( + { + 'data': pl.from_dict({'x': [0.1], 'y': [0.2]}), + 'experiment': pm.Experiment(1024, 768, 38, 30, 60, 'center', 1000), + 'acceleration_columns': ['x', 'y'], + }, + pl.exceptions.ColumnNotFoundError, + ( + "Neither 'position' is in the columns of the dataframe: ['acceleration'] nor " + 'is the position column specified. Available dataframe columns are ' + "['acceleration']" ), id='no_position_column', ), From 5121607c34a9d3343e1b5202827b506fabd71bb5 Mon Sep 17 00:00:00 2001 From: "David R. Reich" <43832476+SiQube@users.noreply.github.com> Date: Wed, 13 Sep 2023 09:08:58 -0400 Subject: [PATCH 2/7] docs: Update CONTRIBUTING.md (#544) --- CONTRIBUTING.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e79c22115..040727e4e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -93,6 +93,8 @@ cd pymovements pip install -e . ``` +If you have a problem e.g. `command not found: pip`, check whether you have activated a virtual enviroment. + ### Creating a Branch @@ -125,12 +127,23 @@ We use [`flake8`](https://pypi.org/project/flake8/) for quick style checks and [`pylint`](https://pypi.org/project/pylint/) for thorough style checks and [`mypy`]( https://pypi.org/project/mypy/) for checking type annotations. +You can check your code style by using [pre-commit](https://www.pre-commit.com). You can install `pre-commit` via pip: + +```bash +pip install pre-commit +pre-commit install +``` ### Testing Tests are written using [Pytest](https://docs.pytest.org) and executed in a separate environment using [Tox](https://tox.readthedocs.io/en/latest/). +If you have not yet installed `tox` you can do so via +```bash +pip install tox +``` + A full style check and all tests can be run by simply calling `tox` in the repository root. ```bash tox @@ -143,9 +156,9 @@ If you add a new feature, please also include appropriate tests to verify its in functionality. We try to keep our code coverage close to 100%. It is possible to limit the scope of testing to specific environments and files. For example, to -only test event related functionality using the Python 3.7 environment use: +only test event related functionality using the Python 3.8 environment use: ```bash -tox -e py37 tests/events +tox -e py38 tests/events ``` From ce3d2ea92ae54e4bbe1c5ec0a5e42655eb1948ab Mon Sep 17 00:00:00 2001 From: "Daniel G. Krakowczyk" Date: Wed, 13 Sep 2023 15:25:47 +0200 Subject: [PATCH 3/7] chore: Migrate to polars v0.19 (#545) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 70529e939..6f28ebd84 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,7 @@ dependencies = [ "matplotlib>=3.0.0,<4", "numpy>=1.10.0,<2", "pandas>1.0.0,<3", - "polars>=0.18.3,<0.19.0", + "polars>=0.19.0,<0.20.0", "pyarrow>=11.0.0,<12", "scipy>=1.5.4,<2", "tqdm>=4.0.0,<5" From a73c7d332fc3683f6c8f998965dcc224c3adbf99 Mon Sep 17 00:00:00 2001 From: "Daniel G. Krakowczyk" Date: Wed, 13 Sep 2023 15:49:27 +0200 Subject: [PATCH 4/7] chore: Integrate dependabot to check for dependency updates (#546) --- .github/dependabot.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..a4ec782ca --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + commit-message: + prefix: "build: " + include: "scope" From 7e33adfa1037fb3b7b234fa3c2815e0cd9498f3e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Sep 2023 19:41:09 +0200 Subject: [PATCH 5/7] build: (deps-dev): update pyarrow requirement (#547) Updates the requirements on [pyarrow](https://github.com/apache/arrow) to permit the latest version. - [Commits](https://github.com/apache/arrow/compare/go/v11.0.0...go/v13.0.0) --- updated-dependencies: - dependency-name: pyarrow dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6f28ebd84..d50f1b34e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ dependencies = [ "numpy>=1.10.0,<2", "pandas>1.0.0,<3", "polars>=0.19.0,<0.20.0", - "pyarrow>=11.0.0,<12", + "pyarrow>=11.0.0,<14", "scipy>=1.5.4,<2", "tqdm>=4.0.0,<5" ] From f3dd4e3b0b7900a3079e820ca8d2621d3740cff0 Mon Sep 17 00:00:00 2001 From: "Daniel G. Krakowczyk" Date: Thu, 14 Sep 2023 12:28:10 +0200 Subject: [PATCH 6/7] build: don't include scope in dependabot commits (#549) --- .github/dependabot.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a4ec782ca..604870b4f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,4 +6,3 @@ updates: interval: "weekly" commit-message: prefix: "build: " - include: "scope" From 097cbbb9d7442863de2bd79ab8a5acad8eefa704 Mon Sep 17 00:00:00 2001 From: "Daniel G. Krakowczyk" Date: Thu, 14 Sep 2023 13:06:32 +0200 Subject: [PATCH 7/7] feat: Add EventDataFrame.copy() (#552) --- src/pymovements/events/frame.py | 10 ++++++++++ tests/events/frame_test.py | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/pymovements/events/frame.py b/src/pymovements/events/frame.py index 6ce425bb4..1c8921a79 100644 --- a/src/pymovements/events/frame.py +++ b/src/pymovements/events/frame.py @@ -164,6 +164,16 @@ def event_property_columns(self) -> list[str]: event_property_columns -= set(self._additional_columns) return list(event_property_columns) + def copy(self) -> EventDataFrame: + """Return a copy of the EventDataFrame. + + Returns + ------- + EventDataFrame + A copy of the EventDataFrame. + """ + return EventDataFrame(data=self.frame.clone()) + def _add_minimal_schema_columns(self, df: pl.DataFrame) -> pl.DataFrame: """Add minimal schema columns to :py:class:`polars.DataFrame` if they are missing.""" if len(df) == 0: diff --git a/tests/events/frame_test.py b/tests/events/frame_test.py index 4c616ec75..4cf7d55b0 100644 --- a/tests/events/frame_test.py +++ b/tests/events/frame_test.py @@ -186,3 +186,13 @@ def test_event_dataframe_columns_same_as_frame(): event_df = pm.EventDataFrame(**init_kwargs) assert event_df.columns == event_df.frame.columns + + +def test_event_dataframe_copy(): + events = pm.EventDataFrame(name='saccade', onsets=[0], offsets=[123]) + events_copy = events.copy() + + # We want to have separate dataframes but with the exact same data. + assert events is not events_copy + assert events.frame is not events_copy.frame + assert_frame_equal(events.frame, events_copy.frame)