Repository: pyjanitor-devs/pyjanitor (python)

Issue:
[BUG] Extend `fill_empty`'s `column_names` type range
# Brief Description

<!-- Please provide a brief description of your bug. Do NOT paste the stack trace here. -->

https://github.com/pyjanitor-devs/pyjanitor/blob/3fab49e8c89f1a5e4ca7a6e4fdbbe8e2f7b89c66/janitor/functions/fill.py#L148-L152

Quickly fix this, could add `pd.Index`.

And a little bit more thinking, using `Iterable` is better?
Because `check_column`'s `column_names` also support `Iterable`.
And `pd.Index`'s instance is also `Iterable`.
So it would be `@dispatch(pd.DataFrame, Iterable)`

The `Iterable` is not `typing.Iterable` is `collections.abc.Iterable`.
Or else using `typing.Iterable` would get another error.

# Minimally Reproducible Code

<!-- If you provide minimal code that reproduces the problem, this makes it easier for us to debug what's going on.

Minimal code should be trivially copy/pastable into a Python interpreter in its entirety. Be sure to include imports.
-->

```python
>>> import pandas as pd
>>> import janitor  # noqa

>>> df = pd.DataFrame({"attr":[None, None]})
>>> df.fill_empty(df.columns, 0)
Traceback (most recent call last):
  File "C:\Software\miniforge3\envs\work\lib\site-packages\multipledispatch\dispatcher.py", line 269, in __call__
    func = self._cache[types]
KeyError: (<class 'pandas.core.frame.DataFrame'>, <class 'pandas.core.indexes.base.Index'>)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Software\miniforge3\envs\work\lib\site-packages\pandas_flavor\register.py", line 29, in __call__
    return method(self._obj, *args, **kwargs)
  File "C:\Software\miniforge3\envs\work\lib\site-packages\janitor\utils.py", line 231, in wrapper
    return func(*args, **kwargs)
  File "C:\Software\miniforge3\envs\work\lib\site-packages\janitor\functions\fill.py", line 199, in fill_empty
    return _fill_empty(df, column_names, value=value)
  File "C:\Software\miniforge3\envs\work\lib\site-packages\multipledispatch\dispatcher.py", line 273, in __call__
    raise NotImplementedError(
NotImplementedError: Could not find signature for _fill_empty: <DataFrame, Index>
```

Pull request:
[BUG] Extend `fill_empty`'s `column_names` type range
<!-- Thank you for your PR!

BEFORE YOU CONTINUE! Please add the appropriate three-letter abbreviation to your title.

The abbreviations can be:
- [DOC]: Documentation fixes.
- [ENH]: Code contributions and new features.
- [TST]: Test-related contributions.
- [INF]: Infrastructure-related contributions.

Also, do not forget to tag the relevant issue here as well.

Finally, as commits come in, don't forget to regularly rebase!
-->

# PR Description

Please describe the changes proposed in the pull request:

- Let `column_names` support more type range, from `list` & `tuple` to `Iterable`.

<!-- Doing so provides maintainers with context on what the PR is, and can help us more effectively review your PR. -->

<!-- Please also identify below which issue that has been raised that you are going to close. -->

**This PR resolves #998.**

<!-- As you go down the PR template, please feel free to delete sections that are irrelevant. -->

# PR Checklist

<!-- This checklist exists for newcomers who are not yet familiar with our requirements. If you are experienced with
the project, please feel free to delete this section. -->

Please ensure that you have done the following:

1. [x] PR in from a fork off your branch. Do not PR from `<your_username>`:`dev`, but rather from `<your_username>`:`<feature-branch_name>`.
<!-- Doing this helps us keep the commit history much cleaner than it would otherwise be. -->
2. [x] If you're not on the contributors list, add yourself to `AUTHORS.rst`.
<!-- We'd like to acknowledge your contributions! -->
3. [x] Add a line to `CHANGELOG.md` under the latest version header (i.e. the one that is "on deck") describing the contribution.
    - Do use some discretion here; if there are multiple PRs that are related, keep them in a single line.

# Automatic checks

There will be automatic checks run on the PR. These include:

- Building a preview of the docs on Netlify
- Automatically linting the code
- Making sure the code is documented
- Making sure that all tests are passed
- Making sure that code coverage doesn't go down.

# Relevant Reviewers

<!-- Finally, please tag relevant maintainers to review. -->

Please tag maintainers to review.

- @ericmjl


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cb3e1ee..64b5707 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@
 -   [ENH] Add `xlsx_table`, for reading tables from an Excel sheet. @samukweku
 -   [ENH] minor improvements for conditional_join; equality only joins are no longer supported; there has to be at least one non-equi join present. @samukweku
 -   [BUG] `sort_column_value_order` no longer mutates original dataframe.
+-   [BUG] Extend `fill_empty`'s `column_names` type range. Issue #998. @Zeroto521
 
 ## [v0.22.0] - 2021-11-21
 
diff --git a/janitor/functions/fill.py b/janitor/functions/fill.py
index 169d75e..dd899dd 100644
--- a/janitor/functions/fill.py
+++ b/janitor/functions/fill.py
@@ -1,3 +1,4 @@
+from collections.abc import Iterable as abcIterable
 from enum import Enum
 from operator import methodcaller
 from typing import Hashable, Iterable, Union
@@ -145,7 +146,7 @@ def fill_empty(
     return _fill_empty(df, column_names, value=value)
 
 
-@dispatch(pd.DataFrame, (list, tuple))
+@dispatch(pd.DataFrame, abcIterable)
 def _fill_empty(df, column_names, value=None):
     """Fill empty function for the case that column_names is list or tuple."""
     fill_mapping = {c: value for c in column_names}


Test patch:
diff --git a/tests/functions/test_fill_empty.py b/tests/functions/test_fill_empty.py
index d9155c4..ed8a463 100644
--- a/tests/functions/test_fill_empty.py
+++ b/tests/functions/test_fill_empty.py
@@ -1,4 +1,5 @@
 import pytest
+import pandas as pd
 
 
 @pytest.mark.functions
@@ -11,3 +12,22 @@ def test_fill_empty(null_df):
 def test_fill_empty_column_string(null_df):
     df = null_df.fill_empty(column_names="2", value=3)
     assert set(df.loc[:, "2"]) == set([3])
+
+
+@pytest.mark.functions
+@pytest.mark.parametrize(
+    "column_names",
+    [
+        (0, 1, "2", "3"),  # tuple
+        [0, 1, "2", "3"],  # list
+        {0, 1, "2", "3"},  # set
+        ({0: 0, 1: 1, "2": "2", "3": "3"}).keys(),  # dict key
+        ({0: 0, 1: 1, "2": "2", "3": "3"}).values(),  # dict value
+        pd.Index([0, 1, "2", "3"]),  # Index
+    ],
+)
+def test_column_names_iterable_type(null_df, column_names):
+    result = null_df.fill_empty(column_names=column_names, value=3)
+    excepted = null_df.fillna(3)
+
+    assert result.equals(excepted)