Repository: aeye-lab/pymovements
Pull request #734 (2024-06-04 10:28:34)

fix: operator precedence when building group_filter_expression
## Description

If there are multiple trial columns, the `group_filter_expression` in `GazeDataFrame.detect()` is built incorrectly, leading to `polars.exceptions.InvalidOperationError: bitand operation not supported for dtype str`. This is because the `&` operator has precedence over `==`.

## Implemented changes

- [x] Put parentheses around the `==` expression

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change is or requires a documentation update

## How Has This Been Tested?

- [x] `tests/unit/gaze/detect_test.py::test_gaze_detect[idt_constant_position_single_fixation_per_trial_multiple_trial_columns]`

## 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/734?dropdown=coverage&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:
> Project coverage is 99.92%. Comparing base [(`3b0e1fd`)](https://app.codecov.io/gh/aeye-lab/pymovements/commit/3b0e1fd5eec234077864ba130730bd7b6f56a3f2?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) to head [(`c708adf`)](https://app.codecov.io/gh/aeye-lab/pymovements/commit/c708adfe25c4a30c747b8e8ef2161949b3a86e1a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab).


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


```diff
@@             Coverage Diff             @@
##              main     #734      +/-   ##
===========================================
- Coverage   100.00%   99.92%   -0.08%     
===========================================
  Files           60       60              
  Lines         2763     2763              
  Branches       715      638      -77     
===========================================
- Hits          2763     2761       -2     
- Misses           0        1       +1     
- Partials         0        1       +1     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/aeye-lab/pymovements/pull/734?dropdown=coverage&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 5b575aa..7579c4c 100644
--- a/src/pymovements/gaze/gaze_dataframe.py
+++ b/src/pymovements/gaze/gaze_dataframe.py
@@ -695,7 +695,7 @@ class GazeDataFrame:
                 else:
                     group_filter_expression = pl.col(self.trial_columns[0]) == group_identifier[0]
                     for name, value in zip(self.trial_columns[1:], group_identifier[1:]):
-                        group_filter_expression = group_filter_expression & pl.col(name) == value
+                        group_filter_expression = group_filter_expression & (pl.col(name) == value)
 
                 # Select group events
                 group_events = pm.EventDataFrame(self.events.frame.filter(group_filter_expression))


Test patch:
diff --git a/tests/unit/gaze/detect_test.py b/tests/unit/gaze/detect_test.py
index c30d6d0..979c465 100644
--- a/tests/unit/gaze/detect_test.py
+++ b/tests/unit/gaze/detect_test.py
@@ -207,6 +207,33 @@ from pymovements.synthetic import step_function
             id='idt_constant_position_single_fixation_per_trial',
         ),
 
+        pytest.param(
+            'idt',
+            {
+                'dispersion_threshold': 1,
+                'minimum_duration': 2,
+            },
+            pm.gaze.from_pandas(
+                data=pl.DataFrame({
+                    'trial': ['A'] * 50 + ['B'] * 50,
+                    'screen': ['1'] * 25 + ['2'] * 25 + ['1'] * 25 + ['2'] * 25,
+                    'position': [(0, 0)] * 100,
+                }).to_pandas(),
+                trial_columns=['trial', 'screen'],
+                experiment=pm.Experiment(1024, 768, 38, 30, 60, 'center', 1000),
+            ),
+            pm.EventDataFrame(
+                pl.DataFrame({
+                    'trial': ['A', 'A', 'B', 'B'],
+                    'screen': ['1', '2', '1', '2'],
+                    'name': ['fixation', 'fixation', 'fixation', 'fixation'],
+                    'onset': [0, 25, 50, 75],
+                    'offset': [24, 49, 74, 99],
+                }),
+            ),
+            id='idt_constant_position_single_fixation_per_trial_multiple_trial_columns',
+        ),
+
         pytest.param(
             'ivt',
             {