internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: pyjanitor-devs/pyjanitor

Issue:
GroupBy, aggregate, but keep within the original DataFrame
I often want aggregates over different values in a dataframe, but still want to see those values alongside non-aggregate values, which can require multiple non-chainable steps of code.

Wonder if anyone has an example of this, or has an opinion?


Discussion:

Resolving patch:
diff --git a/janitor/functions.py b/janitor/functions.py
--- a/janitor/functions.py
+++ b/janitor/functions.py
@@ -6,7 +6,7 @@
 import warnings
 from fnmatch import translate
 from functools import partial, reduce
-from typing import Dict, Iterable, List, Union
+from typing import Callable, Dict, Iterable, List, Union
 
 import numpy as np
 import pandas as pd
@@ -2630,3 +2630,51 @@ def to_datetime(df: pd.DataFrame, column: str, **kwargs) -> pd.DataFrame:
     df[column] = pd.to_datetime(df[column], **kwargs)
 
     return df
+
+
+@pf.register_dataframe_method
+def groupby_agg(
+        df: pd.DataFrame,
+        by: str,
+        new_column: str,
+        agg_column: str,
+        agg: Union[Callable, str, List, Dict],
+        axis: int=0
+) -> pd.DataFrame:
+    """
+
+    Allow one to chain a groupby and a merge
+
+    Without this function, we would have to break out of method chaining:
+
+    .. code-block:: python
+        df_grp = df.groupby(...).agg(...)
+        df = df.merge(df_grp, ...)
+
+    Now, this function can be method-chained:
+
+    .. code-block:: python
+
+        import pandas as pd
+        import janitor
+        df = pd.DataFrame(...).groupby_agg(df, by='col1', agg='mean', new_column='col1_mean')
+
+    :param df: A pandas DataFrame.
+    :param by: Column to groupby on.
+    :param new_column: Name of the aggregation output column.
+    :param agg_column: Name of the column to aggregate over.
+    :param agg: How to aggregate.
+    :param axis: Split along rows (0) or columns (1).
+    :returns: A pandas DataFrame.
+    """
+
+    df_grp = (
+      df.groupby(by, axis=axis)
+        .agg(agg, axis=axis)
+        .reset_index()
+        .rename(columns={agg_column: new_column})
+    )[[by, new_column]]
+
+    df = df.merge(df_grp, on=by)
+
+    return df


Test patch:
diff --git a/tests/functions/test_groupby_agg.py b/tests/functions/test_groupby_agg.py
new file mode 100644
--- /dev/null
+++ b/tests/functions/test_groupby_agg.py
@@ -0,0 +1,21 @@
+import pandas as pd
+import pytest
+
+
+@pytest.mark.functions
+def test_groupby_agg():
+
+    df = pd.DataFrame({
+        "date": ['20190101', '20190101', '20190102', '20190102', '20190304', '20190304'],
+        "values": [1, 1, 2, 2, 3, 3]
+    })
+
+    df_new = df.groupby_agg(
+        by='date',
+        new_column='date_average',
+        agg_column='values',
+        agg=pd.np.mean
+    )
+    assert df.shape[0] == df_new.shape[0]
+    assert 'date_average' in df_new.columns
+    assert df_new['date_average'].iloc[0] == 1
Repository: pyjanitor-devs/pyjanitor

Issue:
Same as Issue 179. Adding examples in Readme.rst
Hi, I will like to add a continuation of issue 179 to improve the examples in the API section of readme.rst 


Discussion:

Resolving patch:
diff --git a/docs/conf.py b/docs/conf.py
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -12,9 +12,18 @@
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
 #
-# import os
-# import sys
-# sys.path.insert(0, os.path.abspath('.'))
+import os
+import sys
+sys.path.insert(0, os.path.abspath('.'))
+sys.path.insert(0, os.path.abspath('../examples'))
+
+# Make a symlink in our sphinx source directory to the top-level
+# examples/notebooks directory so we can include notebooks in the doc
+from pathlib import Path
+notebooks = Path("./notebooks")
+if not notebooks.exists():
+    print("Making symlink to ../examples/notebooks")
+    notebooks.symlink_to("../examples/notebooks")
 
 
 # -- Project information -----------------------------------------------------
