Repository: causify-ai/helpers
Pull request #442: HelpersTask397_Replace_data_with_df_in_hdataframe
Author: login: aangelo9
Labels: name: Documentation
description: Improvements or additions to documentation
Created: 2025-03-29T03:04:25Z | Merged: 2025-04-01T22:47:59Z

Related to issue #397

This PR renames all usages of `data` → `df` in `helpers/hdataframe.py` where the variable represents a Pandas DataFrame, to improve clarity.

- Updated function arguments and internal references
- Verified all logic remains the same, only refactoring



Diff:
diff --git a/helpers/hdataframe.py b/helpers/hdataframe.py
index 1c63c6f4a..5a0af8714 100644
--- a/helpers/hdataframe.py
+++ b/helpers/hdataframe.py
@@ -7,7 +7,6 @@
 """
 
 # TODO(gp): Consider merging with `helpers/pandas_helpers.py`.
-# TODO(gp): data -> df
 
 import collections
 import functools
@@ -43,7 +42,7 @@ def _combine_masks(
 
 
 def filter_data_by_values(
-    data: pd.DataFrame,
+    df: pd.DataFrame,
     filters: Dict[Union[int, str], Tuple[Any, ...]],
     mode: str,
     info: Optional[collections.OrderedDict] = None,
@@ -51,7 +50,7 @@ def filter_data_by_values(
     """
     Filter dataframe rows based on column values.
 
-    :param data: dataframe
+    :param df: dataframe
     :param filters: `{col_name: (possible_values)}`
     :param mode: `and` for conjunction and `or` for disjunction of filters
     :param info: information storage