@@ -47,6 +56,7 @@
     "sphinx.ext.viewcode",
     "sphinx.ext.githubpages",
     "sphinxcontrib.fulltoc",
+    "nbsphinx",
 ]
 
 # Add any paths that contain templates here, relative to this directory.
@@ -56,7 +66,7 @@
 # You can specify multiple suffix as a list of string:
 #
 # source_suffix = ['.rst', '.md']
-source_suffix = [".md", ".rst"]
+source_suffix = [".md", ".rst", ".ipynb"]
 
 # The master toctree document.
 master_doc = "index"
@@ -71,7 +81,7 @@
 # List of patterns, relative to source directory, that match files and
 # directories to ignore when looking for source files.
 # This pattern also affects html_static_path and html_extra_path .
-exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "**.ipynb_checkpoints"]
 
 # The name of the Pygments (syntax highlighting) style to use.
 pygments_style = "sphinx"


Test patch:
Repository: pyjanitor-devs/pyjanitor

Issue:
Resetting the index in remove_empty()
In the remove_empty() function, I'm proposing changing this: 

`df.drop(index=nanrows, inplace=True)`

to : 

`df = df.drop(index=nanrows).reset_index(drop=True)`

This helps reset the index which prevent bad things from happening further down the data pipeline! 😄 

Let me know if I can begin working on this! 🔥 🚀 



Discussion:

Resolving patch:
diff --git a/janitor/functions.py b/janitor/functions.py
--- a/janitor/functions.py
+++ b/janitor/functions.py
@@ -144,9 +144,11 @@ def _remove_special(col):
 @pf.register_dataframe_method
 def remove_empty(df):
     """
-    Drop all rows and columns that are completely null.
+    Drop all rows and columns that are completely null. This method also
+    resets the index(by default) since it doesn't make sense to preserve
+    the index of a completely empty row.
 
-    Implementation is shamelessly copied from `StackOverflow`_.
+    Implementation is inspired from `StackOverflow`_.
 
     .. _StackOverflow: https://stackoverflow.com/questions/38884538/python-pandas-find-all-rows-where-all-values-are-nan  # noqa: E501
 
@@ -169,7 +171,7 @@ def remove_empty(df):
     :returns: A pandas DataFrame.
     """
     nanrows = df.index[df.isnull().all(axis=1)]
-    df.drop(index=nanrows, inplace=True)
+    df = df.drop(index=nanrows).reset_index(drop=True)
 
     nancols = df.columns[df.isnull().all(axis=0)]
     df.drop(columns=nancols, inplace=True)


Test patch:
diff --git a/tests/functions/test_remove_empty.py b/tests/functions/test_remove_empty.py
--- a/tests/functions/test_remove_empty.py
+++ b/tests/functions/test_remove_empty.py
@@ -1,3 +1,4 @@
+import numpy as np
 import pandas as pd
 import pytest
 from hypothesis import given
@@ -14,3 +15,12 @@ def test_remove_empty(df):
         assert not pd.isnull(df[col]).all()
     for r, d in df.iterrows():
         assert not pd.isnull(d).all()
+
+@pytest.mark.functions
+def test_index_after_remove_empty():
+    # This test ensures that the indexed is reset correctly.
+    df = pd.DataFrame()
+    df["a"] = [1, np.nan, np.nan, 3, np.nan, 6]
+    df["b"] = [1, np.nan, 1, 3, np.nan, 6]
+    df_nonempty = df.remove_empty()
+    assert np.array_equal(np.asarray(df_nonempty.index), np.asarray(range(0, len(df_nonempty))))
\ No newline at end of file
Repository: pyjanitor-devs/pyjanitor

Issue:
test_convert_excel_date fails
This is because of a recent PR in which the example data was moved.


Discussion:

Resolving patch:
diff --git a/docs/conf.py b/docs/conf.py
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -15,8 +15,9 @@
 import os
 import sys
 from pathlib import Path
-sys.path.insert(0, os.path.abspath('.'))
-sys.path.insert(0, os.path.abspath('../examples'))
+
+sys.path.insert(0, os.path.abspath("."))
+sys.path.insert(0, os.path.abspath("../examples"))
 
 # Make a symlink in our sphinx source directory to the top-level
 # examples/notebooks directory so we can include notebooks in the doc
diff --git a/nbconvert_config.py b/nbconvert_config.py
--- a/nbconvert_config.py
+++ b/nbconvert_config.py
@@ -54,9 +54,7 @@
 
 ## List of notebooks to convert. Wildcards are supported. Filenames passed
 #  positionally will be added to the list.
-c.NbConvertApp.notebooks = [
-    "examples/notebooks/*.ipynb",
-]
+c.NbConvertApp.notebooks = ["examples/notebooks/*.ipynb"]
 
 ## overwrite base name use for output files. can only be used when converting one
 #  notebook at a time.
@@ -486,4 +484,4 @@
 # c.ServePostProcessor.reveal_cdn = 'https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.1.0'
 
 ## URL prefix for reveal.js
-# c.ServePostProcessor.reveal_prefix = 'reveal.js'
\ No newline at end of file
+# c.ServePostProcessor.reveal_prefix = 'reveal.js'


Test patch:
diff --git a/tests/functions/test_convert_excel_date.py b/tests/functions/test_convert_excel_date.py
--- a/tests/functions/test_convert_excel_date.py
+++ b/tests/functions/test_convert_excel_date.py
@@ -1,4 +1,5 @@
 import os
+from pathlib import Path
 
 import pandas as pd
 import pytest
@@ -7,7 +8,9 @@
 @pytest.mark.functions
 def test_convert_excel_date():
     df = (
-        pd.read_excel(os.path.join(pytest.EXAMPLES_DIR, "dirty_data.xlsx"))
+        pd.read_excel(
+            Path(pytest.EXAMPLES_DIR) / "notebooks" / "dirty_data.xlsx"
+        )
         .clean_names()
         .convert_excel_date("hire_date")
     )
diff --git a/tests/functions/test_to_datetime.py b/tests/functions/test_to_datetime.py
--- a/tests/functions/test_to_datetime.py
+++ b/tests/functions/test_to_datetime.py
@@ -6,11 +6,8 @@
 @pytest.mark.functions
 def test_to_datetime():
 
-    df = pd.DataFrame({
-        "date1": ['20190101', '20190102', '20190304', np.nan],
-    }).to_datetime(
-        "date1",
-        format='%Y%m%d'
-    )
-    assert df["date1"].dtype == pd.np.dtype('datetime64[ns]')
-    assert df["date1"].iloc[0].isoformat() == '2019-01-01T00:00:00'
+    df = pd.DataFrame(
+        {"date1": ["20190101", "20190102", "20190304", np.nan]}
+    ).to_datetime("date1", format="%Y%m%d")
+    assert df["date1"].dtype == pd.np.dtype("datetime64[ns]")
+    assert df["date1"].iloc[0].isoformat() == "2019-01-01T00:00:00"
Repository: pyjanitor-devs/pyjanitor

Issue:
isort command needs to change
https://github.com/ericmjl/pyjanitor/blob/dev/Makefile#L25

Correct version:

```
isort -rc . -y -up -tc
```


Discussion:
I will add it

Resolving patch:
diff --git a/janitor/functions.py b/janitor/functions.py
--- a/janitor/functions.py
+++ b/janitor/functions.py
@@ -1229,7 +1229,7 @@ def change_type(df, column: str, dtype, ignore_exception=False):
         def convert(x, dtype):
             try:
                 return dtype(x)
-            except:
+            except ValueError:
                 return None
 
         df[column] = df[column].apply(lambda x: convert(x, dtype))


Test patch:
diff --git a/tests/functions/test_remove_columns.py b/tests/functions/test_remove_columns.py
--- a/tests/functions/test_remove_columns.py
+++ b/tests/functions/test_remove_columns.py
@@ -46,7 +46,8 @@ def test_remove_columns_strange_cols(dataframe):
 
 @pytest.mark.functions
 def test_remove_columns_strange_cols(multilevel_dataframe):
-    # When creating a multi level dataframe with 4 columns * 2 columns (16 columns in total)
+    # When creating a multi level dataframe with 4 columns * 2 columns
+    # (16 columns in total)
     # From input
 
     # If 2 columns (2 tuples = 4 codes) are removed