@@ -59,26 +58,26 @@ def filter_data_by_values(
     """
     if info is None:
         info = collections.OrderedDict()
-    info["nrows"] = data.shape[0]
+    info["nrows"] = df.shape[0]
     if not filters:
-        info["nrows_remaining"] = data.shape[0]
-        return data.copy()
+        info["nrows_remaining"] = df.shape[0]
+        return df.copy()
     # Create filter masks for each column.
     masks = []
     for col_name, vals in filters.items():
         hdbg.dassert_isinstance(vals, tuple)
-        mask = data[col_name].isin(vals)
+        mask = df[col_name].isin(vals)
         info[f"n_{col_name}"] = mask.sum()
-        info[f"perc_{col_name}"] = hprint.perc(mask.sum(), data.shape[0])
+        info[f"perc_{col_name}"] = hprint.perc(mask.sum(), df.shape[0])
         masks.append(mask)
     masks = pd.concat(masks, axis=1)
     combined_mask = _combine_masks(masks, mode, info)
-    filtered_data = data.loc[combined_mask].copy()
-    return filtered_data
+    filtered_df = df.loc[combined_mask].copy()
+    return filtered_df
 
 
 def filter_data_by_comparison(
-    data: pd.DataFrame,
+    df: pd.DataFrame,
     filters: Dict[
         Union[int, str], Union[Tuple[str, Any], Tuple[Tuple[str, Any], ...]]
     ],
@@ -88,7 +87,7 @@ def filter_data_by_comparison(
     """
     Filter dataframe by comparing columns to values.
 
-    :param data: dataframe
+    :param df: dataframe
     :param filters: `{col_name: (comparison_method, value)}` or
         `{col_name: ((comparison_method_i, value_i))}`.
         `comparison_method` is one of the ("eq", "ne", "le", "lt", "ge", "gt")
@@ -99,10 +98,10 @@ def filter_data_by_comparison(
     """
     if info is None:
         info = collections.OrderedDict()
-    info["nrows"] = data.shape[0]
+    info["nrows"] = df.shape[0]
     if not filters:
-        info["nrows_remaining"] = data.shape[0]
-        return data.copy()
+        info["nrows_remaining"] = df.shape[0]
+        return df.copy()
     # Create filter masks for each column.
     masks = []
     for col_name, tuple_ in filters.items():
@@ -112,20 +111,20 @@ def filter_data_by_comparison(
             hdbg.dassert_in(
                 comparison_method, ("eq", "ne", "le", "lt", "ge", "gt")
             )
-            mask = getattr(data[col_name], comparison_method)(val)
+            mask = getattr(df[col_name], comparison_method)(val)
             info[f"n_{col_name}_{comparison_method}_{val}"] = mask.sum()
             info[f"perc_{col_name}_{comparison_method}_{val}"] = hprint.perc(
-                mask.sum(), data.shape[0]
+                mask.sum(), df.shape[0]
             )
             masks.append(mask)
     masks = pd.concat(masks, axis=1)
     combined_mask = _combine_masks(masks, mode, info)
-    filtered_data = data.loc[combined_mask].copy()
-    return filtered_data
+    filtered_df = df.loc[combined_mask].copy()
+    return filtered_df
 
 
 def filter_data_by_method(
-    data: pd.DataFrame,
+    df: pd.DataFrame,
     filters: Dict[Union[int, str], _METHOD_TO_APPLY],
     mode: str,
     info: Optional[collections.OrderedDict] = None,
@@ -133,7 +132,7 @@ def filter_data_by_method(
     """
     Filter dataframe by calling a method specified for each column.
 
-    :param data: dataframe
+    :param df: dataframe
     :param filters: `{col_name: {method: kwargs}}`, where `method` is the
         method called on the dataframe column, e.g. "isin" or "str.contains",
         and `kwargs` are the kwargs for this method
@@ -143,22 +142,22 @@ def filter_data_by_method(
     """
     if info is None:
         info = collections.OrderedDict()
-    info["nrows"] = data.shape[0]
+    info["nrows"] = df.shape[0]
     if not filters:
-        info["nrows_remaining"] = data.shape[0]
-        return data.copy()
+        info["nrows_remaining"] = df.shape[0]
+        return df.copy()
     # Create filter masks for each column.
     masks = []
     for col_name, method_dict in filters.items():
         for method, kwargs in method_dict.items():
-            mask = operator.attrgetter(method)(data[col_name])(**kwargs)
+            mask = operator.attrgetter(method)(df[col_name])(**kwargs)
             info[f"n_{col_name}"] = mask.sum()
-            info[f"perc_{col_name}"] = hprint.perc(mask.sum(), data.shape[0])
+            info[f"perc_{col_name}"] = hprint.perc(mask.sum(), df.shape[0])
             masks.append(mask)
     masks = pd.concat(masks, axis=1)
     combined_mask = _combine_masks(masks, mode, info)
-    filtered_data = data.loc[combined_mask].copy()
-    return filtered_data
+    filtered_df = df.loc[combined_mask].copy()
+    return filtered_df
 
 
 # #############################################################################
@@ -226,18 +225,18 @@ def apply_nan_mode(
 # #############################################################################
 
 
-def infer_sampling_points_per_year(data: Union[pd.Series, pd.DataFrame]) -> float:
+def infer_sampling_points_per_year(df: Union[pd.Series, pd.DataFrame]) -> float:
     """
     Return the number of index time points per year.
 
     TODO(*): Consider extending to all frequencies and count points by
         explicitly building indices of the given frequency.
 
-    :param data: series or dataframe with non-null `data.index.freq`
+    :param df: series or dataframe with non-null `df.index.freq`
     :return: number of time points per year (approximate)
     """
-    hdbg.dassert(data.index.freq)
-    freq = data.index.freq
+    hdbg.dassert(df.index.freq)
+    freq = df.index.freq
     # TODO(*): Make start, end dates parameters that can be passed in.
     return compute_points_per_year_for_given_freq(freq)
 
@@ -264,17 +263,17 @@ def compute_points_per_year_for_given_freq(freq: str) -> float:
         return 0.0
 
 
-def compute_count_per_year(data: Union[pd.Series, pd.DataFrame]) -> float:
+def compute_count_per_year(df: Union[pd.Series, pd.DataFrame]) -> float:
     """
-    Return data.count() divided by the length of `data` in years.
+    Return df.count() divided by the length of `df` in years.
     """
-    freq = data.index.freq
-    hdbg.dassert(freq, msg="`data` must have a `DatetimeIndex` with a `freq`")
-    # Calculate the time span of `data` in years.
+    freq = df.index.freq
+    hdbg.dassert(freq, msg="`df` must have a `DatetimeIndex` with a `freq`")
+    # Calculate the time span of `df` in years.
     points_per_year = compute_points_per_year_for_given_freq(freq)
-    span_in_years = data.size / points_per_year
+    span_in_years = df.size / points_per_year
     # Determine the number of non-NaN/inf/etc. data points per year.
-    count_per_year = data.count() / span_in_years
+    count_per_year = df.count() / span_in_years
     count_per_year = cast(float, count_per_year)
     return count_per_year
 
@@ -283,23 +282,24 @@ def compute_count_per_year(data: Union[pd.Series, pd.DataFrame]) -> float:
 
 
 def remove_duplicates(
-    data: pd.DataFrame,
+    df: pd.DataFrame,
     duplicate_columns: Optional[List[str]],
     control_column: Optional[str],
 ) -> pd.DataFrame:
     """
     Remove duplicates from DataFrame.
 
-    :param data: DataFrame to process
+    :param df: DataFrame to process
     :param duplicate_columns: subset of column names, None for all
-    :param control_column: column max value of which determines the kept row
+    :param control_column: column max value of which determines the kept
+        row
     :return: DataFrame with removed duplicates
     """
     # Fix maximum value of control column at the bottom.
     if control_column:
-        data = data.sort_values(by=control_column)
-    duplicate_columns = duplicate_columns or data.columns
-    data = data.drop_duplicates(subset=duplicate_columns)
+        df = df.sort_values(by=control_column)
+    duplicate_columns = duplicate_columns or df.columns
+    df = df.drop_duplicates(subset=duplicate_columns)
     # Sort by index to return to original view.
-    data = data.sort_index()
-    return data
\ No newline at end of file
+    df = df.sort_index()
+    return df
diff --git a/helpers/test/test_hdataframe.py b/helpers/test/test_hdataframe.py
index cdf9d4ed8..f93dbf851 100644
--- a/helpers/test/test_hdataframe.py
+++ b/helpers/test/test_hdataframe.py
@@ -19,7 +19,13 @@
 _LOG = logging.getLogger(__name__)
 
 
+# #############################################################################
+# Test_filter_data_by_values1
+# #############################################################################
+
+
 class Test_filter_data_by_values1(hunitest.TestCase):
+
     def test_conjunction1(self) -> None:
         data = pd.DataFrame([[1, 2, 3], [4, 5, 6]])
         data = data.add_prefix("col_")
@@ -54,7 +60,13 @@ def test_disjunction1(self) -> None:
         self.check_string(str_output)
 
 
+# #############################################################################
+# Test_filter_data_by_comparison
+# #############################################################################
+
+
 class Test_filter_data_by_comparison(hunitest.TestCase):
+
     def test_conjunction1(self) -> None:
         data = pd.DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])
         data = data.add_prefix("col_")
@@ -92,6 +104,11 @@ def test_disjunction1(self) -> None:
         self.check_string(str_output)
 
 
+# #############################################################################
+# TestFilterDataByMethod
+# #############################################################################
+
+
 class TestFilterDataByMethod(hunitest.TestCase):
     """
     Test was generated automatically with Playback.
@@ -110,14 +127,20 @@ def test1(self) -> None:
         info: collections.OrderedDict = collections.OrderedDict()
         # Call function to test.
         act = hdatafr.filter_data_by_method(
-            data=data, filters=filters, mode=mode, info=info
+            df=data, filters=filters, mode=mode, info=info
         )
         act = hpandas.df_to_str(act, precision=3)
         # Check output.
         self.check_string(act, fuzzy_match=True)
 
 
+# #############################################################################
+# Test_apply_nan_mode
+# #############################################################################
+
+
 class Test_apply_nan_mode(hunitest.TestCase):
+
     def test1(self) -> None:
         """
         Test for `mode=leave_unchanged`.
@@ -182,7 +205,13 @@ def _get_series_with_nans(seed: int) -> pd.Series:
         return series
 
 
+# #############################################################################
+# Test_compute_points_per_year_for_given_freq
+# #############################################################################
+
+
 class Test_compute_points_per_year_for_given_freq(hunitest.TestCase):
+
     def test1(self) -> None:
         actual = hdatafr.compute_points_per_year_for_given_freq("T")
         np.testing.assert_equal(actual, 525780.125)
@@ -212,7 +241,13 @@ def test7(self) -> None:
         np.testing.assert_equal(actual, 0.0)
 
 
+# #############################################################################
+# TestRemoveDuplicates
+# #############################################################################
+
+
 class TestRemoveDuplicates(hunitest.TestCase):
+
     def test_remove_duplicates1(self) -> None:
         test_data = {
             "dummy_value_1": [1, 2, 1],


Reviews:
user: login: gpsaggese
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T00:19:31Z
user: login: aangelo9
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-01T00:26:00Z
user: login: sonniki
body: Small things and then done.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T22:03:27Z
user: login: aangelo9
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-01T22:18:34Z
user: login: gpsaggese
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T22:20:29Z
user: login: sonniki
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T22:26:32Z
user: login: aangelo9
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-01T22:29:48Z
user: login: sonniki
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T22:47:51Z

Review comments:
diff_hunk: @@ -99,41 +97,41 @@ def filter_data_by_comparison(
     """
     if info is None:
         info = collections.OrderedDict()
-    info["nrows"] = data.shape[0]
+    info["nrows"] = df.shape[0]
     if not filters:
-        info["nrows_remaining"] = data.shape[0]
-        return data.copy()
+        info["nrows_remaining"] = df.shape[0]
+        return df.copy()
     # Create filter masks for each column.
     masks = []
     for col_name, tuple_ in filters.items():
-        if not isinstance(tuple_[0], tuple):
-            tuple_ = (tuple_,)  # type: ignore
-        for comparison_method, val in tuple_:
-            hdbg.dassert_in(
-                comparison_method, ("eq", "ne", "le", "lt", "ge", "gt")
-            )
-            mask = getattr(data[col_name], comparison_method)(val)
+        if isinstance(tuple_, tuple) and isinstance(tuple_[0], str):
+            comparisons: Tuple[Tuple[str, Any], ...] = (cast(Tuple[str, Any], tuple_),)
+        else:
+            comparisons = cast(Tuple[Tuple[str, Any], ...], tuple_)
path: helpers/hdataframe.py
user: login: gpsaggese
body: Ok if you want to fix the lint, but I think you can do it in two steps, making the code clearer

```
hdbg.dassert_isinstance(tuple_, tuple)
if isinstance(tuple_[0], str):
     tuple_ = (tuple_, )
comparisons = cast(Tuple[Tuple[str, Any], ...], tuple_)
```

Also the code looks a bit ChatGPT (correct but incomprehensible by humans).
Our policy is that code generated by AI should be modified to be readable by humans.

@sonniki we should add a note along that line in our doc. WDYT?

comparisons: Tuple[Tuple[str, Any], ...]
created_at: 2025-04-01T00:19:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -99,41 +97,41 @@ def filter_data_by_comparison(
     """
     if info is None:
         info = collections.OrderedDict()
-    info["nrows"] = data.shape[0]
+    info["nrows"] = df.shape[0]
     if not filters:
-        info["nrows_remaining"] = data.shape[0]
-        return data.copy()
+        info["nrows_remaining"] = df.shape[0]
+        return df.copy()
     # Create filter masks for each column.
     masks = []
     for col_name, tuple_ in filters.items():
-        if not isinstance(tuple_[0], tuple):
-            tuple_ = (tuple_,)  # type: ignore
-        for comparison_method, val in tuple_:
-            hdbg.dassert_in(
-                comparison_method, ("eq", "ne", "le", "lt", "ge", "gt")
-            )
-            mask = getattr(data[col_name], comparison_method)(val)
+        if isinstance(tuple_, tuple) and isinstance(tuple_[0], str):
+            comparisons: Tuple[Tuple[str, Any], ...] = (cast(Tuple[str, Any], tuple_),)
+        else:
+            comparisons = cast(Tuple[Tuple[str, Any], ...], tuple_)
path: helpers/hdataframe.py
user: login: aangelo9
body: That is right. I did get some help from ChatGPT, I will be changing this immediately.
created_at: 2025-04-01T00:26:00Z
author_association: COLLABORATOR
diff_hunk: @@ -43,42 +43,42 @@ def _combine_masks(
 
path: helpers/hdataframe.py
user: login: sonniki
body: Pls remove the related TODO on line 10 of this file.
created_at: 2025-04-01T21:53:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -99,41 +99,43 @@ def filter_data_by_comparison(
     """
     if info is None:
         info = collections.OrderedDict()
-    info["nrows"] = data.shape[0]
+    info["nrows"] = df.shape[0]
     if not filters:
-        info["nrows_remaining"] = data.shape[0]
-        return data.copy()
+        info["nrows_remaining"] = df.shape[0]
+        return df.copy()
     # Create filter masks for each column.
     masks = []
     for col_name, tuple_ in filters.items():
-        if not isinstance(tuple_[0], tuple):
-            tuple_ = (tuple_,)  # type: ignore
-        for comparison_method, val in tuple_:
+        hdbg.dassert_isinstance(tuple_, tuple)
+        if tuple_ and isinstance(tuple_[0], str):
+            tuple_ = (cast(Tuple[str, Any], tuple_),)
+        comparisons = cast(Tuple[Tuple[str, Any], ...], tuple_)
+        for comparison_method, val in comparisons:
path: helpers/hdataframe.py
user: login: sonniki
body: I'm not sure this is now comprehensible enough... Maybe a comment would help with clarity.
created_at: 2025-04-01T22:00:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -99,41 +99,43 @@ def filter_data_by_comparison(
     """
     if info is None:
         info = collections.OrderedDict()
-    info["nrows"] = data.shape[0]
+    info["nrows"] = df.shape[0]
     if not filters:
-        info["nrows_remaining"] = data.shape[0]
-        return data.copy()
+        info["nrows_remaining"] = df.shape[0]
+        return df.copy()
     # Create filter masks for each column.
     masks = []
     for col_name, tuple_ in filters.items():
-        if not isinstance(tuple_[0], tuple):
-            tuple_ = (tuple_,)  # type: ignore
-        for comparison_method, val in tuple_:
+        hdbg.dassert_isinstance(tuple_, tuple)
+        if tuple_ and isinstance(tuple_[0], str):
+            tuple_ = (cast(Tuple[str, Any], tuple_),)
+        comparisons = cast(Tuple[Tuple[str, Any], ...], tuple_)
+        for comparison_method, val in comparisons:
path: helpers/hdataframe.py
user: login: aangelo9
body: The type-casts are just to make mypy happy. Should I revert it to GP's suggestion for better readability?
```
hdbg.dassert_isinstance(tuple_, tuple)
if isinstance(tuple_[0], str):
     tuple_ = (tuple_, )
comparisons = cast(Tuple[Tuple[str, Any], ...], tuple_)
```
created_at: 2025-04-01T22:18:33Z
author_association: COLLABORATOR
diff_hunk: @@ -99,41 +99,43 @@ def filter_data_by_comparison(
     """
     if info is None:
         info = collections.OrderedDict()
-    info["nrows"] = data.shape[0]
+    info["nrows"] = df.shape[0]
     if not filters:
-        info["nrows_remaining"] = data.shape[0]
-        return data.copy()
+        info["nrows_remaining"] = df.shape[0]
+        return df.copy()
     # Create filter masks for each column.
     masks = []
     for col_name, tuple_ in filters.items():
-        if not isinstance(tuple_[0], tuple):
-            tuple_ = (tuple_,)  # type: ignore
-        for comparison_method, val in tuple_:
+        hdbg.dassert_isinstance(tuple_, tuple)
+        if tuple_ and isinstance(tuple_[0], str):
+            tuple_ = (cast(Tuple[str, Any], tuple_),)
+        comparisons = cast(Tuple[Tuple[str, Any], ...], tuple_)
+        for comparison_method, val in comparisons:
path: helpers/hdataframe.py
user: login: gpsaggese
body: The code is better than before but an explanation of why (and not what it does) helps.

The original code is saying "if it's not a tuple, make it into a tuple" and then there is lots of casting to make the linter happy. TBH the complexity of getting the linter happy might not be warranted (probably that's why we ignored the linter, which we rarely do) for the resulting convoluted code.

I'm ok both reverting or adding a comment. @sonniki to decide
created_at: 2025-04-01T22:20:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -99,41 +99,43 @@ def filter_data_by_comparison(
     """
     if info is None:
         info = collections.OrderedDict()
-    info["nrows"] = data.shape[0]
+    info["nrows"] = df.shape[0]
     if not filters:
-        info["nrows_remaining"] = data.shape[0]
-        return data.copy()
+        info["nrows_remaining"] = df.shape[0]
+        return df.copy()
     # Create filter masks for each column.
     masks = []
     for col_name, tuple_ in filters.items():
-        if not isinstance(tuple_[0], tuple):
-            tuple_ = (tuple_,)  # type: ignore
-        for comparison_method, val in tuple_:
+        hdbg.dassert_isinstance(tuple_, tuple)
+        if tuple_ and isinstance(tuple_[0], str):
+            tuple_ = (cast(Tuple[str, Any], tuple_),)
+        comparisons = cast(Tuple[Tuple[str, Any], ...], tuple_)
+        for comparison_method, val in comparisons:
path: helpers/hdataframe.py
user: login: sonniki
body: Mypy can have unrealistic expectations sometimes. I'm voting for reverting to the way it was (in GP's comment or even in master). Mypy complaints shouldn't affect whether the file fails or passes the Linter check on GH Actions BTW
created_at: 2025-04-01T22:26:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -99,41 +99,43 @@ def filter_data_by_comparison(
     """
     if info is None:
         info = collections.OrderedDict()
-    info["nrows"] = data.shape[0]
+    info["nrows"] = df.shape[0]
     if not filters:
-        info["nrows_remaining"] = data.shape[0]
-        return data.copy()
+        info["nrows_remaining"] = df.shape[0]
+        return df.copy()
     # Create filter masks for each column.
     masks = []
     for col_name, tuple_ in filters.items():
-        if not isinstance(tuple_[0], tuple):
-            tuple_ = (tuple_,)  # type: ignore
-        for comparison_method, val in tuple_:
+        hdbg.dassert_isinstance(tuple_, tuple)
+        if tuple_ and isinstance(tuple_[0], str):
+            tuple_ = (cast(Tuple[str, Any], tuple_),)
+        comparisons = cast(Tuple[Tuple[str, Any], ...], tuple_)
+        for comparison_method, val in comparisons:
path: helpers/hdataframe.py
user: login: aangelo9
body: Understood. I will revert it back to master since both codes have the same mypy complaints.
created_at: 2025-04-01T22:29:48Z
author_association: COLLABORATOR

PR comments:
user: login: aangelo9
created_at: 2025-03-29T03:44:20Z
body: Run_linter and run_fast_tests failed.

## For Run_linter:

Error states:

1. Unpacking a string is disallowed
> helpers/hdataframe.py:111: error: Unpacking a string is disallowed  [misc] [mypy]

In hdataframe.py, Refers to line:
```python
for comparison_method, val in tuple_:
```

2. Git Glient Not Clean
> AssertionError: The Git client is not clean: helpers/hdataframe.py

### Solution:

For error 1, we could:

Strengthen the type check:
```python
if not isinstance(tuple_[0], tuple):
    hdbg.dassert_isinstance(tuple_, tuple)
    tuple_ = (tuple_,)
```
or
```python
if isinstance(tuple_, tuple) and all(isinstance(x, str) for x in tuple_):
    tuple_ = (tuple_,)
```

For error 2:
Likely will be fixed when error 1 is fixed.

## For run_fast_tests

Under `TestFilterDataByMethod`, the test is calling:
```python
act = hdatafr.filter_data_by_method(
            data=data, filters=filters, mode=mode, info=info
        )
```
But since the function is refactored, it should be:
```python
act = hdatafr.filter_data_by_method(
            df=df, filters=filters, mode=mode, info=info
        )
```

author_association: COLLABORATOR
user: login: aangelo9
created_at: 2025-03-29T03:45:38Z
body: Should I proceed with the test fixes?
author_association: COLLABORATOR
user: login: sonniki
created_at: 2025-03-29T08:15:31Z
body: > Should I proceed with the test fixes?

Of course. In the future, go ahead and fix the failing checks before requesting review. Also, you don't have to provide a full report of the failing tests, etc -- it's all part of debugging that you are expected to carry out before you present the "finished product" to the reviewers.

BTW the Linter check more likely fails because when you run `i lint` on the file, it modifies it (fixes formatting, etc), and these modifications are not checked in here. I.e. try to run `i lint` on the file again, it will automatically stage the file for commit, and then you commit and push it. Other Linter complaints, like the mypy one, are nice to fix but not absolutely required in this case.
author_association: CONTRIBUTOR
user: login: samarth9008
created_at: 2025-03-30T14:18:07Z
body: > Of course. In the future, go ahead and fix the failing checks before requesting review. Also, you don't have to provide a full report of the failing tests, etc -- it's all part of debugging that you are expected to carry out before you present the "finished product" to the reviewers.

@sonniki I have been seeing this behavior from last year. People focus more on writing the code and requesting for review rather than checking if their work is correct. We should update the doc and add something like "make sure tests are green before requesting for a review".
author_association: CONTRIBUTOR
user: login: aangelo9
created_at: 2025-04-01T21:28:08Z
body: All tests are green. Awaiting review.
author_association: COLLABORATOR
user: login: sonniki
created_at: 2025-04-01T22:34:00Z
body: @aangelo9 pls also make sure to [resolve conversations](https://github.com/causify-ai/helpers/blob/master/docs/coding/all.submit_code_for_review.how_to_guide.md#resolve-conversations) after you've addressed them and keep the branch [up to date with master](https://github.com/causify-ai/helpers/blob/master/docs/coding/all.submit_code_for_review.how_to_guide.md#merge-master-into-your-branch) (you can use "Update branch" button on the PR page).
author_association: CONTRIBUTOR