diff --git a/tests/functions/test_remove_empty.py b/tests/functions/test_remove_empty.py
--- a/tests/functions/test_remove_empty.py
+++ b/tests/functions/test_remove_empty.py
@@ -16,6 +16,7 @@ def test_remove_empty(df):
     for r, d in df.iterrows():
         assert not pd.isnull(d).all()
 
+
 @pytest.mark.functions
 def test_index_after_remove_empty():
     # This test ensures that the indexed is reset correctly.
@@ -23,4 +24,6 @@ def test_index_after_remove_empty():
     df["a"] = [1, np.nan, np.nan, 3, np.nan, 6]
     df["b"] = [1, np.nan, 1, 3, np.nan, 6]
     df_nonempty = df.remove_empty()
-    assert np.array_equal(np.asarray(df_nonempty.index), np.asarray(range(0, len(df_nonempty))))
\ No newline at end of file
+    assert np.array_equal(
+        np.asarray(df_nonempty.index), np.asarray(range(0, len(df_nonempty)))
+    )
Repository: pyjanitor-devs/pyjanitor

Issue:
Replace all occurences of "inplace" 
A proposal suggested by @ericmjl  to replace all occurrences of "inplace" appropriately!

This is done in-line with the future of pandas having no "inplace" functionality.

waiting on #214 by @Rajat-181  to begin work on this issue! 🔥 🚀 



Discussion:

Resolving patch:
diff --git a/janitor/functions.py b/janitor/functions.py
--- a/janitor/functions.py
+++ b/janitor/functions.py
@@ -174,7 +174,7 @@ def remove_empty(df):
     df = df.drop(index=nanrows).reset_index(drop=True)
 
     nancols = df.columns[df.isnull().all(axis=0)]
-    df.drop(columns=nancols, inplace=True)
+    df = df.drop(columns=nancols)
 
     return df
 
@@ -1657,10 +1657,10 @@ def row_to_names(
     df.columns.name = None
 
     if remove_row:
-        df.drop(df.index[row_number], inplace=True)
+        df = df.drop(df.index[row_number])
 
     if remove_rows_above:
-        df.drop(df.index[range(row_number)], inplace=True)
+        df = df.drop(df.index[range(row_number)])
 
     return df
 


Test patch:
Repository: pyjanitor-devs/pyjanitor

Issue:
[ENH] Deprecate "reset_index_inplace"
In an effort to move away from all "inplace" optimizations to futureproof pyjanitor, I propose deprecating the reset_index_inplace.

@zbarry  Thoughts? 😄 : 👴-> 💀  



Discussion:
Kill it ded 👍

Resolving patch:
diff --git a/janitor/functions.py b/janitor/functions.py
--- a/janitor/functions.py
+++ b/janitor/functions.py
@@ -2118,6 +2118,12 @@ def reset_index_inplace(df: pd.DataFrame, *args, **kwargs):
     :returns: df
     """
 
+    # Deprecation Warning
+    warnings.warn(
+        "reset_index_inplace will be deprecated in the "
+        "upcoming 0.18 release. Use .reset_index() instead"
+    )
+
     kwargs.update(inplace=True)
 
     df.reset_index(*args, **kwargs)


Test patch:
Repository: pyjanitor-devs/pyjanitor

Issue:
Utilize autosummary Sphinx directive in API Reference
## Proposal

A consolidated list of functionality would go a long way in [our API Reference](https://pyjanitor.readthedocs.io/api.html) section.

Other libraries have leveraged the [autosummary](http://www.sphinx-doc.org/en/master/usage/extensions/autosummary.html#directive-autosummary) Sphinx directive to achieve this to great effect. For instance:

* Pandas: [Docs](https://pandas.pydata.org/pandas-docs/stable/reference/indexing.html), [Raw](https://raw.githubusercontent.com/pandas-dev/pandas/master/doc/source/reference/indexing.rst)
* Matplotlib: [Docs](https://matplotlib.org/api/axes_api.html), [Raw](https://matplotlib.org/_sources/api/axes_api.rst.txt)

## Implementation Details

Apart from rolling `sphinx.ext.autosummary` into the `conf.py` this would also involve going through and enumerating the different functions in the `api.rst` documentation.

A concern here, though-- this would mean that all future feature introductions would have to get appended to the lists in these files, **which necessitates adding this step to the PR checklist**... Until someone figures out a more programmatic way to do this, anyhow 😉 


Discussion:

Resolving patch:
diff --git a/docs/conf.py b/docs/conf.py
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -58,6 +58,7 @@
     "sphinx.ext.githubpages",
     "sphinxcontrib.fulltoc",
     "nbsphinx",
+    "sphinx.ext.autosummary",
 ]
 
 # Add any paths that contain templates here, relative to this directory.


Test patch:
Repository: pyjanitor-devs/pyjanitor

Issue:
[DOC] Fix Doc string for "encode_categorical" method
The doc string for "encode_categorical" needs a fix.
**from this:** 
```
    Functional usage example:
    .. code-block:: python
        encode_categorical(df, columns="my_categorical_column")  # one way
    Method chaining example:
    .. code-block:: python
        import pandas as pd
        import janitor
        df = pd.DataFrame(...)
        categorical_cols = ['col1', 'col2', 'col4']
        df = df.encode_categorical(columns=categorical_cols)
```

**to I believe this?:** 

```
    Functional usage example:
    .. code-block:: python
        categorical_cols = ['col1', 'col2', 'col4']
        df = df.encode_categorical(columns=categorical_cols)  # one way
    Method chaining example:
    .. code-block:: python
        import pandas as pd
        import janitor
        categorical_cols = ['col1', 'col2', 'col4']
        df = pd.DataFrame(...).encode_categorical(columns=categorical_cols)
```

This is a rough idea of what it should be. Please verify that it works!

Feel free to pick this up! 😄 🔥 🚀 



Discussion:

Resolving patch:
diff --git a/janitor/functions.py b/janitor/functions.py
--- a/janitor/functions.py
+++ b/janitor/functions.py
@@ -224,17 +224,16 @@ def encode_categorical(df, column_names=None, **kwargs):
 
     .. code-block:: python
 
-        encode_categorical(df, column_names="my_categorical_column")  # one way
+        categorical_cols = ['col1', 'col2', 'col4']
+        df = df.encode_categorical(columns=categorical_cols)  # one way
 
     Method chaining example:
 
     .. code-block:: python
-
         import pandas as pd
         import janitor
-        df = pd.DataFrame(...)
         categorical_cols = ['col1', 'col2', 'col4']
-        df = df.encode_categorical(column_names=categorical_cols)
+        df = pd.DataFrame(...).encode_categorical(columns=categorical_cols)
 
     :param df: The pandas DataFrame object.
     :param str/iterable column_names: A column name or an iterable (list or
@@ -2121,6 +2120,12 @@ def reset_index_inplace(df: pd.DataFrame, *args, **kwargs):
     :returns: df
     """
 
+    # Deprecation Warning
+    warnings.warn(
+        "reset_index_inplace will be deprecated in the "
+        "upcoming 0.18 release. Use .reset_index() instead"
+    )
+
     kwargs.update(inplace=True)
 
     df.reset_index(*args, **kwargs)


Test patch:
Repository: pyjanitor-devs/pyjanitor

Issue:
[DOC] Remove example.py in examples directory
The code in the example.py file currently reads some data frame from a file called 'dirty_data.xls'.
We can change this to include a concrete example.


Discussion:
`example.py` should be deleted. I will be changing the title of this issue.

Resolving patch:
diff --git a/examples/example.py b/examples/example.py
deleted file mode 100644
--- a/examples/example.py
+++ /dev/null
@@ -1,17 +0,0 @@
-import pandas as pd
-
-import janitor as jn
-
-df = (
-    pd.read_excel("dirty_data.xlsx")
-    .clean_names()
-    .remove_empty()
-    .rename_column("%_allocated", "percent_allocated")
-    .rename_column("full_time_", "full_time")
-    .coalesce(["certification", "certification_1"], "certification")
-    .encode_categorical(["subject", "employee_status", "full_time"])
-    .convert_excel_date("hire_date")
-)
-
-print(df)
-print(df.original_names)


Test patch:
Repository: pyjanitor-devs/pyjanitor

Issue:
[ENH] Add __repr__ to `DataDescription` class 
which is probably preferable to `display()`


Discussion:

Resolving patch:
diff --git a/janitor/functions.py b/janitor/functions.py
--- a/janitor/functions.py
+++ b/janitor/functions.py
@@ -2894,8 +2894,6 @@ class DataDescription:
     High-level description of data present in this DataFrame.
 
     This is a custom data accessor.
-
-    TODO: add repr, fail on type error in desc
     """
 
     def __init__(self, data):
@@ -2920,9 +2918,13 @@ def df(self) -> pd.DataFrame:
         """Get a table of descriptive information in a DataFrame format."""
         return self._get_data_df()
 
+    def __repr__(self):
+        """Human-readable representation of the `DataDescription` object."""
+        return str(self._get_data_df())
+
     def display(self):
         """Print the table of descriptive information about this DataFrame."""
-        print(self._get_data_df())
+        print(self)
 
     def set_description(self, desc: Union[List, Dict]):
         """
@@ -2931,7 +2933,13 @@ def set_description(self, desc: Union[List, Dict]):
         :param desc: The structure containing the descriptions to update
         """
         if isinstance(desc, list):
-            assert len(desc) == len(self._data.columns)
+            if len(desc) != len(self._data.columns):
+                raise ValueError(
+                    f"Length of description list "
+                    f"({len(desc)}) does not match number of columns in "
+                    f"DataFrame ({len(self._data.columns)})"
+                )
+
             self._desc = dict(zip(self._data.columns, desc))
 
         elif isinstance(desc, dict):


Test patch:
diff --git a/tests/functions/test_data_description.py b/tests/functions/test_data_description.py
--- a/tests/functions/test_data_description.py
+++ b/tests/functions/test_data_description.py
@@ -21,10 +21,10 @@ def test_description_list(dataframe):
 
     assert (df["description"] == desc).all()
 
-    with pytest.raises(AssertionError):
+    with pytest.raises(ValueError):
         dataframe.data_description.set_description([])
 
-    with pytest.raises(AssertionError):
+    with pytest.raises(ValueError):
         dataframe.data_description.set_description(desc[0:3])
Repository: pyjanitor-devs/pyjanitor

Issue:
[ENH] clean_names() in functions is too verbose
* Improvement can be made by using re
* remove `_`if column_name begins with `_`
* change`%`to `_pct_` or other suggested value


Discussion:
non-alphanumeric characters includes punctuations and symbols

punctuations includes : ! @ # & ( ) – [ { } ] : ; ', ? / *

symbols includes: ` ~ $ ^ + = < > “

By using re, non-alphanumeric characters `\W` can be easily replaced to `_`.
@jekwatt I would like to assign you to this issue please!
@jekwatt please do go ahead and open a relevant PR if you're working on this. And don't forget to reference this issue! :smile:
I've created a branch `issue-203` and set upstream of this (I don't see it now and I may have to do it again).  When ready to open a PR, I will reference this issue.

Resolving patch:
diff --git a/janitor/functions.py b/janitor/functions.py
--- a/janitor/functions.py
+++ b/janitor/functions.py
@@ -89,19 +89,7 @@ def clean_names(
         elif case_type.lower() == "lower":
             df = df.rename(columns=lambda x: x.lower())
 
-    df = df.rename(
-        columns=lambda x: x.replace(" ", "_")
-        .replace("/", "_")
-        .replace(":", "_")
-        .replace("'", "")
-        .replace("’", "")
-        .replace(",", "_")
-        .replace("?", "_")
-        .replace("-", "_")
-        .replace("(", "_")
-        .replace(")", "_")
-        .replace(".", "_")
-    )
+    df = df.rename(columns=_normalize_1)
 
     def _remove_special(col):
         return "".join(item for item in col if item.isalnum() or "_" in item)
@@ -118,6 +106,16 @@ def _remove_special(col):
     return df
 
 
+FIXES = [(r"[ /:,?()\.-]", "_"), (r"['’]", "")]
+
+
+def _normalize_1(col_name):
+    result = col_name
+    for search, replace in FIXES:
+        result = re.sub(search, replace, result)
+    return result
+
+
 @pf.register_dataframe_method
 def remove_empty(df: pd.DataFrame) -> pd.DataFrame:
     """


Test patch: